4 require 'swiftcore/swiftiplied_mongrel'
5 puts "Using Swiftiplied Mongrel"
7 require 'swiftcore/evented_mongrel'
8 puts "Using Evented Mongrel"
13 require File.dirname(__FILE__) + '/sinatra/mime_types'
14 require File.dirname(__FILE__) + '/sinatra/halt_results'
15 require File.dirname(__FILE__) + '/sinatra/logger'
18 old_verbose, $VERBOSE = $VERBOSE, nil
21 $VERBOSE = old_verbose
36 map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
40 self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
44 reject { |k,v| !keys.include?(k) }
50 Proc.new { |*args| args.shift.__send__(self, *args) }
56 self.inject({}) { |h, (k, v)| h[k] = v; h }
60 Proc.new { |*args| args.shift.__send__(self[0], args + self[1..-1]) }
72 find { |e| result = block[e] and break result }
84 puts "== Sinatra has taken the stage on port #{Sinatra.config[:port]} for #{Sinatra.config[:env]}"
86 Rack::Handler::Mongrel.run(Sinatra, :Port => Sinatra.config[:port]) do |server|
89 puts "\n== Sinatra has ended his set (crowd applauds)"
92 rescue Errno::EADDRINUSE => e
93 puts "== Someone is already performing on port #{Sinatra.config[:port]}!"
100 attr_reader :request, :response, :route_params
106 def initialize(request, response, route_params)
107 @request, @response, @route_params =
108 request, response, route_params
112 @params ||= request.params.merge(route_params).symbolize_keys
116 self.instance_eval(&b)
119 # redirect to another url It can be like /foo/bar
120 # for redirecting within your same app. Or it can
121 # be a fully qualified url to another site.
123 logger.info "Redirecting to: #{url}"
125 headers.merge!('Location' => url)
129 def method_missing(name, *args)
130 if args.size == 1 && response.respond_to?("#{name}=")
131 response.send("#{name}=", args.first)
133 response.send(name, *args)
140 self.logger = Sinatra::Logger.new(
141 config[:root] + "/#{Sinatra.config[:env]}.log"
145 def setup_default_events!
147 "<h2>#{$!.message}</h2>#{$!.backtrace.join("<br/>")}"
156 @request_types ||= [:get, :put, :post, :delete]
160 @routes ||= Hash.new do |hash, key|
161 hash[key] = [] if request_types.include?(key)
166 @filters ||= Hash.new { |hash, key| hash[key] = [] }
170 @config ||= default_config.dup
178 config[:env] == :development
182 @default_config ||= {
185 :raise_errors => false,
186 :env => :development,
188 :default_static_mime_type => 'text/plain',
189 :default_params => { :format => 'html' }
193 def determine_route(verb, path)
194 routes[verb].eject { |r| r.match(path) } || routes[404]
197 def content_type_for(path)
198 ext = File.extname(path)[1..-1]
199 Sinatra.mime_types[ext] || config[:default_static_mime_type]
202 def serve_static_file(path)
203 path = Sinatra.config[:root] + '/public' + path
206 'Content-Type' => Array(content_type_for(path)),
207 'Content-Length' => Array(File.size(path))
209 [200, headers, File.read(path)]
215 reload! if Sinatra.development?
219 request = Rack::Request.new(env)
221 if found = serve_static_file(request.path_info)
222 log_request_and_response(time, request, Rack::Response.new(found))
226 response = Rack::Response.new
227 route = determine_route(
228 request.request_method.downcase.to_sym,
231 context = EventContext.new(request, response, route.params)
234 context = handle_with_filters(context, &route.block)
235 context.status ||= route.default_status
237 raise e if config[:raise_errors]
238 route = Sinatra.routes[500]
240 context.body Array(context.instance_eval(&route.block))
242 log_request_and_response(time, request, response)
249 def define_route(verb, path, &b)
250 routes[verb] << route = Route.new(path, &b)
254 def define_error(code, &b)
255 routes[code] = Error.new(code, &b)
258 def define_filter(type, &b)
266 setup_default_events!
271 self.config[:reloading] = true
273 self.config[:reloading] = false
278 def log_request_and_response(time, request, response)
281 # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
282 # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
283 # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
284 logger.info %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} %
286 request.env["REMOTE_ADDR"] || "-",
287 request.env["REMOTE_USER"] || "-",
288 now.strftime("%d/%b/%Y %H:%M:%S"),
289 request.env["REQUEST_METHOD"],
290 request.env["PATH_INFO"],
291 request.env["QUERY_STRING"].empty? ?
293 "?" + request.env["QUERY_STRING"],
294 request.env["HTTP_VERSION"],
295 response.status.to_s[0..3].to_i,
296 (response.body.length.zero? ? "-" : response.body.length.to_s),
301 def handle_with_filters(cx, &b)
302 caught = catch(:halt) do
303 filters[:before].each { |x| cx.instance_eval(&x) }
306 caught = catch(:halt) do
309 result = caught.to_result(cx) if caught
310 filters[:after].each { |x| cx.instance_eval(&x) }
311 cx.body Array(result.to_s)
317 URI_CHAR = '[^/?:,&#]'.freeze unless defined?(URI_CHAR)
318 PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
320 attr_reader :block, :path
322 def initialize(path, &b)
323 @path, @block = path, b
325 @struct = Struct.new(:path, :block, :params, :default_status)
326 regex = path.to_s.gsub(PARAM) do
327 @param_keys << $1.intern
330 if path =~ /:format$/
331 @pattern = /^#{regex}$/
333 @param_keys << :format
334 @pattern = /^#{regex}(?:\.(#{URI_CHAR}+))?$/
339 return nil unless path =~ @pattern
340 params = @param_keys.zip($~.captures.compact.map(&:from_param)).to_hash
341 @struct.new(@path, @block, include_format(params), 200)
344 def include_format(h)
345 h.delete(:format) unless h[:format]
346 Sinatra.config[:default_params].merge(h)
350 pp.text "{Route: #{@pattern} : [#{@param_keys.map(&:inspect).join(",")}] }"
359 def initialize(code, &b)
360 @code, @block = code, b
373 paths.map { |path| Sinatra.define_route(:get, path, &b) }
377 paths.map { |path| Sinatra.define_route(:post, path, &b) }
381 paths.map { |path| Sinatra.define_route(:put, path, &b) }
384 def delete(*paths, &b)
385 paths.map { |path| Sinatra.define_route(:delete, path, &b) }
388 def error(*codes, &b)
389 raise 'You must specify a block to assciate with an error' if b.nil?
390 codes.each { |code| Sinatra.define_error(code, &b) }
394 Sinatra.define_filter(:before, &b)
398 Sinatra.define_filter(:after, &b)
401 def mime_type(content_type, *exts)
402 exts.each { |ext| Sinatra::MIME_TYPES.merge(ext.to_s, content_type) }
406 Sinatra::EventContext.class_eval(&b)
409 def configures(*envs)
410 return if Sinatra.config[:reloading]
411 yield if (envs.include?(Sinatra.config[:env]) || envs.empty?)
413 alias :configure :configures
415 Sinatra.setup_default_events!
420 Sinatra.run if Sinatra.config[:run]