├── Gemfile ├── app ├── tutorial_1 │ ├── classic.rb │ └── tutorial_1.md ├── tutorial_2 │ ├── classic.rb │ ├── classic.ru │ ├── modular.ru │ ├── modular.rb │ ├── classic_2.rb │ ├── middleware_stack.rb │ └── tutorial_2.md ├── tutorial_3 │ ├── classic.rb │ └── tutorial_3.md └── tutorial_4 │ └── tutorial_4.md ├── .gitmodules ├── Gemfile.lock └── README.md /Gemfile: -------------------------------------------------------------------------------- 1 | gem 'sinatra', :path => 'sinatra' -------------------------------------------------------------------------------- /app/tutorial_1/classic.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | 3 | get '/' do 4 | 'Hello world!' 5 | end -------------------------------------------------------------------------------- /app/tutorial_2/classic.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | 3 | get '/' do 4 | 'Hello world!' 5 | end -------------------------------------------------------------------------------- /app/tutorial_3/classic.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | 3 | get '/' do 4 | 'Hello world!' 5 | end -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sinatra"] 2 | path = sinatra 3 | url = git://github.com/sinatra/sinatra.git 4 | -------------------------------------------------------------------------------- /app/tutorial_2/classic.ru: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/classic') 2 | run Sinatra::Application -------------------------------------------------------------------------------- /app/tutorial_2/modular.ru: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/modular') 2 | run Sinatra::Base 3 | # or run Modular -------------------------------------------------------------------------------- /app/tutorial_2/modular.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | 3 | class Modular < Sinatra::Base 4 | 5 | get '/' do 6 | 'Hello world!' 7 | end 8 | 9 | run! 10 | end -------------------------------------------------------------------------------- /app/tutorial_2/classic_2.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | 3 | include Sinatra::Delegator 4 | 5 | get '/' do 6 | 'Hello world!' 7 | end 8 | 9 | Sinatra::Application.run! 10 | # Sinatra::Base.run! -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: sinatra 3 | specs: 4 | sinatra (1.3.0.c) 5 | rack (~> 1.2) 6 | tilt (~> 1.2, >= 1.2.2) 7 | 8 | GEM 9 | specs: 10 | rack (1.2.2) 11 | tilt (1.2.2) 12 | 13 | PLATFORMS 14 | ruby 15 | 16 | DEPENDENCIES 17 | sinatra! 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I write this while I dig into sinatra source code myself. Please correct me if you find any mistakes or unclarity. Since Sinatra is short and concise, I hope the tutorial makes sense and it can keep pace with sinatra development with contributions from the community. The tutorial is prepared based on Sinatra 1.3.0c. 2 | 3 | Sinatra is added as a git submodule in the sinatra folder. 4 | As this tutorial is for sinatra, I don't include the full rack source. I only list some relevant code based on rack 1.2.2 (https://github.com/rack/rack/tree/1.2.2). 5 | If you need I recommend to use tux to play with sinatra https://github.com/cldwalker/tux 6 | 7 | It's assumed that you have read the sinatra README. Sinatra is well documented so that's the only thing you need for this tutorial. 8 | 9 | Content: 10 | ________ 11 | 12 | * tutorial_1: sinatra startup (https://github.com/zhengjia/sinatra-explained/blob/master/app/tutorial_1/tutorial_1.md) 13 | * tutorial_2: extensions and middleware (https://github.com/zhengjia/sinatra-explained/blob/master/app/tutorial_2/tutorial_2.md) 14 | * tutorial_3: routing (https://github.com/zhengjia/sinatra-explained/blob/master/app/tutorial_3/tutorial_3.md) 15 | * tutorial_4: request and response (work in progress) 16 | * tutorial_5: request cycle 17 | * tutorial_6: sinatra helpers 18 | * tutorial_7: templates 19 | -------------------------------------------------------------------------------- /app/tutorial_4/tutorial_4.md: -------------------------------------------------------------------------------- 1 | A few attr_accessor defined on Sinatra::Base: 2 | 3 | ```ruby 4 | attr_accessor :env, :request, :response, :params 5 | ``` 6 | 7 | Request 8 | ------- 9 | 10 | Request.new is removed later. https://github.com/sinatra/sinatra/issues/239 11 | 12 | ```ruby 13 | # The request object. See Rack::Request for more info: 14 | # http://rack.rubyforge.org/doc/classes/Rack/Request.html 15 | class Request < Rack::Request 16 | def self.new(env) 17 | env['sinatra.request'] ||= super 18 | end 19 | 20 | # Returns an array of acceptable media types for the response 21 | def accept 22 | @env['sinatra.accept'] ||= begin 23 | entries = @env['HTTP_ACCEPT'].to_s.split(',') 24 | entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first) 25 | end 26 | end 27 | 28 | def preferred_type(*types) 29 | return accept.first if types.empty? 30 | types.flatten! 31 | accept.detect do |pattern| 32 | type = types.detect { |t| File.fnmatch(pattern, t) } 33 | return type if type 34 | end 35 | end 36 | 37 | alias accept? preferred_type 38 | alias secure? ssl? 39 | 40 | def forwarded? 41 | @env.include? "HTTP_X_FORWARDED_HOST" 42 | end 43 | 44 | def route 45 | @route ||= Rack::Utils.unescape(path_info) 46 | end 47 | 48 | def path_info=(value) 49 | @route = nil 50 | super 51 | end 52 | 53 | private 54 | 55 | def accept_entry(entry) 56 | type, *options = entry.gsub(/\s/, '').split(';') 57 | quality = 0 # we sort smalles first 58 | options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' } 59 | [type, [quality, type.count('*'), 1 - options.size]] 60 | end 61 | end 62 | ``` 63 | 64 | Response 65 | -------- 66 | 67 | Let's see the response generated by sinatra error handling. 68 | 69 | NotFound is an exception class inherited from ruby's NameError whose parent is StandardError. It has one method `code` which just returns 404. 70 | 71 | ```ruby 72 | class NotFound < NameError #:nodoc: 73 | def code ; 404 ; end 74 | end 75 | ``` 76 | 77 | 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. 78 | 79 | ```ruby 80 | # No matching route was found or all routes passed. The default 81 | # implementation is to forward the request downstream when running 82 | # as middleware (@app is non-nil); when no downstream app is set, raise 83 | # a NotFound exception. Subclasses can override this method to perform 84 | # custom route miss logic. 85 | def route_missing 86 | if @app 87 | forward 88 | else 89 | raise NotFound 90 | end 91 | end 92 | ``` 93 | 94 | ```ruby 95 | # Run routes defined on the class and all superclasses. 96 | def route!(base = settings, pass_block=nil) 97 | if routes = base.routes[@request.request_method] 98 | routes.each do |pattern, keys, conditions, block| 99 | pass_block = process_route(pattern, keys, conditions) do 100 | route_eval(&block) 101 | end 102 | end 103 | end 104 | 105 | # Run routes defined in superclass. 106 | if base.superclass.respond_to?(:routes) 107 | return route!(base.superclass, pass_block) 108 | end 109 | 110 | route_eval(&pass_block) if pass_block 111 | route_missing 112 | end 113 | ``` 114 | 115 | ```ruby 116 | # Dispatch a request with error handling. 117 | def dispatch! 118 | static! if settings.static? && (request.get? || request.head?) 119 | filter! :before 120 | route! 121 | rescue NotFound => boom 122 | handle_not_found!(boom) 123 | rescue ::Exception => boom 124 | handle_exception!(boom) 125 | ensure 126 | filter! :after unless env['sinatra.static_file'] 127 | end 128 | ``` 129 | 130 | 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. 131 | 132 | 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 `['
\\n \"\n\n\n; _erbout.concat((h frames.first.filename.split(\"/\").last ).to_s); _erbout.concat \"\"\n; _erbout.concat((h frames.first.function ).to_s); _erbout.concat \"\\n \"\n; _erbout.concat((h frame.filename ).to_s); _erbout.concat \" in\\n \"\n; _erbout.concat((h frame.function ).to_s); _erbout.concat \"\\n \"\n; _erbout.concat((h line ).to_s); _erbout.concat \"\"; _erbout.concat((\n h frame.context_line ).to_s); _erbout.concat \"\"\n; _erbout.concat((h line ).to_s); _erbout.concat \"| Variable | \\nValue | \\n
|---|---|
| \"\n\n; _erbout.concat((h key ).to_s); _erbout.concat \" | \\n\"\n; _erbout.concat((h val.inspect ).to_s); _erbout.concat \" | \\n
No GET data.
\\n \"\n\n; end ; _erbout.concat \"\\n \\n| Variable | \\nValue | \\n
|---|---|
| \"\n\n; _erbout.concat((h key ).to_s); _erbout.concat \" | \\n\"\n; _erbout.concat((h val.inspect ).to_s); _erbout.concat \" | \\n
No POST data.
\\n \"\n\n; end ; _erbout.concat \"\\n \\n| Variable | \\nValue | \\n
|---|---|
| \"\n\n; _erbout.concat((h key ).to_s); _erbout.concat \" | \\n\"\n; _erbout.concat((h val ).to_s); _erbout.concat \" | \\n
You're seeing this error because you have\\nenabled the show_exceptions setting.