raptor-io 0.0.1
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.
- checksums.yaml +15 -0
- data/LICENSE +30 -0
- data/README.md +51 -0
- data/lib/rack/handler/raptor-io.rb +130 -0
- data/lib/raptor-io.rb +11 -0
- data/lib/raptor-io/error.rb +19 -0
- data/lib/raptor-io/protocol.rb +6 -0
- data/lib/raptor-io/protocol/error.rb +10 -0
- data/lib/raptor-io/protocol/http.rb +34 -0
- data/lib/raptor-io/protocol/http/client.rb +685 -0
- data/lib/raptor-io/protocol/http/error.rb +16 -0
- data/lib/raptor-io/protocol/http/headers.rb +132 -0
- data/lib/raptor-io/protocol/http/message.rb +67 -0
- data/lib/raptor-io/protocol/http/request.rb +307 -0
- data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
- data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
- data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
- data/lib/raptor-io/protocol/http/response.rb +166 -0
- data/lib/raptor-io/protocol/http/server.rb +446 -0
- data/lib/raptor-io/ruby.rb +4 -0
- data/lib/raptor-io/ruby/hash.rb +24 -0
- data/lib/raptor-io/ruby/ipaddr.rb +15 -0
- data/lib/raptor-io/ruby/openssl.rb +23 -0
- data/lib/raptor-io/ruby/string.rb +27 -0
- data/lib/raptor-io/socket.rb +175 -0
- data/lib/raptor-io/socket/comm.rb +143 -0
- data/lib/raptor-io/socket/comm/local.rb +94 -0
- data/lib/raptor-io/socket/comm/sapni.rb +75 -0
- data/lib/raptor-io/socket/comm/socks.rb +237 -0
- data/lib/raptor-io/socket/comm_chain.rb +30 -0
- data/lib/raptor-io/socket/error.rb +45 -0
- data/lib/raptor-io/socket/switch_board.rb +183 -0
- data/lib/raptor-io/socket/switch_board/route.rb +42 -0
- data/lib/raptor-io/socket/tcp.rb +231 -0
- data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
- data/lib/raptor-io/socket/tcp_server.rb +16 -0
- data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
- data/lib/raptor-io/socket/udp.rb +0 -0
- data/lib/raptor-io/version.rb +6 -0
- data/lib/tasks/yard.rake +26 -0
- data/spec/rack/handler/raptor_spec.rb +140 -0
- data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
- data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
- data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
- data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
- data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
- data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
- data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
- data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
- data/spec/raptor-io/ruby/hash_spec.rb +20 -0
- data/spec/raptor-io/ruby/string_spec.rb +20 -0
- data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
- data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
- data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
- data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
- data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
- data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
- data/spec/raptor-io/socket/tcp_spec.rb +14 -0
- data/spec/raptor-io/socket_spec.rb +16 -0
- data/spec/raptor-io/version_spec.rb +10 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
- data/spec/support/lib/path_helpers.rb +11 -0
- data/spec/support/lib/webserver_option_parser.rb +26 -0
- data/spec/support/lib/webservers.rb +120 -0
- data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
- data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
- data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
- data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
- data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
- data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
- data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
- data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
- metadata +336 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#coding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe RaptorIO::Protocol::HTTP::Response do
|
|
5
|
+
it_should_behave_like 'RaptorIO::Protocol::HTTP::Message'
|
|
6
|
+
|
|
7
|
+
let(:url) { 'http://test.com' }
|
|
8
|
+
|
|
9
|
+
let(:response) do
|
|
10
|
+
"HTTP/1.1 404 Not Found\r\n" +
|
|
11
|
+
"Content-Type: text/html;charset=utf-8\r\n" +
|
|
12
|
+
"Content-Length: 27\r\n\r\n" +
|
|
13
|
+
"<!DOCTYPE html>\n" +
|
|
14
|
+
"More stuff\n".force_encoding( 'ASCII-8BIT')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:response_cr) do
|
|
18
|
+
"HTTP/1.1 404 Not Found\n" +
|
|
19
|
+
"Content-Type: text/html;charset=utf-8\n" +
|
|
20
|
+
"Content-Length: 27\n\n" +
|
|
21
|
+
"<!DOCTYPE html>\n" +
|
|
22
|
+
"More stuff\n".force_encoding( 'ASCII-8BIT')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'forces the body to UTF-8' do
|
|
26
|
+
described_class.parse( response ).body.encoding.to_s.should == 'UTF-8'
|
|
27
|
+
described_class.new( body: "stuff τεστblah" ).body.should == 'stuff τεστblah'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#text?' do
|
|
31
|
+
context 'when the content-type is' do
|
|
32
|
+
context 'text/*' do
|
|
33
|
+
it 'returns true' do
|
|
34
|
+
h = {
|
|
35
|
+
headers: { 'Content-Type' => 'text/stuff' },
|
|
36
|
+
body: "stuff"
|
|
37
|
+
}
|
|
38
|
+
described_class.new( h ).text?.should be_true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context 'application/*' do
|
|
43
|
+
context 'and the response body is' do
|
|
44
|
+
context 'binary' do
|
|
45
|
+
it 'returns false' do
|
|
46
|
+
h = {
|
|
47
|
+
headers: { 'Content-Type' => 'application/stuff' },
|
|
48
|
+
body: "\00\00\00"
|
|
49
|
+
}
|
|
50
|
+
described_class.new( h ).text?.should be_false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'text' do
|
|
55
|
+
it 'returns true' do
|
|
56
|
+
h = {
|
|
57
|
+
headers: { 'Content-Type' => 'application/stuff' },
|
|
58
|
+
body: "stuff"
|
|
59
|
+
}
|
|
60
|
+
described_class.new( h ).text?.should be_true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context 'other' do
|
|
67
|
+
it 'returns false' do
|
|
68
|
+
h = {
|
|
69
|
+
headers: { 'Content-Type' => 'blah/stuff' },
|
|
70
|
+
body: "stuff"
|
|
71
|
+
}
|
|
72
|
+
described_class.new( h ).text?.should be_false
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context nil do
|
|
77
|
+
context 'and the response body is' do
|
|
78
|
+
context 'binary' do
|
|
79
|
+
it 'returns false' do
|
|
80
|
+
h = { body: "\00\00\00" }
|
|
81
|
+
described_class.new( h ).text?.should be_false
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context 'text' do
|
|
86
|
+
it 'returns true' do
|
|
87
|
+
h = { body: "stuff" }
|
|
88
|
+
described_class.new( h ).text?.should be_true
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe '#code' do
|
|
97
|
+
it 'returns the HTTP status code' do
|
|
98
|
+
described_class.new( url: url, code: 200 ).code.should == 200
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'defaults to 0' do
|
|
102
|
+
described_class.new( url: url ).code.should == 0
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe '#body' do
|
|
107
|
+
it 'returns the HTTP response body' do
|
|
108
|
+
described_class.parse( response ).body.should == "<!DOCTYPE html>\nMore stuff\n"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'is forced to UTF8' do
|
|
112
|
+
described_class.parse( response ).body.encoding.to_s.should == 'UTF-8'
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe '#redirect?' do
|
|
117
|
+
context 'when the code is in the 3xx family' do
|
|
118
|
+
context 'and there is a Location in the headers' do
|
|
119
|
+
it 'returns true' do
|
|
120
|
+
300.upto( 399 ) do |code|
|
|
121
|
+
described_class.new(
|
|
122
|
+
url: url,
|
|
123
|
+
code: code,
|
|
124
|
+
headers: { 'Location' => url }
|
|
125
|
+
).redirect?.should be_true
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
context 'and there is no Location in the headers' do
|
|
130
|
+
it 'returns false' do
|
|
131
|
+
300.upto( 399 ) do |code|
|
|
132
|
+
described_class.new(
|
|
133
|
+
url: url,
|
|
134
|
+
code: code
|
|
135
|
+
).redirect?.should be_false
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
context 'when the code is not in the 3xx family' do
|
|
142
|
+
it 'returns false' do
|
|
143
|
+
described_class.new(
|
|
144
|
+
url: url,
|
|
145
|
+
code: 200
|
|
146
|
+
).redirect?.should be_false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
context 'and there is a Location in the headers' do
|
|
150
|
+
it 'returns true' do
|
|
151
|
+
described_class.new(
|
|
152
|
+
url: url,
|
|
153
|
+
code: 200,
|
|
154
|
+
headers: { 'Location' => url }
|
|
155
|
+
).redirect?.should be_false
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
describe '#modified?' do
|
|
162
|
+
context 'when the code is 304' do
|
|
163
|
+
it 'returns false' do
|
|
164
|
+
described_class.new( url: url, code: 304 ).modified?.should be_false
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
context 'when the code is not 304' do
|
|
168
|
+
it 'returns true' do
|
|
169
|
+
described_class.new( url: url, code: 301 ).modified?.should be_true
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe '#request' do
|
|
175
|
+
it 'returns the assigned request' do
|
|
176
|
+
r = RaptorIO::Protocol::HTTP::Request.new( url: url )
|
|
177
|
+
described_class.new( url: url, request: r ).request.should == r
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '.parse' do
|
|
182
|
+
it 'supports CRLF terminators' do
|
|
183
|
+
r = described_class.parse( response )
|
|
184
|
+
r.version.should == '1.1'
|
|
185
|
+
r.code.should == 404
|
|
186
|
+
r.message.should == 'Not Found'
|
|
187
|
+
r.body.should == "<!DOCTYPE html>\nMore stuff\n"
|
|
188
|
+
r.headers.should == {
|
|
189
|
+
'Content-Type' => 'text/html;charset=utf-8',
|
|
190
|
+
'Content-Length' => '27'
|
|
191
|
+
}
|
|
192
|
+
r.to_s.should == response
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'supports CR terminators' do
|
|
196
|
+
r = described_class.parse( response_cr )
|
|
197
|
+
r.version.should == '1.1'
|
|
198
|
+
r.code.should == 404
|
|
199
|
+
r.message.should == 'Not Found'
|
|
200
|
+
r.body.should == "<!DOCTYPE html>\nMore stuff\n"
|
|
201
|
+
r.headers.should == {
|
|
202
|
+
'Content-Type' => 'text/html;charset=utf-8',
|
|
203
|
+
'Content-Length' => '27'
|
|
204
|
+
}
|
|
205
|
+
r.to_s.should == response
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
context 'when passed an empty string' do
|
|
209
|
+
it 'returns an empty response' do
|
|
210
|
+
r = described_class.parse( '' )
|
|
211
|
+
r.version.should == '1.1'
|
|
212
|
+
r.code.should == 0
|
|
213
|
+
r.message.should be_nil
|
|
214
|
+
r.body.should be_nil
|
|
215
|
+
r.headers.should == {}
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
describe '#to_s' do
|
|
221
|
+
it 'returns a String representation of the response' do
|
|
222
|
+
r = described_class.new(
|
|
223
|
+
version: '1.1',
|
|
224
|
+
code: 404,
|
|
225
|
+
message: 'Not Found',
|
|
226
|
+
body: "<!DOCTYPE html>\nMore stuff\n",
|
|
227
|
+
headers: {
|
|
228
|
+
'Content-Type' => 'text/html;charset=utf-8',
|
|
229
|
+
'Content-Length' => '431'
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
r.to_s.should == response
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#coding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
require 'net/https'
|
|
4
|
+
|
|
5
|
+
describe RaptorIO::Protocol::HTTP::Server do
|
|
6
|
+
|
|
7
|
+
after :each do
|
|
8
|
+
next if !@server
|
|
9
|
+
@server.stop
|
|
10
|
+
@server = nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_server( server_or_url )
|
|
14
|
+
test_request server_or_url
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_request( server_or_url )
|
|
18
|
+
request( server_or_url ).code.should == '418'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def request( server_or_url )
|
|
22
|
+
uri = URI( argument_to_url( server_or_url ) )
|
|
23
|
+
|
|
24
|
+
https = Net::HTTP.new( uri.host, uri.port )
|
|
25
|
+
https.use_ssl = (uri.scheme == 'https')
|
|
26
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
27
|
+
|
|
28
|
+
https.start { |cx| cx.request( Net::HTTP::Get.new( uri.path ) ) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_request_with_body( server_or_url )
|
|
32
|
+
response = request_with_body( server_or_url )
|
|
33
|
+
response.code.should == '418'
|
|
34
|
+
response.body.should == 'name=value'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def request_with_body( server_or_url )
|
|
38
|
+
Net::HTTP.post_form( URI( argument_to_url( server_or_url ) ), 'name' => 'value' )
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def argument_to_url( server_or_url )
|
|
42
|
+
server_or_url.is_a?( String ) ? server_or_url : server_or_url.url
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
let(:switch_board) { RaptorIO::Socket::SwitchBoard.new }
|
|
46
|
+
let(:ssl_context) do
|
|
47
|
+
ssl_context = OpenSSL::SSL::SSLContext.new( :TLSv1 )
|
|
48
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
49
|
+
|
|
50
|
+
ca = OpenSSL::X509::Name.parse( "/C=US/ST=SomeState/L=SomeCity/O=Organization/OU=Unit/CN=localhost" )
|
|
51
|
+
key = OpenSSL::PKey::RSA.new( 1024 )
|
|
52
|
+
crt = OpenSSL::X509::Certificate.new
|
|
53
|
+
|
|
54
|
+
crt.version = 2
|
|
55
|
+
crt.serial = 1
|
|
56
|
+
crt.subject = ca
|
|
57
|
+
crt.issuer = ca
|
|
58
|
+
crt.public_key = key.public_key
|
|
59
|
+
crt.not_before = Time.now
|
|
60
|
+
crt.not_after = Time.now + 1 * 365 * 24 * 60 * 60 # 1 year
|
|
61
|
+
|
|
62
|
+
ssl_context.cert = crt
|
|
63
|
+
ssl_context.key = key
|
|
64
|
+
ssl_context
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def new_server( options = {}, &block )
|
|
68
|
+
@server = described_class.new(
|
|
69
|
+
{ switch_board: switch_board }.merge(options),
|
|
70
|
+
&block
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe '#run_nonblock' do
|
|
75
|
+
it 'starts the server but does not block' do
|
|
76
|
+
server = new_server
|
|
77
|
+
server.run_nonblock
|
|
78
|
+
|
|
79
|
+
test_server server
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe '#stop' do
|
|
84
|
+
it 'stops the server' do
|
|
85
|
+
server = new_server
|
|
86
|
+
server.run_nonblock
|
|
87
|
+
|
|
88
|
+
test_server server
|
|
89
|
+
server.stop
|
|
90
|
+
|
|
91
|
+
expect { request( server ) }.to raise_error Errno::ECONNREFUSED
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context 'when a request has Transfer-Encoding' do
|
|
96
|
+
context 'other' do
|
|
97
|
+
it 'returns a 501 response' do
|
|
98
|
+
server = new_server
|
|
99
|
+
server.run_nonblock
|
|
100
|
+
|
|
101
|
+
socket = TCPSocket.new( server.address, server.port )
|
|
102
|
+
[
|
|
103
|
+
'GET / HTTP/1.1',
|
|
104
|
+
'Transfer-Encoding: Stuff'
|
|
105
|
+
].each do |l|
|
|
106
|
+
socket.puts l
|
|
107
|
+
end
|
|
108
|
+
socket.puts
|
|
109
|
+
|
|
110
|
+
[
|
|
111
|
+
'Data' * 12,
|
|
112
|
+
'More data' * 20,
|
|
113
|
+
'Even more data' * 102
|
|
114
|
+
].each do |l|
|
|
115
|
+
socket << "#{l.size.to_s( 16 )}\r\n#{l}\r\n"
|
|
116
|
+
end
|
|
117
|
+
socket << "0\r\n\r\n"
|
|
118
|
+
|
|
119
|
+
buff = ''
|
|
120
|
+
while !(buff =~ RaptorIO::Protocol::HTTP::HEADER_SEPARATOR_PATTERN)
|
|
121
|
+
buff << socket.gets
|
|
122
|
+
end
|
|
123
|
+
socket.close
|
|
124
|
+
|
|
125
|
+
RaptorIO::Protocol::HTTP::Response.parse( buff ).code.should == 501
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe 'Chunked' do
|
|
130
|
+
it 'supports it' do
|
|
131
|
+
server = new_server
|
|
132
|
+
server.run_nonblock
|
|
133
|
+
|
|
134
|
+
socket = TCPSocket.new( server.address, server.port )
|
|
135
|
+
[
|
|
136
|
+
'GET / HTTP/1.1',
|
|
137
|
+
'Transfer-Encoding: Chunked'
|
|
138
|
+
].each do |l|
|
|
139
|
+
socket.puts l
|
|
140
|
+
end
|
|
141
|
+
socket.puts
|
|
142
|
+
|
|
143
|
+
[
|
|
144
|
+
'Data' * 12,
|
|
145
|
+
'More data' * 20,
|
|
146
|
+
'Even more data' * 102
|
|
147
|
+
].each do |l|
|
|
148
|
+
socket << "#{l.size.to_s( 16 )}\r\n#{l}\r\n"
|
|
149
|
+
end
|
|
150
|
+
socket << "0\r\n\r\n"
|
|
151
|
+
|
|
152
|
+
buff = ''
|
|
153
|
+
while !(buff =~ RaptorIO::Protocol::HTTP::HEADER_SEPARATOR_PATTERN)
|
|
154
|
+
buff << socket.gets
|
|
155
|
+
end
|
|
156
|
+
socket.close
|
|
157
|
+
|
|
158
|
+
RaptorIO::Protocol::HTTP::Response.parse( buff ).code.should == 418
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe '#initialize' do
|
|
164
|
+
describe :switch_board do
|
|
165
|
+
it 'uses that switchboard' do
|
|
166
|
+
server = described_class.new( switch_board: switch_board )
|
|
167
|
+
server.switch_board.should == switch_board
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context 'when nil' do
|
|
171
|
+
it 'raises ArgumentError' do
|
|
172
|
+
expect { described_class.new }.to raise_error ArgumentError
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe :ssl_context do
|
|
178
|
+
it 'uses it to establish an SSL stream' do
|
|
179
|
+
server = new_server( ssl_context: ssl_context )
|
|
180
|
+
server.url.should == "https://#{server.address}:#{server.port}/"
|
|
181
|
+
server.ssl?.should be_true
|
|
182
|
+
|
|
183
|
+
server.run_nonblock
|
|
184
|
+
|
|
185
|
+
test_server server
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
describe :address do
|
|
190
|
+
it 'binds to this address' do
|
|
191
|
+
address = '127.0.0.1'
|
|
192
|
+
server = new_server( address: address )
|
|
193
|
+
server.address.should == address
|
|
194
|
+
server.run_nonblock
|
|
195
|
+
|
|
196
|
+
test_server server
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'defaults to 0.0.0.0' do
|
|
200
|
+
server = new_server
|
|
201
|
+
server.address.should == '0.0.0.0'
|
|
202
|
+
|
|
203
|
+
server.run_nonblock
|
|
204
|
+
|
|
205
|
+
test_server server
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
describe :port do
|
|
210
|
+
it 'binds to this port' do
|
|
211
|
+
server = new_server( port: 8080 )
|
|
212
|
+
server.port.should == 8080
|
|
213
|
+
server.run_nonblock
|
|
214
|
+
|
|
215
|
+
test_server server
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'defaults to 4567' do
|
|
219
|
+
server = new_server
|
|
220
|
+
server.port.should == 4567
|
|
221
|
+
|
|
222
|
+
server.run_nonblock
|
|
223
|
+
|
|
224
|
+
test_server server
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
describe :timeout do
|
|
229
|
+
it 'defaults to 10', speed: 'slow' do
|
|
230
|
+
server = new_server
|
|
231
|
+
server.timeout.should == 10
|
|
232
|
+
server.run_nonblock
|
|
233
|
+
|
|
234
|
+
server.timeouts.should == 0
|
|
235
|
+
|
|
236
|
+
socket = Socket.new( Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0 )
|
|
237
|
+
sockaddr = Socket.pack_sockaddr_in( server.port, server.address )
|
|
238
|
+
socket.connect( sockaddr )
|
|
239
|
+
|
|
240
|
+
sleep 9
|
|
241
|
+
|
|
242
|
+
server.timeouts.should == 0
|
|
243
|
+
|
|
244
|
+
sleep 11
|
|
245
|
+
socket.close
|
|
246
|
+
|
|
247
|
+
server.timeouts.should == 1
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'sets the request timeout' do
|
|
251
|
+
server = new_server( timeout: 1 )
|
|
252
|
+
server.timeout.should == 1
|
|
253
|
+
server.run_nonblock
|
|
254
|
+
|
|
255
|
+
server.timeouts.should == 0
|
|
256
|
+
|
|
257
|
+
socket = Socket.new( Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0 )
|
|
258
|
+
sockaddr = Socket.pack_sockaddr_in( server.port, server.address )
|
|
259
|
+
socket.connect( sockaddr )
|
|
260
|
+
|
|
261
|
+
sleep 2
|
|
262
|
+
socket.close
|
|
263
|
+
|
|
264
|
+
server.timeouts.should == 1
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
describe :request_mtu do
|
|
269
|
+
it 'reads bodies larger than :request_mtu in :request_mtu sized chunks' do
|
|
270
|
+
server = new_server( request_mtu: 1 )
|
|
271
|
+
server.request_mtu.should == 1
|
|
272
|
+
server.run_nonblock
|
|
273
|
+
|
|
274
|
+
test_request_with_body server
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
describe :response_mtu do
|
|
279
|
+
it 'sends responses larger than :response_mtu in :response_mtu sized chunks' do
|
|
280
|
+
server = new_server( response_mtu: 1 )
|
|
281
|
+
server.response_mtu.should == 1
|
|
282
|
+
server.run_nonblock
|
|
283
|
+
|
|
284
|
+
test_request_with_body server
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
describe :handler do
|
|
289
|
+
it 'passes each request to the given handler' do
|
|
290
|
+
request = nil
|
|
291
|
+
|
|
292
|
+
server = new_server do |response|
|
|
293
|
+
request = response.request
|
|
294
|
+
response.code = 200
|
|
295
|
+
response.body = 'Success!'
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
server.run_nonblock
|
|
299
|
+
|
|
300
|
+
response = request( server )
|
|
301
|
+
response.code.should == '200'
|
|
302
|
+
response.body.should == 'Success!'
|
|
303
|
+
|
|
304
|
+
request.should be_kind_of RaptorIO::Protocol::HTTP::Request
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
describe '#run' do
|
|
310
|
+
it 'starts the server' do
|
|
311
|
+
server = new_server
|
|
312
|
+
Thread.new { server.run }
|
|
313
|
+
sleep 0.1 while !server.running?
|
|
314
|
+
|
|
315
|
+
test_server server
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
describe '#running?' do
|
|
320
|
+
context 'when the server is running' do
|
|
321
|
+
it 'returns true' do
|
|
322
|
+
server = new_server
|
|
323
|
+
server.run_nonblock
|
|
324
|
+
server.running?.should be_true
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
context 'when the server is not running' do
|
|
328
|
+
it 'returns true' do
|
|
329
|
+
server = new_server
|
|
330
|
+
server.running?.should be_false
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
describe '#url' do
|
|
336
|
+
it 'returns the URL of the server' do
|
|
337
|
+
server = new_server
|
|
338
|
+
server.run_nonblock
|
|
339
|
+
|
|
340
|
+
server.url.should == "http://#{server.address}:#{server.port}/"
|
|
341
|
+
test_server server.url
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
end
|