class Sinatra::Base
Base class for all Sinatra applications and middleware.
def self.force_encoding(data, encoding = default_encoding)
def self.force_encoding(data, encoding = default_encoding) return if data == settings || data.is_a?(Tempfile) if data.respond_to? :force_encoding data.force_encoding(encoding).encode! elsif data.respond_to? :each_value data.each_value { |v| force_encoding(v, encoding) } elsif data.respond_to? :each data.each { |v| force_encoding(v, encoding) } end data end
def self.force_encoding(data, *) data end
def self.force_encoding(data, *) data end
def self.settings
def self.settings self end
def add_filter(type, path = nil, options = {}, &block)
def add_filter(type, path = nil, options = {}, &block) path, options = //, path if path.respond_to?(:each_pair) filters[type] << compile!(type, path || //, block, options) end
def after(path = nil, options = {}, &block)
context as route handlers and may access/modify the request and
Define an after filter; runs after all requests within the same
def after(path = nil, options = {}, &block) add_filter(:after, path, options, &block) end
def before(path = nil, options = {}, &block)
context as route handlers and may access/modify the request and
Define a before filter; runs before all requests within the same
def before(path = nil, options = {}, &block) add_filter(:before, path, options, &block) end
def build(app)
Creates a Rack::Builder instance with all the middleware set up and
def build(app) builder = Rack::Builder.new setup_default_middleware builder setup_middleware builder builder.run app builder end
def call(env)
def call(env) dup.call!(env) end
def call(env)
def call(env) synchronize { prototype.call(env) } end
def call!(env) # :nodoc:
def call!(env) # :nodoc: @env = env @request = Request.new(env) @response = Response.new @params = indifferent_params(@request.params) template_cache.clear if settings.reload_templates force_encoding(@params) @response['Content-Type'] = nil invoke { dispatch! } invoke { error_block!(response.status) } unless @env['sinatra.error'] unless @response['Content-Type'] if Array === body and body[0].respond_to? :content_type content_type body[0].content_type else content_type :html end end @response.finish end
def caller_files
Like Kernel#caller but excluding certain magic entries and without
def caller_files cleaned_caller(1).flatten end
def caller_locations
Like caller_files, but containing Arrays rather than strings with the
def caller_locations cleaned_caller 2 end
def cleaned_caller(keep = 3)
def cleaned_caller(keep = 3) caller(1). map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } end
def compile(path)
def compile(path) keys = [] if path.respond_to? :to_str ignore = "" pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c| ignore << escaped(c).join if c.match(/[\.@]/) patt = encoded(c) patt.gsub(/%[\da-fA-F]{2}/) do |match| match.split(//).map {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join end end pattern.gsub!(/((:\w+)|\*)/) do |match| if match == "*" keys << 'splat' "(.*?)" else keys << $2[1..-1] ignore_pattern = safe_ignore(ignore) ignore_pattern end end [/\A#{pattern}\z/, keys] elsif path.respond_to?(:keys) && path.respond_to?(:match) [path, path.keys] elsif path.respond_to?(:names) && path.respond_to?(:match) [path, path.names] elsif path.respond_to? :match [path, keys] else raise TypeError, path end end
def compile!(verb, path, block, options = {})
def compile!(verb, path, block, options = {}) options.each_pair { |option, args| send(option, *args) } method_name = "#{verb} #{path}" unbound_method = generate_method(method_name, &block) pattern, keys = compile path conditions, @conditions = @conditions, [] [ pattern, keys, conditions, block.arity != 0 ? proc { |a,p| unbound_method.bind(a).call(*p) } : proc { |a,p| unbound_method.bind(a).call } ] end
def condition(name = "#{caller.first[/`.*'/]} condition", &block)
Add a route condition. The route is considered non-matching when the
def condition(name = "#{caller.first[/`.*'/]} condition", &block) @conditions << generate_method(name, &block) end
def configure(*envs, &block)
Set configuration options for Sinatra and/or the app.
def configure(*envs, &block) yield self if envs.empty? || envs.include?(environment.to_sym) end
def define_singleton(name, content = Proc.new)
def define_singleton(name, content = Proc.new) # replace with call to singleton_class once we're 1.9 only (class << self; self; end).class_eval do undef_method(name) if method_defined? name String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) end end
def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
def detect_rack_handler
def detect_rack_handler servers = Array(server) servers.each do |server_name| begin return Rack::Handler.get(server_name.to_s) rescue LoadError, NameError end end fail "Server handler (#{servers.join(',')}) not found." end
def development?; environment == :development end
def development?; environment == :development end
def disable(*opts)
def disable(*opts) opts.each { |key| set(key, false) } end
def dispatch!
def dispatch! invoke do static! if settings.static? && (request.get? || request.head?) filter! :before route! end rescue ::Exception => boom invoke { handle_exception!(boom) } ensure filter! :after unless env['sinatra.static_file'] end
def dump_errors!(boom)
def dump_errors!(boom) msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") @env['rack.errors'].puts(msg) end
def enable(*opts)
def enable(*opts) opts.each { |key| set(key, true) } end
def encoded(char)
def encoded(char) enc = URI.escape(char) enc = "(?:#{escaped(char, enc).join('|')})" if enc == char enc = "(?:#{enc}|#{encoded('+')})" if char == " " enc end
def error(*codes, &block)
class, or an HTTP status code to specify which errors should be
Define a custom error handler. Optionally takes either an Exception
def error(*codes, &block) args = compile! "ERROR", //, block codes = codes.map { |c| Array(c) }.flatten codes << Exception if codes.empty? codes.each { |c| (@errors[c] ||= []) << args } end
def error_block!(key, *block_params)
def error_block!(key, *block_params) base = settings while base.respond_to?(:errors) next base = base.superclass unless args_array = base.errors[key] args_array.reverse_each do |args| first = args == args_array.first args += [block_params] resp = process_route(*args) return resp unless resp.nil? && !first end end return false unless key.respond_to? :superclass and key.superclass < Exception error_block!(key.superclass, *block_params) end
def escaped(char, enc = URI.escape(char))
def escaped(char, enc = URI.escape(char)) [Regexp.escape(enc), URI.escape(char, /./)] end
def extensions
def extensions if superclass.respond_to?(:extensions) (@extensions + superclass.extensions).uniq else @extensions end end
def filter!(type, base = settings)
def filter!(type, base = settings) filter! type, base.superclass if base.superclass.respond_to?(:filters) base.filters[type].each { |args| process_route(*args) } end
def force_encoding(*args) settings.force_encoding(*args) end
The latter might not be necessary if Rack handles it one day.
* casting params to Encoding.default_external
* defaulting to UTF-8
Fixes encoding issues by
def force_encoding(*args) settings.force_encoding(*args) end
def forward
def forward fail "downstream app not set" unless @app.respond_to? :call status, headers, body = @app.call env @response.status = status @response.body = body @response.headers.merge! headers nil end
def generate_method(method_name, &block)
def generate_method(method_name, &block) define_method(method_name, &block) method = instance_method method_name remove_method method_name method end
def get(path, opts = {}, &block)
Defining a `GET` handler also automatically defines
def get(path, opts = {}, &block) conditions = @conditions.dup route('GET', path, opts, &block) @conditions = conditions route('HEAD', path, opts, &block) end
def halt(*response)
Exit the current block, halts any further processing
def halt(*response) response = response.first if response.length == 1 throw :halt, response end
def handle_exception!(boom)
def handle_exception!(boom) @env['sinatra.error'] = boom if boom.respond_to? :http_status status(boom.http_status) elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 status(boom.code) else status(500) end status(500) unless status.between? 400, 599 if server_error? dump_errors! boom if settings.dump_errors? raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler end if not_found? headers['X-Cascade'] = 'pass' if settings.x_cascade? body '<h1>Not Found</h1>' end res = error_block!(boom.class, boom) || error_block!(status, boom) return res if res or not server_error? raise boom if settings.raise_errors? or settings.show_exceptions? error_block! Exception, boom end
def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
def helpers(*extensions, &block)
Makes the methods defined in the block and in the Modules given
def helpers(*extensions, &block) class_eval(&block) if block_given? include(*extensions) if extensions.any? end
def host_name(pattern)
def host_name(pattern) condition { pattern === request.host } end
def indifferent_hash
def indifferent_hash Hash.new {|hash,key| hash[key.to_s] if Symbol === key } end
def indifferent_params(object)
def indifferent_params(object) case object when Hash new_hash = indifferent_hash object.each { |key, value| new_hash[key] = indifferent_params(value) } new_hash when Array object.map { |item| indifferent_params(item) } else object end end
def inherited(subclass)
def inherited(subclass) subclass.reset! subclass.set :app_file, caller_files.first unless subclass.app_file? super end
def initialize(app = nil)
def initialize(app = nil) super() @app = app @template_cache = Tilt::Cache.new yield self if block_given? end
def inline_templates=(file = nil)
Load embeded templates from the file; uses the caller's __FILE__
def inline_templates=(file = nil) file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file begin io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2) rescue Errno::ENOENT app, data = nil end if data if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m encoding = $2 else encoding = settings.default_encoding end lines = app.count("\n") + 1 template = nil force_encoding data, encoding data.each_line do |line| lines += 1 if line =~ /^@@\s*(.*\S)\s*$/ template = force_encoding('', encoding) templates[$1.to_sym] = [template, file, lines] elsif template template << line end end end end
def invoke
def invoke res = catch(:halt) { yield } res = [res] if Fixnum === res or String === res if Array === res and Fixnum === res.first res = res.dup status(res.shift) body(res.pop) headers(*res) elsif res.respond_to? :each body res end nil # avoid double setting the same response tuple twice end
def invoke_hook(name, *args)
def invoke_hook(name, *args) extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } end
def layout(name = :layout, &block)
def layout(name = :layout, &block) template name, &block end
def middleware
def middleware if superclass.respond_to?(:middleware) superclass.middleware + @middleware else @middleware end end
def mime_type(type, value = nil)
def mime_type(type, value = nil) return type if type.nil? || type.to_s.include?('/') type = ".#{type}" unless type.to_s[0] == ?. return Rack::Mime.mime_type(type, nil) unless value Rack::Mime::MIME_TYPES[type] = value end
def mime_types(type)
mime_types :html # => ['text/html']
provides all mime types matching type, including deprecated types:
def mime_types(type) type = mime_type type type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] end
def new(*args, &bk)
pipeline. The object is guaranteed to respond to #call but may not be
Create a new instance of the class fronted by its middleware
def new(*args, &bk) instance = new!(*args, &bk) Wrapper.new(build(instance).to_app, instance) end
def not_found(&block)
def not_found(&block) error(404, &block) error(Sinatra::NotFound, &block) end
def options
def options warn "Sinatra::Base#options is deprecated and will be removed, " \ "use #settings instead." settings end
def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
def pass(&block)
If there are no more matching routes, Sinatra will
Pass control to the next matching route.
def pass(&block) throw :pass, block end
def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
def process_route(pattern, keys, conditions, block = nil, values = [])
Revert params afterwards.
with keys and call the given block.
If the current request matches pattern and conditions, fill params
def process_route(pattern, keys, conditions, block = nil, values = []) route = @request.path_info route = '/' if route.empty? and not settings.empty_path_info? return unless match = pattern.match(route) values += match.captures.to_a.map { |v| force_encoding URI.decode(v) if v } if values.any? original, @params = params, params.merge('splat' => [], 'captures' => values) keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v } end catch(:pass) do conditions.each { |c| throw :pass if c.bind(self).call == false } block ? block[self, values] : yield(self, values) end ensure @params = original if original end
def production?; environment == :production end
def production?; environment == :production end
def prototype
def prototype @prototype ||= new end
def provides(*types)
def provides(*types) types.map! { |t| mime_types(t) } types.flatten! condition do if type = response['Content-Type'] types.include? type or types.include? type[/^[^;]+/] elsif type = request.preferred_type(types) params = (type.respond_to?(:params) ? type.params : {}) content_type(type, params) true else false end end end
def public=(value)
def public=(value) warn ":public is no longer used to avoid overloading Module#public, use :public_dir instead" set(:public_folder, value) end
def public_dir
def public_dir public_folder end
def public_dir=(value)
def public_dir=(value) self.public_folder = value end
def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
def quit!(server, handler_name)
def quit!(server, handler_name) # Use Thin's hard #stop! if available, otherwise just #stop. server.respond_to?(:stop!) ? server.stop! : server.stop $stderr.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i end
def register(*extensions, &block)
Register an extension. Alternatively take a block from which an
def register(*extensions, &block) extensions << Module.new(&block) if block_given? @extensions += extensions extensions.each do |extension| extend extension extension.registered(self) if extension.respond_to?(:registered) end end
def reset!
Removes all routes, filters, middleware and extension hooks from the
def reset! @conditions = [] @routes = {} @filters = {:before => [], :after => []} @errors = {} @middleware = [] @prototype = nil @extensions = [] if superclass.respond_to?(:templates) @templates = Hash.new { |hash,key| superclass.templates[key] } else @templates = {} end end
def route(verb, path, options = {}, &block)
def route(verb, path, options = {}, &block) # Because of self.options.host host_name(options.delete(:host)) if options.key?(:host) enable :empty_path_info if path == "" and empty_path_info.nil? signature = compile!(verb, path, block, options) (@routes[verb] ||= []) << signature invoke_hook(:route_added, verb, path, block) signature end
def route!(base = settings, pass_block = nil)
def route!(base = settings, pass_block = nil) if routes = base.routes[@request.request_method] routes.each do |pattern, keys, conditions, block| pass_block = process_route(pattern, keys, conditions) do |*args| route_eval { block[*args] } end end end # Run routes defined in superclass. if base.superclass.respond_to?(:routes) return route!(base.superclass, pass_block) end route_eval(&pass_block) if pass_block route_missing end
def route_eval
def route_eval throw :halt, yield end
def route_missing
a NotFound exception. Subclasses can override this method to perform
as middleware (@app is non-nil); when no downstream app is set, raise
implementation is to forward the request downstream when running
No matching route was found or all routes passed. The default
def route_missing if @app forward else raise NotFound end end
def run!(options = {})
Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
Run the Sinatra app as a self-hosted server using
def run!(options = {}) set options handler = detect_rack_handler handler_name = handler.name.gsub(/.*::/, '') server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} handler.run self, server_settings.merge(:Port => port, :Host => bind) do |server| unless handler_name =~ /cgi/i $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " + "on #{port} for #{environment} with backup from #{handler_name}" end [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } } server.threaded = settings.threaded if server.respond_to? :threaded= set :running, true yield server if block_given? end rescue Errno::EADDRINUSE $stderr.puts "== Someone is already performing on port #{port}!" end
def safe_ignore(ignore)
def safe_ignore(ignore) unsafe_ignore = [] ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex| unsafe_ignore << hex[1..2] '' end unsafe_patterns = unsafe_ignore.map do |unsafe| chars = unsafe.split(//).map do |char| if char =~ /[A-Z]/ char <<= char.tr('A-Z', 'a-z') end char end "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])" end if unsafe_patterns.length > 0 "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)" else "([^#{ignore}/?#]+)" end end
def set(option, value = (not_set = true), ignore_setter = false, &block)
Sets an option to the given value. If the value is a proc,
def set(option, value = (not_set = true), ignore_setter = false, &block) raise ArgumentError if block and !not_set value, not_set = block, false if block if not_set raise ArgumentError unless option.respond_to?(:each) option.each { |k,v| set(k, v) } return self end if respond_to?("#{option}=") and not ignore_setter return __send__("#{option}=", value) end setter = proc { |val| set option, val, true } getter = proc { value } case value when Proc getter = value when Symbol, Fixnum, FalseClass, TrueClass, NilClass getter = value.inspect when Hash setter = proc do |val| val = value.merge val if Hash === val set option, val, true end end define_singleton("#{option}=", setter) if setter define_singleton(option, getter) if getter define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" self end
def settings
def settings self.class.settings end
def setup_common_logger(builder)
def setup_common_logger(builder) builder.use Sinatra::CommonLogger end
def setup_custom_logger(builder)
def setup_custom_logger(builder) if logging.respond_to? :to_int builder.use Rack::Logger, logging else builder.use Rack::Logger end end
def setup_default_middleware(builder)
def setup_default_middleware(builder) builder.use ExtendedRack builder.use ShowExceptions if show_exceptions? builder.use Rack::MethodOverride if method_override? builder.use Rack::Head setup_logging builder setup_sessions builder setup_protection builder end
def setup_logging(builder)
def setup_logging(builder) if logging? setup_common_logger(builder) setup_custom_logger(builder) elsif logging == false setup_null_logger(builder) end end
def setup_middleware(builder)
def setup_middleware(builder) middleware.each { |c,a,b| builder.use(c, *a, &b) } end
def setup_null_logger(builder)
def setup_null_logger(builder) builder.use Rack::NullLogger end
def setup_protection(builder)
def setup_protection(builder) return unless protection? options = Hash === protection ? protection.dup : {} protect_session = options.fetch(:session) { sessions? } options[:except] = Array options[:except] options[:except] += [:session_hijacking, :remote_token] unless protect_session options[:reaction] ||= :drop_session builder.use Rack::Protection, options end
def setup_sessions(builder)
def setup_sessions(builder) return unless sessions? options = {} options[:secret] = session_secret if session_secret? options.merge! sessions.to_hash if sessions.respond_to? :to_hash builder.use Rack::Session::Cookie, options end
def static!
Attempt to serve static files from public directory. Throws :halt when
def static! return if (public_dir = settings.public_folder).nil? public_dir = File.expand_path(public_dir) path = File.expand_path(public_dir + unescape(request.path_info)) return unless path.start_with?(public_dir) and File.file?(path) env['sinatra.static_file'] = path cache_control(*settings.static_cache_control) if settings.static_cache_control? send_file path, :disposition => nil end
def synchronize(&block)
def synchronize(&block) if lock? @@mutex.synchronize(&block) else yield end end
def template(name, &block)
def template(name, &block) filename, line = caller_locations.first templates[name] = [block, filename, line.to_i] end
def test?; environment == :test end
def test?; environment == :test end
def use(middleware, *args, &block)
def use(middleware, *args, &block) @prototype = nil @middleware << [middleware, args, block] end
def user_agent(pattern)
Condition for matching user agent. Parameter should be Regexp.
def user_agent(pattern) condition do if request.user_agent.to_s =~ pattern @params[:agent] = $~[1..-1] true else false end end end
def warn(message)
def warn(message) super message + "\n\tfrom #{cleaned_caller.first.join(':')}" end