raptor-io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +30 -0
  3. data/README.md +51 -0
  4. data/lib/rack/handler/raptor-io.rb +130 -0
  5. data/lib/raptor-io.rb +11 -0
  6. data/lib/raptor-io/error.rb +19 -0
  7. data/lib/raptor-io/protocol.rb +6 -0
  8. data/lib/raptor-io/protocol/error.rb +10 -0
  9. data/lib/raptor-io/protocol/http.rb +34 -0
  10. data/lib/raptor-io/protocol/http/client.rb +685 -0
  11. data/lib/raptor-io/protocol/http/error.rb +16 -0
  12. data/lib/raptor-io/protocol/http/headers.rb +132 -0
  13. data/lib/raptor-io/protocol/http/message.rb +67 -0
  14. data/lib/raptor-io/protocol/http/request.rb +307 -0
  15. data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
  16. data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
  17. data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
  18. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
  19. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
  20. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
  21. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
  22. data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
  23. data/lib/raptor-io/protocol/http/response.rb +166 -0
  24. data/lib/raptor-io/protocol/http/server.rb +446 -0
  25. data/lib/raptor-io/ruby.rb +4 -0
  26. data/lib/raptor-io/ruby/hash.rb +24 -0
  27. data/lib/raptor-io/ruby/ipaddr.rb +15 -0
  28. data/lib/raptor-io/ruby/openssl.rb +23 -0
  29. data/lib/raptor-io/ruby/string.rb +27 -0
  30. data/lib/raptor-io/socket.rb +175 -0
  31. data/lib/raptor-io/socket/comm.rb +143 -0
  32. data/lib/raptor-io/socket/comm/local.rb +94 -0
  33. data/lib/raptor-io/socket/comm/sapni.rb +75 -0
  34. data/lib/raptor-io/socket/comm/socks.rb +237 -0
  35. data/lib/raptor-io/socket/comm_chain.rb +30 -0
  36. data/lib/raptor-io/socket/error.rb +45 -0
  37. data/lib/raptor-io/socket/switch_board.rb +183 -0
  38. data/lib/raptor-io/socket/switch_board/route.rb +42 -0
  39. data/lib/raptor-io/socket/tcp.rb +231 -0
  40. data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
  41. data/lib/raptor-io/socket/tcp_server.rb +16 -0
  42. data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
  43. data/lib/raptor-io/socket/udp.rb +0 -0
  44. data/lib/raptor-io/version.rb +6 -0
  45. data/lib/tasks/yard.rake +26 -0
  46. data/spec/rack/handler/raptor_spec.rb +140 -0
  47. data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
  48. data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
  49. data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
  50. data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
  51. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
  52. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
  53. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
  54. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
  55. data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
  56. data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
  57. data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
  58. data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
  59. data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
  60. data/spec/raptor-io/ruby/hash_spec.rb +20 -0
  61. data/spec/raptor-io/ruby/string_spec.rb +20 -0
  62. data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
  63. data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
  64. data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
  65. data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
  66. data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
  67. data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
  68. data/spec/raptor-io/socket/tcp_spec.rb +14 -0
  69. data/spec/raptor-io/socket_spec.rb +16 -0
  70. data/spec/raptor-io/version_spec.rb +10 -0
  71. data/spec/spec_helper.rb +56 -0
  72. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
  73. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
  74. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
  75. data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
  76. data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
  77. data/spec/support/lib/path_helpers.rb +11 -0
  78. data/spec/support/lib/webserver_option_parser.rb +26 -0
  79. data/spec/support/lib/webservers.rb +120 -0
  80. data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
  81. data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
  82. data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
  83. data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
  84. data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
  85. data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
  86. data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
  87. data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
  88. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
  89. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
  90. data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
  91. metadata +336 -0
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+ require 'rack/handler/raptor-io'
3
+
4
+ class RackValidatorApp
5
+ def call( environment )
6
+
7
+ environment.delete( 'rack.input' ).class.should == StringIO
8
+ environment.delete( 'rack.errors' ).class.should == IO
9
+
10
+ request = environment.delete( 'raptor.request' )
11
+
12
+ http_version = environment.delete( 'HTTP_VERSION' )
13
+
14
+ http_version.should == 'HTTP/1.1'
15
+ environment.delete( 'SERVER_NAME' ).should == environment['HTTP_HOST'].split( ':' ).first
16
+
17
+ environment.dup.each do |k, v|
18
+ next if !k.start_with?( 'HTTP_' )
19
+
20
+ environment.delete( k ).should ==
21
+ request.headers[ k.gsub( 'HTTP_', '' ).gsub( '_', '-' ) ]
22
+ end
23
+
24
+ environment.should == ({
25
+ 'REQUEST_METHOD' => 'GET',
26
+ 'SCRIPT_NAME' => '',
27
+ 'PATH_INFO' => '/',
28
+ 'REQUEST_PATH' => '/',
29
+ 'QUERY_STRING' => '',
30
+ 'SERVER_PORT' => '9292',
31
+ 'SERVER_PROTOCOL' => http_version,
32
+ 'REMOTE_ADDR' => '127.0.0.1',
33
+ 'rack.version' => [ 1, 2 ],
34
+ 'rack.multithread' => true,
35
+ 'rack.multiprocess' => false,
36
+ 'rack.run_once' => false,
37
+ 'rack.url_scheme' => 'http',
38
+ 'rack.hijack?' => false
39
+ })
40
+
41
+ [ 200, { 'Content-Type' => 'text/html' }, 'Hello Rack!' ]
42
+ end
43
+ end
44
+
45
+ class ErrorRackApp
46
+ def call( environment )
47
+ raise 'Stuff'
48
+ end
49
+ end
50
+
51
+ describe Rack::Handler::RaptorIO do
52
+
53
+ def argument_to_url( server_or_url )
54
+ server_or_url.is_a?( String ) ? server_or_url : server_or_url.url
55
+ end
56
+
57
+ def request( server_or_url )
58
+ Net::HTTP.get_response( URI( argument_to_url( server_or_url ) ) )
59
+ end
60
+
61
+ def run_server( app, options = {}, &block )
62
+ options = { switch_board: RaptorIO::Socket::SwitchBoard.new }.merge( options )
63
+ described_class.run( app, options, &block )
64
+ end
65
+
66
+ describe '.run' do
67
+ it 'starts the server' do
68
+ server = nil
69
+
70
+ t = Thread.new do
71
+ run_server( RackValidatorApp.new, Host: '0.0.0.0', Port: 9292 ) { |s| server = s }
72
+ end
73
+ sleep 1
74
+
75
+ request( server ).body.should == 'Hello Rack!'
76
+
77
+ described_class.shutdown
78
+ t.join
79
+ end
80
+
81
+ context 'when the Rack application raises an exception' do
82
+ it 'returns the error in the response body' do
83
+ server = nil
84
+ t = Thread.new do
85
+ run_server( ErrorRackApp.new, Host: '0.0.0.0', Port: 9292 ) { |s| server = s }
86
+ end
87
+ sleep 1
88
+
89
+ request( server ).body.should == 'Stuff (RuntimeError)'
90
+
91
+ described_class.shutdown
92
+ t.join
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '.shutdown' do
98
+ it 'stops the server' do
99
+ server = nil
100
+ t = Thread.new do
101
+ run_server( RackValidatorApp.new, Host: '0.0.0.0', Port: 9292 ) { |s| server = s }
102
+ end
103
+ sleep 1
104
+
105
+ request( server ).body.should == 'Hello Rack!'
106
+
107
+ described_class.shutdown
108
+ t.join
109
+
110
+ expect { request( server ) }.to raise_error Errno::ECONNREFUSED
111
+ end
112
+ end
113
+
114
+ describe '.default_host' do
115
+ context 'by default' do
116
+ it 'returns localhost' do
117
+ ENV['RACK_ENV'] = nil
118
+ described_class.default_host.should == 'localhost'
119
+ end
120
+ end
121
+
122
+ context 'when RACK_ENV is' do
123
+ context 'development' do
124
+ it 'returns localhost' do
125
+ ENV['RACK_ENV'] = 'development'
126
+ described_class.default_host.should == 'localhost'
127
+ end
128
+ end
129
+
130
+ context 'else' do
131
+ it 'returns 0.0.0.0' do
132
+ ENV['RACK_ENV'] = 'stuff'
133
+ described_class.default_host.should == '0.0.0.0'
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+
140
+ end
@@ -0,0 +1,671 @@
1
+ #coding: utf-8
2
+ require 'spec_helper'
3
+ require 'raptor-io/socket'
4
+
5
+ describe RaptorIO::Protocol::HTTP::Client do
6
+
7
+ before :all do
8
+ WebServers.start :client_close_connection
9
+ WebServers.start :client_https
10
+ WebServers.start :client
11
+
12
+ @url = WebServers.url_for( :client )
13
+ @https_url = WebServers.url_for( :client_https ).gsub( 'http', 'https' )
14
+ end
15
+
16
+ before( :each ) do
17
+ RaptorIO::Protocol::HTTP::Request::Manipulators.reset
18
+ RaptorIO::Protocol::HTTP::Request::Manipulators.library = manipulator_fixtures_path
19
+ end
20
+
21
+ let(:url) { 'http://test.com' }
22
+ let(:switch_board) { RaptorIO::Socket::SwitchBoard.new }
23
+ let(:options) { {} }
24
+ subject(:client) do
25
+ described_class.new(
26
+ { switch_board: switch_board }.merge(options)
27
+ )
28
+ end
29
+
30
+ describe '#initialize' do
31
+
32
+ describe :ssl_version do
33
+ let(:options) do
34
+ { ssl_version: 'stuff' }
35
+ end
36
+
37
+ it 'sets the SSL version to use' do
38
+ client.ssl_version.should == 'stuff'
39
+ end
40
+ end
41
+
42
+ describe :ssl_verify_mode do
43
+ let(:options) do
44
+ { ssl_verify_mode: 'stuff' }
45
+ end
46
+
47
+ it 'sets the SSL version to use' do
48
+ client.ssl_verify_mode.should == 'stuff'
49
+ end
50
+ end
51
+
52
+ describe :ssl_context do
53
+ let(:options) do
54
+ { ssl_context: 'stuff' }
55
+ end
56
+
57
+ it 'sets the SSL version to use' do
58
+ client.ssl_context.should == 'stuff'
59
+ end
60
+ end
61
+
62
+ describe :manipulators do
63
+ it 'defaults to an empty Hash' do
64
+ client.manipulators.should == {}
65
+ end
66
+
67
+ context 'when the options are invalid' do
68
+ it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
69
+ expect do
70
+ described_class.new(
71
+ switch_board: switch_board,
72
+ manipulators: {
73
+ options_validator: { mandatory_string: 12 }
74
+ }
75
+ )
76
+ end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
77
+ end
78
+ end
79
+
80
+ context 'with manipulators' do
81
+ let(:manipulators) do
82
+ { 'manifoolators/fooer' => { times: 15 } }
83
+ end
84
+ let(:options) do
85
+ { manipulators: manipulators }
86
+ end
87
+ it 'sets the manipulators option' do
88
+ client.manipulators.should == manipulators
89
+ end
90
+
91
+ context 'when a request is queued' do
92
+ it 'runs the configured manipulators' do
93
+ request = RaptorIO::Protocol::HTTP::Request.new( url: "#{@url}/" )
94
+ client.queue( request )
95
+ request.url.should == "#{@url}/" + ('foo' * 15)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ describe :timeout do
102
+ context 'without a value' do
103
+ it 'defaults to 10' do
104
+ client.timeout.should == 10
105
+ end
106
+ end
107
+
108
+ context 'with a value' do
109
+ let(:options) do
110
+ { timeout: 1 }
111
+ end
112
+
113
+ it 'sets the timeout option' do
114
+ client.timeout.should == 1
115
+ end
116
+
117
+ context 'when a timeout occurs' do
118
+ it 'raises RaptorIO::Error::Timeout', speed: 'slow' do
119
+ expect {
120
+ client.get( "#{@url}/sleep", mode: :sync )
121
+ }.to raise_error RaptorIO::Error::Timeout
122
+ end
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ describe :concurrency do
129
+ context 'without a value' do
130
+ it 'defaults to 20' do
131
+ client.concurrency.should == 20
132
+ end
133
+ end
134
+ context 'with a value' do
135
+ let(:options) do
136
+ { concurrency: 10 }
137
+ end
138
+
139
+ it 'sets the request concurrency option' do
140
+ client.concurrency.should == 10
141
+ end
142
+
143
+ it 'sets the amount of maximum open connections at any given time', speed: 'slow' do
144
+ cnt = 0
145
+ times = 10
146
+
147
+ url = "#{@url}/sleep"
148
+
149
+ client.concurrency = 1
150
+ times.times do
151
+ client.get url do
152
+ cnt += 1
153
+ end
154
+ end
155
+
156
+ t = Time.now
157
+ client.run
158
+ runtime_1 = Time.now - t
159
+ cnt.should == times
160
+
161
+ cnt = 0
162
+ client.concurrency = 20
163
+ times.times do
164
+ client.get url do
165
+ cnt += 1
166
+ end
167
+ end
168
+
169
+ t = Time.now
170
+ client.run
171
+ runtime_2 = Time.now - t
172
+
173
+ cnt.should == times
174
+ runtime_1.should > runtime_2
175
+ end
176
+ end
177
+ end
178
+
179
+ describe :user_agent do
180
+ context 'without a value' do
181
+ it "defaults to 'RaptorIO::HTTP/#{RaptorIO::VERSION}'" do
182
+ client.user_agent.should == "RaptorIO::HTTP/#{RaptorIO::VERSION}"
183
+ end
184
+ end
185
+
186
+ context 'with a value' do
187
+ let(:ua) { 'Stuff' }
188
+ let(:options) do
189
+ { user_agent: ua }
190
+ end
191
+ it 'sets the user-agent option' do
192
+ client.user_agent.should == ua
193
+ end
194
+
195
+ it 'sets the User-Agent for the requests' do
196
+ client.request( @url ).headers['User-Agent'].should == ua
197
+ end
198
+
199
+ end
200
+ end
201
+ end
202
+
203
+ describe '#update_manipulators' do
204
+ context 'when the options are invalid' do
205
+ it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
206
+ expect do
207
+ client.update_manipulators( options_validator: { mandatory_string: 12 } )
208
+ end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
209
+ end
210
+ end
211
+
212
+ it 'updates the client-wide manipulators' do
213
+ manipulators = { 'manifoolators/fooer' => { times: 16 } }
214
+ client.update_manipulators( manipulators )
215
+ client.manipulators.should == manipulators
216
+ end
217
+ end
218
+
219
+ describe '#datastore' do
220
+ it 'returns a hash' do
221
+ client.datastore.should == {}
222
+ end
223
+
224
+ it 'has empty hashes as default values' do
225
+ client.datastore['stuff'].should == {}
226
+ end
227
+ end
228
+
229
+ describe '#concurrency=' do
230
+ it 'sets the concurrency option' do
231
+ client.concurrency.should_not == 10
232
+ client.concurrency = 10
233
+ client.concurrency.should == 10
234
+ end
235
+ end
236
+
237
+ describe '#user_agent=' do
238
+ it 'sets the user_agent option' do
239
+ ua = 'stuff'
240
+ client.user_agent.should_not == ua
241
+ client.user_agent = ua
242
+ client.user_agent.should == ua
243
+ end
244
+ end
245
+
246
+ describe '#request' do
247
+ it 'supports SSL' do
248
+ res = client.get( @https_url, mode: :sync )
249
+ res.should be_kind_of RaptorIO::Protocol::HTTP::Response
250
+ res.code.should == 200
251
+ res.body.should == 'Stuff...'
252
+ end
253
+
254
+ it 'handles responses without body (1xx, 204, 304)' do
255
+ client.get( "#{@url}/204", mode: :sync ).should be_kind_of RaptorIO::Protocol::HTTP::Response
256
+ end
257
+
258
+ it 'properly transmits raw binary data' do
259
+ client.get( "#{@url}/echo_body",
260
+ raw: true,
261
+ mode: :sync,
262
+ body: "\ff\ff"
263
+ ).body.should == "\ff\ff"
264
+ end
265
+
266
+ it 'properly transmits raw multibyte data' do
267
+ client.get( "#{@url}/echo_body",
268
+ raw: true,
269
+ mode: :sync,
270
+ body: 'τεστ'
271
+ ).body.should == 'τεστ'
272
+ end
273
+
274
+ it 'forwards the given options to the Request object' do
275
+ options = { parameters: { 'name' => 'value' }}
276
+ client.request( '/blah/', options ).parameters.should == options[:parameters]
277
+ end
278
+
279
+ context 'when passed a block' do
280
+ it 'sets it as a callback' do
281
+ passed_response = nil
282
+ response = RaptorIO::Protocol::HTTP::Response.new( url: url )
283
+
284
+ request = client.request( '/blah/' ) { |res| passed_response = res }
285
+ request.handle_response( response )
286
+ passed_response.should == response
287
+ end
288
+ end
289
+
290
+ describe 'option' do
291
+ describe :manipulators do
292
+ context 'when the options are invalid' do
293
+ it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
294
+ expect do
295
+ client.get( "#{@url}/",
296
+ manipulators: { options_validator: { mandatory_string: 12 } }
297
+ )
298
+ end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
299
+ end
300
+ end
301
+
302
+ it 'loads and configures the given manipulators' do
303
+ request = client.get( "#{@url}/",
304
+ manipulators: {
305
+ 'manifoolators/fooer' => { times: 10 }
306
+ }
307
+ )
308
+ request.url.should == "#{@url}/" + ('foo' * 10)
309
+ end
310
+ end
311
+
312
+ describe :cookies do
313
+ context Hash do
314
+ it 'formats and sets request cookies' do
315
+ client.get( "#{@url}/cookies",
316
+ cookies: {
317
+ 'name' => 'value',
318
+ 'name2' => 'value2'
319
+ },
320
+ mode: :sync
321
+ ).body.should == 'name=value;name2=value2'
322
+ end
323
+ end
324
+
325
+ context String do
326
+ it 'sets request cookies' do
327
+ client.get( "#{@url}/cookies",
328
+ cookies: 'name=value;name2=value2',
329
+ mode: :sync
330
+ ).body.should == 'name=value;name2=value2'
331
+ end
332
+ end
333
+
334
+ end
335
+
336
+ describe :continue do
337
+ context 'default' do
338
+ it 'handles responses with status "100" automatically' do
339
+ body = 'stuff-here'
340
+ client.get( "#{@url}/100",
341
+ headers: {
342
+ 'Expect' => '100-continue'
343
+ },
344
+ body: body,
345
+ mode: :sync
346
+ ).body.should == body
347
+ end
348
+ end
349
+
350
+ context true do
351
+ it 'handles responses with status "100" automatically' do
352
+ body = 'stuff-here'
353
+ client.get( "#{@url}/100",
354
+ headers: {
355
+ 'Expect' => '100-continue'
356
+ },
357
+ body: body,
358
+ continue: true,
359
+ mode: :sync
360
+ ).body.should == body
361
+ end
362
+ end
363
+
364
+ context false do
365
+ it 'does not handle responses with status "100" automatically' do
366
+ body = 'stuff-here'
367
+ client.get( "#{@url}/100",
368
+ headers: {
369
+ 'Expect' => '100-continue'
370
+ },
371
+ body: body,
372
+ continue: false,
373
+ mode: :sync
374
+ ).code.should == 100
375
+ end
376
+ end
377
+ end
378
+
379
+ describe :timeout do
380
+ it 'handles timeouts progressively and in groups', speed: 'slow' do
381
+
382
+ 2.times do
383
+ client.get( "#{@url}/long-sleep", timeout: 20 ) do |response|
384
+ response.error.should be_nil
385
+ end
386
+ end
387
+
388
+ 2.times do
389
+ client.get( "#{@url}/long-sleep", timeout: 2 ) do |response|
390
+ response.error.should be_kind_of RaptorIO::Error::Timeout
391
+ end
392
+ end
393
+
394
+ 2.times do
395
+ client.get( "#{@url}/long-sleep", timeout: 3 ) do |response|
396
+ response.error.should be_kind_of RaptorIO::Error::Timeout
397
+ end
398
+ end
399
+
400
+ 2.times do
401
+ client.get( "#{@url}/long-sleep", timeout: 7 ) do |response|
402
+ response.error.should be_nil
403
+ end
404
+ end
405
+
406
+ 2.times do
407
+ client.get( "#{@url}/long-sleep", timeout: 10 ) do |response|
408
+ response.error.should be_nil
409
+ end
410
+ end
411
+
412
+ t = Time.now
413
+ client.run
414
+ runtime = Time.now - t
415
+
416
+ (runtime >= 5.0 || runtime < 6.0).should be_true
417
+ end
418
+
419
+ context 'when a timeout occurs' do
420
+ let (:options) do
421
+ { timeout: 1 }
422
+ end
423
+ it 'raises RaptorIO::Error::Timeout', speed: 'slow' do
424
+ expect {
425
+ client.get( "#{@url}/sleep", mode: :sync )
426
+ }.to raise_error RaptorIO::Error::Timeout
427
+ end
428
+ end
429
+ end
430
+ describe :mode do
431
+ describe :sync do
432
+ it 'performs the request synchronously and returns the response' do
433
+ options = {
434
+ parameters: { 'name' => 'value' },
435
+ mode: :sync
436
+ }
437
+ response = client.request( @url, options )
438
+ response.should be_kind_of RaptorIO::Protocol::HTTP::Response
439
+ response.request.parameters.should == options[:parameters]
440
+ end
441
+ end
442
+ end
443
+ end
444
+
445
+ it 'increments the queue size' do
446
+ client.queue_size.should == 0
447
+ client.request( '/blah/' )
448
+ client.queue_size.should == 1
449
+ end
450
+
451
+ it 'returns the request' do
452
+ client.request( '/blah/' ).should be_kind_of RaptorIO::Protocol::HTTP::Request
453
+ end
454
+
455
+ it 'treats a closed connection as a signal for end of a response' do
456
+ url = WebServers.url_for( :client_close_connection )
457
+ client.get( url, mode: :sync ).body.should == "Success\n.\n"
458
+ client.get( url, mode: :sync ).body.should == "Success\n.\n"
459
+ client.get( url, mode: :sync ).body.should == "Success\n.\n"
460
+ end
461
+
462
+ describe 'Content-Encoding' do
463
+ it 'supports gzip' do
464
+ client.get( "#{@url}/gzip", mode: :sync ).body.should == 'gzip'
465
+ end
466
+
467
+ it 'supports deflate' do
468
+ client.get( "#{@url}/deflate", mode: :sync ).body.should == 'deflate'
469
+ end
470
+ end
471
+
472
+ describe 'Content-Encoding' do
473
+ context 'supports' do
474
+ it 'chunked', speed: 'slow' do
475
+ res = client.get( "#{@url}/chunked", mode: :sync )
476
+ res.body.should == "foo\nbara\rbaraf\r\n"
477
+ res.headers.should_not include 'Transfer-Encoding'
478
+ res.headers['Content-Length'].to_i.should == res.body.size
479
+ end
480
+ end
481
+ end
482
+ end
483
+
484
+ describe '#get' do
485
+ it 'queues a GET request' do
486
+ client.get( '/blah/' ).http_method.should == :get
487
+ end
488
+ end
489
+
490
+ describe '#post' do
491
+ it 'queues a POST request' do
492
+ client.post( '/blah/' ).http_method.should == :post
493
+ end
494
+ end
495
+
496
+ describe '#queue_size' do
497
+ it 'returns the amount of queued requests' do
498
+ client.queue_size.should == 0
499
+ 10.times { client.request( '/' ) }
500
+ client.queue_size.should == 10
501
+ end
502
+ end
503
+
504
+ describe '#queue' do
505
+ it 'queues a request' do
506
+ client.queue_size.should == 0
507
+ client.queue( RaptorIO::Protocol::HTTP::Request.new( url: url ) )
508
+ client.queue_size.should == 1
509
+ end
510
+ it 'returns the queued request' do
511
+ request = RaptorIO::Protocol::HTTP::Request.new( url: url )
512
+ client.queue(request).should == request
513
+ end
514
+
515
+ describe :manipulators do
516
+ context 'when the options are invalid' do
517
+ it 'raises RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions' do
518
+ expect do
519
+ request = RaptorIO::Protocol::HTTP::Request.new( url: "#{@url}/" )
520
+ client.queue( request, options_validator: { mandatory_string: 12 } )
521
+ end.to raise_error RaptorIO::Protocol::HTTP::Request::Manipulator::Error::InvalidOptions
522
+ end
523
+ end
524
+
525
+ it 'loads and configures the given manipulators' do
526
+ request = RaptorIO::Protocol::HTTP::Request.new( url: "#{@url}/" )
527
+ client.queue( request, 'manifoolators/fooer' => { times: 10 } )
528
+ request.url.should == "#{@url}/" + ('foo' * 10)
529
+ end
530
+ end
531
+
532
+ end
533
+
534
+ describe '#<<' do
535
+ it 'alias of #queue' do
536
+ client.queue_size.should == 0
537
+ client << RaptorIO::Protocol::HTTP::Request.new( url: url )
538
+ client.queue_size.should == 1
539
+ end
540
+ end
541
+
542
+ describe '#run' do
543
+ context 'when a request fails' do
544
+ context 'in asynchronous mode' do
545
+ context 'due to a closed port' do
546
+ it 'passes the callback an empty response' do
547
+ url = 'http://localhost:9696969'
548
+
549
+ response = nil
550
+ client.get( url ){ |r| response = r }
551
+ client.run
552
+
553
+ response.version.should == '1.1'
554
+ response.code.should == 0
555
+ response.message.should be_nil
556
+ response.body.should be_nil
557
+ response.headers.should == {}
558
+ end
559
+
560
+ it 'assigns RaptorIO::Socket::Error::ConnectionError to #error' do
561
+ url = 'http://localhost:9696969'
562
+
563
+ response = nil
564
+ client.get( url ){ |r| response = r }
565
+ client.run
566
+
567
+ response.error.should be_kind_of RaptorIO::Socket::Error::ConnectionError
568
+ end
569
+
570
+ end
571
+
572
+ context 'due to an invalid IP address' do
573
+ it 'passes the callback an empty response', speed: 'slow' do
574
+ url = 'http://10.11.12.13'
575
+
576
+ response = nil
577
+ client.get( url ){ |r| response = r }
578
+ client.run
579
+
580
+ response.version.should == '1.1'
581
+ response.code.should == 0
582
+ response.message.should be_nil
583
+ response.body.should be_nil
584
+ response.headers.should == {}
585
+ end
586
+
587
+ it 'assigns RaptorIO::Socket::Error::ConnectionError to #error', speed: 'slow' do
588
+ url = 'http://10.11.12.13'
589
+
590
+ response = nil
591
+ client.get( url ){ |r| response = r }
592
+ client.run
593
+
594
+ response.error.should be_kind_of RaptorIO::Socket::Error::ConnectionError
595
+ end
596
+ end
597
+
598
+ context 'due to an invalid hostname' do
599
+ it 'passes the callback an empty response' do
600
+ url = 'http://stuffhereblahblahblah'
601
+
602
+ response = nil
603
+ client.get( url ){ |r| response = r }
604
+ client.run
605
+
606
+ response.version.should == '1.1'
607
+ response.code.should == 0
608
+ response.message.should be_nil
609
+ response.body.should be_nil
610
+ response.headers.should == {}
611
+ end
612
+
613
+ it 'assigns RaptorIO::Socket::Error::CouldNotResolve to #error' do
614
+ url = 'http://stuffhereblahblahblah'
615
+
616
+ response = nil
617
+ client.get( url ){ |r| response = r }
618
+ client.run
619
+
620
+ response.error.should be_kind_of RaptorIO::Socket::Error::CouldNotResolve
621
+ end
622
+ end
623
+ end
624
+
625
+ context 'in synchronous mode' do
626
+ context 'due to a closed port' do
627
+ it 'raises RaptorIO::Socket::Error::ConnectionRefused' do
628
+ expect {
629
+ client.get( 'http://localhost:858589', mode: :sync )
630
+ }.to raise_error RaptorIO::Socket::Error::ConnectionRefused
631
+ end
632
+ end
633
+
634
+ context 'due to an invalid address' do
635
+ it 'raises RaptorIO::Socket::Error::CouldNotResolve' do
636
+ expect {
637
+ client.get( 'http://stuffhereblahblahblah', mode: :sync )
638
+ }.to raise_error RaptorIO::Socket::Error::CouldNotResolve
639
+ end
640
+ end
641
+ end
642
+
643
+ end
644
+
645
+ it 'runs all the queued requests' do
646
+ cnt = 0
647
+ times = 2
648
+
649
+ times.times do
650
+ client.get @url do |r|
651
+ cnt += 1
652
+ end
653
+ end
654
+
655
+ client.run
656
+ cnt.should == times
657
+ end
658
+
659
+ it 'runs requests queued via callbacks' do
660
+ called = false
661
+ client.get @url do
662
+ client.get @url do
663
+ called = true
664
+ end
665
+ end
666
+
667
+ client.run
668
+ called.should be_true
669
+ end
670
+ end
671
+ end