class Sinatra::Base
Base class for all Sinatra applications and middleware.
def before(&block)
within the same context as route handlers and may access/modify the
Define a before filter. Filters are run before all requests
def before(&block) @filters << block 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)
def call!(env) @env = env @request = Request.new(env) @response = Response.new @params = nil invoke { dispatch! } invoke { error_block!(response.status) } status, header, body = @response.finish # Never produce a body on HEAD requests. Do retain the Content-Length # unless it's "0", in which case we assume it was calculated erroneously # for a manual HEAD response and remove it entirely. if @env['REQUEST_METHOD'] == 'HEAD' body = [] header.delete('Content-Length') if header['Content-Length'] == '0' end [status, header, body] end
def caller_files
Like Kernel#caller but excluding certain magic entries and without
def caller_files caller_locations. map { |file,line| file } end
def caller_locations
def caller_locations caller(1). map { |line| line.split(/:(?=\d|in )/)[0,2] }. reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } end
def clean_backtrace(trace)
def clean_backtrace(trace) return trace unless options.clean_trace? trace.reject { |line| line =~ /lib\/sinatra.*\.rb/ || (defined?(Gem) && line.include?(Gem.dir)) }.map! { |line| line.gsub(/^\.\//, '') } end
def compile(path)
def compile(path) keys = [] if path.respond_to? :to_str special_chars = %w{. + ( )} pattern = path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match| case match when "*" keys << 'splat' "(.*?)" when *special_chars Regexp.escape(match) else keys << $2[1..-1] "([^/?&#]+)" end end [/^#{pattern}$/, keys] elsif path.respond_to?(:keys) && path.respond_to?(:match) [path, path.keys] elsif path.respond_to? :match [path, keys] else raise TypeError, path end end
def condition(&block)
Add a route condition. The route is considered non-matching when the
def condition(&block) @conditions << 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 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(self.server) servers.each do |server_name| begin return Rack::Handler.get(server_name.downcase) rescue LoadError rescue 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! route! rescue NotFound => boom handle_not_found!(boom) rescue ::Exception => boom handle_exception!(boom) end
def dump_errors!(boom)
def dump_errors!(boom) backtrace = clean_backtrace(boom.backtrace) msg = ["#{boom.class} - #{boom.message}:", *backtrace].join("\n ") @env['rack.errors'].write(msg) end
def dupe_routes
def dupe_routes routes.inject({}) do |hash,(request_method,routes)| hash[request_method] = routes.dup hash end end
def enable(*opts)
def enable(*opts) opts.each { |key| set(key, true) } end
def error(codes=Exception, &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=Exception, &block) if codes.respond_to? :each codes.each { |err| error(err, &block) } else @errors[codes] = block end end
def error_block!(*keys)
def error_block!(*keys) errmap = self.class.errors keys.each do |key| if block = errmap[key] res = instance_eval(&block) return res end end nil end
def extensions
def extensions (@extensions + (superclass.extensions rescue [])).uniq end
def forward
def forward fail "downstream app not set" unless @app.respond_to? :call status, headers, body = @app.call(@request.env) @response.status = status @response.body = body @response.headers.merge! headers nil 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 dump_errors!(boom) if options.dump_errors? raise boom if options.raise_errors? || options.show_exceptions? @response.status = 500 error_block! boom.class, Exception end
def handle_not_found!(boom)
def handle_not_found!(boom) @env['sinatra.error'] = boom @response.status = 404 @response.body = ['<h1>Not Found</h1>'] error_block! boom.class, NotFound 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(params)
def indifferent_params(params) params = indifferent_hash.merge(params) params.each do |key, value| next unless value.is_a?(Hash) params[key] = indifferent_params(value) end end
def inherited(subclass)
def inherited(subclass) subclass.reset! self super end
def initialize(app=nil)
def initialize(app=nil) @app = app 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 begin app, data = ::IO.read(file).gsub("\r\n", "\n").split(/^__END__$/, 2) rescue Errno::ENOENT app, data = nil end if data lines = app.count("\n") + 1 template = nil data.each_line do |line| lines += 1 if line =~ /^@@\s*(.*)/ template = '' templates[$1.to_sym] = { :filename => file, :line => lines, :template => template } elsif template template << line end end end end
def invoke(&block)
def invoke(&block) res = catch(:halt) { instance_eval(&block) } return if res.nil? case when res.respond_to?(:to_str) @response.body = [res] when res.respond_to?(:to_ary) res = res.to_ary if Fixnum === res.first if res.length == 3 @response.status, headers, body = res @response.body = body if body headers.each { |k, v| @response.headers[k] = v } if headers elsif res.length == 2 @response.status = res.first @response.body = res.last else raise TypeError, "#{res.inspect} not supported" end else @response.body = res end when res.respond_to?(:each) @response.body = res when (100...599) === res @response.status = res end res 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 media_type(type, value=nil)
def media_type(type, value=nil) sinatra_warn "media_type is deprecated; use mime_type instead" mime_type(type, value) end
def metadef(message, &block)
def metadef(message, &block) (class << self; self; end). send :define_method, message, &block 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 nested_params(params)
hash. Rack 1.0 has a built in implementation of this method - remove
Recursively replace the params hash with a nested indifferent
def nested_params(params) return indifferent_hash.merge(params) if !params.keys.join.include?('[') params.inject indifferent_hash do |res, (key,val)| if key.include?('[') head = key.split(/[\]\[]+/) last = head.pop head.inject(res){ |hash,k| hash[k] ||= indifferent_hash }[last] = val else res[key] = val end res end 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) builder = Rack::Builder.new builder.use Rack::Session::Cookie if sessions? && !test? builder.use Rack::CommonLogger if logging? builder.use Rack::MethodOverride if method_override? builder.use ShowExceptions if show_exceptions? @middleware.each { |c,a,b| builder.use(c, *a, &b) } builder.run super builder.to_app end
def not_found(&block)
def not_found(&block) error 404, &block end
def pass
If there are no more matching routes, Sinatra will
Pass control to the next matching route.
def pass throw :pass end
def post(path, opts={}, &bk); route 'POST', path, opts, &bk end
def post(path, opts={}, &bk); route 'POST', path, opts, &bk 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 = [types] unless types.kind_of? Array types.map!{|t| mime_type(t)} condition { matching_types = (request.accept & types) unless matching_types.empty? response.headers['Content-Type'] = matching_types.first true else false end } end
def put(path, opts={}, &bk); route 'PUT', path, opts, &bk end
def put(path, opts={}, &bk); route 'PUT', path, opts, &bk end
def register(*extensions, &block)
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!(base=superclass)
def reset!(base=superclass) @routes = base.dupe_routes @templates = base.templates.dup @conditions = [] @filters = base.filters.dup @errors = base.errors.dup @middleware = base.middleware.dup @prototype = nil @extensions = [] 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) options.each {|option, args| send(option, *args)} pattern, keys = compile(path) conditions, @conditions = @conditions, [] define_method "#{verb} #{path}", &block unbound_method = instance_method("#{verb} #{path}") block = if block.arity != 0 lambda { unbound_method.bind(self).call(*@block_params) } else lambda { unbound_method.bind(self).call } end invoke_hook(:route_added, verb, path, block) (routes[verb] ||= []). push([pattern, keys, conditions, block]).last end
def route!
def route! # enable nested params in Rack < 1.0; allow indifferent access @params = if Rack::Utils.respond_to?(:parse_nested_query) indifferent_params(@request.params) else nested_params(@request.params) end # before filters self.class.filters.each { |block| instance_eval(&block) } # routes if routes = self.class.routes[@request.request_method] original_params = @params path = unescape(@request.path_info) routes.each do |pattern, keys, conditions, block| if match = pattern.match(path) values = match.captures.to_a params = if keys.any? keys.zip(values).inject({}) do |hash,(k,v)| if k == 'splat' (hash[k] ||= []) << v else hash[k] = v end hash end elsif values.any? {'captures' => values} else {} end @params = original_params.merge(params) @block_params = values catch(:pass) do conditions.each { |cond| throw :pass if instance_eval(&cond) == false } route_eval(&block) end end end end route_missing end
def route_eval(&block)
def route_eval(&block) throw :halt, instance_eval(&block) 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={})
Run the Sinatra app as a self-hosted server using
def run!(options={}) set options handler = detect_rack_handler handler_name = handler.name.gsub(/.*::/, '') puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " + "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i handler.run self, :Host => host, :Port => port do |server| trap(:INT) do ## Use thins' hard #stop! if available, otherwise just #stop server.respond_to?(:stop!) ? server.stop! : server.stop puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i end end rescue Errno::EADDRINUSE => e puts "== Someone is already performing on port #{port}!" end
def set(option, value=self)
Sets an option to the given value. If the value is a proc,
def set(option, value=self) if value.kind_of?(Proc) metadef(option, &value) metadef("#{option}?") { !!__send__(option) } metadef("#{option}=") { |val| metadef(option, &Proc.new{val}) } elsif value == self && option.respond_to?(:to_hash) option.to_hash.each { |k,v| set(k, v) } elsif respond_to?("#{option}=") __send__ "#{option}=", value else set option, Proc.new{value} end self end
def settings
def settings self.class 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] = { :filename => filename, :line => line, :template => block } 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 use_in_file_templates!(file=nil)
def use_in_file_templates!(file=nil) sinatra_warn "use_in_file_templates! is deprecated; " \ "use enable :inline_templates instead" set :inline_templates, file end
def user_agent(pattern)
def user_agent(pattern) condition { if request.user_agent =~ pattern @params[:agent] = $~[1..-1] true else false end } end