reel 0.2.0 → 0.3.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.

@@ -0,0 +1,128 @@
1
+ module Reel
2
+ class Stream
3
+
4
+ def initialize &proc
5
+ @proc = proc
6
+ end
7
+
8
+ def call socket
9
+ @socket = socket
10
+ @proc.call self
11
+ end
12
+
13
+ def write data
14
+ write! data
15
+ self
16
+ end
17
+ alias :<< :write
18
+
19
+ # behaves like a true Rack::Response/BodyProxy object
20
+ def each(*)
21
+ yield self
22
+ end
23
+
24
+ def on_error &proc
25
+ @on_error = proc
26
+ self
27
+ end
28
+
29
+ def close
30
+ @socket.close unless closed?
31
+ end
32
+ alias finish close
33
+
34
+ def closed?
35
+ @socket.closed?
36
+ end
37
+
38
+ private
39
+ def write! string
40
+ @socket << string
41
+ rescue => e
42
+ @on_error ? @on_error.call(e) : raise(e)
43
+ end
44
+
45
+ end
46
+
47
+ class EventStream < Stream
48
+
49
+ # EventSource-related helpers
50
+ #
51
+ # @example
52
+ # Reel::EventStream.new do |socket|
53
+ # socket.event 'some event'
54
+ # socket.retry 10
55
+ # end
56
+ #
57
+ # @note
58
+ # though retry is a reserved word, it is ok to use it as `object#retry`
59
+ #
60
+ %w[event id retry].each do |meth|
61
+ define_method meth do |data|
62
+ # unlike on #data, these messages expects a single \n at the end.
63
+ write! "%s: %s\n" % [meth, data]
64
+ end
65
+ end
66
+
67
+ def data data
68
+ # - any single message should not contain \n except at the end.
69
+ # - EventSource expects \n\n at the end of each single message.
70
+ write! "data: %s\n\n" % data.gsub(/\n|\r/, '')
71
+ self
72
+ end
73
+
74
+ end
75
+
76
+ class ChunkStream < Stream
77
+
78
+ def write chunk
79
+ chunk_header = chunk.bytesize.to_s(16)
80
+ write! chunk_header + Response::CRLF
81
+ write! chunk + Response::CRLF
82
+ self
83
+ end
84
+ alias :<< :write
85
+
86
+ # finish does not actually close the socket,
87
+ # it only inform the browser there are no more messages
88
+ def finish
89
+ write! "0#{Response::CRLF * 2}"
90
+ end
91
+
92
+ def close
93
+ finish
94
+ super
95
+ end
96
+
97
+ end
98
+
99
+ class StreamResponse < Response
100
+
101
+ IDENTITY = 'identity'.freeze
102
+
103
+ def initialize status, headers, body
104
+ self.status = status
105
+ @body = body
106
+
107
+ case @body
108
+ when EventStream
109
+ # EventSource behaves extremely bad on chunked Transfer-Encoding
110
+ headers[TRANSFER_ENCODING] = IDENTITY
111
+ when ChunkStream
112
+ headers[TRANSFER_ENCODING] = CHUNKED
113
+ when Stream
114
+ else
115
+ raise TypeError, "can't render #{@body.class} as a response body"
116
+ end
117
+
118
+ @headers = canonicalize_headers(headers)
119
+ @version = http_version
120
+ end
121
+
122
+ def render socket
123
+ socket << render_header
124
+ @body.call socket
125
+ end
126
+ end
127
+
128
+ end
data/lib/reel/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Reel
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0.pre"
3
3
  end
@@ -1,11 +1,16 @@
1
+ require 'forwardable'
1
2
  require 'websocket_parser'
2
3
 
3
4
  module Reel
4
5
  class WebSocket
5
- attr_reader :url, :headers
6
+ extend Forwardable
7
+ include ConnectionMixin
8
+ include RequestMixin
6
9
 
7
- def initialize(socket, url, headers)
8
- @socket, @url, @headers = socket, url, headers
10
+ def_delegators :@socket, :addr, :peeraddr
11
+
12
+ def initialize(http_parser, socket)
13
+ @http_parser, @socket = http_parser, socket
9
14
 
10
15
  handshake = ::WebSocket::ClientHandshake.new(:get, url, headers)
11
16
 
@@ -36,20 +41,45 @@ module Reel
36
41
  end
37
42
  end
38
43
 
39
- def [](header)
40
- @headers[header]
44
+ [:next_message, :next_messages, :on_message, :on_error, :on_close, :on_ping, :on_pong].each do |meth|
45
+ define_method meth do |&proc|
46
+ @parser.send __method__, &proc
47
+ end
48
+ end
49
+
50
+ def read_every n, unit = :s
51
+ cancel_timer! # only one timer allowed per stream
52
+ seconds = case unit.to_s
53
+ when /\Am/
54
+ n * 60
55
+ when /\Ah/
56
+ n * 3600
57
+ else
58
+ n
59
+ end
60
+ @timer = Celluloid.every(seconds) { read }
41
61
  end
62
+ alias read_interval read_every
63
+ alias read_frequency read_every
42
64
 
43
65
  def read
44
66
  @parser.append @socket.readpartial(Connection::BUFFER_SIZE) until msg = @parser.next_message
45
67
  msg
68
+ rescue => e
69
+ cancel_timer!
70
+ @on_error ? @on_error.call(e) : raise(e)
71
+ end
72
+
73
+ def body
74
+ nil
46
75
  end
47
76
 
48
77
  def write(msg)
49
78
  @socket << ::WebSocket::Message.new(msg).to_data
50
79
  msg
51
- rescue Errno::EPIPE
52
- raise SocketError, "error writing to socket"
80
+ rescue => e
81
+ cancel_timer!
82
+ @on_error ? @on_error.call(e) : raise(e)
53
83
  end
54
84
  alias_method :<<, :write
55
85
 
@@ -58,7 +88,13 @@ module Reel
58
88
  end
59
89
 
60
90
  def close
61
- @socket.close
91
+ cancel_timer!
92
+ @socket.close unless closed?
62
93
  end
94
+
95
+ def cancel_timer!
96
+ @timer && @timer.cancel
97
+ end
98
+
63
99
  end
64
100
  end
@@ -94,4 +94,93 @@ describe Reel::Connection do
94
94
  connection.request.should be_false
95
95
  end
96
96
  end
97
+
98
+ describe "Connection#read behaving like IO#read" do
99
+ it "raises an exception if length is a negative value" do
100
+ with_socket_pair do |client, connection|
101
+ example_request = ExampleRequest.new
102
+
103
+ client << example_request.to_s
104
+ request = connection.request
105
+
106
+ lambda { request.read(-1) }.should raise_error(ArgumentError)
107
+ end
108
+ end
109
+
110
+ it "returns an empty string if the length is zero" do
111
+ with_socket_pair do |client, connection|
112
+ example_request = ExampleRequest.new
113
+
114
+ client << example_request.to_s
115
+ request = connection.request
116
+
117
+ request.read(0).should be_empty
118
+ end
119
+ end
120
+
121
+ it "reads to EOF if length is nil" do
122
+ with_socket_pair do |client, connection|
123
+ body = "Hello, world!"
124
+ example_request = ExampleRequest.new
125
+ example_request.body = body
126
+
127
+ client << example_request.to_s
128
+ request = connection.request
129
+
130
+ request.read.should eq "Hello, world!"
131
+ end
132
+ end
133
+
134
+ it "uses the optional buffer to recieve data" do
135
+ with_socket_pair do |client, connection|
136
+ body = "Hello, world!"
137
+ example_request = ExampleRequest.new
138
+ example_request.body = body
139
+
140
+ client << example_request.to_s
141
+ request = connection.request
142
+
143
+ buffer = ''
144
+ request.read(nil, buffer).should eq "Hello, world!"
145
+ buffer.should eq "Hello, world!"
146
+ end
147
+ end
148
+
149
+ it "returns with the content it could read when the length longer than EOF" do
150
+ with_socket_pair do |client, connection|
151
+ body = "Hello, world!"
152
+ example_request = ExampleRequest.new
153
+ example_request.body = body
154
+
155
+ client << example_request.to_s
156
+ request = connection.request
157
+
158
+ request.read(1024).should eq "Hello, world!"
159
+ end
160
+ end
161
+
162
+ it "returns nil at EOF if a length is passed" do
163
+ with_socket_pair do |client, connection|
164
+ example_request = ExampleRequest.new
165
+
166
+ client << example_request.to_s
167
+ request = connection.request
168
+
169
+ request.read(1024).should be_nil
170
+ end
171
+ end
172
+
173
+ it "returns an empty string at EOF if length is nil" do
174
+ with_socket_pair do |client, connection|
175
+ example_request = ExampleRequest.new
176
+
177
+ client << example_request.to_s
178
+ request = connection.request
179
+
180
+ request.read.should be_empty
181
+ end
182
+ end
183
+
184
+ end
185
+
97
186
  end
@@ -1,17 +1,15 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Reel::RackWorker do
4
-
5
4
  let(:endpoint) { URI(example_url) }
6
5
 
7
- let(:worker) do
8
- app = Proc.new do |env|
9
- [200, {'Content-Type' => 'text/plain'}, ['Hello world!']]
10
- [200, {'Content-Type' => 'text/plain'}, ['Hello rack world!']]
11
- end
6
+ RackApp = Proc.new do |env|
7
+ [200, {'Content-Type' => 'text/plain'}, ['Hello rack world!']]
8
+ end
12
9
 
10
+ let(:worker) do
13
11
  handler = Rack::Handler::Reel.new
14
- handler.options[:app] = app
12
+ handler.options[:app] = RackApp
15
13
 
16
14
  Reel::RackWorker.new(handler)
17
15
  end
@@ -20,37 +18,51 @@ describe Reel::RackWorker do
20
18
  with_socket_pair do |client, connection|
21
19
  client << ExampleRequest.new(:get, '/test?hello=true').to_s
22
20
  request = connection.request
23
- env = worker.rack_env(request, connection)
21
+ env = worker.request_env(request, connection)
24
22
 
25
23
  Reel::RackWorker::PROTO_RACK_ENV.each do |k, v|
26
24
  env[k].should == v
27
25
  end
28
26
 
29
- env["SERVER_NAME"].should == '0.0.0.0'
30
- env["SERVER_PORT"].should == 3000
27
+ env["SERVER_NAME"].should == 'www.example.com'
28
+ env["SERVER_PORT"].should == "3000"
31
29
  env["REMOTE_ADDR"].should == "127.0.0.1"
32
30
  env["PATH_INFO"].should == "/test"
33
31
  env["REQUEST_METHOD"].should == "GET"
34
- env["REQUEST_PATH"].should == "/test"
35
- env["ORIGINAL_FULLPATH"].should == "/test"
36
32
  env["QUERY_STRING"].should == "hello=true"
37
33
  env["HTTP_HOST"].should == 'www.example.com'
38
34
  env["HTTP_ACCEPT_LANGUAGE"].should == "en-US,en;q=0.8"
39
- env["REQUEST_URI"].should == 'http://www.example.com/test'
40
-
41
- %w(localhost 127.0.0.1).should include env["REMOTE_HOST"]
42
35
 
43
36
  env["rack.input"].should be_kind_of(StringIO)
44
37
  env["rack.input"].string.should == ''
38
+
39
+ validator = ::Rack::Lint.new(RackApp)
40
+ status, *rest = validator.call(env)
41
+ status.should == 200
45
42
  end
46
43
  end
47
44
 
45
+ context "WebSocket" do
46
+ include WebSocketHelpers
47
+
48
+ it "places websocket into rack env" do
49
+ with_socket_pair do |client, connection|
50
+ client << handshake.to_data
51
+ request = connection.request
52
+ env = worker.websocket_env(request)
53
+
54
+ env["REMOTE_ADDR"].should == "127.0.0.1"
55
+ env["rack.websocket"].should be_a Reel::WebSocket
56
+ end
57
+ end
58
+ end
59
+
48
60
  it "delegates web requests to the rack app" do
49
61
  ex = nil
50
62
 
51
63
  handler = proc do |connection|
52
64
  begin
53
- worker.handle!(connection.detach)
65
+ worker.async.handle(connection.detach)
54
66
  rescue => ex
55
67
  end
56
68
  end
@@ -10,8 +10,8 @@ describe Reel::Response do
10
10
  connection.close
11
11
 
12
12
  response = client.read(4096)
13
- crlf = "\r\n"
14
- fixture = "5#{crlf}Hello5#{crlf}World0#{crlf*2}"
13
+ crlf = Reel::Response::CRLF
14
+ fixture = "5#{crlf}Hello#{crlf}5#{crlf}World#{crlf}0#{crlf*2}"
15
15
  response[(response.length - fixture.length)..-1].should eq fixture
16
16
  end
17
17
  end
@@ -11,7 +11,7 @@ describe Reel::Server do
11
11
  handler = proc do |connection|
12
12
  begin
13
13
  request = connection.request
14
- request.method.should eq :get
14
+ request.method.should eq 'GET'
15
15
  request.version.should eq "1.1"
16
16
  request.url.should eq example_path
17
17
 
@@ -34,7 +34,7 @@ describe Reel::Server do
34
34
  handler = proc do |connection|
35
35
  begin
36
36
  request = connection.request
37
- request.method.should eq :post
37
+ request.method.should eq 'POST'
38
38
  connection.respond :ok, request.body
39
39
  rescue => ex
40
40
  end
@@ -1,26 +1,11 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Reel::WebSocket do
4
- let(:example_host) { "www.example.com" }
5
- let(:example_path) { "/example"}
6
- let(:example_url) { "ws://#{example_host}#{example_path}" }
4
+ include WebSocketHelpers
5
+
7
6
  let(:example_message) { "Hello, World!" }
8
7
  let(:another_message) { "What's going on?" }
9
8
 
10
- let :handshake_headers do
11
- {
12
- "Host" => example_host,
13
- "Upgrade" => "websocket",
14
- "Connection" => "Upgrade",
15
- "Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
16
- "Origin" => "http://example.com",
17
- "Sec-WebSocket-Protocol" => "chat, superchat",
18
- "Sec-WebSocket-Version" => "13"
19
- }
20
- end
21
-
22
- let(:handshake) { WebSocket::ClientHandshake.new(:get, example_url, handshake_headers) }
23
-
24
9
  it "performs websocket handshakes" do
25
10
  with_socket_pair do |client, connection|
26
11
  client << handshake.to_data