| Class | Sinatra::Base |
| In: |
lib/sinatra/base.rb
|
| Parent: | Object |
Base class for all Sinatra applications and middleware.
| CALLERS_TO_IGNORE | = | [ /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code /lib\/tilt.*\.rb$/, # all tilt code /\(.*\)/, # generated code /custom_require\.rb$/, # rubygems require hacks /active_support/, # active_support require hacks ] |
| user_agent | -> | agent |
| after_filters | [R] | |
| app | [RW] | |
| before_filters | [R] | |
| env | [RW] | |
| errors | [R] | |
| params | [RW] | |
| request | [RW] | |
| response | [RW] | |
| routes | [R] | |
| templates | [R] |
# File lib/sinatra/base.rb, line 962
962: def call(env)
963: synchronize { prototype.call(env) }
964: end
Like Kernel#caller but excluding certain magic entries and without line / method information; the resulting array contains filenames only.
# File lib/sinatra/base.rb, line 1012
1012: def caller_files
1013: caller_locations.
1014: map { |file,line| file }
1015: end
# File lib/sinatra/base.rb, line 1017
1017: def caller_locations
1018: caller(1).
1019: map { |line| line.split(/:(?=\d|in )/)[0,2] }.
1020: reject { |file,line| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1021: end
# File lib/sinatra/base.rb, line 830
830: def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk end
Defining a `GET` handler also automatically defines a `HEAD` handler.
# File lib/sinatra/base.rb, line 820
820: def get(path, opts={}, &block)
821: conditions = @conditions.dup
822: route('GET', path, opts, &block)
823:
824: @conditions = conditions
825: route('HEAD', path, opts, &block)
826: end
# File lib/sinatra/base.rb, line 831
831: def head(path, opts={}, &bk); route 'HEAD', path, opts, &bk end
Makes the methods defined in the block and in the Modules given in `extensions` available to the handlers and templates
# File lib/sinatra/base.rb, line 892
892: def helpers(*extensions, &block)
893: class_eval(&block) if block_given?
894: include(*extensions) if extensions.any?
895: end
Create a new instance of the class fronted by its middleware pipeline. The object is guaranteed to respond to call but may not be an instance of the class new was called on.
# File lib/sinatra/base.rb, line 950
950: def new(*args, &bk)
951: builder = Rack::Builder.new
952: builder.use Rack::Session::Cookie if sessions?
953: builder.use Rack::CommonLogger if logging?
954: builder.use Rack::MethodOverride if methodoverride?
955: builder.use ShowExceptions if show_exceptions?
956: middleware.each { |c,a,b| builder.use(c, *a, &b) }
957:
958: builder.run super
959: builder.to_app
960: end
# File lib/sinatra/base.rb, line 372
372: def initialize(app=nil)
373: @app = app
374: @template_cache = Tilt::Cache.new
375: yield self if block_given?
376: end
# File lib/sinatra/base.rb, line 829
829: def post(path, opts={}, &bk); route 'POST', path, opts, &bk end
# File lib/sinatra/base.rb, line 828
828: def put(path, opts={}, &bk); route 'PUT', path, opts, &bk end
# File lib/sinatra/base.rb, line 897
897: def register(*extensions, &block)
898: extensions << Module.new(&block) if block_given?
899: @extensions += extensions
900: extensions.each do |extension|
901: extend extension
902: extension.registered(self) if extension.respond_to?(:registered)
903: end
904: end
Run the Sinatra app as a self-hosted server using Thin, Mongrel or WEBrick (in that order)
# File lib/sinatra/base.rb, line 924
924: def run!(options={})
925: set options
926: handler = detect_rack_handler
927: handler_name = handler.name.gsub(/.*::/, '')
928: puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
929: "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
930: handler.run self, :Host => host, :Port => port do |server|
931: trap(:INT) do
932: ## Use thins' hard #stop! if available, otherwise just #stop
933: server.respond_to?(:stop!) ? server.stop! : server.stop
934: puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
935: end
936: set :running, true
937: end
938: rescue Errno::EADDRINUSE => e
939: puts "== Someone is already performing on port #{port}!"
940: end
Use the specified Rack middleware
# File lib/sinatra/base.rb, line 917
917: def use(middleware, *args, &block)
918: @prototype = nil
919: @middleware << [middleware, args, block]
920: end
# File lib/sinatra/base.rb, line 862
862: def compile(path)
863: keys = []
864: if path.respond_to? :to_str
865: special_chars = %w{. + ( )}
866: pattern =
867: path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
868: case match
869: when "*"
870: keys << 'splat'
871: "(.*?)"
872: when *special_chars
873: Regexp.escape(match)
874: else
875: keys << $2[1..-1]
876: "([^/?&#]+)"
877: end
878: end
879: [/^#{pattern}$/, keys]
880: elsif path.respond_to?(:keys) && path.respond_to?(:match)
881: [path, path.keys]
882: elsif path.respond_to? :match
883: [path, keys]
884: else
885: raise TypeError, path
886: end
887: end
# File lib/sinatra/base.rb, line 967
967: def detect_rack_handler
968: servers = Array(self.server)
969: servers.each do |server_name|
970: begin
971: return Rack::Handler.get(server_name.downcase)
972: rescue LoadError
973: rescue NameError
974: end
975: end
976: fail "Server handler (#{servers.join(',')}) not found."
977: end
Extension modules registered on this class and all superclasses.
# File lib/sinatra/base.rb, line 663
663: def extensions
664: if superclass.respond_to?(:extensions)
665: (@extensions + superclass.extensions).uniq
666: else
667: @extensions
668: end
669: end
# File lib/sinatra/base.rb, line 786
786: def host_name(pattern)
787: condition { pattern === request.host }
788: end
# File lib/sinatra/base.rb, line 979
979: def inherited(subclass)
980: subclass.reset!
981: super
982: end
Load embeded templates from the file; uses the caller‘s FILE when no file is specified.
# File lib/sinatra/base.rb, line 732
732: def inline_templates=(file=nil)
733: file = (file.nil? || file == true) ? caller_files.first : file
734:
735: begin
736: app, data =
737: ::IO.read(file).gsub("\r\n", "\n").split(/^__END__$/, 2)
738: rescue Errno::ENOENT
739: app, data = nil
740: end
741:
742: if data
743: lines = app.count("\n") + 1
744: template = nil
745: data.each_line do |line|
746: lines += 1
747: if line =~ /^@@\s*(.*)/
748: template = ''
749: templates[$1.to_sym] = [template, file, lines]
750: elsif template
751: template << line
752: end
753: end
754: end
755: end
# File lib/sinatra/base.rb, line 858
858: def invoke_hook(name, *args)
859: extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
860: end
# File lib/sinatra/base.rb, line 993
993: def metadef(message, &block)
994: (class << self; self; end).
995: send :define_method, message, &block
996: end
Middleware used in this class and all superclasses.
# File lib/sinatra/base.rb, line 672
672: def middleware
673: if superclass.respond_to?(:middleware)
674: superclass.middleware + @middleware
675: else
676: @middleware
677: end
678: end
Lookup or register a mime type in Rack‘s mime registry.
# File lib/sinatra/base.rb, line 758
758: def mime_type(type, value=nil)
759: return type if type.nil? || type.to_s.include?('/')
760: type = ".#{type}" unless type.to_s[0] == ?.
761: return Rack::Mime.mime_type(type, nil) unless value
762: Rack::Mime::MIME_TYPES[type] = value
763: end
# File lib/sinatra/base.rb, line 802
802: def provides(*types)
803: types = [types] unless types.kind_of? Array
804: types.map!{|t| mime_type(t)}
805:
806: condition {
807: matching_types = (request.accept & types)
808: unless matching_types.empty?
809: response.headers['Content-Type'] = matching_types.first
810: true
811: else
812: false
813: end
814: }
815: end
# File lib/sinatra/base.rb, line 645
645: def reset!
646: @conditions = []
647: @routes = {}
648: @before_filters = []
649: @after_filters = []
650: @errors = {}
651: @middleware = []
652: @prototype = nil
653: @extensions = []
654:
655: if superclass.respond_to?(:templates)
656: @templates = Hash.new { |hash,key| superclass.templates[key] }
657: else
658: @templates = {}
659: end
660: end
# File lib/sinatra/base.rb, line 834
834: def route(verb, path, options={}, &block)
835: # Because of self.options.host
836: host_name(options.delete(:host)) if options.key?(:host)
837:
838: options.each {|option, args| send(option, *args)}
839:
840: pattern, keys = compile(path)
841: conditions, @conditions = @conditions, []
842:
843: define_method "#{verb} #{path}", &block
844: unbound_method = instance_method("#{verb} #{path}")
845: block =
846: if block.arity != 0
847: proc { unbound_method.bind(self).call(*@block_params) }
848: else
849: proc { unbound_method.bind(self).call }
850: end
851:
852: invoke_hook(:route_added, verb, path, block)
853:
854: (@routes[verb] ||= []).
855: push([pattern, keys, conditions, block]).last
856: end
Sets an option to the given value. If the value is a proc, the proc will be called every time the option is accessed.
# File lib/sinatra/base.rb, line 682
682: def set(option, value=self)
683: if value.kind_of?(Proc)
684: metadef(option, &value)
685: metadef("#{option}?") { !!__send__(option) }
686: metadef("#{option}=") { |val| set(option, Proc.new{val}) }
687: elsif value == self && option.respond_to?(:to_hash)
688: option.to_hash.each { |k,v| set(k, v) }
689: elsif respond_to?("#{option}=")
690: __send__ "#{option}=", value
691: else
692: set option, Proc.new{value}
693: end
694: self
695: end
# File lib/sinatra/base.rb, line 985
985: def synchronize(&block)
986: if lock?
987: @@mutex.synchronize(&block)
988: else
989: yield
990: end
991: end
# File lib/sinatra/base.rb, line 790
790: def user_agent(pattern)
791: condition {
792: if request.user_agent =~ pattern
793: @params[:agent] = $~[1..-1]
794: true
795: else
796: false
797: end
798: }
799: end
# File lib/sinatra/base.rb, line 385
385: def call!(env)
386: @env = env
387: @request = Request.new(env)
388: @response = Response.new
389: @params = indifferent_params(@request.params)
390: @template_cache.clear if settings.reload_templates
391:
392: invoke { dispatch! }
393: invoke { error_block!(response.status) }
394:
395: status, header, body = @response.finish
396:
397: # Never produce a body on HEAD requests. Do retain the Content-Length
398: # unless it's "0", in which case we assume it was calculated erroneously
399: # for a manual HEAD response and remove it entirely.
400: if @env['REQUEST_METHOD'] == 'HEAD'
401: body = []
402: header.delete('Content-Length') if header['Content-Length'] == '0'
403: end
404:
405: [status, header, body]
406: end
Forward the request to the downstream app — middleware only.
# File lib/sinatra/base.rb, line 429
429: def forward
430: fail "downstream app not set" unless @app.respond_to? :call
431: status, headers, body = @app.call(@request.env)
432: @response.status = status
433: @response.body = body
434: @response.headers.merge! headers
435: nil
436: end
Exit the current block, halts any further processing of the request, and returns the specified response.
# File lib/sinatra/base.rb, line 416
416: def halt(*response)
417: response = response.first if response.length == 1
418: throw :halt, response
419: end
Run after filters defined on the class and all superclasses.
# File lib/sinatra/base.rb, line 446
446: def after_filter!(base=self.class)
447: after_filter!(base.superclass) if base.superclass.respond_to?(:after_filters)
448: base.after_filters.each { |block| instance_eval(&block) }
449: end
Run before filters defined on the class and all superclasses.
# File lib/sinatra/base.rb, line 440
440: def before_filter!(base=self.class)
441: before_filter!(base.superclass) if base.superclass.respond_to?(:before_filters)
442: base.before_filters.each { |block| instance_eval(&block) }
443: end
# File lib/sinatra/base.rb, line 633
633: def clean_backtrace(trace)
634: return trace unless settings.clean_trace?
635:
636: trace.reject { |line|
637: line =~ /lib\/sinatra.*\.rb/ ||
638: (defined?(Gem) && line.include?(Gem.dir))
639: }.map! { |line| line.gsub(/^\.\//, '') }
640: end
Dispatch a request with error handling.
# File lib/sinatra/base.rb, line 579
579: def dispatch!
580: static! if settings.static? && (request.get? || request.head?)
581: before_filter!
582: route!
583: rescue NotFound => boom
584: handle_not_found!(boom)
585: rescue ::Exception => boom
586: handle_exception!(boom)
587: ensure
588: after_filter! unless env['sinatra.static_file']
589: end
# File lib/sinatra/base.rb, line 626
626: def dump_errors!(boom)
627: backtrace = clean_backtrace(boom.backtrace)
628: msg = ["#{boom.class} - #{boom.message}:",
629: *backtrace].join("\n ")
630: @env['rack.errors'].puts(msg)
631: end
Find an custom error block for the key(s) specified.
# File lib/sinatra/base.rb, line 610
610: def error_block!(*keys)
611: keys.each do |key|
612: base = self.class
613: while base.respond_to?(:errors)
614: if block = base.errors[key]
615: # found a handler, eval and return result
616: res = instance_eval(&block)
617: return res
618: else
619: base = base.superclass
620: end
621: end
622: end
623: nil
624: end
# File lib/sinatra/base.rb, line 599
599: def handle_exception!(boom)
600: @env['sinatra.error'] = boom
601:
602: dump_errors!(boom) if settings.dump_errors?
603: raise boom if settings.raise_errors? || settings.show_exceptions?
604:
605: @response.status = 500
606: error_block! boom.class, Exception
607: end
# File lib/sinatra/base.rb, line 591
591: def handle_not_found!(boom)
592: @env['sinatra.error'] = boom
593: @response.status = 404
594: @response.headers['X-Cascade'] = 'pass'
595: @response.body = ['<h1>Not Found</h1>']
596: error_block! boom.class, NotFound
597: end
# File lib/sinatra/base.rb, line 541
541: def indifferent_hash
542: Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
543: end
Enable string or symbol key access to the nested params hash.
# File lib/sinatra/base.rb, line 533
533: def indifferent_params(params)
534: params = indifferent_hash.merge(params)
535: params.each do |key, value|
536: next unless value.is_a?(Hash)
537: params[key] = indifferent_params(value)
538: end
539: end
Run the block with ‘throw :halt’ support and apply result to the response.
# File lib/sinatra/base.rb, line 546
546: def invoke(&block)
547: res = catch(:halt) { instance_eval(&block) }
548: return if res.nil?
549:
550: case
551: when res.respond_to?(:to_str)
552: @response.body = [res]
553: when res.respond_to?(:to_ary)
554: res = res.to_ary
555: if Fixnum === res.first
556: if res.length == 3
557: @response.status, headers, body = res
558: @response.body = body if body
559: headers.each { |k, v| @response.headers[k] = v } if headers
560: elsif res.length == 2
561: @response.status = res.first
562: @response.body = res.last
563: else
564: raise TypeError, "#{res.inspect} not supported"
565: end
566: else
567: @response.body = res
568: end
569: when res.respond_to?(:each)
570: @response.body = res
571: when (100...599) === res
572: @response.status = res
573: end
574:
575: res
576: end
Run routes defined on the class and all superclasses.
# File lib/sinatra/base.rb, line 452
452: def route!(base=self.class, pass_block=nil)
453: if routes = base.routes[@request.request_method]
454: original_params = @params
455: path = unescape(@request.path_info)
456:
457: routes.each do |pattern, keys, conditions, block|
458: if match = pattern.match(path)
459: values = match.captures.to_a
460: params =
461: if keys.any?
462: keys.zip(values).inject({}) do |hash,(k,v)|
463: if k == 'splat'
464: (hash[k] ||= []) << v
465: else
466: hash[k] = v
467: end
468: hash
469: end
470: elsif values.any?
471: {'captures' => values}
472: else
473: {}
474: end
475: @params = original_params.merge(params)
476: @block_params = values
477:
478: pass_block = catch(:pass) do
479: conditions.each { |cond|
480: throw :pass if instance_eval(&cond) == false }
481: route_eval(&block)
482: end
483: end
484: end
485:
486: @params = original_params
487: end
488:
489: # Run routes defined in superclass.
490: if base.superclass.respond_to?(:routes)
491: route! base.superclass, pass_block
492: return
493: end
494:
495: route_eval(&pass_block) if pass_block
496:
497: route_missing
498: end
No matching route was found or all routes passed. The default implementation is to forward the request downstream when running as middleware (@app is non-nil); when no downstream app is set, raise a NotFound exception. Subclasses can override this method to perform custom route miss logic.
# File lib/sinatra/base.rb, line 510
510: def route_missing
511: if @app
512: forward
513: else
514: raise NotFound
515: end
516: end
Attempt to serve static files from public directory. Throws :halt when a matching file is found, returns nil otherwise.
# File lib/sinatra/base.rb, line 520
520: def static!
521: return if (public_dir = settings.public).nil?
522: public_dir = File.expand_path(public_dir)
523:
524: path = File.expand_path(public_dir + unescape(request.path_info))
525: return if path[0, public_dir.length] != public_dir
526: return unless File.file?(path)
527:
528: env['sinatra.static_file'] = path
529: send_file path, :disposition => nil
530: end