lib/sinatra/base.rb in Syd-sinatra-0.9.0.2 vs lib/sinatra/base.rb in Syd-sinatra-0.9.0.4

- old
+ new

@@ -123,27 +123,37 @@ params = '; filename="%s"' % File.basename(filename) response['Content-Disposition'] << params end end - # Use the contents of the file as the response body and attempt to + # Use the contents of the file at +path+ as the response body. def send_file(path, opts={}) stat = File.stat(path) last_modified stat.mtime + content_type media_type(opts[:type]) || media_type(File.extname(path)) || response['Content-Type'] || 'application/octet-stream' + response['Content-Length'] ||= (opts[:length] || stat.size).to_s + + if opts[:disposition] == 'attachment' || opts[:filename] + attachment opts[:filename] || path + elsif opts[:disposition] == 'inline' + response['Content-Disposition'] = 'inline' + end + halt StaticFile.open(path, 'rb') rescue Errno::ENOENT not_found end class StaticFile < ::File #:nodoc: alias_method :to_path, :path def each + rewind while buf = read(8192) yield buf end end end @@ -241,11 +251,10 @@ instance = ::ERB.new(data) locals = options[:locals] || {} locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" } src = "#{locals_assigns.join("\n")}\n#{instance.src}" eval src, binding, '(__ERB__)', locals_assigns.length + 1 - instance.result(binding) end def haml(template, options={}) require 'haml' unless defined? ::Haml options[:options] ||= self.class.haml if self.class.respond_to? :haml @@ -308,40 +317,47 @@ def call!(env) @env = env @request = Request.new(env) @response = Response.new @params = nil - error_detection { dispatch! } + + invoke { dispatch! } + invoke { error_block!(response.status) } + + @response.body = [] if @env['REQUEST_METHOD'] == 'HEAD' @response.finish end def options self.class end def halt(*response) - throw :halt, *response + response = response.first if response.length == 1 + throw :halt, response end def pass throw :pass end private - def dispatch! - self.class.filters.each do |block| - res = catch(:halt) { instance_eval(&block) ; :continue } - return unless res == :continue - end + # Run before filters and then locate and run a matching route. + def route! + @params = nested_params(@request.params) + # before filters + self.class.filters.each { |block| instance_eval(&block) } + + # routes if routes = self.class.routes[@request.request_method] + original_params = @params path = @request.path_info - original_params = nested_params(@request.params) - routes.each do |pattern, keys, conditions, method_name| - if pattern =~ path - values = $~.captures.map{|val| val && unescape(val) } + routes.each do |pattern, keys, conditions, block| + if match = pattern.match(path) + values = match.captures.map{|val| val && unescape(val) } params = if keys.any? keys.zip(values).inject({}) do |hash,(k,v)| if k == 'splat' (hash[k] ||= []) << v @@ -355,38 +371,42 @@ else {} end @params = original_params.merge(params) - catch(:pass) { + catch(:pass) do conditions.each { |cond| throw :pass if instance_eval(&cond) == false } - return invoke(method_name) - } + throw :halt, instance_eval(&block) + end end end end + raise NotFound end def nested_params(params) return indifferent_hash.merge(params) if !params.keys.join.include?('[') params.inject indifferent_hash do |res, (key,val)| - if key =~ /\[.*\]/ - splat = key.scan(/(^[^\[]+)|\[([^\]]+)\]/).flatten.compact - head, last = splat[0..-2], splat[-1] - head.inject(res){ |s,v| s[v] ||= indifferent_hash }[last] = 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 indifferent_hash Hash.new {|hash,key| hash[key.to_s] if Symbol === key } end - def invoke(block) + # Run the block with 'throw :halt' support and apply result to the response. + def invoke(&block) res = catch(:halt) { instance_eval(&block) } return if res.nil? case when res.respond_to?(:to_str) @@ -414,36 +434,54 @@ end res end - def error_detection - errmap = self.class.errors - yield + # Dispatch a request with error handling. + def dispatch! + route! rescue NotFound => boom @env['sinatra.error'] = boom @response.status = 404 @response.body = ['<h1>Not Found</h1>'] - handler = errmap[boom.class] || errmap[NotFound] - invoke handler unless handler.nil? + error_block! boom.class, NotFound + rescue ::Exception => boom @env['sinatra.error'] = boom if options.dump_errors? - msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n ") - @env['rack.errors'] << msg + backtrace = clean_backtrace(boom.backtrace) + msg = ["#{boom.class} - #{boom.message}:", *backtrace].join("\n ") + @env['rack.errors'].write(msg) end raise boom if options.raise_errors? @response.status = 500 - invoke errmap[boom.class] || errmap[Exception] - ensure - if @response.status >= 400 && errmap.key?(response.status) - invoke errmap[response.status] + error_block! boom.class, Exception + end + + # Find an custom error block for the key(s) specified. + 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 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 + @routes = {} @filters = [] @conditions = [] @templates = {} @middleware = [] @@ -567,11 +605,11 @@ def get(path, opts={}, &block) conditions = @conditions.dup route('GET', path, opts, &block) @conditions = conditions - head(path, opts) { invoke(block) ; [] } + route('HEAD', path, opts, &block) end def put(path, opts={}, &bk); route 'PUT', path, opts, &bk; end def post(path, opts={}, &bk); route 'POST', path, opts, &bk; end def delete(path, opts={}, &bk); route 'DELETE', path, opts, &bk; end @@ -595,29 +633,38 @@ end def compile(path) keys = [] if path.respond_to? :to_str + special_chars = %w{. + ( )} pattern = - URI.encode(path).gsub(/((:\w+)|\*)/) do |match| - if match == "*" + URI.encode(path).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? :=~ + elsif path.respond_to? :match [path, keys] else raise TypeError, path end end public + def helpers(*modules, &block) + include *modules unless modules.empty? + class_eval(&block) if block + end + def development? ; environment == :development ; end def test? ; environment == :test ; end def production? ; environment == :production ; end def configure(*envs, &block) @@ -628,12 +675,12 @@ reset_middleware @middleware << [middleware, args, block] end def run!(options={}) - set(options) - handler = Rack::Handler.get(server) + 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}" handler.run self, :Host => host, :Port => port do |server| trap(:INT) do @@ -650,10 +697,22 @@ construct_middleware if @callsite.nil? @callsite.call(env) end private + def detect_rack_handler + servers = Array(self.server) + servers.each do |server_name| + begin + return Rack::Handler.get(server_name) + rescue LoadError + rescue NameError + end + end + fail "Server handler (#{servers.join(',')}) not found." + end + def construct_middleware(builder=Rack::Builder.new) builder.use Rack::Session::Cookie if sessions? builder.use Rack::CommonLogger if logging? builder.use Rack::MethodOverride if methodoverride? @middleware.each { |c, args, bk| builder.use(c, *args, &bk) } @@ -689,18 +748,19 @@ end end set :raise_errors, true set :dump_errors, false + set :clean_trace, true set :sessions, false set :logging, false set :methodoverride, false set :static, false set :environment, (ENV['RACK_ENV'] || :development).to_sym set :run, false - set :server, (defined?(Rack::Handler::Thin) ? "thin" : "mongrel") + set :server, %w[thin mongrel webrick] set :host, '0.0.0.0' set :port, 4567 set :app_file, nil set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } @@ -769,11 +829,11 @@ </head> <body> <div id="c"> <img src="/__sinatra__/500.png"> <h1>#{escape_html(heading)}</h1> - <pre class='trace'>#{escape_html(err.backtrace.join("\n"))}</pre> + <pre>#{escape_html(clean_backtrace(err.backtrace) * "\n")}</pre> <h2>Params</h2> <pre>#{escape_html(params.inspect)}</pre> </div> </body> </html> @@ -788,52 +848,65 @@ set :sessions, false set :logging, true set :methodoverride, true set :static, true set :run, false - set :reload, Proc.new { app_file? && development? } + set :reload, Proc.new { app_file? && app_file !~ /\.ru$/i && development? } + set :lock, Proc.new { reload? } def self.reloading? @reloading ||= false end def self.configure(*envs) super unless reloading? end def self.call(env) - reload! if reload? - super + synchronize do + reload! if reload? + super + end end def self.reload! @reloading = true superclass.send :inherited, self $LOADED_FEATURES.delete("sinatra.rb") ::Kernel.load app_file @reloading = false end + private + @@mutex = Mutex.new + def self.synchronize(&block) + if lock? + @@mutex.synchronize(&block) + else + yield + end + end end class Application < Default end module Delegator - METHODS = %w[ - get put post delete head template layout before error not_found - configures configure set set_option set_options enable disable use - development? test? production? use_in_file_templates! - ] - - METHODS.each do |method_name| - eval <<-RUBY, binding, '(__DELEGATE__)', 1 - def #{method_name}(*args, &b) - ::Sinatra::Application.#{method_name}(*args, &b) - end - private :#{method_name} - RUBY + def self.delegate(*methods) + methods.each do |method_name| + eval <<-RUBY, binding, '(__DELEGATE__)', 1 + def #{method_name}(*args, &b) + ::Sinatra::Application.#{method_name}(*args, &b) + end + private :#{method_name} + RUBY + end end + + delegate :get, :put, :post, :delete, :head, :template, :layout, :before, + :error, :not_found, :configures, :configure, :set, :set_option, + :set_options, :enable, :disable, :use, :development?, :test?, + :production?, :use_in_file_templates!, :helpers end def self.new(base=Base, options={}, &block) base = Class.new(base) base.send :class_eval, &block if block_given?