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.
- checksums.yaml +4 -4
- data/.travis.yml +5 -2
- data/Gemfile +6 -0
- data/README.md +26 -16
- data/benchmarks/hello_reel.rb +1 -1
- data/benchmarks/reel_pool.rb +27 -0
- data/examples/hello_world.rb +2 -2
- data/examples/{ssl_hello_world.rb → https_hello_world.rb} +1 -1
- data/examples/roundtrip.rb +1 -1
- data/examples/server_sent_events.rb +22 -18
- data/examples/spy_hello_world.rb +22 -0
- data/examples/websockets.rb +3 -3
- data/lib/reel.rb +6 -9
- data/lib/reel/connection.rb +40 -42
- data/lib/reel/mixins.rb +3 -1
- data/lib/reel/request.rb +40 -9
- data/lib/reel/request/body.rb +65 -0
- data/lib/reel/request/info.rb +21 -0
- data/lib/reel/{request_parser.rb → request/parser.rb} +2 -2
- data/lib/reel/request/state_machine.rb +26 -0
- data/lib/reel/response.rb +4 -11
- data/lib/reel/response/writer.rb +59 -0
- data/lib/reel/server.rb +30 -6
- data/lib/reel/server/http.rb +20 -0
- data/lib/reel/server/https.rb +63 -0
- data/lib/reel/spy.rb +71 -0
- data/lib/reel/stream.rb +2 -2
- data/lib/reel/version.rb +2 -2
- data/lib/reel/websocket.rb +1 -1
- data/reel.gemspec +3 -3
- data/spec/fixtures/ca.crt +27 -0
- data/spec/fixtures/ca.key +27 -0
- data/spec/fixtures/client.crt +81 -20
- data/spec/fixtures/client.unsigned.crt +22 -0
- data/spec/fixtures/server.crt +80 -20
- data/spec/reel/connection_spec.rb +50 -11
- data/spec/reel/{server_spec.rb → http_server_spec.rb} +1 -1
- data/spec/reel/https_server_spec.rb +119 -0
- data/spec/reel/{response_writer_spec.rb → response/writer_spec.rb} +10 -2
- data/spec/reel/websocket_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -34
- data/spec/support/example_request.rb +34 -0
- metadata +56 -43
- data/examples/chunked.rb +0 -25
- data/lib/reel/request_body.rb +0 -56
- data/lib/reel/request_info.rb +0 -19
- data/lib/reel/response_writer.rb +0 -76
- data/lib/reel/ssl_server.rb +0 -41
- data/spec/reel/ssl_server_spec.rb +0 -54
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96b038743e66387d895d56854f1f09709b55200c
|
4
|
+
data.tar.gz: 03f4a385c206da2723ba5850014da73468b89e09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
6
|
+
- jruby
|
6
7
|
- jruby-head
|
7
|
-
- rbx
|
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
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
|
11
|
-
[
|
12
|
-
|
13
|
-
|
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
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
#
|
101
|
-
|
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
|
data/benchmarks/hello_reel.rb
CHANGED
@@ -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
|
data/examples/hello_world.rb
CHANGED
@@ -7,8 +7,8 @@ require 'reel'
|
|
7
7
|
|
8
8
|
addr, port = '127.0.0.1', 1234
|
9
9
|
|
10
|
-
puts "*** Starting server on
|
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::
|
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.
|
data/examples/roundtrip.rb
CHANGED
@@ -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
|
-
|
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
|
-
@
|
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
|
-
@
|
27
|
-
@history << {id: @
|
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, @
|
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
|
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
|
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
|
-
|
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
|
-
|
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,
|
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' => '*'},
|
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
|
data/examples/websockets.rb
CHANGED
@@ -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
|
-
|
13
|
+
|
20
14
|
require 'reel/server'
|
21
|
-
require 'reel/
|
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
|
data/lib/reel/connection.rb
CHANGED
@@ -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
|
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
|
-
@
|
27
|
+
@request_fsm = Request::StateMachine.new(@socket)
|
27
28
|
|
28
29
|
reset_request
|
29
|
-
@response_state = :
|
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
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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 != :
|
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
|
-
|
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
|
-
|
114
|
-
|
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
|
-
@
|
122
|
-
|
119
|
+
@current_request = nil
|
120
|
+
@parser.reset
|
121
|
+
@request_fsm.transition :closed
|
123
122
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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 @
|
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
|
-
@
|
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
|
170
|
-
@
|
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
|