reel 0.4.0.pre → 0.4.0.pre2

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,69 @@
1
+ module Reel
2
+ class Response
3
+ class Writer
4
+
5
+ CRLF = "\r\n"
6
+ def initialize(socket, connection)
7
+ @socket = socket
8
+ @connection = connection
9
+ end
10
+
11
+ # Write body chunks directly to the connection
12
+ def write(chunk)
13
+ chunk_header = chunk.bytesize.to_s(16)
14
+ @socket << chunk_header + CRLF
15
+ @socket << chunk + CRLF
16
+ end
17
+
18
+ # Finish the response and reset the response state to header
19
+ def finish_response
20
+ @socket << "0#{CRLF * 2}"
21
+ end
22
+
23
+ # Takes a Reel::Response and renders it
24
+ # back over the socket.
25
+ def handle_response(response)
26
+ @socket << render_header(response)
27
+ if response.respond_to?(:render)
28
+ response.render(@socket)
29
+ else
30
+ case response.body
31
+ when String
32
+ @socket << response.body
33
+ when IO
34
+ begin
35
+ if !defined?(JRUBY_VERSION)
36
+ IO.copy_stream(response.body, @socket)
37
+ else
38
+ # JRuby 1.6.7 doesn't support IO.copy_stream :(
39
+ while data = response.body.read(4096)
40
+ @socket << data
41
+ end
42
+ end
43
+ ensure
44
+ response.body.close
45
+ end
46
+ when Enumerable
47
+ response.body.each do |chunk|
48
+ write(chunk)
49
+ end
50
+
51
+ finish_response
52
+ end
53
+ end
54
+ end
55
+
56
+ # Convert headers into a string
57
+ def render_header(response)
58
+ response_header = "#{response.version} #{response.status} #{response.reason}#{CRLF}"
59
+ unless response.headers.empty?
60
+ response_header << response.headers.map do |header, value|
61
+ "#{header}: #{value}"
62
+ end.join(CRLF) << CRLF
63
+ end
64
+ response_header << CRLF
65
+ end
66
+ private :render_header
67
+ end
68
+ end
69
+ end
@@ -5,20 +5,19 @@ module Reel
5
5
  # How many connections to backlog in the TCP accept queue
6
6
  DEFAULT_BACKLOG = 100
7
7
 
8
- # FIXME: remove respond_to? check after Celluloid 1.0
9
- finalizer :finalize if respond_to?(:finalizer)
8
+ execute_block_on_receiver :initialize
9
+ finalizer :shutdown
10
10
 
11
11
  def initialize(host, port, backlog = DEFAULT_BACKLOG, &callback)
12
12
  # This is actually an evented Celluloid::IO::TCPServer
13
13
  @server = TCPServer.new(host, port)
14
+ @server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
14
15
  @server.listen(backlog)
15
16
  @callback = callback
16
17
  async.run
17
18
  end
18
19
 
19
- execute_block_on_receiver :initialize
20
-
21
- def finalize
20
+ def shutdown
22
21
  @server.close if @server
23
22
  end
24
23
 
@@ -28,8 +27,9 @@ module Reel
28
27
 
29
28
  def handle_connection(socket)
30
29
  connection = Connection.new(socket)
30
+
31
31
  begin
32
- @callback[connection]
32
+ @callback.call(connection)
33
33
  ensure
34
34
  if connection.attached?
35
35
  connection.close rescue nil
@@ -0,0 +1,39 @@
1
+ module Reel
2
+ class SSLServer < Server
3
+ execute_block_on_receiver :initialize
4
+
5
+ def initialize(host, port, options = {}, &callback)
6
+ backlog = options.fetch(:backlog, DEFAULT_BACKLOG)
7
+
8
+ # Ideally we can encapsulate this rather than making Ruby OpenSSL a
9
+ # mandatory part of the Reel API. It would be nice to support
10
+ # alternatives (e.g. Puma's MiniSSL)
11
+ ssl_context = OpenSSL::SSL::SSLContext.new
12
+ ssl_context.cert = OpenSSL::X509::Certificate.new options.fetch(:cert)
13
+ ssl_context.key = OpenSSL::PKey::RSA.new options.fetch(:key)
14
+
15
+ # FIXME: VERY VERY VERY VERY BAD RELEASE BLOCKER BAD
16
+ ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
17
+
18
+ @tcpserver = Celluloid::IO::TCPServer.new(host, port)
19
+ @server = Celluloid::IO::SSLServer.new(@tcpserver, ssl_context)
20
+ @server.listen(backlog)
21
+ @callback = callback
22
+
23
+ async.run
24
+ end
25
+
26
+ def run
27
+ loop do
28
+ begin
29
+ socket = @server.accept
30
+ rescue OpenSSL::SSL::SSLError
31
+ # TODO: log this?
32
+ retry
33
+ end
34
+
35
+ async.handle_connection socket
36
+ end
37
+ end
38
+ end
39
+ end
@@ -31,7 +31,7 @@ module Reel
31
31
  @socket.closed?
32
32
  end
33
33
  end
34
-
34
+
35
35
  class EventStream < Stream
36
36
 
37
37
  # EventSource-related helpers
@@ -60,12 +60,12 @@ module Reel
60
60
  end
61
61
 
62
62
  end
63
-
63
+
64
64
  class ChunkStream < Stream
65
65
  def write(chunk)
66
66
  chunk_header = chunk.bytesize.to_s(16)
67
- write chunk_header + Response::CRLF
68
- write chunk + Response::CRLF
67
+ write chunk_header + Response::Writer::CRLF
68
+ write chunk + Response::Writer::CRLF
69
69
  self
70
70
  end
71
71
  alias :<< :write
@@ -73,7 +73,7 @@ module Reel
73
73
  # finish does not actually close the socket,
74
74
  # it only inform the browser there are no more messages
75
75
  def finish
76
- write "0#{Response::CRLF * 2}"
76
+ write "0#{Response::Writer::CRLF * 2}"
77
77
  end
78
78
 
79
79
  def close
@@ -107,7 +107,6 @@ module Reel
107
107
  end
108
108
 
109
109
  def render(socket)
110
- socket << render_header
111
110
  @body.call socket
112
111
  end
113
112
  end
@@ -1,3 +1,3 @@
1
1
  module Reel
2
- VERSION = "0.4.0.pre"
2
+ VERSION = "0.4.0.pre2"
3
3
  end
@@ -7,10 +7,12 @@ module Reel
7
7
  include ConnectionMixin
8
8
  include RequestMixin
9
9
 
10
+ attr_reader :socket
10
11
  def_delegators :@socket, :addr, :peeraddr
11
12
 
12
- def initialize(http_parser, socket)
13
- @http_parser, @socket = http_parser, socket
13
+ def initialize(info, socket)
14
+ @request_info = info
15
+ @socket = socket
14
16
 
15
17
  handshake = ::WebSocket::ClientHandshake.new(:get, url, headers)
16
18
 
@@ -36,8 +38,8 @@ module Reel
36
38
  close
37
39
  end
38
40
 
39
- @parser.on_ping do
40
- @socket << ::WebSocket::Message.pong.to_data
41
+ @parser.on_ping do |payload|
42
+ @socket << ::WebSocket::Message.pong(payload).to_data
41
43
  end
42
44
  end
43
45
 
@@ -15,10 +15,11 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Reel::VERSION
17
17
 
18
- gem.add_runtime_dependency 'celluloid-io', '>= 0.8.0'
18
+ gem.add_runtime_dependency 'celluloid', '>= 0.14.1'
19
+ gem.add_runtime_dependency 'celluloid-io', '>= 0.14.1'
19
20
  gem.add_runtime_dependency 'http', '>= 0.2.0'
20
- gem.add_runtime_dependency 'http_parser.rb', '>= 0.5.3'
21
- gem.add_runtime_dependency 'websocket_parser', '>= 0.1.2'
21
+ gem.add_runtime_dependency 'http_parser.rb', '>= 0.6.0.beta.2'
22
+ gem.add_runtime_dependency 'websocket_parser', '>= 0.1.4'
22
23
  gem.add_runtime_dependency 'rack', '>= 1.4.0'
23
24
 
24
25
  gem.add_development_dependency 'rake'
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDmjCCAoICCQDwZ8yE/0n4PjANBgkqhkiG9w0BAQUFADCBjjELMAkGA1UEBhMC
3
+ VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
4
+ EjAQBgNVBAoTCUNlbGx1bG9pZDEbMBkGA1UEAxMSY2xpZW50LmV4YW1wbGUuY29t
5
+ MSEwHwYJKoZIhvcNAQkBFhJjbGllbnRAZXhhbXBsZS5jb20wHhcNMTIxMTI1MTkx
6
+ NjI2WhcNMjIxMTIzMTkxNjI2WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNh
7
+ bGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoTCUNlbGx1
8
+ bG9pZDEbMBkGA1UEAxMSY2xpZW50LmV4YW1wbGUuY29tMSEwHwYJKoZIhvcNAQkB
9
+ FhJjbGllbnRAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
10
+ AoIBAQDV6zzpkAIX4FijnytX84GgHb8hYdNJAn+9g57XGrqtAH6BlLoANFl4n/+y
11
+ nEQwBqlrNnfstPrf7sPezytntSVyufSE+LGUGBJA/jyjQCMcEe8+4bfOC2ZhCpvn
12
+ I2dKNKwsmM+DyWs/PVl7XEAZF2P4iQ8eGLVFjph+KA/D79cHkIeGt4FEA4xJWqKf
13
+ +Kjftxo1cBqLx2dUiucRL7tva3ingAqYSs/i82jKLGlj7fdRMytOx87Nhs35RWpu
14
+ 66l7hvpetx3t2wU2obKOzKhS4ycaZ2AptEDNXKaBTQ5lejSRxFBCpYQtqmkd0bMG
15
+ /T5ZfXC45axj9a2rj8AKZct+mLCzAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAHzr
16
+ b4VTktAi+8baGRJCXupt0Ari8ffoWhsYerELFLQF7K2sluxOqCsGEEF21e99fZxP
17
+ lisLi0DIQ7cNlOGjRJ3xaydE74Fsry3xBNKoR8I7OMr9VFsrC54tc0x7NQ7bRHy6
18
+ kCjSwKN4I2KWJjQ8yf8mIalmUKOmb/hirzna8io4CiDeJGZ1XNAQ9dl1RHRW442G
19
+ GTu2ofAtU8TlzilZyclMY/lN7whw7sKP+pPr6fpAOJZsR64IzbBcWHHjJhx70XOx
20
+ jnd5FB1oXnuupgPqEKmatSCytrue8GTkanB8VZ6+Zd/4XgTkie3UtCZW8R+NL/Lo
21
+ us/+Ks3WIDyYdPSPnbE=
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpAIBAAKCAQEA1es86ZACF+BYo58rV/OBoB2/IWHTSQJ/vYOe1xq6rQB+gZS6
3
+ ADRZeJ//spxEMAapazZ37LT63+7D3s8rZ7Ulcrn0hPixlBgSQP48o0AjHBHvPuG3
4
+ zgtmYQqb5yNnSjSsLJjPg8lrPz1Ze1xAGRdj+IkPHhi1RY6YfigPw+/XB5CHhreB
5
+ RAOMSVqin/io37caNXAai8dnVIrnES+7b2t4p4AKmErP4vNoyixpY+33UTMrTsfO
6
+ zYbN+UVqbuupe4b6Xrcd7dsFNqGyjsyoUuMnGmdgKbRAzVymgU0OZXo0kcRQQqWE
7
+ LappHdGzBv0+WX1wuOWsY/Wtq4/ACmXLfpiwswIDAQABAoIBAQCFDDcpaUj0ArP+
8
+ qEuz+x6/MGEk6QwZV7WNcGSFkvlSCoGkJJV+9RBExvao5yo92JbcuNbj4Tg7uOwY
9
+ EzAC45a0AVZEscz4t/P6emXKf2SW28y6hnbkbxCxAIEwxENE0vfXEP/YDplmjsit
10
+ whWXxYWHGe/OHz33UhYkONR9YBmUeLrtloRNUV82XDSpn4d7toLKaZW2kOFl4nFR
11
+ SQ3pDPk1hleG8AZcfnF2LwaPx1XjPwBnXY9FK2jyNupVghfCw/Sv91dbbVkkIG14
12
+ A8WpZKAXjXXOcTroof5+NJSPXzYrRuvP8K6H2zGj7F/AsP4hiZqGkb4tel0yH5VM
13
+ oLCUTHqhAoGBAPysxeoT1ytajQ55UV1yjsnQ3jF9YcWsZwPEJgMI+bt+QzAfqqZs
14
+ Kuvg8Gi7ukbcc2pKwXv+ma9HLJq/pQbWlfxcMNulY0VCPY/ceaPen+EfCJTApVpY
15
+ YFS25i/JnIp9IudpQBuLHz9Yy4f1W2VoeG/mFqOmUxiTx4LM87G6rdtDAoGBANi7
16
+ 5raiwDS+rD91l5FLq3kdvgSDgYk4hh7BBJNJt8vhJYInIev5eb/Z41X8PfqWa2ir
17
+ 9aanpMYhWDJxbdSQDd3/op6jtOZM7InLceAm2O29VY2+HW5ePpc21AHsqoZpFYEZ
18
+ YP8xvbSjJzfkrYr4y+aAMXONVAi4afqG7x6GqCXRAoGBAPbzFWu1gHKKyZn/0Bn4
19
+ wL1WOhM8a7Z6zSPNLSmCODGbMadzC6Ijzb9D1TNHZsOi6dpUvc2mBCZe9aU48N1C
20
+ FMzUfZvuhJtIJkrYPLp/9tpbLlPUBMfL4Dprl4XVEf34V4i8QT+qNRwAeMukbXMr
21
+ K6qRwkanZEd9B107WmG2Bf1pAoGACpld1g6tgabVe6D/kY52y0yGD2hy/Ef0Xyqn
22
+ U6CmSWUwVWYehZDEwHoiYQEd8tRKWmsWb1kBeOMGkijz6xJEa1fmFwYAgca/RpnZ
23
+ btHXiADbXzwt6kjXnMOEqLdvO3WGJLMeCDzhfyT/dP9M8V/rcNFSGcmOk4KZRDQ3
24
+ G3IQZRECgYBqHqvxHeL087UHXE1tdAGFVfxOiYE1afksJbkV06VnO8XXr9sNSWwy
25
+ YjXVY9k6U1BFo7jHrWr6TkeMkB45wyn/fasHKU7qsPt0joRFkXMCzwl376hto3Tg
26
+ RGXQCA4RUQXkxaDctJ5TgcF7MhK7tAFd1aBVlxwGENLYUVL0ZPaMrw==
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDmjCCAoICCQD+dJ16wNIKnzANBgkqhkiG9w0BAQUFADCBjjELMAkGA1UEBhMC
3
+ VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
4
+ EjAQBgNVBAoTCUNlbGx1bG9pZDEbMBkGA1UEAxMSc2VydmVyLmV4YW1wbGUuY29t
5
+ MSEwHwYJKoZIhvcNAQkBFhJzZXJ2ZXJAZXhhbXBsZS5jb20wHhcNMTIxMTI1MTkx
6
+ NjAwWhcNMjIxMTIzMTkxNjAwWjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNh
7
+ bGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoTCUNlbGx1
8
+ bG9pZDEbMBkGA1UEAxMSc2VydmVyLmV4YW1wbGUuY29tMSEwHwYJKoZIhvcNAQkB
9
+ FhJzZXJ2ZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
10
+ AoIBAQCpLs3Xg00RtoG4+SKaaNfw6Dve9d0Kkg0CNfU8AsxOgTIU1Qu8s+bzKkqe
11
+ 66NfCa6T8VPpk9VbIlF2ONdgY4o8muV1q+mS6j2HDAtWPiDjP+9YOwGf/DT3LhSb
12
+ g8k+alL2cqe7B1XNUsNFEvQ+yQLlj9MWKb7nbYDM8aqdv46KGoDj9v9rfm4QiKwI
13
+ B6u/KoQG22YF7sT4O44jU/u20xcm3rV1Elg0gC/UP/YqnuMPCZYcK/Z9vYGtDJ6G
14
+ 8OYDcPZSZBdkqlffhYBssSj3R7sTCqoh4ms08ukcGycbvUO+cWrPKnmySsGaCYtG
15
+ kp7QsH1ec7QGA01hZL2yL8CuJMUbAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBABE4
16
+ gYVSdC87NhpA49k0vcLLU7v7mU3a3no/vu1CIqQxzx8/xh26Qi3aGb1s9MgHpF2Z
17
+ NiB1irXER2tyz/F3qCi8OCo7eNsndmDjj4GnkBjEPTtqRxH9imRWw4bJyqwqFHcu
18
+ 1kczCZa+2VFQFEL4ErGycPFKM59ppTcJ0IxbK7PCGzO75TRQoAl52+3Aob+oejPP
19
+ qFbiqNlV1T3EKa5yLdvOC5sLrEcfm3iMxmOtNVzp9OBhjXfm8Q1zgYs4VyJXzLMK
20
+ wf956w2YEbpTAAzNc53zly/Jhr4MnFsa9Mn1oYp9Rfjzw/qJtXw+a3PtEKrO4XNe
21
+ TsKHsAkj8XvUrhliiNQ=
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEogIBAAKCAQEAqS7N14NNEbaBuPkimmjX8Og73vXdCpINAjX1PALMToEyFNUL
3
+ vLPm8ypKnuujXwmuk/FT6ZPVWyJRdjjXYGOKPJrldavpkuo9hwwLVj4g4z/vWDsB
4
+ n/w09y4Um4PJPmpS9nKnuwdVzVLDRRL0PskC5Y/TFim+522AzPGqnb+OihqA4/b/
5
+ a35uEIisCAervyqEBttmBe7E+DuOI1P7ttMXJt61dRJYNIAv1D/2Kp7jDwmWHCv2
6
+ fb2BrQyehvDmA3D2UmQXZKpX34WAbLEo90e7EwqqIeJrNPLpHBsnG71DvnFqzyp5
7
+ skrBmgmLRpKe0LB9XnO0BgNNYWS9si/AriTFGwIDAQABAoIBAGKRoV4p4rIqOiQy
8
+ CuYZpY53T8KUToeFFk0ucMXY/33RqgMXKTJ1Ql50SmuS8GlDs9IALZqOBiWFth6B
9
+ +YHwHK84s+2+DmUJUnWnH8fMhM7CBknKfyTeBWHqGBmPS6WwvstVe8HtASGSUbCh
10
+ 3WnjJWvoQtzLz6z4UK2XM4ea/ooY+hlcw6DM+jZuTstzLFE/9BPoHueaW8UemjaH
11
+ ZUXMKm3+I3iIjHszUUWM59bS9pOBn/YvIJbVE5wMIVCP2YXDCgfpV2Z4nDiAHppn
12
+ fnha2TzHzlPMgwhBpz06r6G8X+A6gJl98TDSK41nIMyXqiZ2aoALL3UOssAMfUHr
13
+ 2y9CGdECgYEA27F1IyUW3JgqCeqZ7eCeT4emhAvyohzM5pzWI7C8ZosF14zFRpek
14
+ TgmjdTGMQ1EZVVkyj85RyvMm3MkcKOHetc5g2jJg3EkxvAV+PMs7yjpqg3itEjC6
15
+ vIhXLoXdq+FuruA2h4O0hi6yuf1FCQYtpNLTe49qetjsaWzwwowHwlMCgYEAxSRo
16
+ k+AdpoNXblnIhd0EaKjGAsHFrC039o7JqQe/mKAiXaGiidIDk5Vt/ChT6Qa6fiLq
17
+ cdysCn+tSCt/DdRrELZohc0ipuy5/agQmJgWoW7oay8ldzxHP9VevWo4UuqVudW9
18
+ evhKe0a9uXCrSimvZ5PJk91lmBx92FBeP6Y+qRkCgYAXQsvPQ88O3kGdOSzBJgY9
19
+ D3TPCGDRT1FWnYaC0uSvysp8jxgYKFgqNxUKhIuAWSbghYg397VrUqFrwRNtNLUa
20
+ 9NYGZE0jJdDRQpeiIjaba+H5N57DjUtISPtKHrxgxYatl2nOoWBM0Mb1sF5N3UyZ
21
+ 5gSkUYQJq8wkQXegcakkpwKBgEdvvgV3vMbN6SyvlB4NzL8wCTCOjtapPBI4A5Mg
22
+ n6jqvgk3vPI8C9e62jP5WQ6jxYhXlqTT1fOn+F6ihFO6mWFg99ckUl4ygeMMt5bT
23
+ 5b9xtP7CAs2GJjtXUhFJIEfLgZ3pedPJjRPGupEr5qXlHQ5nWzAdlebczC1KUhy2
24
+ XRZhAoGAGA3SAAF79PK3c3+bOOviXxDFuH5TCBmbtEpJ+/jCbwR6Z8fMRswZJ3Gc
25
+ l8eNMsB+Kfif+806xAgLxsyhYuyvF6rE/V34GKjW22T1gwk6gY/rOgFn42jo8lwl
26
+ HFbSB+IG0+Go0m+0QmyRh7SyPvDNtyYzBFFdl9U8JYYGM0Nfgd0=
27
+ -----END RSA PRIVATE KEY-----
@@ -33,11 +33,11 @@ describe Reel::Connection do
33
33
  request.url.should eq "/"
34
34
  request.version.should eq "1.1"
35
35
  request['Content-Length'].should eq body.length.to_s
36
- request.body.should eq example_request.body
36
+ request.body.to_s.should eq example_request.body
37
37
  end
38
38
  end
39
39
 
40
- it "serves static files" do
40
+ it "reads requests with large bodies" do
41
41
  with_socket_pair do |client, connection|
42
42
  client << ExampleRequest.new.to_s
43
43
  request = connection.request
@@ -53,18 +53,34 @@ describe Reel::Connection do
53
53
  end
54
54
  end
55
55
 
56
+ it "enumerates requests with #each_request" do
57
+ with_socket_pair do |client, connection|
58
+ client << ExampleRequest.new.to_s
59
+
60
+ request_count = 0
61
+ connection.each_request do |request|
62
+ request_count += 1
63
+ request.url.should eq "/"
64
+ request.respond :ok
65
+ client.close
66
+ end
67
+
68
+ request_count.should eq 1
69
+ end
70
+ end
71
+
56
72
  it "streams responses when transfer-encoding is chunked" do
57
73
  with_socket_pair do |client, connection|
58
74
  client << ExampleRequest.new.to_s
59
75
  request = connection.request
60
76
 
61
77
  # Sending transfer_encoding chunked without a body enables streaming mode
62
- connection.respond :ok, :transfer_encoding => :chunked
78
+ request.respond :ok, :transfer_encoding => :chunked
63
79
 
64
80
  # This will send individual chunks
65
- connection << "Hello"
66
- connection << "World"
67
- connection.finish_response # Write trailer and reset connection to header mode
81
+ request << "Hello"
82
+ request << "World"
83
+ request.finish_response # Write trailer and reset connection to header mode
68
84
  connection.close
69
85
 
70
86
  response = ""
@@ -95,7 +111,203 @@ describe Reel::Connection do
95
111
  end
96
112
  end
97
113
 
98
- describe "Connection#read behaving like IO#read" do
114
+ it "raises an error trying to read two pipelines without responding first" do
115
+ with_socket_pair do |client, connection|
116
+ 2.times do
117
+ client << ExampleRequest.new.to_s
118
+ end
119
+
120
+ lambda{
121
+ 2.times do
122
+ request = connection.request
123
+ end
124
+ }.should raise_error(Reel::Connection::StateError)
125
+ end
126
+ end
127
+
128
+ it "reads pipelined requests without bodies" do
129
+ with_socket_pair do |client, connection|
130
+ 3.times do
131
+ client << ExampleRequest.new.to_s
132
+ end
133
+
134
+ 3.times do
135
+ request = connection.request
136
+
137
+ request.url.should eq "/"
138
+ request.version.should eq "1.1"
139
+
140
+ request['Host'].should eq "www.example.com"
141
+ request['Connection'].should eq "keep-alive"
142
+ request['User-Agent'].should eq "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.78 S"
143
+ request['Accept'].should eq "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
144
+ request['Accept-Encoding'].should eq "gzip,deflate,sdch"
145
+ request['Accept-Language'].should eq "en-US,en;q=0.8"
146
+ request['Accept-Charset'].should eq "ISO-8859-1,utf-8;q=0.7,*;q=0.3"
147
+ connection.respond :ok, {}, ""
148
+ end
149
+ end
150
+ end
151
+
152
+ it "reads pipelined requests with bodies" do
153
+ with_socket_pair do |client, connection|
154
+ 3.times do |i|
155
+ body = "Hello, world number #{i}!"
156
+ example_request = ExampleRequest.new
157
+ example_request.body = body
158
+
159
+ client << example_request.to_s
160
+ end
161
+
162
+ 3.times do |i|
163
+ request = connection.request
164
+
165
+ expected_body = "Hello, world number #{i}!"
166
+ request.url.should eq "/"
167
+ request.version.should eq "1.1"
168
+ request['Content-Length'].should eq expected_body.length.to_s
169
+ request.body.to_s.should eq expected_body
170
+
171
+ connection.respond :ok, {}, ""
172
+ end
173
+ end
174
+ end
175
+
176
+ it "reads pipelined requests with streamed bodies" do
177
+ with_socket_pair(4) do |client, connection|
178
+ 3.times do |i|
179
+ body = "Hello, world number #{i}!"
180
+ example_request = ExampleRequest.new
181
+ example_request.body = body
182
+
183
+ client << example_request.to_s
184
+ end
185
+
186
+ 3.times do |i|
187
+ request = connection.request
188
+
189
+ expected_body = "Hello, world number #{i}!"
190
+ request.url.should eq "/"
191
+ request.version.should eq "1.1"
192
+ request['Content-Length'].should eq expected_body.length.to_s
193
+ request.should_not be_finished_reading
194
+ new_content = ""
195
+ while chunk = request.body.readpartial(1)
196
+ new_content << chunk
197
+ end
198
+ new_content.should == expected_body
199
+ request.should be_finished_reading
200
+
201
+ connection.respond :ok, {}, ""
202
+ end
203
+ end
204
+ end
205
+
206
+ # This test will deadlock rspec waiting unless
207
+ # connection.request works properly
208
+ it "does not block waiting for body to read before handling request" do
209
+ with_socket_pair do |client, connection|
210
+ example_request = ExampleRequest.new
211
+ content = "Hi guys! Sorry I'm late to the party."
212
+ example_request['Content-Length'] = content.length
213
+ client << example_request.to_s
214
+
215
+ request = connection.request
216
+ request.should be_a(Reel::Request)
217
+ client << content
218
+ request.body.to_s.should == content
219
+ end
220
+ end
221
+
222
+ it "blocks on read until written" do
223
+ with_socket_pair do |client, connection|
224
+ example_request = ExampleRequest.new
225
+ content = "Hi guys! Sorry I'm late to the party."
226
+ example_request['Content-Length'] = content.length
227
+ client << example_request.to_s
228
+
229
+ request = connection.request
230
+ timers = Timers.new
231
+ timers.after(0.2){
232
+ client << content
233
+ }
234
+ read_body = ""
235
+ timers.after(0.1){
236
+ timers.wait # continue timers, the next bit will block waiting for content
237
+ read_body = request.read(8)
238
+ }
239
+ timers.wait
240
+
241
+ request.should be_a(Reel::Request)
242
+ read_body.should == content[0..7]
243
+ end
244
+ end
245
+
246
+ it "streams body properly with #read and buffered body" do
247
+ with_socket_pair do |client, connection|
248
+ example_request = ExampleRequest.new
249
+ content = "I'm data you can stream!"
250
+ example_request['Content-Length'] = content.length
251
+ client << example_request.to_s
252
+
253
+ request = connection.request
254
+ request.should be_a(Reel::Request)
255
+ request.should_not be_finished_reading
256
+ client << content
257
+ rebuilt = []
258
+ connection.readpartial(64) # Buffer some body
259
+ while chunk = request.read(8)
260
+ rebuilt << chunk
261
+ end
262
+ request.should be_finished_reading
263
+ rebuilt.should == ["I'm data", " you can", " stream!"]
264
+ end
265
+ end
266
+
267
+ context "#readpartial" do
268
+ it "streams request bodies" do
269
+ with_socket_pair(8) do |client, connection|
270
+ example_request = ExampleRequest.new
271
+ content = "I'm data you can stream!"
272
+ example_request['Content-Length'] = content.length
273
+ client << example_request.to_s
274
+
275
+ request = connection.request
276
+ request.should be_a(Reel::Request)
277
+ request.should_not be_finished_reading
278
+ client << content
279
+ rebuilt = []
280
+ while chunk = request.body.readpartial(8)
281
+ rebuilt << chunk
282
+ end
283
+ request.should be_finished_reading
284
+ rebuilt.should == ["I'm data", " you can", " stream!"]
285
+ end
286
+ end
287
+ end
288
+
289
+ context "#each" do
290
+ it "streams request bodies" do
291
+ with_socket_pair(8) do |client, connection|
292
+ example_request = ExampleRequest.new
293
+ content = "I'm data you can stream!"
294
+ example_request['Content-Length'] = content.length
295
+ client << example_request.to_s
296
+
297
+ request = connection.request
298
+ request.should be_a(Reel::Request)
299
+ request.should_not be_finished_reading
300
+ client << content
301
+
302
+ data = ""
303
+ request.body.each { |chunk| data << chunk }
304
+ request.should be_finished_reading
305
+ data.should == "I'm data you can stream!"
306
+ end
307
+ end
308
+ end
309
+
310
+ describe "IO#read duck typing" do
99
311
  it "raises an exception if length is a negative value" do
100
312
  with_socket_pair do |client, connection|
101
313
  example_request = ExampleRequest.new
@@ -118,6 +330,20 @@ describe Reel::Connection do
118
330
  end
119
331
  end
120
332
 
333
+ it "reads to EOF if length is nil, even small buffer" do
334
+ with_socket_pair(4) do |client, connection|
335
+ body = "Hello, world!"
336
+ example_request = ExampleRequest.new
337
+ example_request.body = body
338
+ connection.buffer_size.should == 4
339
+
340
+ client << example_request.to_s
341
+ request = connection.request
342
+
343
+ request.read.should eq "Hello, world!"
344
+ end
345
+ end
346
+
121
347
  it "reads to EOF if length is nil" do
122
348
  with_socket_pair do |client, connection|
123
349
  body = "Hello, world!"