webmachine 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +5 -4
- data/README.md +25 -2
- data/examples/debugger.rb +7 -0
- data/examples/logging.rb +41 -0
- data/lib/webmachine/adapters.rb +1 -1
- data/lib/webmachine/adapters/hatetepe.rb +5 -1
- data/lib/webmachine/adapters/lazy_request_body.rb +7 -7
- data/lib/webmachine/adapters/mongrel.rb +27 -12
- data/lib/webmachine/adapters/rack.rb +42 -6
- data/lib/webmachine/adapters/reel.rb +91 -23
- data/lib/webmachine/adapters/webrick.rb +12 -6
- data/lib/webmachine/application.rb +1 -0
- data/lib/webmachine/cookie.rb +1 -1
- data/lib/webmachine/dispatcher.rb +7 -1
- data/lib/webmachine/events.rb +179 -0
- data/lib/webmachine/events/instrumented_event.rb +19 -0
- data/lib/webmachine/response.rb +1 -1
- data/lib/webmachine/streaming/io_encoder.rb +5 -1
- data/lib/webmachine/trace.rb +18 -0
- data/lib/webmachine/trace/fsm.rb +4 -1
- data/lib/webmachine/trace/listener.rb +12 -0
- data/lib/webmachine/version.rb +1 -1
- data/spec/spec_helper.rb +11 -0
- data/spec/support/adapter_lint.rb +125 -0
- data/spec/support/test_resource.rb +73 -0
- data/spec/webmachine/adapters/hatetepe_spec.rb +2 -2
- data/spec/webmachine/adapters/mongrel_spec.rb +6 -51
- data/spec/webmachine/adapters/rack_spec.rb +22 -155
- data/spec/webmachine/adapters/reel_spec.rb +59 -7
- data/spec/webmachine/adapters/webrick_spec.rb +7 -13
- data/spec/webmachine/cookie_spec.rb +1 -1
- data/spec/webmachine/decision/helpers_spec.rb +10 -0
- data/spec/webmachine/events_spec.rb +58 -0
- data/spec/webmachine/request_spec.rb +41 -0
- data/webmachine.gemspec +1 -1
- metadata +63 -24
    
        data/Gemfile
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            require 'rbconfig'
         | 
| 2 2 |  | 
| 3 | 
            -
            source  | 
| 3 | 
            +
            source 'https://rubygems.org'
         | 
| 4 4 |  | 
| 5 5 | 
             
            gemspec
         | 
| 6 6 |  | 
| @@ -8,10 +8,11 @@ gem 'bundler' | |
| 8 8 |  | 
| 9 9 | 
             
            group :webservers do
         | 
| 10 10 | 
             
              gem 'mongrel',  '~> 1.2.beta', :platform => [:mri, :rbx]
         | 
| 11 | 
            +
             | 
| 11 12 | 
             
              if RUBY_VERSION >= '1.9'
         | 
| 12 | 
            -
                gem 'reel', | 
| 13 | 
            -
                gem 'nio4r'
         | 
| 13 | 
            +
                gem 'reel', '~> 0.3.0', :platform => [:ruby_19, :ruby_20, :jruby]
         | 
| 14 14 | 
             
              end
         | 
| 15 | 
            +
             | 
| 15 16 | 
             
              gem 'hatetepe', '~> 0.5'
         | 
| 16 17 | 
             
            end
         | 
| 17 18 |  | 
| @@ -29,7 +30,7 @@ group :guard do | |
| 29 30 | 
             
            end
         | 
| 30 31 |  | 
| 31 32 | 
             
            group :docs do
         | 
| 32 | 
            -
              platform : | 
| 33 | 
            +
              platform :mri_19, :mri_20 do
         | 
| 33 34 | 
             
                gem 'redcarpet'
         | 
| 34 35 | 
             
              end
         | 
| 35 36 | 
             
            end
         | 
    
        data/README.md
    CHANGED
    
    | @@ -179,14 +179,16 @@ for an example of how to enable the debugger. | |
| 179 179 | 
             
            ## Documentation & Finding Help
         | 
| 180 180 |  | 
| 181 181 | 
             
            * [API documentation](http://rubydoc.info/gems/webmachine/frames/file/README.md)
         | 
| 182 | 
            +
            * [Mailing list](mailto:webmachine.rb@librelist.com)
         | 
| 182 183 | 
             
            * IRC channel #webmachine on freenode
         | 
| 183 184 |  | 
| 184 185 | 
             
            ## Related libraries
         | 
| 185 186 |  | 
| 186 187 | 
             
            * [irwebmachine](https://github.com/robgleeson/irwebmachine) - IRB/Pry debugging of Webmachine applications
         | 
| 187 188 | 
             
            * [webmachine-test](https://github.com/bernd/webmachine-test) - Helpers for testing Webmachine applications
         | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 189 | 
            +
            * [webmachine-linking](https://github.com/petejohanson/webmachine-linking) - Helpers for linking between Resources, and Web Linking
         | 
| 190 | 
            +
            * [webmachine-sprockets](https://github.com/lgierth/webmachine-sprockets) - Integration with Sprockets assets packaging system
         | 
| 191 | 
            +
            * [webmachine-actionview](https://github.com/rgarner/webmachine-actionview) - Integration of some Rails-style view conventions into Webmachine
         | 
| 190 192 |  | 
| 191 193 | 
             
            ## LICENSE
         | 
| 192 194 |  | 
| @@ -196,6 +198,27 @@ LICENSE for details. | |
| 196 198 |  | 
| 197 199 | 
             
            ## Changelog
         | 
| 198 200 |  | 
| 201 | 
            +
            ### 1.2.0
         | 
| 202 | 
            +
             | 
| 203 | 
            +
            1.2.0 is a major feature release that adds the Events instrumentation
         | 
| 204 | 
            +
            framework, support for Websockets in Reel adapter and a bunch of bugfixes.
         | 
| 205 | 
            +
            Added Justin McPherson and Hendrik Beskow as contributors. Thank you
         | 
| 206 | 
            +
            for your contributions!
         | 
| 207 | 
            +
             | 
| 208 | 
            +
            * Websockets support in Reel adapter.
         | 
| 209 | 
            +
            * Added `Events` framework implementing ActiveSupport::Notifications
         | 
| 210 | 
            +
              instrumentation API.
         | 
| 211 | 
            +
            * Linked mailing list and related library in README.
         | 
| 212 | 
            +
            * Fixed operator precedence in `IOEncoder#each`.
         | 
| 213 | 
            +
            * Fixed typo in Max-Age cookie attribute.
         | 
| 214 | 
            +
            * Allowed attributes to be set in a `Cookie`.
         | 
| 215 | 
            +
            * Fixed streaming in Rack adapter from Fiber that is expected
         | 
| 216 | 
            +
              to block
         | 
| 217 | 
            +
            * Added a more comprehensive adapter test suite and fixed various bugs
         | 
| 218 | 
            +
              in the existing adapters.
         | 
| 219 | 
            +
            * Webmachine::LazyRequestBody no longer double-buffers the request
         | 
| 220 | 
            +
              body and cannot be rewound.
         | 
| 221 | 
            +
             | 
| 199 222 | 
             
            ### 1.1.0 January 12, 2013
         | 
| 200 223 |  | 
| 201 224 | 
             
            1.1.0 is a major feature release that adds the Reel and Hatetepe
         | 
    
        data/examples/debugger.rb
    CHANGED
    
    | @@ -20,7 +20,14 @@ class MyTracedResource < Webmachine::Resource | |
| 20 20 | 
             
              end
         | 
| 21 21 | 
             
            end
         | 
| 22 22 |  | 
| 23 | 
            +
            class MyTracer
         | 
| 24 | 
            +
              def call(name, payload)
         | 
| 25 | 
            +
                puts "MyTracer #{name} #{payload.inspect}"
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 23 29 | 
             
            # Webmachine::Trace.trace_store = :pstore, "./trace"
         | 
| 30 | 
            +
            # Webmachine::Trace.trace_listener = [Webmachine::Trace::Listener.new, MyTracer.new]
         | 
| 24 31 |  | 
| 25 32 | 
             
            TraceExample = Webmachine::Application.new do |app|
         | 
| 26 33 | 
             
              app.routes do
         | 
    
        data/examples/logging.rb
    ADDED
    
    | @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'webmachine'
         | 
| 2 | 
            +
            require 'time'
         | 
| 3 | 
            +
            require 'logger'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class HelloResource < Webmachine::Resource
         | 
| 6 | 
            +
              def to_html
         | 
| 7 | 
            +
                "<html><head><title>Hello from Webmachine</title></head><body>Hello, world!</body></html>\n"
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
            end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            class LogListener
         | 
| 12 | 
            +
              def call(*args)
         | 
| 13 | 
            +
                handle_event(Webmachine::Events::InstrumentedEvent.new(*args))
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def handle_event(event)
         | 
| 17 | 
            +
                request = event.payload[:request]
         | 
| 18 | 
            +
                resource = event.payload[:resource]
         | 
| 19 | 
            +
                code = event.payload[:code]
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                puts "[%s] method=%s uri=%s code=%d resource=%s time=%.4f" % [
         | 
| 22 | 
            +
                  Time.now.iso8601, request.method, request.uri.to_s, code, resource,
         | 
| 23 | 
            +
                  event.duration
         | 
| 24 | 
            +
                ]
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            Webmachine::Events.subscribe('wm.dispatch', LogListener.new)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            App = Webmachine::Application.new do |app|
         | 
| 31 | 
            +
              app.routes do
         | 
| 32 | 
            +
                add_route [], HelloResource
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              app.configure do |config|
         | 
| 36 | 
            +
                config.adapter = :WEBrick
         | 
| 37 | 
            +
                config.adapter_options = {:AccessLog => [], :Logger => Logger.new('/dev/null')}
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            App.run
         | 
    
        data/lib/webmachine/adapters.rb
    CHANGED
    
    | @@ -6,7 +6,7 @@ module Webmachine | |
| 6 6 | 
             
              # application servers.
         | 
| 7 7 | 
             
              module Adapters
         | 
| 8 8 | 
             
                autoload :Mongrel,  'webmachine/adapters/mongrel'
         | 
| 9 | 
            -
             | 
| 9 | 
            +
                autoload :Reel,     'webmachine/adapters/reel'
         | 
| 10 10 | 
             
                autoload :Hatetepe, 'webmachine/adapters/hatetepe'
         | 
| 11 11 | 
             
              end
         | 
| 12 12 | 
             
            end
         | 
| @@ -30,10 +30,14 @@ module Webmachine | |
| 30 30 | 
             
                    EM.epoll
         | 
| 31 31 | 
             
                    EM.synchrony do
         | 
| 32 32 | 
             
                      ::Hatetepe::Server.start(options)
         | 
| 33 | 
            -
                      trap("INT") {  | 
| 33 | 
            +
                      trap("INT") { shutdown }
         | 
| 34 34 | 
             
                    end
         | 
| 35 35 | 
             
                  end
         | 
| 36 36 |  | 
| 37 | 
            +
                  def shutdown
         | 
| 38 | 
            +
                    EM.stop
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 37 41 | 
             
                  def call(request, &respond)
         | 
| 38 42 | 
             
                    response = Webmachine::Response.new
         | 
| 39 43 | 
             
                    dispatcher.dispatch(convert_request(request), response)
         | 
| @@ -11,7 +11,12 @@ module Webmachine | |
| 11 11 | 
             
                  # Converts the body to a String so you can work with the entire
         | 
| 12 12 | 
             
                  # thing.
         | 
| 13 13 | 
             
                  def to_s
         | 
| 14 | 
            -
                    @ | 
| 14 | 
            +
                    @request.body
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  # Converts the body to a String and checks if it is empty.
         | 
| 18 | 
            +
                  def empty?
         | 
| 19 | 
            +
                    to_s.empty?
         | 
| 15 20 | 
             
                  end
         | 
| 16 21 |  | 
| 17 22 | 
             
                  # Iterates over the body in chunks. If the body has previously
         | 
| @@ -20,12 +25,7 @@ module Webmachine | |
| 20 25 | 
             
                  # @yield [chunk]
         | 
| 21 26 | 
             
                  # @yieldparam [String] chunk a chunk of the request body
         | 
| 22 27 | 
             
                  def each
         | 
| 23 | 
            -
                     | 
| 24 | 
            -
                      @value.each {|chunk| yield chunk }
         | 
| 25 | 
            -
                    else
         | 
| 26 | 
            -
                      @value = []
         | 
| 27 | 
            -
                      @request.body {|chunk| @value << chunk; yield chunk }
         | 
| 28 | 
            -
                    end
         | 
| 28 | 
            +
                    @request.body {|chunk| yield chunk }
         | 
| 29 29 | 
             
                  end
         | 
| 30 30 | 
             
                end # class RequestBody
         | 
| 31 31 | 
             
              end # module Adapters
         | 
| @@ -18,14 +18,20 @@ module Webmachine | |
| 18 18 | 
             
                      :host => configuration.ip,
         | 
| 19 19 | 
             
                      :dispatcher => dispatcher
         | 
| 20 20 | 
             
                    }.merge(configuration.adapter_options)
         | 
| 21 | 
            -
                    config = ::Mongrel::Configurator.new(defaults) do
         | 
| 21 | 
            +
                    @config = ::Mongrel::Configurator.new(defaults) do
         | 
| 22 22 | 
             
                      listener do
         | 
| 23 23 | 
             
                        uri '/', :handler => Webmachine::Adapters::Mongrel::Handler.new(defaults[:dispatcher])
         | 
| 24 24 | 
             
                      end
         | 
| 25 25 | 
             
                      trap("INT") { stop }
         | 
| 26 26 | 
             
                      run
         | 
| 27 27 | 
             
                    end
         | 
| 28 | 
            -
                    config.join
         | 
| 28 | 
            +
                    @config.join
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def shutdown
         | 
| 32 | 
            +
                    # The second argument tells mongrel to block until all listeners are shut down.
         | 
| 33 | 
            +
                    # This causes the mongrel tests to be very slow, but faster methods cause errors.
         | 
| 34 | 
            +
                    @config.stop(false, true) if @config
         | 
| 29 35 | 
             
                  end
         | 
| 30 36 |  | 
| 31 37 | 
             
                  # A Mongrel handler for Webmachine
         | 
| @@ -51,11 +57,12 @@ module Webmachine | |
| 51 57 | 
             
                        wres.status = response.code.to_i
         | 
| 52 58 | 
             
                        wres.send_status(nil)
         | 
| 53 59 |  | 
| 54 | 
            -
                        response.headers.each  | 
| 55 | 
            -
                          vs. | 
| 60 | 
            +
                        response.headers.each do |k, vs|
         | 
| 61 | 
            +
                          [*vs].each do |v|
         | 
| 56 62 | 
             
                            wres.header[k] = v
         | 
| 57 | 
            -
                           | 
| 58 | 
            -
                         | 
| 63 | 
            +
                          end
         | 
| 64 | 
            +
                        end
         | 
| 65 | 
            +
             | 
| 59 66 | 
             
                        wres.header['Server'] = [Webmachine::SERVER_STRING, "Mongrel/#{::Mongrel::Const::MONGREL_VERSION}"].join(" ")
         | 
| 60 67 | 
             
                        wres.send_header
         | 
| 61 68 |  | 
| @@ -64,16 +71,24 @@ module Webmachine | |
| 64 71 | 
             
                          wres.write response.body
         | 
| 65 72 | 
             
                          wres.socket.flush
         | 
| 66 73 | 
             
                        when Enumerable
         | 
| 67 | 
            -
                           | 
| 68 | 
            -
             | 
| 69 | 
            -
                             | 
| 70 | 
            -
             | 
| 74 | 
            +
                          # This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
         | 
| 75 | 
            +
                          if response.headers["Transfer-Encoding"] == "chunked"
         | 
| 76 | 
            +
                            Webmachine::ChunkedBody.new(response.body).each do |part|
         | 
| 77 | 
            +
                              wres.write part
         | 
| 78 | 
            +
                              wres.socket.flush
         | 
| 79 | 
            +
                            end
         | 
| 80 | 
            +
                          else
         | 
| 81 | 
            +
                            response.body.each do |part|
         | 
| 82 | 
            +
                              wres.write part
         | 
| 83 | 
            +
                              wres.socket.flush
         | 
| 84 | 
            +
                            end
         | 
| 85 | 
            +
                          end
         | 
| 71 86 | 
             
                        else
         | 
| 72 87 | 
             
                          if response.body.respond_to?(:call)
         | 
| 73 | 
            -
                            Webmachine::ChunkedBody.new(Array(response.body.call)).each  | 
| 88 | 
            +
                            Webmachine::ChunkedBody.new(Array(response.body.call)).each do |part|
         | 
| 74 89 | 
             
                              wres.write part
         | 
| 75 90 | 
             
                              wres.socket.flush
         | 
| 76 | 
            -
                             | 
| 91 | 
            +
                            end
         | 
| 77 92 | 
             
                          end
         | 
| 78 93 | 
             
                        end
         | 
| 79 94 | 
             
                      ensure
         | 
| @@ -31,16 +31,23 @@ module Webmachine | |
| 31 31 | 
             
                #     MyApplication.run
         | 
| 32 32 | 
             
                #
         | 
| 33 33 | 
             
                class Rack < Adapter
         | 
| 34 | 
            +
                  # Used to override default Rack server options (useful in testing)
         | 
| 35 | 
            +
                  DEFAULT_OPTIONS = {}
         | 
| 34 36 |  | 
| 35 37 | 
             
                  # Start the Rack adapter
         | 
| 36 38 | 
             
                  def run
         | 
| 37 | 
            -
                    options = {
         | 
| 39 | 
            +
                    options = DEFAULT_OPTIONS.merge({
         | 
| 38 40 | 
             
                      :app => self,
         | 
| 39 41 | 
             
                      :Port => configuration.port,
         | 
| 40 42 | 
             
                      :Host => configuration.ip
         | 
| 41 | 
            -
                    }.merge(configuration.adapter_options)
         | 
| 43 | 
            +
                    }).merge(configuration.adapter_options)
         | 
| 42 44 |  | 
| 43 | 
            -
                    ::Rack::Server. | 
| 45 | 
            +
                    @server = ::Rack::Server.new(options)
         | 
| 46 | 
            +
                    @server.start
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def shutdown
         | 
| 50 | 
            +
                    @server.server.shutdown if @server
         | 
| 44 51 | 
             
                  end
         | 
| 45 52 |  | 
| 46 53 | 
             
                  # Handles a Rack-based request.
         | 
| @@ -59,7 +66,7 @@ module Webmachine | |
| 59 66 |  | 
| 60 67 | 
             
                    response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
         | 
| 61 68 |  | 
| 62 | 
            -
                    rack_status | 
| 69 | 
            +
                    rack_status  = response.code
         | 
| 63 70 | 
             
                    rack_headers = response.headers.flattened("\n")
         | 
| 64 71 | 
             
                    rack_body = case response.body
         | 
| 65 72 | 
             
                                when String # Strings are enumerable in ruby 1.8
         | 
| @@ -68,16 +75,45 @@ module Webmachine | |
| 68 75 | 
             
                                  if response.body.respond_to?(:call)
         | 
| 69 76 | 
             
                                    Webmachine::ChunkedBody.new(Array(response.body.call))
         | 
| 70 77 | 
             
                                  elsif response.body.respond_to?(:each)
         | 
| 71 | 
            -
                                     | 
| 78 | 
            +
                                    # This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
         | 
| 79 | 
            +
                                    if response.headers["Transfer-Encoding"] == "chunked"
         | 
| 80 | 
            +
                                      Webmachine::ChunkedBody.new(response.body)
         | 
| 81 | 
            +
                                    else
         | 
| 82 | 
            +
                                      response.body
         | 
| 83 | 
            +
                                    end
         | 
| 72 84 | 
             
                                  else
         | 
| 73 85 | 
             
                                    [response.body.to_s]
         | 
| 74 86 | 
             
                                  end
         | 
| 75 87 | 
             
                                end
         | 
| 76 88 |  | 
| 77 | 
            -
                    rack_res =  | 
| 89 | 
            +
                    rack_res = RackResponse.new(rack_body, rack_status, rack_headers)
         | 
| 78 90 | 
             
                    rack_res.finish
         | 
| 79 91 | 
             
                  end
         | 
| 80 92 |  | 
| 93 | 
            +
                  class RackResponse
         | 
| 94 | 
            +
                    def initialize(body, status, headers)
         | 
| 95 | 
            +
                      @body    = body
         | 
| 96 | 
            +
                      @status  = status
         | 
| 97 | 
            +
                      @headers = headers
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    def finish
         | 
| 101 | 
            +
                      @headers['Content-Type'] ||= 'text/html' if rack_release_enforcing_content_type
         | 
| 102 | 
            +
                      @headers.delete('Content-Type')          if response_without_body
         | 
| 103 | 
            +
                      [@status, @headers, @body]
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    protected
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    def response_without_body
         | 
| 109 | 
            +
                      ::Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? @status
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                    def rack_release_enforcing_content_type
         | 
| 113 | 
            +
                      ::Rack.release < '1.5'
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 81 117 | 
             
                  # Wraps the Rack input so it can be treated like a String or
         | 
| 82 118 | 
             
                  # Enumerable.
         | 
| 83 119 | 
             
                  # @api private
         | 
| @@ -4,40 +4,108 @@ require 'webmachine/headers' | |
| 4 4 | 
             
            require 'webmachine/request'
         | 
| 5 5 | 
             
            require 'webmachine/response'
         | 
| 6 6 | 
             
            require 'webmachine/dispatcher'
         | 
| 7 | 
            -
            require 'webmachine/ | 
| 7 | 
            +
            require 'webmachine/adapters/lazy_request_body'
         | 
| 8 | 
            +
            require 'set'
         | 
| 8 9 |  | 
| 9 10 | 
             
            module Webmachine
         | 
| 10 11 | 
             
              module Adapters
         | 
| 11 12 | 
             
                class Reel < Adapter
         | 
| 12 13 | 
             
                  def run
         | 
| 13 | 
            -
                    options = {
         | 
| 14 | 
            +
                    @options = {
         | 
| 14 15 | 
             
                      :port => configuration.port,
         | 
| 15 16 | 
             
                      :host => configuration.ip
         | 
| 16 17 | 
             
                    }.merge(configuration.adapter_options)
         | 
| 17 | 
            -
             | 
| 18 | 
            -
                     | 
| 19 | 
            -
             | 
| 18 | 
            +
             | 
| 19 | 
            +
                    if extra_verbs = configuration.adapter_options[:extra_verbs]
         | 
| 20 | 
            +
                      @extra_verbs = Set.new(extra_verbs.map(&:to_s).map(&:upcase))
         | 
| 21 | 
            +
                    else
         | 
| 22 | 
            +
                      @extra_verbs = Set.new
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    @server = ::Reel::Server.supervise(@options[:host], @options[:port], &method(:process))
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    # FIXME: this will no longer work on Ruby 2.0. We need Celluloid.trap
         | 
| 28 | 
            +
                    trap("INT") { @server.terminate; exit 0 }
         | 
| 29 | 
            +
                    Celluloid::Actor.join(@server)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def shutdown
         | 
| 33 | 
            +
                    @server.terminate! if @server
         | 
| 20 34 | 
             
                  end
         | 
| 21 35 |  | 
| 22 36 | 
             
                  def process(connection)
         | 
| 23 | 
            -
                    while  | 
| 24 | 
            -
                       | 
| 25 | 
            -
                       | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
                         | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
                       | 
| 38 | 
            -
                       | 
| 39 | 
            -
             | 
| 40 | 
            -
                       | 
| 37 | 
            +
                    while request = connection.request
         | 
| 38 | 
            +
                      # Users of the adapter can configure a custom WebSocket handler
         | 
| 39 | 
            +
                      if request.is_a? ::Reel::WebSocket
         | 
| 40 | 
            +
                        if handler = @options[:websocket_handler]
         | 
| 41 | 
            +
                          handler.call(request)
         | 
| 42 | 
            +
                        else
         | 
| 43 | 
            +
                          # Pretend we don't know anything about the WebSocket protocol
         | 
| 44 | 
            +
                          # FIXME: This isn't strictly what RFC 6455 would have us do
         | 
| 45 | 
            +
                          request.respond :bad_request, "WebSockets not supported"
         | 
| 46 | 
            +
                        end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                        next
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      # Optional support for e.g. WebDAV verbs not included in Webmachine's
         | 
| 52 | 
            +
                      # state machine. Do the "Railsy" thing and handle them like POSTs
         | 
| 53 | 
            +
                      # with a magical parameter
         | 
| 54 | 
            +
                      if @extra_verbs.include?(request.method)
         | 
| 55 | 
            +
                        method = "POST"
         | 
| 56 | 
            +
                        param  = "_method=#{request.method}"
         | 
| 57 | 
            +
                        uri    = request_uri(request.url, request.headers, param)
         | 
| 58 | 
            +
                      else
         | 
| 59 | 
            +
                        method = request.method
         | 
| 60 | 
            +
                        uri    = request_uri(request.url, request.headers)
         | 
| 61 | 
            +
                      end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      wm_headers  = Webmachine::Headers[request.headers.dup]
         | 
| 64 | 
            +
                      wm_request  = Webmachine::Request.new(method, uri, wm_headers,
         | 
| 65 | 
            +
                                                            LazyRequestBody.new(request))
         | 
| 66 | 
            +
                      wm_response = Webmachine::Response.new
         | 
| 67 | 
            +
                      @dispatcher.dispatch(wm_request, wm_response)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      fixup_headers(wm_response)
         | 
| 70 | 
            +
                      fixup_callable_encoder(wm_response)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                      request.respond ::Reel::Response.new(wm_response.code,
         | 
| 73 | 
            +
                                                           wm_response.headers,
         | 
| 74 | 
            +
                                                           wm_response.body)
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  def request_uri(path, headers, extra_query_params = nil)
         | 
| 79 | 
            +
                    host_parts = headers.fetch('Host').split(':')
         | 
| 80 | 
            +
                    path_parts = path.split('?')
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    uri_hash = {host: host_parts.first, path: path_parts.first}
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    uri_hash[:port]  = host_parts.last.to_i if host_parts.length == 2
         | 
| 85 | 
            +
                    uri_hash[:query] = path_parts.last      if path_parts.length == 2
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    if extra_query_params
         | 
| 88 | 
            +
                      if uri_hash[:query]
         | 
| 89 | 
            +
                        uri_hash[:query] << "&#{extra_query_params}"
         | 
| 90 | 
            +
                      else
         | 
| 91 | 
            +
                        uri_hash[:query] = extra_query_params
         | 
| 92 | 
            +
                      end
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    URI::HTTP.build(uri_hash)
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  def fixup_headers(response)
         | 
| 99 | 
            +
                    response.headers.each do |key, value|
         | 
| 100 | 
            +
                      if value.is_a?(Array)
         | 
| 101 | 
            +
                        response.headers[key] = value.join(", ")
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  def fixup_callable_encoder(response)
         | 
| 107 | 
            +
                    if response.body.is_a?(Streaming::CallableEncoder)
         | 
| 108 | 
            +
                      response.body = [response.body.call]
         | 
| 41 109 | 
             
                    end
         | 
| 42 110 | 
             
                  end
         | 
| 43 111 | 
             
                end
         |