diff --git a/lib/sinatra.rb b/lib/sinatra.rb index 165f3a49..689d6467 100644 --- a/lib/sinatra.rb +++ b/lib/sinatra.rb @@ -2,6 +2,8 @@ require "rubygems" require "rack" require 'sinatra/mime_types' +require 'sinatra/send_file_mixin' +require 'sinatra/halt_results' def silence_warnings old_verbose, $VERBOSE = $VERBOSE, nil @@ -70,6 +72,10 @@ module Sinatra @params ||= request.params.merge(route_params).symbolize_keys end + def complete(b) + self.instance_eval(&b) + end + def method_missing(name, *args) if args.size == 1 && response.respond_to?("#{name}=") response.send("#{name}=", args.first) @@ -99,6 +105,10 @@ module Sinatra end end + def filters + @filters ||= Hash.new { |hash, key| hash[key] = [] } + end + def config @config ||= @default_config.dup end @@ -153,9 +163,8 @@ module Sinatra context = EventContext.new(request, response, route.params) context.status = nil begin - result = context.instance_eval(&route.block) + context = handle_with_filters(context, &route.block) context.status ||= route.default_status - context.body = Array(result.to_s) context.finish rescue => e raise e if config[:raise_errors] @@ -175,6 +184,19 @@ module Sinatra routes[code] = Error.new(code, &b) end + protected + + def handle_with_filters(cx, &b) + caught = catch(:halt) do + filters[:before].each { |x| cx.instance_eval(&x) } + [:complete, b] + end + result = caught.to_result(cx) + filters[:after].each { |x| cx.instance_eval(&x) } + cx.body Array(result.to_s) + cx + end + class Route URI_CHAR = '[^/?:,&#]'.freeze unless defined?(URI_CHAR) @@ -247,3 +269,7 @@ def helpers(&b) end Sinatra.setup_default_events! + +Sinatra::EventContext.send :include, Sinatra::SendFileMixin + + diff --git a/lib/sinatra/halt_results.rb b/lib/sinatra/halt_results.rb new file mode 100644 index 00000000..d6a67bd8 --- /dev/null +++ b/lib/sinatra/halt_results.rb @@ -0,0 +1,30 @@ +class Proc + def to_result(cx, *args) + cx.instance_eval(&self) + end +end + +class String + def to_result(cx, *args) + cx.body self + end +end + +class Array + def to_result(cx, *args) + self.shift.to_result(cx, *self) + end +end + +class Symbol + def to_result(cx, *args) + cx.send(self, *args) + end +end + +class Fixnum + def to_result(cx, *args) + cx.status self + cx.body args.first + end +end diff --git a/lib/sinatra/send_file_mixin.rb b/lib/sinatra/send_file_mixin.rb new file mode 100644 index 00000000..50492b96 --- /dev/null +++ b/lib/sinatra/send_file_mixin.rb @@ -0,0 +1,72 @@ +module Sinatra + + def default_send_file_options + @default_send_file_options ||= { + :type => 'application/octet-stream'.freeze, + :disposition => 'attachment'.freeze + }.freeze + end + + # Adapted from Merb greatness + module SendFileMixin + + # redirect to another url It can be like /foo/bar + # for redirecting within your same app. Or it can + # be a fully qualified url to another site. + def redirect(url) + # MERB_LOGGER.info("Redirecting to: #{url}") + status(302) + headers.merge!({'Location'=> url}) + return '' + end + + # pass in a path to a file and this will set the + # right headers and let mongrel do its thang and + # serve the static file directly. + def send_file(file, opts={}) + opts.update(Sinatra.default_send_file_options.merge(opts)) + disposition = opts[:disposition].dup || 'attachment' + disposition << %(; filename="#{opts[:filename] ? opts[:filename] : File.basename(file)}") + headers.update( + 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers + 'Content-Disposition' => disposition, + 'Content-Transfer-Encoding' => 'binary', + 'X-SENDFILE' => file + ) + return + end + + # stream_file( { :filename => file_name, + # :type => content_type, + # :content_length => content_length }) do + # AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk| + # response.write chunk + # end + # end + def stream_file(opts={}, &stream) + opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts)) + disposition = opts[:disposition].dup || 'attachment' + disposition << %(; filename="#{opts[:filename]}") + response.headers.update( + 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers + 'Content-Disposition' => disposition, + 'Content-Transfer-Encoding' => 'binary', + 'CONTENT-LENGTH' => opts[:content_length] + ) + response.send_status(opts[:content_length]) + response.send_header + stream + end + + + # This uses nginx X-Accel-Redirect header to send + # a file directly from nginx. See the nginx wiki: + # http://wiki.codemongers.com/NginxXSendfile + def nginx_send_file(file) + headers['X-Accel-Redirect'] = File.expand_path(file) + return + end + + end + +end \ No newline at end of file diff --git a/test/event_dsl_test.rb b/test/event_dsl_test.rb index e5b6056f..9d269535 100644 --- a/test/event_dsl_test.rb +++ b/test/event_dsl_test.rb @@ -9,9 +9,11 @@ context "Event's DSL" do end get_it '/' + should.be.ok body.should.equal 'hello from me' get_it '/foo' + should.be.ok body.should.equal 'hello from me' end diff --git a/test/filter_test.rb b/test/filter_test.rb new file mode 100644 index 00000000..e69de29b