reel 0.4.0 → 0.5.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of reel might be problematic. Click here for more details.

Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -2
  3. data/Gemfile +6 -0
  4. data/README.md +26 -16
  5. data/benchmarks/hello_reel.rb +1 -1
  6. data/benchmarks/reel_pool.rb +27 -0
  7. data/examples/hello_world.rb +2 -2
  8. data/examples/{ssl_hello_world.rb → https_hello_world.rb} +1 -1
  9. data/examples/roundtrip.rb +1 -1
  10. data/examples/server_sent_events.rb +22 -18
  11. data/examples/spy_hello_world.rb +22 -0
  12. data/examples/websockets.rb +3 -3
  13. data/lib/reel.rb +6 -9
  14. data/lib/reel/connection.rb +40 -42
  15. data/lib/reel/mixins.rb +3 -1
  16. data/lib/reel/request.rb +40 -9
  17. data/lib/reel/request/body.rb +65 -0
  18. data/lib/reel/request/info.rb +21 -0
  19. data/lib/reel/{request_parser.rb → request/parser.rb} +2 -2
  20. data/lib/reel/request/state_machine.rb +26 -0
  21. data/lib/reel/response.rb +4 -11
  22. data/lib/reel/response/writer.rb +59 -0
  23. data/lib/reel/server.rb +30 -6
  24. data/lib/reel/server/http.rb +20 -0
  25. data/lib/reel/server/https.rb +63 -0
  26. data/lib/reel/spy.rb +71 -0
  27. data/lib/reel/stream.rb +2 -2
  28. data/lib/reel/version.rb +2 -2
  29. data/lib/reel/websocket.rb +1 -1
  30. data/reel.gemspec +3 -3
  31. data/spec/fixtures/ca.crt +27 -0
  32. data/spec/fixtures/ca.key +27 -0
  33. data/spec/fixtures/client.crt +81 -20
  34. data/spec/fixtures/client.unsigned.crt +22 -0
  35. data/spec/fixtures/server.crt +80 -20
  36. data/spec/reel/connection_spec.rb +50 -11
  37. data/spec/reel/{server_spec.rb → http_server_spec.rb} +1 -1
  38. data/spec/reel/https_server_spec.rb +119 -0
  39. data/spec/reel/{response_writer_spec.rb → response/writer_spec.rb} +10 -2
  40. data/spec/reel/websocket_spec.rb +2 -2
  41. data/spec/spec_helper.rb +3 -34
  42. data/spec/support/example_request.rb +34 -0
  43. metadata +56 -43
  44. data/examples/chunked.rb +0 -25
  45. data/lib/reel/request_body.rb +0 -56
  46. data/lib/reel/request_info.rb +0 -19
  47. data/lib/reel/response_writer.rb +0 -76
  48. data/lib/reel/ssl_server.rb +0 -41
  49. data/spec/reel/ssl_server_spec.rb +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea63b536704d29dc28c875c4160bc25937d989b5
4
- data.tar.gz: e3d5d422b33b49c35932238dc00dd0847eaca7f1
3
+ metadata.gz: 96b038743e66387d895d56854f1f09709b55200c
4
+ data.tar.gz: 03f4a385c206da2723ba5850014da73468b89e09
5
5
  SHA512:
6
- metadata.gz: f5d23ce9c146c86b313f5aa35f218e37e172001a7e29f8a39945a68e995ed9dcaf61bebc987558224b2c950917628a10f39b5428c50fd3451eda7e4ea15eb2f9
7
- data.tar.gz: 73f35c9e99d133135d39ec18d77a04f2a71ce56170f23ccb36f766194a722595b31fa3856769b5c701a8e468c77293a317302711544f13aef89d9375c847e805
6
+ metadata.gz: 1563d75814c208fda3080219201950714a2167c70afa07e3346a7624d31e9d66cf0dd56e15d97fa1438bb6781f083080164eae391fd3fc9ec938a647a46408ed
7
+ data.tar.gz: 8f3f2fb7aa0cdd0bae37495a7f5185abfb76e8bf11c5a1b082db9dfc8221b0accde6dff5b6cdce25756dca87537ca4cf490cbad5ffc818a3c41da33d0a4feed9
data/.travis.yml CHANGED
@@ -1,15 +1,18 @@
1
1
  rvm:
2
2
  - 1.9.3
3
3
  - 2.0.0
4
+ - 2.1.0
4
5
  - ruby-head
5
- - jruby-19mode
6
+ - jruby
6
7
  - jruby-head
7
- - rbx-19mode
8
+ - rbx
8
9
 
9
10
  matrix:
10
11
  allow_failures:
11
12
  - rvm: ruby-head
13
+ - rvm: jruby
12
14
  - rvm: jruby-head
15
+ - rvm: rbx
13
16
 
14
17
  notifications:
15
18
  irc: "irc.freenode.org#celluloid"
data/Gemfile CHANGED
@@ -14,3 +14,9 @@ group :development do
14
14
  gem 'guard-rspec'
15
15
  gem 'pry'
16
16
  end
17
+
18
+ platforms :rbx do
19
+ gem 'racc'
20
+ gem 'rubinius-coverage'
21
+ gem 'rubysl', '~> 2.0'
22
+ end
data/README.md CHANGED
@@ -7,10 +7,11 @@
7
7
 
8
8
  > "A dizzying lifetime... reeling by on celluloid" _-- Rush / Between The Wheels_
9
9
 
10
- Reel is a fast, non-blocking "evented" web server built on [http_parser.rb][parser],
11
- [websocket_parser][websockets], [Celluloid::IO][celluloidio], and [nio4r][nio4r]. Thanks
12
- to Celluloid, Reel also works great for multithreaded applications and provides
13
- traditional multithreaded blocking I/O support too.
10
+ Reel is a fast, non-blocking "evented" web server
11
+ built on [http_parser.rb][parser], [websocket_parser][websockets],
12
+ [Celluloid::IO][celluloidio], and [nio4r][nio4r]. Thanks to Celluloid,
13
+ Reel also works great for multithreaded applications
14
+ and provides traditional multithreaded blocking I/O support too.
14
15
 
15
16
  [parser]: https://github.com/tmm1/http_parser.rb
16
17
  [websockets]: https://github.com/afcapel/websocket_parser
@@ -18,11 +19,13 @@ traditional multithreaded blocking I/O support too.
18
19
  [nio4r]: https://github.com/celluloid/nio4r
19
20
 
20
21
  Connections to Reel can be either non-blocking and handled entirely within
21
- the Reel::Server thread, or the same connections can be dispatched to worker
22
- threads where they will perform ordinary blocking IO. Reel provides no
23
- built-in thread pool, however you can build one yourself using Celluloid.pool,
24
- or because Celluloid already pools threads to begin with, you can simply use
25
- an actor per connection.
22
+ the Reel::Server thread (handling HTTP, HTTPS, or UNIX sockets),
23
+ or the same connections can be dispatched to worker threads
24
+ where they will perform ordinary blocking IO.
25
+ Reel provides no built-in thread pool,
26
+ however you can build one yourself using Celluloid.pool,
27
+ or because Celluloid already pools threads to begin with,
28
+ you can simply use an actor per connection.
26
29
 
27
30
  This gives you the best of both worlds: non-blocking I/O for when you're
28
31
  primarily I/O bound, and threads for where you're compute bound.
@@ -59,6 +62,15 @@ Node.js (0.6.5) 11735 reqs/s (0.1 ms/req)
59
62
  All Ruby benchmarks done on Ruby 1.9.3. Latencies given are average-per-request
60
63
  and are not amortized across all concurrent requests.
61
64
 
65
+ Documentation
66
+ -------------
67
+
68
+ [Please see the Reel Wiki](https://github.com/celluloid/reel/wiki)
69
+ for detailed documentation and usage notes.
70
+
71
+ [YARD documentation](http://rubydoc.info/github/celluloid/reel/master/frames) is
72
+ also available.
73
+
62
74
  Framework Adapters
63
75
  ------------------
64
76
 
@@ -97,8 +109,8 @@ MyApp = Webmachine::Application.new do |app|
97
109
 
98
110
  # Optional: handler for incoming websockets
99
111
  config.adapter_options[:websocket_handler] = proc do |websocket|
100
- # socket is a Reel::WebSocket
101
- socket << "hello, world"
112
+ # websocket is a Reel::WebSocket
113
+ websocket << "hello, world"
102
114
  end
103
115
  end
104
116
  end
@@ -112,8 +124,6 @@ for further information
112
124
  Ruby API
113
125
  --------
114
126
 
115
- *NOTE: these examples are for the Reel 0.4.0.pre2 API*
116
-
117
127
  Reel aims to provide a "bare metal" API that other frameworks (such as Rack
118
128
  and Webmachine) can leverage. This API can also be nice in performance critical
119
129
  applications.
@@ -125,7 +135,7 @@ Reel lets you pass a block to initialize which receives connections:
125
135
  ```ruby
126
136
  require 'reel'
127
137
 
128
- Reel::Server.supervise("0.0.0.0", 3000) do |connection|
138
+ Reel::Server::HTTP.supervise("0.0.0.0", 3000) do |connection|
129
139
  # Support multiple keep-alive requests per connection
130
140
  connection.each_request do |request|
131
141
  # WebSocket support
@@ -156,7 +166,7 @@ You can also subclass Reel, which allows additional customizations:
156
166
  ```ruby
157
167
  require 'reel'
158
168
 
159
- class MyServer < Reel::Server
169
+ class MyServer < Reel::Server::HTTP
160
170
  def initialize(host = "127.0.0.1", port = 3000)
161
171
  super(host, port, &method(:on_connection))
162
172
  end
@@ -164,7 +174,7 @@ class MyServer < Reel::Server
164
174
  def on_connection(connection)
165
175
  connection.each_request do |request|
166
176
  if request.websocket?
167
- handle_websocket(request)
177
+ handle_websocket(request.websocket)
168
178
  else
169
179
  handle_request(request)
170
180
  end
@@ -5,7 +5,7 @@ require 'reel'
5
5
  addr, port = '127.0.0.1', 1234
6
6
 
7
7
  puts "*** Starting server on #{addr}:#{port}"
8
- Reel::Server.new(addr, port) do |connection|
8
+ Reel::Server::HTTP.new(addr, port) do |connection|
9
9
  connection.respond :ok, "Hello World"
10
10
  end
11
11
 
@@ -0,0 +1,27 @@
1
+ require 'bundler/setup'
2
+ require 'reel'
3
+
4
+ class MyConnectionHandler
5
+ include Celluloid
6
+
7
+ def handle_connection(connection)
8
+ connection.each_request { |req| handle_request(req) }
9
+ rescue Reel::SocketError
10
+ connection.close
11
+ end
12
+
13
+ def handle_request(request)
14
+ request.respond :ok, ''
15
+ end
16
+ end
17
+
18
+ connectionPool = MyConnectionHandler.pool
19
+
20
+ Reel::Server::HTTP.run('127.0.0.1', 3000) do |connection|
21
+ # We're handing this connection off to another actor, so
22
+ # we detach it first before handing it off
23
+ connection.detach
24
+
25
+ # Let a Connection Pool handle the connections for Roflscale Applications
26
+ connectionPool.async.handle_connection(connection)
27
+ end
@@ -7,8 +7,8 @@ require 'reel'
7
7
 
8
8
  addr, port = '127.0.0.1', 1234
9
9
 
10
- puts "*** Starting server on #{addr}:#{port}"
11
- Reel::Server.run(addr, port) do |connection|
10
+ puts "*** Starting server on http://#{addr}:#{port}"
11
+ Reel::Server::HTTP.run(addr, port) do |connection|
12
12
  # For keep-alive support
13
13
  connection.each_request do |request|
14
14
  # Ordinarily we'd route the request here, e.g.
@@ -12,7 +12,7 @@ options = {
12
12
  }
13
13
 
14
14
  puts "*** Starting server on #{addr}:#{port}"
15
- Reel::SSLServer.supervise(addr, port, options) do |connection|
15
+ Reel::Server::HTTPS.supervise(addr, port, options) do |connection|
16
16
  # For keep-alive support
17
17
  connection.each_request do |request|
18
18
  # Ordinarily we'd route the request here, e.g.
@@ -61,7 +61,7 @@ class Reader
61
61
  end
62
62
  end
63
63
 
64
- class WebServer < Reel::Server
64
+ class WebServer < Reel::Server::HTTP
65
65
  include Celluloid::Logger
66
66
 
67
67
  def initialize(host = "0.0.0.0", port = 9000)
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # See: http://www.w3.org/TR/eventsource/
2
3
  # Run with: bundle exec examples/server_sent_events.rb
3
4
  # Test with: curl -vNH 'Accept: text/event-stream' -H 'Last-Event-ID: 1' -H 'Cache-Control: no-cache' http://localhost:63310
4
5
 
@@ -6,62 +7,65 @@ require 'bundler/setup'
6
7
  require 'time'
7
8
  require 'reel'
8
9
 
9
- class ServerSentEvents < Reel::Server
10
+
11
+ class ServerSentEvents < Reel::Server::HTTP
10
12
  include Celluloid::Logger
11
13
 
12
14
  def initialize(ip = '127.0.0.1', port = 63310)
13
15
  @connections = []
14
16
  @history = []
15
- @lastMessageId = 0
17
+ @lastEventId = 0
16
18
  async.ping
17
- async.ring
19
+ async.ring #not needed for Production, only to have some events here.
18
20
  super(ip, port, &method(:on_connection))
19
21
  end
20
22
 
23
+ #broadcasts events to all clients
21
24
  def broadcast(event, data)
22
25
  #only keep the last 5000 Events
23
26
  if @history.size >= 6000
24
27
  @history.slice!(0, @history.size - 1000)
25
28
  end
26
- @lastMessageId += 1
27
- @history << {id: @lastMessageId, event: event, data: data}
29
+ @lastEventId += 1
30
+ @history << {id: @lastEventId, event: event, data: data}
28
31
  info "Sending Event: #{event} Data: #{data} to #{@connections.count} Clients"
29
32
  @connections.each do |socket|
30
- async.send_sse(socket, data, event, @lastMessageId)
33
+ async.send_sse(socket, data, event, @lastEventId)
31
34
  end
32
35
  true
33
36
  end
34
37
 
35
38
  private
36
- #event and id are optional
39
+ #event and id are optional, Eventsource only needs data
37
40
  def send_sse(socket, data, event = nil, id = nil)
38
41
  begin
39
42
  socket.id id if id
40
43
  socket.event event if event
41
44
  socket.data data
42
- rescue IOError, Errno::ECONNRESET, Errno::EPIPE
43
- @connections.delete(socket)
45
+ rescue Reel::SocketError, NoMethodError
46
+ @connections.delete(socket) if @connections.include?(socket)
44
47
  end
45
48
  end
46
49
 
50
+ #Lines that start with a Colon are Comments and will be ignored
47
51
  def send_ping
48
52
  @connections.each do |socket|
49
53
  begin
50
- #Lines that start with a Colon are Comments and will be ignored
51
54
  socket << ":\n"
52
- rescue IOError, Errno::ECONNRESET, Errno::EPIPE
55
+ rescue Reel::SocketError
53
56
  @connections.delete(socket)
54
57
  end
55
58
  end
56
59
  end
57
60
 
61
+ #apache 2.2 closes connections after five seconds when nothing is send, see this as a poor mans Keep-Alive
58
62
  def ping
59
- #apache 2.2 closes connections after five seconds when nothing is send
60
63
  every(5) do
61
64
  send_ping
62
65
  end
63
66
  end
64
67
 
68
+ #only used to have some events here, not needed for Production.
65
69
  def ring
66
70
  every(2) do
67
71
  broadcast(:time, Time.now.httpdate)
@@ -77,19 +81,19 @@ class ServerSentEvents < Reel::Server
77
81
  query[key] = value
78
82
  end
79
83
  end
80
- body = Reel::EventStream.new do |socket|
84
+ #see https://github.com/celluloid/reel/blob/master/lib/reel/stream.rb#L35
85
+ eventStream = Reel::EventStream.new do |socket|
81
86
  @connections << socket
82
87
  socket.retry 5000
83
- #after a Connection reset resend newer Messages to the Client
88
+ #after a Connection reset resend newer Messages to the Client, query['lastEventId'] is needed for https://github.com/Yaffle/EventSource
84
89
  if @history.count > 0 && id = (request.headers['Last-Event-ID'] || query['lastEventId'])
85
90
  begin
86
- id = Integer(id)
87
- if history = @history.select {|h| h[:id] >= id}.map {|a| "id: %d\nevent: %s\ndata: %s" % [a[:id], a[:event], a[:data]]}.join("\n\n")
91
+ if history = @history.select {|h| h[:id] >= Integer(id)}.map {|a| "id: %d\nevent: %s\ndata: %s" % [a[:id], a[:event], a[:data]]}.join("\n\n")
88
92
  socket << "%s\n\n" % [history]
89
93
  else
90
94
  socket << "id\n\n"
91
95
  end
92
- rescue ArgumentError, IOError, Errno::ECONNRESET, Errno::EPIPE
96
+ rescue ArgumentError, Reel::SocketError
93
97
  @connections.delete(socket)
94
98
  request.close
95
99
  end
@@ -102,7 +106,7 @@ class ServerSentEvents < Reel::Server
102
106
  'Content-Type' => 'text/event-stream; charset=utf-8',
103
107
  'Cache-Control' => 'no-cache',
104
108
  'X-Accel-Buffering' => 'no',
105
- 'Access-Control-Allow-Origin' => '*'}, body)
109
+ 'Access-Control-Allow-Origin' => '*'}, eventStream)
106
110
  end
107
111
 
108
112
  def on_connection(connection)
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ # Run with: bundle exec examples/hello_world.rb
3
+
4
+ require 'rubygems'
5
+ require 'bundler/setup'
6
+ require 'reel'
7
+
8
+ addr, port = '127.0.0.1', 1234
9
+
10
+ puts "*** Starting server on http://#{addr}:#{port}"
11
+ Reel::Server::HTTP.run(addr, port, spy: true) do |connection|
12
+ # For keep-alive support
13
+ connection.each_request do |request|
14
+ # Ordinarily we'd route the request here, e.g.
15
+ # route request.url
16
+ request.respond :ok, "hello, world!\n"
17
+ end
18
+
19
+ # Reel takes care of closing the connection for you
20
+ # If you would like to hand the connection off to another thread or actor,
21
+ # use, connection.detach and then manually call connection.close when done
22
+ end
@@ -38,7 +38,7 @@ class TimeClient
38
38
  end
39
39
  end
40
40
 
41
- class WebServer < Reel::Server
41
+ class WebServer < Reel::Server::HTTP
42
42
  include Celluloid::Logger
43
43
 
44
44
  def initialize(host = "127.0.0.1", port = 1234)
@@ -53,11 +53,11 @@ class WebServer < Reel::Server
53
53
 
54
54
  # We're going to hand off this connection to another actor (TimeClient)
55
55
  # However, initially Reel::Connections are "attached" to the
56
- # Reel::Server actor, meaning that the server manages the connection
56
+ # Reel::Server::HTTP actor, meaning that the server manages the connection
57
57
  # lifecycle (e.g. error handling) for us.
58
58
  #
59
59
  # If we want to hand this connection off to another actor, we first
60
- # need to detach it from the Reel::Server
60
+ # need to detach it from the Reel::Server (in this case, Reel::Server::HTTP)
61
61
  connection.detach
62
62
 
63
63
  route_websocket request.websocket
data/lib/reel.rb CHANGED
@@ -2,29 +2,24 @@ require 'uri'
2
2
 
3
3
  require 'http/parser'
4
4
  require 'http'
5
-
6
- require 'celluloid/autostart'
7
5
  require 'celluloid/io'
8
6
 
9
7
  require 'reel/version'
10
-
11
8
  require 'reel/mixins'
12
9
  require 'reel/connection'
13
10
  require 'reel/logger'
14
- require 'reel/request_info'
15
11
  require 'reel/request'
16
- require 'reel/request_body'
17
- require 'reel/request_parser'
18
12
  require 'reel/response'
19
- require 'reel/response_writer'
13
+
20
14
  require 'reel/server'
21
- require 'reel/ssl_server'
15
+ require 'reel/server/http'
16
+ require 'reel/server/https'
17
+
22
18
  require 'reel/websocket'
23
19
  require 'reel/stream'
24
20
 
25
21
  # A Reel good HTTP server
26
22
  module Reel
27
-
28
23
  # Error reading a request
29
24
  class RequestError < StandardError; end
30
25
 
@@ -44,4 +39,6 @@ module Reel
44
39
  # The method given was not understood
45
40
  class UnsupportedMethodError < ArgumentError; end
46
41
 
42
+ # wrong state for a given operation
43
+ class StateError < RuntimeError; end
47
44
  end
@@ -1,17 +1,18 @@
1
+ require 'reel/request'
2
+
1
3
  module Reel
2
4
  # A connection to the HTTP server
3
5
  class Connection
4
6
  include HTTPVersionsMixin
5
7
  include ConnectionMixin
6
8
 
7
- class StateError < RuntimeError; end # wrong state for a given operation
8
-
9
9
  CONNECTION = 'Connection'.freeze
10
10
  TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
11
11
  KEEP_ALIVE = 'Keep-Alive'.freeze
12
12
  CLOSE = 'close'.freeze
13
13
 
14
- attr_reader :socket, :parser
14
+ attr_reader :socket, :parser, :current_request
15
+ attr_accessor :request_state, :response_state
15
16
 
16
17
  # Attempt to read this much data
17
18
  BUFFER_SIZE = 16384
@@ -23,10 +24,10 @@ module Reel
23
24
  @keepalive = true
24
25
  @buffer_size = buffer_size || BUFFER_SIZE
25
26
  @parser = Request::Parser.new(self)
26
- @writer = Response::Writer.new(socket)
27
+ @request_fsm = Request::StateMachine.new(@socket)
27
28
 
28
29
  reset_request
29
- @response_state = :header
30
+ @response_state = :headers
30
31
  end
31
32
 
32
33
  # Is the connection still active?
@@ -42,12 +43,11 @@ module Reel
42
43
  end
43
44
 
44
45
  def readpartial(size = @buffer_size)
45
- raise StateError, "can't read in the '#{@request_state}' request state" unless @request_state == :ready
46
- @parser.readpartial(size)
47
- end
46
+ unless @request_fsm.state == :headers || @request_fsm.state == :body
47
+ raise StateError, "can't read in the '#{@request_fsm.state}' request state"
48
+ end
48
49
 
49
- def current_request
50
- @current_request
50
+ @parser.readpartial(size)
51
51
  end
52
52
 
53
53
  # Read a request object from the connection
@@ -55,14 +55,13 @@ module Reel
55
55
  raise StateError, "already processing a request" if current_request
56
56
 
57
57
  req = @parser.current_request
58
- @request_state = :ready
58
+ @request_fsm.transition :headers
59
59
  @keepalive = false if req[CONNECTION] == CLOSE || req.version == HTTP_VERSION_1_0
60
60
  @current_request = req
61
61
 
62
62
  req
63
63
  rescue IOError, Errno::ECONNRESET, Errno::EPIPE
64
- # The client is disconnected
65
- @request_state = :closed
64
+ @request_fsm.transition :closed
66
65
  @keepalive = false
67
66
  nil
68
67
  end
@@ -83,7 +82,7 @@ module Reel
83
82
  # Send a response back to the client
84
83
  # Response can be a symbol indicating the status code or a Reel::Response
85
84
  def respond(response, headers_or_body = {}, body = nil)
86
- raise StateError, "not in header state" if @response_state != :header
85
+ raise StateError, "not in header state" if @response_state != :headers
87
86
 
88
87
  if headers_or_body.is_a? Hash
89
88
  headers = headers_or_body
@@ -105,36 +104,26 @@ module Reel
105
104
  else raise TypeError, "invalid response: #{response.inspect}"
106
105
  end
107
106
 
108
- @writer.handle_response(response)
107
+ if current_request
108
+ current_request.handle_response(response)
109
+ else
110
+ raise RequestError
111
+ end
109
112
 
110
113
  # Enable streaming mode
111
114
  if response.chunked? and response.body.nil?
112
115
  @response_state = :chunked_body
113
- end
114
- rescue IOError, Errno::ECONNRESET, Errno::EPIPE
115
- # The client disconnected early
116
- @keepalive = false
117
- ensure
118
- if @keepalive
119
- reset_request(:ready)
116
+ elsif @keepalive
117
+ reset_request
120
118
  else
121
- @socket.close unless @socket.closed?
122
- reset_request(:closed)
119
+ @current_request = nil
120
+ @parser.reset
121
+ @request_fsm.transition :closed
123
122
  end
124
- end
125
-
126
- # Write body chunks directly to the connection
127
- def write(chunk)
128
- raise StateError, "not in chunked body mode" unless @response_state == :chunked_body
129
- @writer.write(chunk)
130
- end
131
- alias_method :<<, :write
132
-
133
- # Finish the response and reset the response state to header
134
- def finish_response
135
- raise StateError, "not in body state" if @response_state != :chunked_body
136
- @writer.finish_response
137
- @response_state = :header
123
+ rescue IOError, Errno::ECONNRESET, Errno::EPIPE, RequestError
124
+ # The client disconnected early, or there is no request
125
+ @keepalive = false
126
+ @request_fsm.transition :closed
138
127
  end
139
128
 
140
129
  # Close the connection
@@ -149,11 +138,12 @@ module Reel
149
138
  def hijack_socket
150
139
  # FIXME: this doesn't do a great job of ensuring we can hijack the socket
151
140
  # in its current state. Improve the state detection.
152
- if @request_state != :ready && @response_state != :header
141
+ if @request_fsm != :ready && @response_state != :headers
153
142
  raise StateError, "connection is not in a hijackable state"
154
143
  end
155
144
 
156
- @request_state = @response_state = :hijacked
145
+ @request_fsm.transition :hijacked
146
+ @response_state = :hijacked
157
147
  socket = @socket
158
148
  @socket = nil
159
149
  socket
@@ -166,11 +156,19 @@ module Reel
166
156
  end
167
157
 
168
158
  # Reset the current request state
169
- def reset_request(state = :ready)
170
- @request_state = state
159
+ def reset_request
160
+ @request_fsm.transition :headers
171
161
  @current_request = nil
172
162
  @parser.reset
173
163
  end
174
164
  private :reset_request
165
+
166
+ # Set response state for the connection.
167
+ def response_state=(state)
168
+ if state == :headers
169
+ reset_request
170
+ end
171
+ @response_state = state
172
+ end
175
173
  end
176
174
  end