em-http-request 0.3.0 → 1.0.0.beta.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.
Potentially problematic release.
This version of em-http-request might be problematic. Click here for more details.
- data/.gitignore +1 -0
- data/Changelog.md +10 -0
- data/README.md +43 -160
- data/Rakefile +2 -73
- data/em-http-request.gemspec +7 -7
- data/examples/fetch.rb +30 -30
- data/examples/fibered-http.rb +38 -38
- data/examples/oauth-tweet.rb +49 -49
- data/lib/em-http.rb +4 -6
- data/lib/em-http/client.rb +101 -522
- data/lib/em-http/http_connection.rb +125 -0
- data/lib/em-http/http_encoding.rb +19 -12
- data/lib/em-http/http_header.rb +2 -17
- data/lib/em-http/http_options.rb +37 -19
- data/lib/em-http/request.rb +33 -66
- data/lib/em-http/version.rb +2 -2
- data/spec/client_spec.rb +575 -0
- data/spec/dns_spec.rb +41 -0
- data/spec/encoding_spec.rb +6 -6
- data/spec/external_spec.rb +99 -0
- data/spec/fixtures/google.ca +13 -17
- data/spec/helper.rb +17 -8
- data/spec/http_proxy_spec.rb +53 -0
- data/spec/middleware_spec.rb +114 -0
- data/spec/multi_spec.rb +11 -38
- data/spec/pipelining_spec.rb +38 -0
- data/spec/redirect_spec.rb +114 -0
- data/spec/socksify_proxy_spec.rb +24 -0
- data/spec/ssl_spec.rb +20 -0
- data/spec/stallion.rb +7 -63
- metadata +59 -39
- data/examples/websocket-handler.rb +0 -28
- data/examples/websocket-server.rb +0 -8
- data/ext/buffer/em_buffer.c +0 -639
- data/ext/buffer/extconf.rb +0 -53
- data/ext/http11_client/ext_help.h +0 -14
- data/ext/http11_client/extconf.rb +0 -6
- data/ext/http11_client/http11_client.c +0 -328
- data/ext/http11_client/http11_parser.c +0 -418
- data/ext/http11_client/http11_parser.h +0 -48
- data/ext/http11_client/http11_parser.rl +0 -170
- data/lib/em-http/mock.rb +0 -137
- data/spec/mock_spec.rb +0 -166
- data/spec/request_spec.rb +0 -1003
data/spec/request_spec.rb
DELETED
@@ -1,1003 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'stallion'
|
3
|
-
require 'stub_server'
|
4
|
-
|
5
|
-
describe EventMachine::HttpRequest do
|
6
|
-
|
7
|
-
def failed(http=nil)
|
8
|
-
EventMachine.stop
|
9
|
-
http ? fail(http.error) : fail
|
10
|
-
end
|
11
|
-
|
12
|
-
it "should fail GET on DNS timeout" do
|
13
|
-
EventMachine.run {
|
14
|
-
EventMachine.heartbeat_interval = 0.1
|
15
|
-
http = EventMachine::HttpRequest.new('http://127.1.1.1/').get :timeout => 1
|
16
|
-
http.callback { failed(http) }
|
17
|
-
http.errback {
|
18
|
-
http.response_header.status.should == 0
|
19
|
-
EventMachine.stop
|
20
|
-
}
|
21
|
-
}
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should fail GET on invalid host" do
|
25
|
-
EventMachine.run {
|
26
|
-
EventMachine.heartbeat_interval = 0.1
|
27
|
-
http = EventMachine::HttpRequest.new('http://somethinglocal/').get :timeout => 1
|
28
|
-
http.callback { failed(http) }
|
29
|
-
http.errback {
|
30
|
-
http.response_header.status.should == 0
|
31
|
-
http.error.should match(/unable to resolve server address/)
|
32
|
-
http.uri.to_s.should match('http://somethinglocal:80/')
|
33
|
-
EventMachine.stop
|
34
|
-
}
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
it "should raise error on invalid URL" do
|
39
|
-
EventMachine.run {
|
40
|
-
lambda {
|
41
|
-
EventMachine::HttpRequest.new('random?text').get
|
42
|
-
}.should raise_error
|
43
|
-
|
44
|
-
EM.stop
|
45
|
-
}
|
46
|
-
end
|
47
|
-
|
48
|
-
it "should succeed GET on missing path" do
|
49
|
-
EventMachine.run {
|
50
|
-
lambda {
|
51
|
-
EventMachine::HttpRequest.new('http://127.0.0.1:8080').get
|
52
|
-
}.should_not raise_error(ArgumentError)
|
53
|
-
|
54
|
-
EventMachine.stop
|
55
|
-
}
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should perform successfull GET" do
|
59
|
-
EventMachine.run {
|
60
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
61
|
-
|
62
|
-
http.errback { failed(http) }
|
63
|
-
http.callback {
|
64
|
-
http.response_header.status.should == 200
|
65
|
-
http.response.should match(/Hello/)
|
66
|
-
EventMachine.stop
|
67
|
-
}
|
68
|
-
}
|
69
|
-
end
|
70
|
-
|
71
|
-
context "host override" do
|
72
|
-
|
73
|
-
it "should accept optional host" do
|
74
|
-
EventMachine.run {
|
75
|
-
http = EventMachine::HttpRequest.new('http://google.com:8080/').get :host => '127.0.0.1'
|
76
|
-
|
77
|
-
http.errback { failed(http) }
|
78
|
-
http.callback {
|
79
|
-
http.response_header.status.should == 200
|
80
|
-
http.response.should match(/Hello/)
|
81
|
-
EventMachine.stop
|
82
|
-
}
|
83
|
-
}
|
84
|
-
end
|
85
|
-
|
86
|
-
it "should reset host on redirect" do
|
87
|
-
EventMachine.run {
|
88
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1, :host => '127.0.0.1'
|
89
|
-
|
90
|
-
http.errback { failed(http) }
|
91
|
-
http.callback {
|
92
|
-
http.response_header.status.should == 200
|
93
|
-
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
94
|
-
http.response.should == "compressed"
|
95
|
-
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
|
96
|
-
http.redirects.should == 1
|
97
|
-
|
98
|
-
EM.stop
|
99
|
-
}
|
100
|
-
}
|
101
|
-
end
|
102
|
-
|
103
|
-
it "should redirect with missing content-length" do
|
104
|
-
EventMachine.run {
|
105
|
-
@s = StubServer.new("HTTP/1.0 301 MOVED PERMANENTLY\r\nlocation: http://127.0.0.1:8080/redirect\r\n\r\n")
|
106
|
-
|
107
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get :redirects => 2
|
108
|
-
http.errback { failed(http) }
|
109
|
-
|
110
|
-
http.callback {
|
111
|
-
http.response_header.status.should == 200
|
112
|
-
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
113
|
-
http.response.should == "compressed"
|
114
|
-
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
|
115
|
-
http.redirects.should == 2
|
116
|
-
|
117
|
-
@s.stop
|
118
|
-
EM.stop
|
119
|
-
}
|
120
|
-
}
|
121
|
-
end
|
122
|
-
|
123
|
-
it "should follow redirects on HEAD method" do
|
124
|
-
EventMachine.run {
|
125
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/head').head :redirects => 1
|
126
|
-
http.errback { failed(http) }
|
127
|
-
http.callback {
|
128
|
-
http.response_header.status.should == 200
|
129
|
-
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
|
130
|
-
EM.stop
|
131
|
-
}
|
132
|
-
}
|
133
|
-
end
|
134
|
-
|
135
|
-
it "should follow redirects on HEAD method (external)" do
|
136
|
-
|
137
|
-
EventMachine.run {
|
138
|
-
http = EventMachine::HttpRequest.new('http://www.google.com/').head :redirects => 1
|
139
|
-
http.errback { failed(http) }
|
140
|
-
http.callback {
|
141
|
-
http.response_header.status.should == 200
|
142
|
-
EM.stop
|
143
|
-
}
|
144
|
-
}
|
145
|
-
end
|
146
|
-
|
147
|
-
end
|
148
|
-
|
149
|
-
it "should perform successfull GET with a URI passed as argument" do
|
150
|
-
EventMachine.run {
|
151
|
-
uri = URI.parse('http://127.0.0.1:8080/')
|
152
|
-
http = EventMachine::HttpRequest.new(uri).get
|
153
|
-
|
154
|
-
http.errback { failed(http) }
|
155
|
-
http.callback {
|
156
|
-
http.response_header.status.should == 200
|
157
|
-
http.response.should match(/Hello/)
|
158
|
-
EventMachine.stop
|
159
|
-
}
|
160
|
-
}
|
161
|
-
end
|
162
|
-
|
163
|
-
it "should perform successfull HEAD with a URI passed as argument" do
|
164
|
-
EventMachine.run {
|
165
|
-
uri = URI.parse('http://127.0.0.1:8080/')
|
166
|
-
http = EventMachine::HttpRequest.new(uri).head
|
167
|
-
|
168
|
-
http.errback { failed(http) }
|
169
|
-
http.callback {
|
170
|
-
http.response_header.status.should == 200
|
171
|
-
http.response.should == ""
|
172
|
-
EventMachine.stop
|
173
|
-
}
|
174
|
-
}
|
175
|
-
end
|
176
|
-
|
177
|
-
# should be no different than a GET
|
178
|
-
it "should perform successfull DELETE with a URI passed as argument" do
|
179
|
-
EventMachine.run {
|
180
|
-
uri = URI.parse('http://127.0.0.1:8080/')
|
181
|
-
http = EventMachine::HttpRequest.new(uri).delete
|
182
|
-
|
183
|
-
http.errback { failed(http) }
|
184
|
-
http.callback {
|
185
|
-
http.response_header.status.should == 200
|
186
|
-
http.response.should == ""
|
187
|
-
EventMachine.stop
|
188
|
-
}
|
189
|
-
}
|
190
|
-
end
|
191
|
-
|
192
|
-
it "should return 404 on invalid path" do
|
193
|
-
EventMachine.run {
|
194
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get
|
195
|
-
|
196
|
-
http.errback { failed(http) }
|
197
|
-
http.callback {
|
198
|
-
http.response_header.status.should == 404
|
199
|
-
EventMachine.stop
|
200
|
-
}
|
201
|
-
}
|
202
|
-
end
|
203
|
-
|
204
|
-
it "should build query parameters from Hash" do
|
205
|
-
EventMachine.run {
|
206
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => {:q => 'test'}
|
207
|
-
|
208
|
-
http.errback { failed(http) }
|
209
|
-
http.callback {
|
210
|
-
http.response_header.status.should == 200
|
211
|
-
http.response.should match(/test/)
|
212
|
-
EventMachine.stop
|
213
|
-
}
|
214
|
-
}
|
215
|
-
end
|
216
|
-
|
217
|
-
it "should pass query parameters string" do
|
218
|
-
EventMachine.run {
|
219
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => "q=test"
|
220
|
-
|
221
|
-
http.errback { failed(http) }
|
222
|
-
http.callback {
|
223
|
-
http.response_header.status.should == 200
|
224
|
-
http.response.should match(/test/)
|
225
|
-
EventMachine.stop
|
226
|
-
}
|
227
|
-
}
|
228
|
-
end
|
229
|
-
|
230
|
-
it "should encode an array of query parameters" do
|
231
|
-
EventMachine.run {
|
232
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get :query => {:hash => ['value1', 'value2']}
|
233
|
-
|
234
|
-
http.errback { failed(http) }
|
235
|
-
http.callback {
|
236
|
-
http.response_header.status.should == 200
|
237
|
-
http.response.should match(/hash\[\]=value1&hash\[\]=value2/)
|
238
|
-
EventMachine.stop
|
239
|
-
}
|
240
|
-
}
|
241
|
-
end
|
242
|
-
|
243
|
-
# should be no different than a POST
|
244
|
-
it "should perform successfull PUT" do
|
245
|
-
EventMachine.run {
|
246
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').put :body => "data"
|
247
|
-
|
248
|
-
http.errback { failed(http) }
|
249
|
-
http.callback {
|
250
|
-
http.response_header.status.should == 200
|
251
|
-
http.response.should match(/data/)
|
252
|
-
EventMachine.stop
|
253
|
-
}
|
254
|
-
}
|
255
|
-
end
|
256
|
-
|
257
|
-
it "should perform successfull POST" do
|
258
|
-
EventMachine.run {
|
259
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => "data"
|
260
|
-
|
261
|
-
http.errback { failed(http) }
|
262
|
-
http.callback {
|
263
|
-
http.response_header.status.should == 200
|
264
|
-
http.response.should match(/data/)
|
265
|
-
EventMachine.stop
|
266
|
-
}
|
267
|
-
}
|
268
|
-
end
|
269
|
-
|
270
|
-
it "should escape body on POST" do
|
271
|
-
EventMachine.run {
|
272
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {:stuff => 'string&string'}
|
273
|
-
|
274
|
-
http.errback { failed(http) }
|
275
|
-
http.callback {
|
276
|
-
http.response_header.status.should == 200
|
277
|
-
http.response.should == "stuff=string%26string"
|
278
|
-
EventMachine.stop
|
279
|
-
}
|
280
|
-
}
|
281
|
-
end
|
282
|
-
|
283
|
-
it "should perform successfull POST with Ruby Hash/Array as params" do
|
284
|
-
EventMachine.run {
|
285
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {"key1" => 1, "key2" => [2,3]}
|
286
|
-
|
287
|
-
http.errback { failed(http) }
|
288
|
-
http.callback {
|
289
|
-
http.response_header.status.should == 200
|
290
|
-
|
291
|
-
http.response.should match(/key1=1&key2\[0\]=2&key2\[1\]=3/)
|
292
|
-
EventMachine.stop
|
293
|
-
}
|
294
|
-
}
|
295
|
-
end
|
296
|
-
|
297
|
-
it "should perform successfull POST with Ruby Hash/Array as params and with the correct content length" do
|
298
|
-
EventMachine.run {
|
299
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_length').post :body => {"key1" => "data1"}
|
300
|
-
|
301
|
-
http.errback { failed(http) }
|
302
|
-
http.callback {
|
303
|
-
http.response_header.status.should == 200
|
304
|
-
|
305
|
-
http.response.to_i.should == 10
|
306
|
-
EventMachine.stop
|
307
|
-
}
|
308
|
-
}
|
309
|
-
end
|
310
|
-
|
311
|
-
it "should perform successfull GET with custom header" do
|
312
|
-
EventMachine.run {
|
313
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'if-none-match' => 'evar!'}
|
314
|
-
|
315
|
-
http.errback { failed(http) }
|
316
|
-
http.callback {
|
317
|
-
http.response_header.status.should == 304
|
318
|
-
EventMachine.stop
|
319
|
-
}
|
320
|
-
}
|
321
|
-
end
|
322
|
-
|
323
|
-
it "should perform a streaming GET" do
|
324
|
-
EventMachine.run {
|
325
|
-
|
326
|
-
# digg.com uses chunked encoding
|
327
|
-
http = EventMachine::HttpRequest.new('http://digg.com/news').get
|
328
|
-
|
329
|
-
http.errback { failed(http) }
|
330
|
-
http.callback {
|
331
|
-
http.response_header.status.should == 200
|
332
|
-
EventMachine.stop
|
333
|
-
}
|
334
|
-
}
|
335
|
-
end
|
336
|
-
|
337
|
-
it "should perform basic auth" do
|
338
|
-
EventMachine.run {
|
339
|
-
|
340
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'authorization' => ['user', 'pass']}
|
341
|
-
|
342
|
-
http.errback { failed(http) }
|
343
|
-
http.callback {
|
344
|
-
http.response_header.status.should == 200
|
345
|
-
EventMachine.stop
|
346
|
-
}
|
347
|
-
}
|
348
|
-
end
|
349
|
-
|
350
|
-
it "should send proper OAuth auth header" do
|
351
|
-
EventMachine.run {
|
352
|
-
oauth_header = 'OAuth oauth_nonce="oqwgSYFUD87MHmJJDv7bQqOF2EPnVus7Wkqj5duNByU", b=c, d=e'
|
353
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/oauth_auth').get :head => {'authorization' => oauth_header}
|
354
|
-
|
355
|
-
http.errback { failed(http) }
|
356
|
-
http.callback {
|
357
|
-
http.response_header.status.should == 200
|
358
|
-
http.response.should == oauth_header
|
359
|
-
EventMachine.stop
|
360
|
-
}
|
361
|
-
}
|
362
|
-
end
|
363
|
-
|
364
|
-
context "keepalive" do
|
365
|
-
it "should default to non-keepalive" do
|
366
|
-
EventMachine.run {
|
367
|
-
headers = {'If-Modified-Since' => 'Thu, 05 Aug 2010 22:54:44 GMT'}
|
368
|
-
http = EventMachine::HttpRequest.new('http://www.google.com/images/logos/ps_logo2.png').get :head => headers
|
369
|
-
|
370
|
-
http.errback { fail }
|
371
|
-
start = Time.now.to_i
|
372
|
-
http.callback {
|
373
|
-
(start - Time.now.to_i).should be_within(1).of(0)
|
374
|
-
EventMachine.stop
|
375
|
-
}
|
376
|
-
}
|
377
|
-
end
|
378
|
-
|
379
|
-
it "should work with keep-alive servers" do
|
380
|
-
EventMachine.run {
|
381
|
-
http = EventMachine::HttpRequest.new('http://mexicodiario.com/touch.public.json.php').get :keepalive => true
|
382
|
-
|
383
|
-
http.errback { failed(http) }
|
384
|
-
http.callback {
|
385
|
-
http.response_header.status.should == 200
|
386
|
-
EventMachine.stop
|
387
|
-
}
|
388
|
-
}
|
389
|
-
end
|
390
|
-
end
|
391
|
-
|
392
|
-
it "should return ETag and Last-Modified headers" do
|
393
|
-
EventMachine.run {
|
394
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get
|
395
|
-
|
396
|
-
http.errback { failed(http) }
|
397
|
-
http.callback {
|
398
|
-
http.response_header.status.should == 200
|
399
|
-
http.response_header.etag.should match('abcdefg')
|
400
|
-
http.response_header.last_modified.should match('Fri, 13 Aug 2010 17:31:21 GMT')
|
401
|
-
EventMachine.stop
|
402
|
-
}
|
403
|
-
}
|
404
|
-
end
|
405
|
-
|
406
|
-
it "should detect deflate encoding" do
|
407
|
-
EventMachine.run {
|
408
|
-
|
409
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate"}
|
410
|
-
|
411
|
-
http.errback { failed(http) }
|
412
|
-
http.callback {
|
413
|
-
http.response_header.status.should == 200
|
414
|
-
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
415
|
-
http.response.should == "compressed"
|
416
|
-
|
417
|
-
EventMachine.stop
|
418
|
-
}
|
419
|
-
}
|
420
|
-
end
|
421
|
-
|
422
|
-
it "should detect gzip encoding" do
|
423
|
-
EventMachine.run {
|
424
|
-
|
425
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/gzip').get :head => {"accept-encoding" => "gzip, compressed"}
|
426
|
-
|
427
|
-
http.errback { failed(http) }
|
428
|
-
http.callback {
|
429
|
-
http.response_header.status.should == 200
|
430
|
-
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
431
|
-
http.response.should == "compressed"
|
432
|
-
|
433
|
-
EventMachine.stop
|
434
|
-
}
|
435
|
-
}
|
436
|
-
end
|
437
|
-
|
438
|
-
it "should timeout after 1 second" do
|
439
|
-
EventMachine.run {
|
440
|
-
t = Time.now.to_i
|
441
|
-
EventMachine.heartbeat_interval = 0.1
|
442
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/timeout').get :timeout => 1
|
443
|
-
|
444
|
-
http.errback {
|
445
|
-
(Time.now.to_i - t).should <= 5
|
446
|
-
EventMachine.stop
|
447
|
-
}
|
448
|
-
http.callback { failed(http) }
|
449
|
-
}
|
450
|
-
end
|
451
|
-
|
452
|
-
context "redirect" do
|
453
|
-
it "should report last_effective_url" do
|
454
|
-
EventMachine.run {
|
455
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
456
|
-
http.errback { failed(http) }
|
457
|
-
http.callback {
|
458
|
-
http.response_header.status.should == 200
|
459
|
-
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
|
460
|
-
|
461
|
-
EM.stop
|
462
|
-
}
|
463
|
-
}
|
464
|
-
end
|
465
|
-
|
466
|
-
it "should follow location redirects" do
|
467
|
-
EventMachine.run {
|
468
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1
|
469
|
-
http.errback { failed(http) }
|
470
|
-
http.callback {
|
471
|
-
http.response_header.status.should == 200
|
472
|
-
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
473
|
-
http.response.should == "compressed"
|
474
|
-
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
|
475
|
-
http.redirects.should == 1
|
476
|
-
|
477
|
-
EM.stop
|
478
|
-
}
|
479
|
-
}
|
480
|
-
end
|
481
|
-
|
482
|
-
it "should default to 0 redirects" do
|
483
|
-
EventMachine.run {
|
484
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get
|
485
|
-
http.errback { failed(http) }
|
486
|
-
http.callback {
|
487
|
-
http.response_header.status.should == 301
|
488
|
-
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
|
489
|
-
http.redirects.should == 0
|
490
|
-
|
491
|
-
EM.stop
|
492
|
-
}
|
493
|
-
}
|
494
|
-
end
|
495
|
-
|
496
|
-
it "should not invoke redirect logic on failed(http) connections" do
|
497
|
-
EventMachine.run {
|
498
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get :timeout => 0.1, :redirects => 5
|
499
|
-
http.callback { failed(http) }
|
500
|
-
http.errback {
|
501
|
-
http.redirects.should == 0
|
502
|
-
EM.stop
|
503
|
-
}
|
504
|
-
}
|
505
|
-
end
|
506
|
-
|
507
|
-
it "should normalize redirect urls" do
|
508
|
-
EventMachine.run {
|
509
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/bad').get :redirects => 1
|
510
|
-
http.errback { failed(http) }
|
511
|
-
http.callback {
|
512
|
-
http.last_effective_url.to_s.should match('http://127.0.0.1:8080/')
|
513
|
-
http.response.should match('Hello, World!')
|
514
|
-
EM.stop
|
515
|
-
}
|
516
|
-
}
|
517
|
-
end
|
518
|
-
|
519
|
-
it "should fail gracefully on a missing host in absolute Location header" do
|
520
|
-
EventMachine.run {
|
521
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/nohost').get :redirects => 1
|
522
|
-
http.callback { failed(http) }
|
523
|
-
http.errback {
|
524
|
-
http.error.should == 'Location header format error'
|
525
|
-
EM.stop
|
526
|
-
}
|
527
|
-
}
|
528
|
-
end
|
529
|
-
|
530
|
-
it "should fail gracefully on an invalid host in Location header" do
|
531
|
-
EventMachine.run {
|
532
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/badhost').get :redirects => 1
|
533
|
-
http.callback { failed(http) }
|
534
|
-
http.errback {
|
535
|
-
http.error.should == 'unable to resolve server address'
|
536
|
-
EM.stop
|
537
|
-
}
|
538
|
-
}
|
539
|
-
end
|
540
|
-
end
|
541
|
-
|
542
|
-
it "should optionally pass the response body progressively" do
|
543
|
-
EventMachine.run {
|
544
|
-
body = ''
|
545
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
546
|
-
|
547
|
-
http.errback { failed(http) }
|
548
|
-
http.stream { |chunk| body += chunk }
|
549
|
-
|
550
|
-
http.callback {
|
551
|
-
http.response_header.status.should == 200
|
552
|
-
http.response.should == ''
|
553
|
-
body.should match(/Hello/)
|
554
|
-
EventMachine.stop
|
555
|
-
}
|
556
|
-
}
|
557
|
-
end
|
558
|
-
|
559
|
-
context "optional header callback" do
|
560
|
-
it "should optionally pass the response headers" do
|
561
|
-
EventMachine.run {
|
562
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
563
|
-
|
564
|
-
http.errback { failed(http) }
|
565
|
-
http.headers { |hash|
|
566
|
-
hash.should be_an_kind_of Hash
|
567
|
-
hash.should include 'CONNECTION'
|
568
|
-
hash.should include 'CONTENT_LENGTH'
|
569
|
-
}
|
570
|
-
|
571
|
-
http.callback {
|
572
|
-
http.response_header.status.should == 200
|
573
|
-
http.response.should match(/Hello/)
|
574
|
-
EventMachine.stop
|
575
|
-
}
|
576
|
-
}
|
577
|
-
end
|
578
|
-
|
579
|
-
it "should allow to terminate current connection from header callback" do
|
580
|
-
EventMachine.run {
|
581
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
582
|
-
|
583
|
-
http.callback { failed(http) }
|
584
|
-
http.headers { |hash|
|
585
|
-
hash.should be_an_kind_of Hash
|
586
|
-
hash.should include 'CONNECTION'
|
587
|
-
hash.should include 'CONTENT_LENGTH'
|
588
|
-
|
589
|
-
http.close('header callback terminated connection')
|
590
|
-
}
|
591
|
-
|
592
|
-
http.errback { |e|
|
593
|
-
http.response_header.status.should == 200
|
594
|
-
http.error.should == 'header callback terminated connection'
|
595
|
-
http.response.should == ''
|
596
|
-
EventMachine.stop
|
597
|
-
}
|
598
|
-
}
|
599
|
-
end
|
600
|
-
end
|
601
|
-
|
602
|
-
it "should optionally pass the deflate-encoded response body progressively" do
|
603
|
-
EventMachine.run {
|
604
|
-
body = ''
|
605
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate, compressed"}
|
606
|
-
|
607
|
-
http.errback { failed(http) }
|
608
|
-
http.stream { |chunk| body += chunk }
|
609
|
-
|
610
|
-
http.callback {
|
611
|
-
http.response_header.status.should == 200
|
612
|
-
http.response_header["CONTENT_ENCODING"].should == "deflate"
|
613
|
-
http.response.should == ''
|
614
|
-
body.should == "compressed"
|
615
|
-
EventMachine.stop
|
616
|
-
}
|
617
|
-
}
|
618
|
-
end
|
619
|
-
|
620
|
-
it "should initiate SSL/TLS on HTTPS connections" do
|
621
|
-
EventMachine.run {
|
622
|
-
http = EventMachine::HttpRequest.new('https://mail.google.com:443/mail/').get
|
623
|
-
|
624
|
-
http.errback { failed(http) }
|
625
|
-
http.callback {
|
626
|
-
http.response_header.status.should == 302
|
627
|
-
EventMachine.stop
|
628
|
-
}
|
629
|
-
}
|
630
|
-
end
|
631
|
-
|
632
|
-
it "should accept & return cookie header to user" do
|
633
|
-
EventMachine.run {
|
634
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/set_cookie').get
|
635
|
-
|
636
|
-
http.errback { failed(http) }
|
637
|
-
http.callback {
|
638
|
-
http.response_header.status.should == 200
|
639
|
-
http.response_header.cookie.should == "id=1; expires=Tue, 09-Aug-2011 17:53:39 GMT; path=/;"
|
640
|
-
EventMachine.stop
|
641
|
-
}
|
642
|
-
}
|
643
|
-
end
|
644
|
-
|
645
|
-
it "should pass cookie header to server from string" do
|
646
|
-
EventMachine.run {
|
647
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => 'id=2;'}
|
648
|
-
|
649
|
-
http.errback { failed(http) }
|
650
|
-
http.callback {
|
651
|
-
http.response.should == "id=2;"
|
652
|
-
EventMachine.stop
|
653
|
-
}
|
654
|
-
}
|
655
|
-
end
|
656
|
-
|
657
|
-
it "should pass cookie header to server from Hash" do
|
658
|
-
EventMachine.run {
|
659
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => {'id' => 2}}
|
660
|
-
|
661
|
-
http.errback { failed(http) }
|
662
|
-
http.callback {
|
663
|
-
http.response.should == "id=2;"
|
664
|
-
EventMachine.stop
|
665
|
-
}
|
666
|
-
}
|
667
|
-
end
|
668
|
-
|
669
|
-
context "when talking to a stub HTTP/1.0 server" do
|
670
|
-
it "should get the body without Content-Length" do
|
671
|
-
EventMachine.run {
|
672
|
-
@s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo")
|
673
|
-
|
674
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
675
|
-
http.errback { failed(http) }
|
676
|
-
http.callback {
|
677
|
-
http.response.should match(/Foo/)
|
678
|
-
http.response_header['CONTENT_LENGTH'].should_not == 0
|
679
|
-
|
680
|
-
@s.stop
|
681
|
-
EventMachine.stop
|
682
|
-
}
|
683
|
-
}
|
684
|
-
end
|
685
|
-
|
686
|
-
it "should work with \\n instead of \\r\\n" do
|
687
|
-
EventMachine.run {
|
688
|
-
@s = StubServer.new("HTTP/1.0 200 OK\nContent-Type: text/plain\nContent-Length: 3\nConnection: close\n\nFoo")
|
689
|
-
|
690
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get
|
691
|
-
http.errback { failed(http) }
|
692
|
-
http.callback {
|
693
|
-
http.response_header.status.should == 200
|
694
|
-
http.response_header['CONTENT_TYPE'].should == 'text/plain'
|
695
|
-
http.response.should match(/Foo/)
|
696
|
-
|
697
|
-
@s.stop
|
698
|
-
EventMachine.stop
|
699
|
-
}
|
700
|
-
}
|
701
|
-
end
|
702
|
-
end
|
703
|
-
|
704
|
-
context "body content-type encoding" do
|
705
|
-
it "should not set content type on string in body" do
|
706
|
-
EventMachine.run {
|
707
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post :body => "data"
|
708
|
-
|
709
|
-
http.errback { failed(http) }
|
710
|
-
http.callback {
|
711
|
-
http.response_header.status.should == 200
|
712
|
-
http.response.should be_empty
|
713
|
-
EventMachine.stop
|
714
|
-
}
|
715
|
-
}
|
716
|
-
end
|
717
|
-
|
718
|
-
it "should set content-type automatically when passed a ruby hash/array for body" do
|
719
|
-
EventMachine.run {
|
720
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post :body => {:a => :b}
|
721
|
-
|
722
|
-
http.errback { failed(http) }
|
723
|
-
http.callback {
|
724
|
-
http.response_header.status.should == 200
|
725
|
-
http.response.should match("application/x-www-form-urlencoded")
|
726
|
-
EventMachine.stop
|
727
|
-
}
|
728
|
-
}
|
729
|
-
end
|
730
|
-
|
731
|
-
it "should not override content-type when passing in ruby hash/array for body" do
|
732
|
-
EventMachine.run {
|
733
|
-
ct = 'text; charset=utf-8'
|
734
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({
|
735
|
-
:body => {:a => :b}, :head => {'content-type' => ct}})
|
736
|
-
|
737
|
-
http.errback { failed(http) }
|
738
|
-
http.callback {
|
739
|
-
http.response_header.status.should == 200
|
740
|
-
http.content_charset.should == Encoding.find('utf-8')
|
741
|
-
http.response_header["CONTENT_TYPE"].should == ct
|
742
|
-
EventMachine.stop
|
743
|
-
}
|
744
|
-
}
|
745
|
-
end
|
746
|
-
|
747
|
-
it "should default to external encoding on invalid encoding" do
|
748
|
-
EventMachine.run {
|
749
|
-
ct = 'text/html; charset=utf-8lias'
|
750
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({
|
751
|
-
:body => {:a => :b}, :head => {'content-type' => ct}})
|
752
|
-
|
753
|
-
http.errback { failed(http) }
|
754
|
-
http.callback {
|
755
|
-
http.response_header.status.should == 200
|
756
|
-
http.content_charset.should == Encoding.find('utf-8')
|
757
|
-
http.response_header["CONTENT_TYPE"].should == ct
|
758
|
-
EventMachine.stop
|
759
|
-
}
|
760
|
-
}
|
761
|
-
end
|
762
|
-
|
763
|
-
it "should processed escaped content-type" do
|
764
|
-
EventMachine.run {
|
765
|
-
ct = "text/html; charset=\"ISO-8859-4\""
|
766
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({
|
767
|
-
:body => {:a => :b}, :head => {'content-type' => ct}})
|
768
|
-
|
769
|
-
http.errback { failed(http) }
|
770
|
-
http.callback {
|
771
|
-
http.response_header.status.should == 200
|
772
|
-
http.content_charset.should == Encoding.find('ISO-8859-4')
|
773
|
-
http.response_header["CONTENT_TYPE"].should == ct
|
774
|
-
EventMachine.stop
|
775
|
-
}
|
776
|
-
}
|
777
|
-
end
|
778
|
-
|
779
|
-
end
|
780
|
-
|
781
|
-
it "should complete a Location: with a relative path" do
|
782
|
-
EventMachine.run {
|
783
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/relative-location').get
|
784
|
-
|
785
|
-
http.errback { failed(http) }
|
786
|
-
http.callback {
|
787
|
-
http.response_header['LOCATION'].should == 'http://127.0.0.1:8080/forwarded'
|
788
|
-
EventMachine.stop
|
789
|
-
}
|
790
|
-
}
|
791
|
-
end
|
792
|
-
|
793
|
-
it "should stream a file off disk" do
|
794
|
-
EventMachine.run {
|
795
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :file => 'spec/fixtures/google.ca'
|
796
|
-
|
797
|
-
http.errback { failed(http) }
|
798
|
-
http.callback {
|
799
|
-
http.response.should match('google')
|
800
|
-
EventMachine.stop
|
801
|
-
}
|
802
|
-
}
|
803
|
-
end
|
804
|
-
|
805
|
-
it 'should let you pass a block to be called once the client is created' do
|
806
|
-
client = nil
|
807
|
-
EventMachine.run {
|
808
|
-
request = EventMachine::HttpRequest.new('http://127.0.0.1:8080/')
|
809
|
-
http = request.post { |c|
|
810
|
-
c.options[:body] = {:callback_run => 'yes'}
|
811
|
-
client = c
|
812
|
-
}
|
813
|
-
http.errback { failed(http) }
|
814
|
-
http.callback {
|
815
|
-
client.should be_kind_of(EventMachine::HttpClient)
|
816
|
-
http.response_header.status.should == 200
|
817
|
-
http.response.should match(/callback_run=yes/)
|
818
|
-
EventMachine.stop
|
819
|
-
}
|
820
|
-
}
|
821
|
-
end
|
822
|
-
|
823
|
-
it "should retrieve multiple cookies" do
|
824
|
-
EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
|
825
|
-
EventMachine.run {
|
826
|
-
|
827
|
-
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
828
|
-
http.errback { fail }
|
829
|
-
http.callback {
|
830
|
-
c1 = "PREF=ID=11955ae9690fd292:TM=1281823106:LM=1281823106:S=wHdloFqGQ_OLSE92; expires=Mon, 13-Aug-2012 21:58:26 GMT; path=/; domain=.google.ca"
|
831
|
-
c2 = "NID=37=USTdOsxOSMbLjphkJ3S5Ueua3Yc23COXuK_pbztcHx7JoyhomwQySrvebCf3_u8eyrBiLWssVzaZcEOiKGEJbNdy8lRhnq_mfrdz693LaMjNPh__ccW4sgn1ZO6nQltE; expires=Sun, 13-Feb-2011 21:58:26 GMT; path=/; domain=.google.ca; HttpOnly"
|
832
|
-
http.response_header.cookie.should == [c1, c2]
|
833
|
-
|
834
|
-
EventMachine.stop
|
835
|
-
}
|
836
|
-
}
|
837
|
-
|
838
|
-
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
839
|
-
end
|
840
|
-
|
841
|
-
context "connections via" do
|
842
|
-
context "direct proxy" do
|
843
|
-
it "should default to skip CONNECT" do
|
844
|
-
EventMachine.run {
|
845
|
-
|
846
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => {
|
847
|
-
:host => '127.0.0.1', :port => 8083
|
848
|
-
}
|
849
|
-
|
850
|
-
http.errback { failed(http) }
|
851
|
-
http.callback {
|
852
|
-
http.response_header.status.should == 200
|
853
|
-
http.response.should match('test')
|
854
|
-
EventMachine.stop
|
855
|
-
}
|
856
|
-
}
|
857
|
-
end
|
858
|
-
|
859
|
-
it "should send absolute URIs to the proxy server" do
|
860
|
-
EventMachine.run {
|
861
|
-
|
862
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => {
|
863
|
-
:host => '127.0.0.1', :port => 8083
|
864
|
-
}
|
865
|
-
|
866
|
-
http.errback { failed(http) }
|
867
|
-
http.callback {
|
868
|
-
http.response_header.status.should == 200
|
869
|
-
# The test proxy server gives the requested uri back in this header
|
870
|
-
http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8080/?q=test'
|
871
|
-
http.response_header['X_THE_REQUESTED_URI'].should_not == '/?q=test'
|
872
|
-
http.response.should match('test')
|
873
|
-
EventMachine.stop
|
874
|
-
}
|
875
|
-
}
|
876
|
-
end
|
877
|
-
|
878
|
-
it "should include query parameters specified in the options" do
|
879
|
-
EventMachine.run {
|
880
|
-
|
881
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(
|
882
|
-
:proxy => { :host => '127.0.0.1', :port => 8083 },
|
883
|
-
:query => { 'q' => 'test' }
|
884
|
-
)
|
885
|
-
|
886
|
-
http.errback { failed(http) }
|
887
|
-
http.callback {
|
888
|
-
http.response_header.status.should == 200
|
889
|
-
http.response.should match('test')
|
890
|
-
EventMachine.stop
|
891
|
-
}
|
892
|
-
}
|
893
|
-
end
|
894
|
-
end
|
895
|
-
|
896
|
-
context "CONNECT proxy" do
|
897
|
-
it "should work with CONNECT proxy servers" do
|
898
|
-
EventMachine.run {
|
899
|
-
|
900
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({
|
901
|
-
:proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true}
|
902
|
-
})
|
903
|
-
|
904
|
-
http.errback { failed(http) }
|
905
|
-
http.callback {
|
906
|
-
http.response_header.status.should == 200
|
907
|
-
http.response.should == 'Hello, World!'
|
908
|
-
EventMachine.stop
|
909
|
-
}
|
910
|
-
}
|
911
|
-
end
|
912
|
-
|
913
|
-
it "should proxy POST data" do
|
914
|
-
EventMachine.run {
|
915
|
-
|
916
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({
|
917
|
-
:body => "data", :proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true}
|
918
|
-
})
|
919
|
-
|
920
|
-
http.errback { failed(http) }
|
921
|
-
http.callback {
|
922
|
-
http.response_header.status.should == 200
|
923
|
-
http.response.should match(/data/)
|
924
|
-
EventMachine.stop
|
925
|
-
}
|
926
|
-
}
|
927
|
-
end
|
928
|
-
end
|
929
|
-
end
|
930
|
-
|
931
|
-
context "websocket connection" do
|
932
|
-
# Spec: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55
|
933
|
-
#
|
934
|
-
# ws.onopen = http.callback
|
935
|
-
# ws.onmessage = http.stream { |msg| }
|
936
|
-
# ws.errback = no connection
|
937
|
-
#
|
938
|
-
|
939
|
-
it "should invoke errback on failed upgrade" do
|
940
|
-
EventMachine.run {
|
941
|
-
http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0
|
942
|
-
|
943
|
-
http.callback { failed(http) }
|
944
|
-
http.errback {
|
945
|
-
http.response_header.status.should == 200
|
946
|
-
EventMachine.stop
|
947
|
-
}
|
948
|
-
}
|
949
|
-
end
|
950
|
-
|
951
|
-
it "should complete websocket handshake and transfer data from client to server and back" do
|
952
|
-
EventMachine.run {
|
953
|
-
MSG = "hello bi-directional data exchange"
|
954
|
-
|
955
|
-
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws|
|
956
|
-
ws.onmessage {|msg| ws.send msg}
|
957
|
-
end
|
958
|
-
|
959
|
-
http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 1
|
960
|
-
http.errback { failed(http) }
|
961
|
-
http.callback {
|
962
|
-
http.response_header.status.should == 101
|
963
|
-
http.response_header['CONNECTION'].should match(/Upgrade/)
|
964
|
-
http.response_header['UPGRADE'].should match(/WebSocket/)
|
965
|
-
|
966
|
-
# push should only be invoked after handshake is complete
|
967
|
-
http.send(MSG)
|
968
|
-
}
|
969
|
-
|
970
|
-
http.stream { |chunk|
|
971
|
-
chunk.should == MSG
|
972
|
-
EventMachine.stop
|
973
|
-
}
|
974
|
-
}
|
975
|
-
end
|
976
|
-
|
977
|
-
it "should split multiple messages from websocket server into separate stream callbacks" do
|
978
|
-
EM.run do
|
979
|
-
messages = %w[1 2]
|
980
|
-
recieved = []
|
981
|
-
|
982
|
-
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws|
|
983
|
-
ws.onopen {
|
984
|
-
ws.send messages[0]
|
985
|
-
ws.send messages[1]
|
986
|
-
}
|
987
|
-
end
|
988
|
-
|
989
|
-
EventMachine.add_timer(0.1) do
|
990
|
-
http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 0
|
991
|
-
http.errback { failed(http) }
|
992
|
-
http.callback { http.response_header.status.should == 101 }
|
993
|
-
http.stream {|msg|
|
994
|
-
msg.should == messages[recieved.size]
|
995
|
-
recieved.push msg
|
996
|
-
|
997
|
-
EventMachine.stop if recieved.size == messages.size
|
998
|
-
}
|
999
|
-
end
|
1000
|
-
end
|
1001
|
-
end
|
1002
|
-
end
|
1003
|
-
end
|