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.
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