Permalink
Browse files

tutorial 4 wip

  • Loading branch information...
1 parent d75f23c commit 116a7bcc8cb9c20ce0ae68a92486f831e0e243b2 @zhengjia committed Oct 26, 2011
Showing with 211 additions and 0 deletions.
  1. +211 −0 app/tutorial_4/tutorial_4.md
@@ -0,0 +1,211 @@
+A few attr_accessor defined on Sinatra::Base:
+
+```ruby
+ attr_accessor :env, :request, :response, :params
+```
+
+Request
+-------
+
+Request.new is removed later. https://github.com/sinatra/sinatra/issues/239
+
+```ruby
+ # The request object. See Rack::Request for more info:
+ # http://rack.rubyforge.org/doc/classes/Rack/Request.html
+ class Request < Rack::Request
+ def self.new(env)
+ env['sinatra.request'] ||= super
+ end
+
+ # Returns an array of acceptable media types for the response
+ def accept
+ @env['sinatra.accept'] ||= begin
+ entries = @env['HTTP_ACCEPT'].to_s.split(',')
+ entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
+ end
+ end
+
+ def preferred_type(*types)
+ return accept.first if types.empty?
+ types.flatten!
+ accept.detect do |pattern|
+ type = types.detect { |t| File.fnmatch(pattern, t) }
+ return type if type
+ end
+ end
+
+ alias accept? preferred_type
+ alias secure? ssl?
+
+ def forwarded?
+ @env.include? "HTTP_X_FORWARDED_HOST"
+ end
+
+ def route
+ @route ||= Rack::Utils.unescape(path_info)
+ end
+
+ def path_info=(value)
+ @route = nil
+ super
+ end
+
+ private
+
+ def accept_entry(entry)
+ type, *options = entry.gsub(/\s/, '').split(';')
+ quality = 0 # we sort smalles first
+ options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
+ [type, [quality, type.count('*'), 1 - options.size]]
+ end
+ end
+```
+
+Response
+--------
+
+Let's see the response generated by sinatra error handling.
+
+NotFound is an exception class inherited from ruby's NameError whose parent is StandardError. It has one method `code` which just returns 404.
+
+```ruby
+ class NotFound < NameError #:nodoc:
+ def code ; 404 ; end
+ end
+```
+
+The NotFound is raised in `route_missing` if no routes can be matched. `route_missing` is called by `route!`, which is called by `dispatch!`. `dispatch!` rescues the NotFound. We have already covered the three methods in tutorial 3, but we still list them here for quick reference.
+
+```ruby
+ # 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.
+ def route_missing
+ if @app
+ forward
+ else
+ raise NotFound
+ end
+ end
+```
+
+```ruby
+ # Run routes defined on the class and all superclasses.
+ def route!(base = settings, pass_block=nil)
+ if routes = base.routes[@request.request_method]
+ routes.each do |pattern, keys, conditions, block|
+ pass_block = process_route(pattern, keys, conditions) do
+ route_eval(&block)
+ end
+ end
+ end
+
+ # Run routes defined in superclass.
+ if base.superclass.respond_to?(:routes)
+ return route!(base.superclass, pass_block)
+ end
+
+ route_eval(&pass_block) if pass_block
+ route_missing
+ end
+```
+
+```ruby
+ # Dispatch a request with error handling.
+ def dispatch!
+ static! if settings.static? && (request.get? || request.head?)
+ filter! :before
+ route!
+ rescue NotFound => boom
+ handle_not_found!(boom)
+ rescue ::Exception => boom
+ handle_exception!(boom)
+ ensure
+ filter! :after unless env['sinatra.static_file']
+ end
+```
+
+We can see `dispatch` also rescue general exceptions. It runs the after filters at last unless env['sinatra.static_file'] is set, which means the request is asking for a static file.
+
+After an exception is rescued `handle_not_found!` is called with the exception object as the parameter. env['sinatra.error'] is set to the exception object and available to the downstream app. `@response.status` is of course set to 404 and the `@response.body` is set to `['<h1>Not Found</h1>']`. @response.headers['X-Cascade'] is set to 'pass' to indicate the rest of the middleware stack that they can try to match the requested route and generate response. Then `error_block!` is called with the exception class as the first param and NotFound as the second param. Let's see what `error_block!` does.
+
+```ruby
+ # Special treatment for 404s in order to play nice with cascades.
+ def handle_not_found!(boom)
+ @env['sinatra.error'] = boom
+ @response.status = 404
+ @response.headers['X-Cascade'] = 'pass'
+ @response.body = ['<h1>Not Found</h1>']
+ error_block! boom.class, NotFound
+ end
+```
+
+```ruby
+ # Find an custom error block for the key(s) specified.
+ def error_block!(*keys)
+ keys.each do |key|
+ base = settings
+ while base.respond_to?(:errors)
+ if block = base.errors[key]
+ # found a handler, eval and return result
+ return instance_eval(&block)
+ else
+ base = base.superclass
+ end
+ end
+ end
+ raise boom if settings.show_exceptions? and keys == Exception
+ nil
+ end
+```
+
+Next is the `not_found` method. There are two of them. One is an instance method in the Helper module:
+
+```ruby
+ # Halt processing and return a 404 Not Found.
+ def not_found(body=nil)
+ error 404, body
+ end
+```
+
+This `not_found` calls the `error` method also in the Helper module.
+
+```
+ # Halt processing and return the error status provided.
+ def error(code, body=nil)
+ code, body = 500, code.to_str if code.respond_to? :to_str
+ response.body = body unless body.nil?
+ halt code
+ end
+```
+
+The Sinatra::Helper is included to the Sinatra::Base, so these two not_found and errors are available in the route handler and views.
+
+The other `not_found` is a singleton method on Sinatra::Base.
+
+# Sugar for `error(404) { ... }`
+def not_found(&block)
+ error 404, &block
+end
+
+It calls the error method also on singleton class of Sinatra::Base
+
+```ruby
+ # Define a custom error handler. Optionally takes either an Exception
+ # class, or an HTTP status code to specify which errors should be
+ # handled.
+ def error(codes=Exception, &block)
+ Array(codes).each { |code| @errors[code] = block }
+ end
+```
+
+
+error MyCustomError do
+ 'So what happened was...' + request.env['sinatra.error'].message
+end
+
+get '/' do
+ raise MyCustomError, 'something bad'
+end

0 comments on commit 116a7bc

Please sign in to comment.