require 'time' require 'ostruct' require 'uri' require 'rack' if ENV['SWIFT'] require 'swiftcore/swiftiplied_mongrel' puts "Using Swiftiplied Mongrel" elsif ENV['EVENT'] require 'swiftcore/evented_mongrel' puts "Using Evented Mongrel" end module Rack #:nodoc: class Request #:nodoc: # Set of request method names allowed via the _method parameter hack. By # default, all request methods defined in RFC2616 are included, with the # exception of TRACE and CONNECT. POST_TUNNEL_METHODS_ALLOWED = %w( PUT DELETE OPTIONS HEAD ) # Return the HTTP request method with support for method tunneling using # the POST _method parameter hack. If the real request method is POST and # a _method param is given and the value is one defined in # +POST_TUNNEL_METHODS_ALLOWED+, return the value of the _method param # instead. def request_method if post_tunnel_method_hack? params['_method'].upcase else @env['REQUEST_METHOD'] end end def user_agent @env['HTTP_USER_AGENT'] end private # Return truthfully if the request is a valid verb-over-post hack. def post_tunnel_method_hack? @env['REQUEST_METHOD'] == 'POST' && POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').upcase) end end module Utils extend self end module Handler autoload :Mongrel, ::File.dirname(__FILE__) + "/sinatra/rack/handler/mongrel" end end module Sinatra extend self VERSION = '0.3.0' class NotFound < RuntimeError def self.code ; 404 ; end end class ServerError < RuntimeError def self.code ; 500 ; end end Result = Struct.new(:block, :params, :status) unless defined?(Result) def options application.options end def application @app ||= Application.new end def application=(app) @app = app end def port application.options.port end def host application.options.host end def env application.options.env end # Deprecated: use application instead of build_application. alias :build_application :application def server options.server ||= defined?(Rack::Handler::Thin) ? "thin" : "mongrel" # Convert the server into the actual handler name handler = options.server.capitalize # If the convenience conversion didn't get us anything, # fall back to what the user actually set. handler = options.server unless Rack::Handler.const_defined?(handler) @server ||= eval("Rack::Handler::#{handler}") end def run begin puts "== Sinatra/#{Sinatra::VERSION} has taken the stage on port #{port} for #{env} with backup by #{server.name}" server.run(application, {:Port => port, :Host => host}) do |server| trap(:INT) do server.stop puts "\n== Sinatra has ended his set (crowd applauds)" end end rescue Errno::EADDRINUSE => e puts "== Someone is already performing on port #{port}!" end end class Event include Rack::Utils URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR) PARAM = /(:(#{URI_CHAR}+)|\*)/.freeze unless defined?(PARAM) SPLAT = /(.*?)/ attr_reader :path, :block, :param_keys, :pattern, :options def initialize(path, options = {}, &b) @path = URI.encode(path) @block = b @param_keys = [] @options = options splats = 0 regex = @path.to_s.gsub(PARAM) do |match| if match == "*" @param_keys << "_splat_#{splats}" splats += 1 SPLAT.to_s else @param_keys << $2 "(#{URI_CHAR}+)" end end @pattern = /^#{regex}$/ end def invoke(request) params = {} if agent = options[:agent] return unless request.user_agent =~ agent params[:agent] = $~[1..-1] end if host = options[:host] return unless host === request.host end return unless pattern =~ request.path_info.squeeze('/') path_params = param_keys.zip($~.captures.map{|s| unescape(s)}).to_hash params.merge!(path_params) splats = params.select { |k, v| k =~ /^_splat_\d+$/ }.sort.map(&:last) unless splats.empty? params.delete_if { |k, v| k =~ /^_splat_\d+$/ } params["splat"] = splats end Result.new(block, params, 200) end end class Error attr_reader :type, :block, :options def initialize(type, options={}, &block) @type = type @block = block @options = options end def invoke(request) Result.new(block, options, code) end def code if type.respond_to?(:code) type.code else 500 end end end class Static include Rack::Utils def invoke(request) return unless File.file?( Sinatra.application.options.public + unescape(request.path_info) ) Result.new(block, {}, 200) end def block Proc.new do send_file Sinatra.application.options.public + unescape(request.path_info), :disposition => nil end end end # Methods for sending files and streams to the browser instead of rendering. module Streaming DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, :disposition => 'attachment'.freeze, :stream => true, :buffer_size => 4096 }.freeze class MissingFile < RuntimeError; end class FileStreamer attr_reader :path, :options def initialize(path, options) @path, @options = path, options end def to_result(cx, *args) self end def each File.open(path, 'rb') do |file| while buf = file.read(options[:buffer_size]) yield buf end end end end protected # Sends the file by streaming it 4096 bytes at a time. This way the # whole file doesn't need to be read into memory at once. This makes # it feasible to send even large files. # # Be careful to sanitize the path parameter if it coming from a web # page. send_file(params[:path]) allows a malicious user to # download any file on your server. # # Options: # * :filename - suggests a filename for the browser to use. # Defaults to File.basename(path). # * :type - specifies an HTTP content type. # Defaults to 'application/octet-stream'. # * :disposition - specifies whether the file will be shown # inline or downloaded. Valid values are 'inline' and 'attachment' # (default). When set to nil, the Content-Disposition and # Content-Transfer-Encoding headers are omitted entirely. # * :stream - whether to send the file to the user agent as it # is read (true) or to read the entire file before sending (false). # Defaults to true. # * :buffer_size - specifies size (in bytes) of the buffer used # to stream the file. Defaults to 4096. # * :status - specifies the status code to send with the # response. Defaults to '200 OK'. # * :last_modified - an optional RFC 2616 formatted date value # (See Time#httpdate) indicating the last modified time of the file. # If the request includes an If-Modified-Since header that matches this # value exactly, a 304 Not Modified response is sent instead of the file. # Defaults to the file's last modified time. # # The default Content-Type and Content-Disposition headers are # set to download arbitrary binary files in as many browsers as # possible. IE versions 4, 5, 5.5, and 6 are all known to have # a variety of quirks (especially when downloading over SSL). # # Simple download: # send_file '/path/to.zip' # # Show a JPEG in the browser: # send_file '/path/to.jpeg', # :type => 'image/jpeg', # :disposition => 'inline' # # Show a 404 page in the browser: # send_file '/path/to/404.html, # :type => 'text/html; charset=utf-8', # :status => 404 # # Read about the other Content-* HTTP headers if you'd like to # provide the user with more information (such as Content-Description). # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 # # Also be aware that the document may be cached by proxies and browsers. # The Pragma and Cache-Control headers declare how the file may be cached # by intermediaries. They default to require clients to validate with # the server before releasing cached responses. See # http://www.mnot.net/cache_docs/ for an overview of web caching and # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 # for the Cache-Control header spec. def send_file(path, options = {}) #:doc: raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) options[:length] ||= File.size(path) options[:filename] ||= File.basename(path) options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain' options[:last_modified] ||= File.mtime(path).httpdate send_file_headers! options if options[:stream] throw :halt, [options[:status] || 200, FileStreamer.new(path, options)] else File.open(path, 'rb') { |file| throw :halt, [options[:status] || 200, file.read] } end end # Send binary data to the user as a file download. May set content type, # apparent file name, and specify whether to show data inline or download # as an attachment. # # Options: # * :filename - Suggests a filename for the browser to use. # * :type - specifies an HTTP content type. # Defaults to 'application/octet-stream'. # * :disposition - specifies whether the file will be shown inline # or downloaded. Valid values are 'inline' and 'attachment' (default). # * :status - specifies the status code to send with the response. # Defaults to '200 OK'. # * :last_modified - an optional RFC 2616 formatted date value (See # Time#httpdate) indicating the last modified time of the response entity. # If the request includes an If-Modified-Since header that matches this # value exactly, a 304 Not Modified response is sent instead of the data. # # Generic data download: # send_data buffer # # Download a dynamically-generated tarball: # send_data generate_tgz('dir'), :filename => 'dir.tgz' # # Display an image Active Record in the browser: # send_data image.data, # :type => image.content_type, # :disposition => 'inline' # # See +send_file+ for more information on HTTP Content-* headers and caching. def send_data(data, options = {}) #:doc: send_file_headers! options.merge(:length => data.size) throw :halt, [options[:status] || 200, data] end private def send_file_headers!(options) options = DEFAULT_SEND_FILE_OPTIONS.merge(options) [:length, :type, :disposition].each do |arg| raise ArgumentError, ":#{arg} option required" unless options.key?(arg) end # Send a "304 Not Modified" if the last_modified option is provided and # matches the If-Modified-Since request header value. if last_modified = options[:last_modified] header 'Last-Modified' => last_modified throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE'] end headers( 'Content-Length' => options[:length].to_s, 'Content-Type' => options[:type].strip # fixes a problem with extra '\r' with some browsers ) # Omit Content-Disposition and Content-Transfer-Encoding headers if # the :disposition option set to nil. if !options[:disposition].nil? disposition = options[:disposition].dup || 'attachment' disposition <<= %(; filename="#{options[:filename]}") if options[:filename] headers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary' end # Fix a problem with IE 6.0 on opening downloaded files: # If Cache-Control: no-cache is set (which Rails does by default), # IE removes the file it just downloaded from its cache immediately # after it displays the "open/save" dialog, which means that if you # hit "open" the file isn't there anymore when the application that # is called for handling the download is run, so let's workaround that header('Cache-Control' => 'private') if headers['Cache-Control'] == 'no-cache' end end # Helper methods for building various aspects of the HTTP response. module ResponseHelpers # Immediately halt response execution by redirecting to the resource # specified. The +path+ argument may be an absolute URL or a path # relative to the site root. Additional arguments are passed to the # halt. # # With no integer status code, a '302 Temporary Redirect' response is # sent. To send a permanent redirect, pass an explicit status code of # 301: # # redirect '/somewhere/else', 301 # # NOTE: No attempt is made to rewrite the path based on application # context. The 'Location' response header is set verbatim to the value # provided. def redirect(path, *args) status(302) header 'Location' => path throw :halt, *args end # Access or modify response headers. With no argument, return the # underlying headers Hash. With a Hash argument, add or overwrite # existing response headers with the values provided: # # headers 'Content-Type' => "text/html;charset=utf-8", # 'Last-Modified' => Time.now.httpdate, # 'X-UA-Compatible' => 'IE=edge' # # This method also available in singular form (#header). def headers(header = nil) @response.headers.merge!(header) if header @response.headers end alias :header :headers # Set the content type of the response body (HTTP 'Content-Type' header). # # The +type+ argument may be an internet media type (e.g., 'text/html', # 'application/xml+atom', 'image/png') or a Symbol key into the # Rack::File::MIME_TYPES table. # # Media type parameters, such as "charset", may also be specified using the # optional hash argument: # # get '/foo.html' do # content_type 'text/html', :charset => 'utf-8' # "

Hello World

" # end # def content_type(type, params={}) type = Rack::File::MIME_TYPES[type.to_s] if type.kind_of?(Symbol) fail "Invalid or undefined media_type: #{type}" if type.nil? if params.any? params = params.collect { |kv| "%s=%s" % kv }.join(', ') type = [ type, params ].join(";") end response.header['Content-Type'] = type end # Set the last modified time of the resource (HTTP 'Last-Modified' header) # and halt if conditional GET matches. The +time+ argument is a Time, # DateTime, or other object that responds to +to_time+. # # When the current request includes an 'If-Modified-Since' header that # matches the time specified, execution is immediately halted with a # '304 Not Modified' response. # # Calling this method before perfoming heavy processing (e.g., lengthy # database queries, template rendering, complex logic) can dramatically # increase overall throughput with caching clients. def last_modified(time) time = time.to_time if time.respond_to?(:to_time) time = time.httpdate if time.respond_to?(:httpdate) response.header['Last-Modified'] = time throw :halt, 304 if time == request.env['HTTP_IF_MODIFIED_SINCE'] time end # Set the response entity tag (HTTP 'ETag' header) and halt if conditional # GET matches. The +value+ argument is an identifier that uniquely # identifies the current version of the resource. The +strength+ argument # indicates whether the etag should be used as a :strong (default) or :weak # cache validator. # # When the current request includes an 'If-None-Match' header with a # matching etag, execution is immediately halted. If the request method is # GET or HEAD, a '304 Not Modified' response is sent. For all other request # methods, a '412 Precondition Failed' response is sent. # # Calling this method before perfoming heavy processing (e.g., lengthy # database queries, template rendering, complex logic) can dramatically # increase overall throughput with caching clients. # # ==== See Also # {RFC2616: ETag}[http://w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19], # ResponseHelpers#last_modified def entity_tag(value, strength=:strong) value = case strength when :strong then '"%s"' % value when :weak then 'W/"%s"' % value else raise TypeError, "strength must be one of :strong or :weak" end response.header['ETag'] = value # Check for If-None-Match request header and halt if match is found. etags = (request.env['HTTP_IF_NONE_MATCH'] || '').split(/\s*,\s*/) if etags.include?(value) || etags.include?('*') # GET/HEAD requests: send Not Modified response throw :halt, 304 if request.get? || request.head? # Other requests: send Precondition Failed response throw :halt, 412 end end alias :etag :entity_tag end module RenderingHelpers def render(renderer, template, options={}) m = method("render_#{renderer}") result = m.call(resolve_template(renderer, template, options), options) if layout = determine_layout(renderer, template, options) result = m.call(resolve_template(renderer, layout, options), options) { result } end result end def determine_layout(renderer, template, options) return if options[:layout] == false layout_from_options = options[:layout] || :layout resolve_template(renderer, layout_from_options, options, false) end private def resolve_template(renderer, template, options, scream = true) case template when String template when Proc template.call when Symbol if proc = templates[template] resolve_template(renderer, proc, options, scream) else read_template_file(renderer, template, options, scream) end else nil end end def read_template_file(renderer, template, options, scream = true) path = File.join( options[:views_directory] || Sinatra.application.options.views, "#{template}.#{renderer}" ) unless File.exists?(path) raise Errno::ENOENT.new(path) if scream nil else File.read(path) end end def templates Sinatra.application.templates end end module Erb def erb(content, options={}) require 'erb' render(:erb, content, options) end private def render_erb(content, options = {}) locals_opt = options.delete(:locals) || {} locals_code = "" locals_hash = {} locals_opt.each do |key, value| locals_code << "#{key} = locals_hash[:#{key}]\n" locals_hash[:"#{key}"] = value end body = ::ERB.new(content).src eval("#{locals_code}#{body}", binding) end end module Haml def haml(content, options={}) require 'haml' render(:haml, content, options) end private def render_haml(content, options = {}, &b) haml_options = (options[:options] || {}). merge(Sinatra.options.haml || {}) ::Haml::Engine.new(content, haml_options). render(options[:scope] || self, options[:locals] || {}, &b) end end # Generate valid CSS using Sass (part of Haml) # # Sass templates can be in external files with .sass extension # or can use Sinatra's in_file_templates. In either case, the file can # be rendered by passing the name of the template to the +sass+ method # as a symbol. # # Unlike Haml, Sass does not support a layout file, so the +sass+ method # will ignore both the default layout.sass file and any parameters # passed in as :layout in the options hash. # # === Sass Template Files # # Sass templates can be stored in separate files with a .sass # extension under the view path. # # Example: # get '/stylesheet.css' do # header 'Content-Type' => 'text/css; charset=utf-8' # sass :stylesheet # end # # The "views/stylesheet.sass" file might contain the following: # # body # #admin # :background-color #CCC # #main # :background-color #000 # #form # :border-color #AAA # :border-width 10px # # And yields the following output: # # body #admin { # background-color: #CCC; } # body #main { # background-color: #000; } # # #form { # border-color: #AAA; # border-width: 10px; } # # # NOTE: Haml must be installed or a LoadError will be raised the first time an # attempt is made to render a Sass template. # # See http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html for comprehensive documentation on Sass. module Sass def sass(content, options = {}) require 'sass' # Sass doesn't support a layout, so we override any possible layout here options[:layout] = false render(:sass, content, options) end private def render_sass(content, options = {}) ::Sass::Engine.new(content).render end end # Generating conservative XML content using Builder templates. # # Builder templates can be inline by passing a block to the builder method, # or in external files with +.builder+ extension by passing the name of the # template to the +builder+ method as a Symbol. # # === Inline Rendering # # If the builder method is given a block, the block is called directly with # an +XmlMarkup+ instance and the result is returned as String: # get '/who.xml' do # builder do |xml| # xml.instruct! # xml.person do # xml.name "Francis Albert Sinatra", # :aka => "Frank Sinatra" # xml.email 'frank@capitolrecords.com' # end # end # end # # Yields the following XML: # # # Francis Albert Sinatra # Frank Sinatra # # # === Builder Template Files # # Builder templates can be stored in separate files with a +.builder+ # extension under the view path. An +XmlMarkup+ object named +xml+ is # automatically made available to template. # # Example: # get '/bio.xml' do # builder :bio # end # # The "views/bio.builder" file might contain the following: # xml.instruct! :xml, :version => '1.1' # xml.person do # xml.name "Francis Albert Sinatra" # xml.aka "Frank Sinatra" # xml.aka "Ol' Blue Eyes" # xml.aka "The Chairman of the Board" # xml.born 'date' => '1915-12-12' do # xml.text! "Hoboken, New Jersey, U.S.A." # end # xml.died 'age' => 82 # end # # And yields the following output: # # # Francis Albert Sinatra # Frank Sinatra # Ol' Blue Eyes # The Chairman of the Board # Hoboken, New Jersey, U.S.A. # # # # NOTE: Builder must be installed or a LoadError will be raised the first # time an attempt is made to render a builder template. # # See http://builder.rubyforge.org/ for comprehensive documentation on # Builder. module Builder def builder(content=nil, options={}, &block) options, content = content, nil if content.is_a?(Hash) content = Proc.new { block } if content.nil? render(:builder, content, options) end private def render_builder(content, options = {}, &b) require 'builder' xml = ::Builder::XmlMarkup.new(:indent => 2) case content when String eval(content, binding, '', 1) when Proc content.call(xml) end xml.target! end end class EventContext include Rack::Utils include ResponseHelpers include Streaming include RenderingHelpers include Erb include Haml include Builder include Sass attr_accessor :request, :response attr_accessor :route_params def initialize(request, response, route_params) @params = nil @data = nil @request = request @response = response @route_params = route_params @response.body = nil end def status(value=nil) response.status = value if value response.status end def body(value=nil) response.body = value if value response.body end def params @params ||= begin hash = Hash.new {|h,k| h[k.to_s] if Symbol === k} hash.merge! @request.params hash.merge! @route_params hash end end def data @data ||= params.keys.first end def stop(*args) throw :halt, args end def complete(returned) @response.body || returned end def session request.env['rack.session'] ||= {} end def reset! @params = nil @data = nil end private def method_missing(name, *args, &b) if @response.respond_to?(name) @response.send(name, *args, &b) else super end end end # The Application class represents the top-level working area of a # Sinatra app. It provides the DSL for defining various aspects of the # application and implements a Rack compatible interface for dispatching # requests. # # Many of the instance methods defined in this class (#get, #post, # #put, #delete, #layout, #before, #error, #not_found, etc.) are # available at top-level scope. When invoked from top-level, the # messages are forwarded to the "default application" (accessible # at Sinatra::application). class Application # Hash of event handlers with request method keys and # arrays of potential handlers as values. attr_reader :events # Hash of error handlers with error status codes as keys and # handlers as values. attr_reader :errors # Hash of template name mappings. attr_reader :templates # Hash of filters with event name keys (:before) and arrays of # handlers as values. attr_reader :filters # Array of objects to clear during reload. The objects in this array # must respond to :clear. attr_reader :clearables # Object including open attribute methods for modifying Application # configuration. attr_reader :options # List of methods available from top-level scope. When invoked from # top-level the method is forwarded to the default application # (Sinatra::application). FORWARD_METHODS = %w[ get put post delete head template layout before error not_found configures configure set set_options set_option enable disable use development? test? production? ] # Create a new Application with a default configuration taken # from the default_options Hash. # # NOTE: A default Application is automatically created the first # time any of Sinatra's DSL related methods is invoked so there # is typically no need to create an instance explicitly. See # Sinatra::application for more information. def initialize @reloading = false @clearables = [ @events = Hash.new { |hash, key| hash[key] = [] }, @errors = Hash.new, @filters = Hash.new { |hash, key| hash[key] = [] }, @templates = Hash.new, @middleware = [] ] @options = OpenStruct.new(self.class.default_options) load_default_configuration! end # Hash of default application configuration options. When a new # Application is created, the #options object takes its initial values # from here. # # Changes to the default_options Hash effect only Application objects # created after the changes are made. For this reason, modifications to # the default_options Hash typically occur at the very beginning of a # file, before any DSL related functions are invoked. def self.default_options return @default_options unless @default_options.nil? root = File.expand_path(File.dirname($0)) @default_options = { :run => true, :port => 4567, :host => '0.0.0.0', :env => :development, :root => root, :views => root + '/views', :public => root + '/public', :sessions => false, :logging => true, :app_file => $0, :raise_errors => false } load_default_options_from_command_line! @default_options end # Search ARGV for command line arguments and update the # Sinatra::default_options Hash accordingly. This method is # invoked the first time the default_options Hash is accessed. # NOTE: Ignores --name so unit/spec tests can run individually def self.load_default_options_from_command_line! #:nodoc: # fixes issue with: gem install --test sinatra return if ARGV.empty? || File.basename($0) =~ /gem/ require 'optparse' OptionParser.new do |op| op.on('-p port') { |port| default_options[:port] = port } op.on('-e env') { |env| default_options[:env] = env.to_sym } op.on('-x') { default_options[:mutex] = true } op.on('-s server') { |server| default_options[:server] = server } end.parse!(ARGV.dup.select { |o| o !~ /--name/ }) end # Determine whether the application is in the process of being # reloaded. def reloading? @reloading == true end # Yield to the block for configuration if the current environment # matches any included in the +envs+ list. Always yield to the block # when no environment is specified. # # NOTE: configuration blocks are not executed during reloads. def configures(*envs, &b) return if reloading? yield self if envs.empty? || envs.include?(options.env) end alias :configure :configures # When both +option+ and +value+ arguments are provided, set the option # specified. With a single Hash argument, set all options specified in # Hash. Options are available via the Application#options object. # # Setting individual options: # set :port, 80 # set :env, :production # set :views, '/path/to/views' # # Setting multiple options: # set :port => 80, # :env => :production, # :views => '/path/to/views' # def set(option, value=self) if value == self && option.kind_of?(Hash) option.each { |key,val| set(key, val) } else options.send("#{option}=", value) end end alias :set_option :set alias :set_options :set # Enable the options specified by setting their values to true. For # example, to enable sessions and logging: # enable :sessions, :logging def enable(*opts) opts.each { |key| set(key, true) } end # Disable the options specified by setting their values to false. For # example, to disable logging and automatic run: # disable :logging, :run def disable(*opts) opts.each { |key| set(key, false) } end # Define an event handler for the given request method and path # spec. The block is executed when a request matches the method # and spec. # # NOTE: The #get, #post, #put, and #delete helper methods should # be used to define events when possible. def event(method, path, options = {}, &b) events[method].push(Event.new(path, options, &b)).last end # Define an event handler for GET requests. def get(path, options={}, &b) event(:get, path, options, &b) end # Define an event handler for POST requests. def post(path, options={}, &b) event(:post, path, options, &b) end # Define an event handler for HEAD requests. def head(path, options={}, &b) event(:head, path, options, &b) end # Define an event handler for PUT requests. # # NOTE: PUT events are triggered when the HTTP request method is # PUT and also when the request method is POST and the body includes a # "_method" parameter set to "PUT". def put(path, options={}, &b) event(:put, path, options, &b) end # Define an event handler for DELETE requests. # # NOTE: DELETE events are triggered when the HTTP request method is # DELETE and also when the request method is POST and the body includes a # "_method" parameter set to "DELETE". def delete(path, options={}, &b) event(:delete, path, options, &b) end # Visits and invokes each handler registered for the +request_method+ in # definition order until a Result response is produced. If no handler # responds with a Result, the NotFound error handler is invoked. # # When the request_method is "HEAD" and no valid Result is produced by # the set of handlers registered for HEAD requests, an attempt is made to # invoke the GET handlers to generate the response before resorting to the # default error handler. def lookup(request) method = request.request_method.downcase.to_sym events[method].eject(&[:invoke, request]) || (events[:get].eject(&[:invoke, request]) if method == :head) || errors[NotFound].invoke(request) end # Define a named template. The template may be referenced from # event handlers by passing the name as a Symbol to rendering # methods. The block is executed each time the template is rendered # and the resulting object is passed to the template handler. # # The following example defines a HAML template named hello and # invokes it from an event handler: # # template :hello do # "h1 Hello World!" # end # # get '/' do # haml :hello # end # def template(name, &b) templates[name] = b end # Define a layout template. def layout(name=:layout, &b) template(name, &b) end # Define a custom error handler for the exception class +type+. The block # is invoked when the specified exception type is raised from an error # handler and can manipulate the response as needed: # # error MyCustomError do # status 500 # 'So what happened was...' + request.env['sinatra.error'].message # end # # The Sinatra::ServerError handler is used by default when an exception # occurs and no matching error handler is found. def error(type=ServerError, options = {}, &b) errors[type] = Error.new(type, options, &b) end # Define a custom error handler for '404 Not Found' responses. This is a # shorthand for: # error NotFound do # .. # end def not_found(options={}, &b) error NotFound, options, &b end # Define a request filter. When type is :before, execute the # block in the context of each request before matching event handlers. def filter(type, &b) filters[type] << b end # Invoke the block in the context of each request before invoking # matching event handlers. def before(&b) filter :before, &b end # True when environment is :development. def development? ; options.env == :development ; end # True when environment is :test. def test? ; options.env == :test ; end # True when environment is :production. def production? ; options.env == :production ; end # Clear all events, templates, filters, and error handlers # and then reload the application source file. This occurs # automatically before each request is processed in development. def reload! clearables.each(&:clear) load_default_configuration! load_development_configuration! if development? @pipeline = nil @reloading = true Kernel.load options.app_file @reloading = false end # Determine whether the application is in the process of being # reloaded. def reloading? @reloading == true end # Mutex instance used for thread synchronization. def mutex @@mutex ||= Mutex.new end # Yield to the block with thread synchronization def run_safely if development? || options.mutex mutex.synchronize { yield } else yield end end # Add a piece of Rack middleware to the pipeline leading to the # application. def use(klass, *args, &block) fail "#{klass} must respond to 'new'" unless klass.respond_to?(:new) @pipeline = nil @middleware.push([ klass, args, block ]).last end private # Rack middleware derived from current state of application options. # These components are plumbed in at the very beginning of the # pipeline. def optional_middleware [ ([ Rack::CommonLogger, [], nil ] if options.logging), ([ Rack::Session::Cookie, [], nil ] if options.sessions) ].compact end # Rack middleware explicitly added to the application with #use. These # components are plumbed into the pipeline downstream from # #optional_middle. def explicit_middleware @middleware end # All Rack middleware used to construct the pipeline. def middleware optional_middleware + explicit_middleware end public # An assembled pipeline of Rack middleware that leads eventually to # the Application#invoke method. The pipeline is built upon first # access. Defining new middleware with Application#use or manipulating # application options may cause the pipeline to be rebuilt. def pipeline @pipeline ||= middleware.inject(method(:dispatch)) do |app,(klass,args,block)| klass.new(app, *args, &block) end end # Rack compatible request invocation interface. def call(env) run_safely do reload! if development? && (options.reload != false) pipeline.call(env) end end # Request invocation handler - called at the end of the Rack pipeline # for each request. # # 1. Create Rack::Request, Rack::Response helper objects. # 2. Lookup event handler based on request method and path. # 3. Create new EventContext to house event handler evaluation. # 4. Invoke each #before filter in context of EventContext object. # 5. Invoke event handler in context of EventContext object. # 6. Return response to Rack. # # See the Rack specification for detailed information on the # +env+ argument and return value. def dispatch(env) request = Rack::Request.new(env) context = EventContext.new(request, Rack::Response.new([], 200), {}) begin returned = catch(:halt) do filters[:before].each { |f| context.instance_eval(&f) } result = lookup(context.request) context.route_params = result.params context.response.status = result.status context.reset! [:complete, context.instance_eval(&result.block)] end body = returned.to_result(context) rescue => e request.env['sinatra.error'] = e context.status(500) raise if options.raise_errors && e.class != NotFound result = (errors[e.class] || errors[ServerError]).invoke(request) returned = catch(:halt) do [:complete, context.instance_eval(&result.block)] end body = returned.to_result(context) end body = '' unless body.respond_to?(:each) body = '' if request.env["REQUEST_METHOD"].upcase == 'HEAD' context.body = body.kind_of?(String) ? [*body] : body context.finish end private # Called immediately after the application is initialized or reloaded to # register default events, templates, and error handlers. def load_default_configuration! events[:get] << Static.new configure do error do '

Internal Server Error

' end not_found { '

Not Found

'} end end # Called before reloading to perform development specific configuration. def load_development_configuration! get '/sinatra_custom_images/:image.png' do content_type :png File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png") end not_found do (<<-HTML).gsub(/^ {8}/, '')

Sinatra doesn't know this diddy.

Try this:
#{request.request_method.downcase} "#{request.path_info}" do\n  .. do something ..\nend
            
HTML end error do @error = request.env['sinatra.error'] (<<-HTML).gsub(/^ {8}/, '')
Params:
#{params.inspect}

#{escape_html(@error.class.name + ' - ' + @error.message.to_s)}

#{escape_html(@error.backtrace.join("\n"))}
HTML end end end end # Delegate DSLish methods to the currently active Sinatra::Application # instance. Sinatra::Application::FORWARD_METHODS.each do |method| eval(<<-EOS, binding, '(__DSL__)', 1) def #{method}(*args, &b) Sinatra.application.#{method}(*args, &b) end EOS end def helpers(&b) Sinatra::EventContext.class_eval(&b) end def use_in_file_templates! require 'stringio' templates = IO.read(caller.first.split(':').first).split('__FILE__').last data = StringIO.new(templates) current_template = nil data.each do |line| if line =~ /^@@\s?(.*)/ current_template = $1.to_sym Sinatra.application.templates[current_template] = '' elsif current_template Sinatra.application.templates[current_template] << line end end end def mime(ext, type) Rack::File::MIME_TYPES[ext.to_s] = type end ### Misc Core Extensions module Kernel def silence_warnings old_verbose, $VERBOSE = $VERBOSE, nil yield ensure $VERBOSE = old_verbose end end class Symbol def to_proc Proc.new { |*args| args.shift.__send__(self, *args) } end end class Array def to_hash self.inject({}) { |h, (k, v)| h[k] = v; h } end def to_proc Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) } end end module Enumerable def eject(&block) find { |e| result = block[e] and break result } end end ### Core Extension results for throw :halt class Proc def to_result(cx, *args) cx.instance_eval(&self) args.shift.to_result(cx, *args) end end class String def to_result(cx, *args) args.shift.to_result(cx, *args) 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 args.shift.to_result(cx, *args) end end class NilClass def to_result(cx, *args) '' end end at_exit do raise $! if $! if Sinatra.application.options.run Sinatra.run end end mime :xml, 'application/xml' mime :js, 'application/javascript' mime :png, 'image/png'