ably-em-http-request 1.1.8

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/.github/workflows/ci.yml +22 -0
  4. data/.gitignore +9 -0
  5. data/.rspec +0 -0
  6. data/Changelog.md +78 -0
  7. data/Gemfile +14 -0
  8. data/LICENSE +21 -0
  9. data/README.md +66 -0
  10. data/Rakefile +10 -0
  11. data/ably-em-http-request.gemspec +33 -0
  12. data/benchmarks/clients.rb +170 -0
  13. data/benchmarks/em-excon.rb +87 -0
  14. data/benchmarks/em-profile.gif +0 -0
  15. data/benchmarks/em-profile.txt +65 -0
  16. data/benchmarks/server.rb +48 -0
  17. data/examples/.gitignore +1 -0
  18. data/examples/digest_auth/client.rb +25 -0
  19. data/examples/digest_auth/server.rb +28 -0
  20. data/examples/fetch.rb +30 -0
  21. data/examples/fibered-http.rb +51 -0
  22. data/examples/multi.rb +25 -0
  23. data/examples/oauth-tweet.rb +35 -0
  24. data/examples/socks5.rb +23 -0
  25. data/lib/em/io_streamer.rb +51 -0
  26. data/lib/em-http/client.rb +343 -0
  27. data/lib/em-http/core_ext/bytesize.rb +6 -0
  28. data/lib/em-http/decoders.rb +252 -0
  29. data/lib/em-http/http_client_options.rb +51 -0
  30. data/lib/em-http/http_connection.rb +408 -0
  31. data/lib/em-http/http_connection_options.rb +72 -0
  32. data/lib/em-http/http_encoding.rb +151 -0
  33. data/lib/em-http/http_header.rb +85 -0
  34. data/lib/em-http/http_status_codes.rb +59 -0
  35. data/lib/em-http/middleware/digest_auth.rb +114 -0
  36. data/lib/em-http/middleware/json_response.rb +17 -0
  37. data/lib/em-http/middleware/oauth.rb +42 -0
  38. data/lib/em-http/middleware/oauth2.rb +30 -0
  39. data/lib/em-http/multi.rb +59 -0
  40. data/lib/em-http/request.rb +25 -0
  41. data/lib/em-http/version.rb +7 -0
  42. data/lib/em-http-request.rb +1 -0
  43. data/lib/em-http.rb +20 -0
  44. data/spec/client_fiber_spec.rb +23 -0
  45. data/spec/client_spec.rb +1000 -0
  46. data/spec/digest_auth_spec.rb +48 -0
  47. data/spec/dns_spec.rb +41 -0
  48. data/spec/encoding_spec.rb +49 -0
  49. data/spec/external_spec.rb +146 -0
  50. data/spec/fixtures/google.ca +16 -0
  51. data/spec/fixtures/gzip-sample.gz +0 -0
  52. data/spec/gzip_spec.rb +91 -0
  53. data/spec/helper.rb +27 -0
  54. data/spec/http_proxy_spec.rb +268 -0
  55. data/spec/middleware/oauth2_spec.rb +15 -0
  56. data/spec/middleware_spec.rb +143 -0
  57. data/spec/multi_spec.rb +104 -0
  58. data/spec/pipelining_spec.rb +62 -0
  59. data/spec/redirect_spec.rb +430 -0
  60. data/spec/socksify_proxy_spec.rb +56 -0
  61. data/spec/spec_helper.rb +25 -0
  62. data/spec/ssl_spec.rb +67 -0
  63. data/spec/stallion.rb +334 -0
  64. data/spec/stub_server.rb +45 -0
  65. metadata +269 -0
@@ -0,0 +1,1000 @@
1
+ require 'helper'
2
+
3
+ describe EventMachine::AblyHttpRequest::HttpRequest do
4
+
5
+ def failed(http=nil)
6
+ EventMachine.stop
7
+ http ? fail(http.error) : fail
8
+ end
9
+
10
+ it "should perform successful GET" do
11
+ EventMachine.run {
12
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').get
13
+
14
+ http.errback { failed(http) }
15
+ http.callback {
16
+ http.response_header.status.should == 200
17
+ http.response.should match(/Hello/)
18
+ EventMachine.stop
19
+ }
20
+ }
21
+ end
22
+
23
+ it "should perform successful GET with a URI passed as argument" do
24
+ EventMachine.run {
25
+ uri = URI.parse('http://127.0.0.1:8090/')
26
+ http = EventMachine::AblyHttpRequest::HttpRequest.new(uri).get
27
+
28
+ http.errback { failed(http) }
29
+ http.callback {
30
+ http.response_header.status.should == 200
31
+ http.response.should match(/Hello/)
32
+ EventMachine.stop
33
+ }
34
+ }
35
+ end
36
+
37
+ it "should succeed GET on missing path" do
38
+ EventMachine.run {
39
+ lambda {
40
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090').get
41
+ http.callback {
42
+ http.response.should match(/Hello/)
43
+ EventMachine.stop
44
+ }
45
+ }.should_not raise_error
46
+
47
+ }
48
+ end
49
+
50
+ it "should raise error on invalid URL" do
51
+ EventMachine.run {
52
+ lambda {
53
+ EventMachine::AblyHttpRequest::HttpRequest.new('random?text').get
54
+ }.should raise_error(Addressable::URI::InvalidURIError)
55
+
56
+ EM.stop
57
+ }
58
+ end
59
+
60
+ it "should perform successful HEAD with a URI passed as argument" do
61
+ EventMachine.run {
62
+ uri = URI.parse('http://127.0.0.1:8090/')
63
+ http = EventMachine::AblyHttpRequest::HttpRequest.new(uri).head
64
+
65
+ http.errback { failed(http) }
66
+ http.callback {
67
+ http.response_header.status.should == 200
68
+ http.response.should == ""
69
+ EventMachine.stop
70
+ }
71
+ }
72
+ end
73
+
74
+ it "should perform successful DELETE with a URI passed as argument" do
75
+ EventMachine.run {
76
+ uri = URI.parse('http://127.0.0.1:8090/')
77
+ http = EventMachine::AblyHttpRequest::HttpRequest.new(uri).delete
78
+
79
+ http.errback { failed(http) }
80
+ http.callback {
81
+ http.response_header.status.should == 200
82
+ http.response.should == ""
83
+ EventMachine.stop
84
+ }
85
+ }
86
+ end
87
+
88
+ it "should return 404 on invalid path" do
89
+ EventMachine.run {
90
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/fail').get
91
+
92
+ http.errback { failed(http) }
93
+ http.callback {
94
+ http.response_header.status.should == 404
95
+ EventMachine.stop
96
+ }
97
+ }
98
+ end
99
+
100
+ it "should return HTTP reason" do
101
+ EventMachine.run {
102
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/fail').get
103
+
104
+ http.errback { failed(http) }
105
+ http.callback {
106
+ http.response_header.status.should == 404
107
+ http.response_header.http_reason.should == 'Not Found'
108
+ EventMachine.stop
109
+ }
110
+ }
111
+ end
112
+
113
+ it "should return HTTP reason 'unknown' on a non-standard status code" do
114
+ EventMachine.run {
115
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/fail_with_nonstandard_response').get
116
+
117
+ http.errback { failed(http) }
118
+ http.callback {
119
+ http.response_header.status.should == 420
120
+ http.response_header.http_reason.should == 'unknown'
121
+ EventMachine.stop
122
+ }
123
+ }
124
+ end
125
+
126
+ it "should build query parameters from Hash" do
127
+ EventMachine.run {
128
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').get :query => {:q => 'test'}
129
+
130
+ http.errback { failed(http) }
131
+ http.callback {
132
+ http.response_header.status.should == 200
133
+ http.response.should match(/test/)
134
+ EventMachine.stop
135
+ }
136
+ }
137
+ end
138
+
139
+ it "should pass query parameters string" do
140
+ EventMachine.run {
141
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').get :query => "q=test"
142
+
143
+ http.errback { failed(http) }
144
+ http.callback {
145
+ http.response_header.status.should == 200
146
+ http.response.should match(/test/)
147
+ EventMachine.stop
148
+ }
149
+ }
150
+ end
151
+
152
+ it "should encode an array of query parameters" do
153
+ EventMachine.run {
154
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_query').get :query => {:hash =>['value1','value2']}
155
+
156
+ http.errback { failed(http) }
157
+ http.callback {
158
+ http.response_header.status.should == 200
159
+ http.response.should match(/hash\[\]=value1&hash\[\]=value2/)
160
+ EventMachine.stop
161
+ }
162
+ }
163
+ end
164
+
165
+ it "should perform successful PUT" do
166
+ EventMachine.run {
167
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').put :body => "data"
168
+
169
+ http.errback { failed(http) }
170
+ http.callback {
171
+ http.response_header.status.should == 200
172
+ http.response.should match(/data/)
173
+ EventMachine.stop
174
+ }
175
+ }
176
+ end
177
+
178
+ it "should perform successful POST" do
179
+ EventMachine.run {
180
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').post :body => "data"
181
+
182
+ http.errback { failed(http) }
183
+ http.callback {
184
+ http.response_header.status.should == 200
185
+ http.response.should match(/data/)
186
+ EventMachine.stop
187
+ }
188
+ }
189
+ end
190
+
191
+ it "should perform successful PATCH" do
192
+ EventMachine.run {
193
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').patch :body => "data"
194
+
195
+ http.errback { failed(http) }
196
+ http.callback {
197
+ http.response_header.status.should == 200
198
+ http.response.should match(/data/)
199
+ EventMachine.stop
200
+ }
201
+ }
202
+ end
203
+
204
+ it "should escape body on POST" do
205
+ EventMachine.run {
206
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').post :body => {:stuff => 'string&string'}
207
+
208
+ http.errback { failed(http) }
209
+ http.callback {
210
+ http.response_header.status.should == 200
211
+ http.response.should == "stuff=string%26string"
212
+ EventMachine.stop
213
+ }
214
+ }
215
+ end
216
+
217
+ it "should perform successful POST with Ruby Hash/Array as params" do
218
+ EventMachine.run {
219
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').post :body => {"key1" => 1, "key2" => [2,3]}
220
+
221
+ http.errback { failed(http) }
222
+ http.callback {
223
+ http.response_header.status.should == 200
224
+
225
+ http.response.should match(/key1=1&key2\[0\]=2&key2\[1\]=3/)
226
+ EventMachine.stop
227
+ }
228
+ }
229
+ end
230
+
231
+ it "should set content-length to 0 on posts with empty bodies" do
232
+ EventMachine.run {
233
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_content_length_from_header').post
234
+
235
+ http.errback { failed(http) }
236
+ http.callback {
237
+ http.response_header.status.should == 200
238
+
239
+ http.response.strip.split(':')[1].should == '0'
240
+ EventMachine.stop
241
+ }
242
+ }
243
+ end
244
+
245
+ it "should perform successful POST with Ruby Hash/Array as params and with the correct content length" do
246
+ EventMachine.run {
247
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_content_length').post :body => {"key1" => "data1"}
248
+
249
+ http.errback { failed(http) }
250
+ http.callback {
251
+ http.response_header.status.should == 200
252
+
253
+ http.response.to_i.should == 10
254
+ EventMachine.stop
255
+ }
256
+ }
257
+ end
258
+
259
+ xit "should support expect-continue header" do
260
+ EventMachine.run {
261
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090').post :body => "data", :head => { 'expect' => '100-continue' }
262
+
263
+ http.errback { failed(http) }
264
+ http.callback {
265
+ http.response_header.status.should == 200
266
+ http.response.should == "data"
267
+ EventMachine.stop
268
+ }
269
+ }
270
+ end
271
+
272
+ it "should perform successful GET with custom header" do
273
+ EventMachine.run {
274
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').get :head => {'if-none-match' => 'evar!'}
275
+
276
+ http.errback { p http; failed(http) }
277
+ http.callback {
278
+ http.response_header.status.should == 304
279
+ EventMachine.stop
280
+ }
281
+ }
282
+ end
283
+
284
+ it "should perform basic auth" do
285
+ EventMachine.run {
286
+
287
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/authtest').get :head => {'authorization' => ['user', 'pass']}
288
+
289
+ http.errback { failed(http) }
290
+ http.callback {
291
+ http.response_header.status.should == 200
292
+ EventMachine.stop
293
+ }
294
+ }
295
+ end
296
+
297
+ it "should perform basic auth via the URL" do
298
+ EventMachine.run {
299
+
300
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://user:pass@127.0.0.1:8090/authtest').get
301
+
302
+ http.errback { failed(http) }
303
+ http.callback {
304
+ http.response_header.status.should == 200
305
+ EventMachine.stop
306
+ }
307
+ }
308
+ end
309
+
310
+ it "should return peer's IP address" do
311
+ EventMachine.run {
312
+
313
+ conn = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/')
314
+ conn.peer.should be_nil
315
+
316
+ http = conn.get
317
+ http.peer.should be_nil
318
+
319
+ http.errback { failed(http) }
320
+ http.callback {
321
+ conn.peer.should == '127.0.0.1'
322
+ http.peer.should == '127.0.0.1'
323
+
324
+ EventMachine.stop
325
+ }
326
+ }
327
+ end
328
+
329
+ it "should remove all newlines from long basic auth header" do
330
+ EventMachine.run {
331
+ auth = {'authorization' => ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz']}
332
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/auth').get :head => auth
333
+ http.errback { failed(http) }
334
+ http.callback {
335
+ http.response_header.status.should == 200
336
+ http.response.should == "Basic YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhOnp6enp6enp6enp6enp6enp6enp6enp6enp6enp6eg=="
337
+ EventMachine.stop
338
+ }
339
+ }
340
+ end
341
+
342
+ it "should send proper OAuth auth header" do
343
+ EventMachine.run {
344
+ oauth_header = 'OAuth oauth_nonce="oqwgSYFUD87MHmJJDv7bQqOF2EPnVus7Wkqj5duNByU", b=c, d=e'
345
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/auth').get :head => {
346
+ 'authorization' => oauth_header
347
+ }
348
+
349
+ http.errback { failed(http) }
350
+ http.callback {
351
+ http.response_header.status.should == 200
352
+ http.response.should == oauth_header
353
+ EventMachine.stop
354
+ }
355
+ }
356
+ end
357
+
358
+ it "should return ETag and Last-Modified headers" do
359
+ EventMachine.run {
360
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_query').get
361
+
362
+ http.errback { failed(http) }
363
+ http.callback {
364
+ http.response_header.status.should == 200
365
+ http.response_header.etag.should match('abcdefg')
366
+ http.response_header.last_modified.should match('Fri, 13 Aug 2010 17:31:21 GMT')
367
+ EventMachine.stop
368
+ }
369
+ }
370
+ end
371
+
372
+ it "should return raw headers in a hash" do
373
+ EventMachine.run {
374
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_headers').get
375
+
376
+ http.errback { failed(http) }
377
+ http.callback {
378
+ http.response_header.status.should == 200
379
+ http.response_header.raw['Set-Cookie'].should match('test=yes')
380
+ http.response_header.raw['X-Forward-Host'].should match('proxy.local')
381
+ EventMachine.stop
382
+ }
383
+ }
384
+ end
385
+
386
+ it "should detect deflate encoding" do
387
+ EventMachine.run {
388
+
389
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/deflate').get :head => {"accept-encoding" => "deflate"}
390
+
391
+ http.errback { failed(http) }
392
+ http.callback {
393
+ http.response_header.status.should == 200
394
+ http.response_header["CONTENT_ENCODING"].should == "deflate"
395
+ http.response.should == "compressed"
396
+
397
+ EventMachine.stop
398
+ }
399
+ }
400
+ end
401
+
402
+ it "should auto-detect and decode gzip encoding" do
403
+ EventMachine.run {
404
+
405
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/gzip').get :head => {"accept-encoding" => "gzip, compressed"}
406
+
407
+ http.errback { failed(http) }
408
+ http.callback {
409
+ http.response_header.status.should == 200
410
+ http.response_header["CONTENT_ENCODING"].should == "gzip"
411
+ http.response.should == "compressed"
412
+
413
+ EventMachine.stop
414
+ }
415
+ }
416
+ end
417
+
418
+ it "should stream gzip responses" do
419
+ expected_response = Zlib::GzipReader.open(File.dirname(__FILE__) + "/fixtures/gzip-sample.gz") { |f| f.read }
420
+ actual_response = ''
421
+
422
+ EventMachine.run {
423
+
424
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/gzip-large').get :head => {"accept-encoding" => "gzip, compressed"}
425
+
426
+ http.errback { failed(http) }
427
+ http.callback {
428
+ http.response_header.status.should == 200
429
+ http.response_header["CONTENT_ENCODING"].should == "gzip"
430
+ http.response.should == ''
431
+
432
+ actual_response.should == expected_response
433
+
434
+ EventMachine.stop
435
+ }
436
+ http.stream do |chunk|
437
+ actual_response << chunk
438
+ end
439
+ }
440
+ end
441
+
442
+ it "should not decode the response when configured so" do
443
+ EventMachine.run {
444
+
445
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/gzip').get :head => {
446
+ "accept-encoding" => "gzip, compressed"
447
+ }, :decoding => false
448
+
449
+ http.errback { failed(http) }
450
+ http.callback {
451
+ http.response_header.status.should == 200
452
+ http.response_header["CONTENT_ENCODING"].should == "gzip"
453
+
454
+ raw = http.response
455
+ Zlib::GzipReader.new(StringIO.new(raw)).read.should == "compressed"
456
+
457
+ EventMachine.stop
458
+ }
459
+ }
460
+ end
461
+
462
+ it "should default to requesting compressed response" do
463
+ EventMachine.run {
464
+
465
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_accept_encoding').get
466
+
467
+ http.errback { failed(http) }
468
+ http.callback {
469
+ http.response_header.status.should == 200
470
+ http.response.should == "gzip, compressed"
471
+
472
+ EventMachine.stop
473
+ }
474
+ }
475
+ end
476
+
477
+ it "should default to requesting compressed response" do
478
+ EventMachine.run {
479
+
480
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_accept_encoding').get :compressed => false
481
+
482
+ http.errback { failed(http) }
483
+ http.callback {
484
+ http.response_header.status.should == 200
485
+ http.response.should == ""
486
+
487
+ EventMachine.stop
488
+ }
489
+ }
490
+ end
491
+
492
+ it "should timeout after 0.1 seconds of inactivity" do
493
+ EventMachine.run {
494
+ t = Time.now.to_i
495
+ EventMachine.heartbeat_interval = 0.1
496
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/timeout', :inactivity_timeout => 0.1).get
497
+
498
+ http.errback {
499
+ http.error.should == Errno::ETIMEDOUT
500
+ (Time.now.to_i - t).should <= 1
501
+ EventMachine.stop
502
+ }
503
+ http.callback { failed(http) }
504
+ }
505
+ end
506
+
507
+ it "should complete a Location: with a relative path" do
508
+ EventMachine.run {
509
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/relative-location').get
510
+
511
+ http.errback { failed(http) }
512
+ http.callback {
513
+ http.response_header['LOCATION'].should == 'http://127.0.0.1:8090/forwarded'
514
+ EventMachine.stop
515
+ }
516
+ }
517
+ end
518
+
519
+ context "body content-type encoding" do
520
+ it "should not set content type on string in body" do
521
+ EventMachine.run {
522
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post :body => "data"
523
+
524
+ http.errback { failed(http) }
525
+ http.callback {
526
+ http.response_header.status.should == 200
527
+ http.response.should be_empty
528
+ EventMachine.stop
529
+ }
530
+ }
531
+ end
532
+
533
+ it "should set content-type automatically when passed a ruby hash/array for body" do
534
+ EventMachine.run {
535
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post :body => {:a => :b}
536
+
537
+ http.errback { failed(http) }
538
+ http.callback {
539
+ http.response_header.status.should == 200
540
+ http.response.should match("application/x-www-form-urlencoded")
541
+ EventMachine.stop
542
+ }
543
+ }
544
+ end
545
+
546
+ it "should not override content-type when passing in ruby hash/array for body" do
547
+ EventMachine.run {
548
+ ct = 'text; charset=utf-8'
549
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post({
550
+ :body => {:a => :b}, :head => {'content-type' => ct}})
551
+
552
+ http.errback { failed(http) }
553
+ http.callback {
554
+ http.response_header.status.should == 200
555
+ http.content_charset.should == Encoding.find('utf-8') if defined? Encoding
556
+ http.response_header["CONTENT_TYPE"].should == ct
557
+ EventMachine.stop
558
+ }
559
+ }
560
+ end
561
+
562
+ it "should default to external encoding on invalid encoding" do
563
+ EventMachine.run {
564
+ ct = 'text/html; charset=utf-8lias'
565
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post({
566
+ :body => {:a => :b}, :head => {'content-type' => ct}})
567
+
568
+ http.errback { failed(http) }
569
+ http.callback {
570
+ http.response_header.status.should == 200
571
+ http.content_charset.should == Encoding.find('utf-8') if defined? Encoding
572
+ http.response_header["CONTENT_TYPE"].should == ct
573
+ EventMachine.stop
574
+ }
575
+ }
576
+ end
577
+
578
+ it "should processed escaped content-type" do
579
+ EventMachine.run {
580
+ ct = "text/html; charset=\"ISO-8859-4\""
581
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_content_type').post({
582
+ :body => {:a => :b}, :head => {'content-type' => ct}})
583
+
584
+ http.errback { failed(http) }
585
+ http.callback {
586
+ http.response_header.status.should == 200
587
+ http.content_charset.should == Encoding.find('ISO-8859-4') if defined? Encoding
588
+ http.response_header["CONTENT_TYPE"].should == ct
589
+ EventMachine.stop
590
+ }
591
+ }
592
+ end
593
+ end
594
+
595
+ context "optional header callback" do
596
+ it "should optionally pass the response headers" do
597
+ EventMachine.run {
598
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').get
599
+
600
+ http.errback { failed(http) }
601
+ http.headers { |hash|
602
+ hash.should be_an_kind_of Hash
603
+ hash.should include 'CONNECTION'
604
+ hash.should include 'CONTENT_LENGTH'
605
+ }
606
+
607
+ http.callback {
608
+ http.response_header.status.should == 200
609
+ http.response.should match(/Hello/)
610
+ EventMachine.stop
611
+ }
612
+ }
613
+ end
614
+
615
+ it "should allow to terminate current connection from header callback" do
616
+ EventMachine.run {
617
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').get
618
+
619
+ http.callback { failed(http) }
620
+ http.headers { |hash|
621
+ hash.should be_an_kind_of Hash
622
+ hash.should include 'CONNECTION'
623
+ hash.should include 'CONTENT_LENGTH'
624
+
625
+ http.close('header callback terminated connection')
626
+ }
627
+
628
+ http.errback { |e|
629
+ http.response_header.status.should == 200
630
+ http.error.should == 'header callback terminated connection'
631
+ http.response.should == ''
632
+ EventMachine.stop
633
+ }
634
+ }
635
+ end
636
+ end
637
+
638
+ it "should optionally pass the response body progressively" do
639
+ EventMachine.run {
640
+ body = ''
641
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').get
642
+
643
+ http.errback { failed(http) }
644
+ http.stream { |chunk| body += chunk }
645
+
646
+ http.callback {
647
+ http.response_header.status.should == 200
648
+ http.response.should == ''
649
+ body.should match(/Hello/)
650
+ EventMachine.stop
651
+ }
652
+ }
653
+ end
654
+
655
+ it "should optionally pass the deflate-encoded response body progressively" do
656
+ EventMachine.run {
657
+ body = ''
658
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/deflate').get :head => {
659
+ "accept-encoding" => "deflate, compressed"
660
+ }
661
+
662
+ http.errback { failed(http) }
663
+ http.stream { |chunk| body += chunk }
664
+
665
+ http.callback {
666
+ http.response_header.status.should == 200
667
+ http.response_header["CONTENT_ENCODING"].should == "deflate"
668
+ http.response.should == ''
669
+ body.should == "compressed"
670
+ EventMachine.stop
671
+ }
672
+ }
673
+ end
674
+
675
+ it "should accept & return cookie header to user" do
676
+ EventMachine.run {
677
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/set_cookie').get
678
+
679
+ http.errback { failed(http) }
680
+ http.callback {
681
+ http.response_header.status.should == 200
682
+ http.response_header.cookie.should == "id=1; expires=Sat, 09 Aug 2031 17:53:39 GMT; path=/;"
683
+ EventMachine.stop
684
+ }
685
+ }
686
+ end
687
+
688
+ it "should return array of cookies on multiple Set-Cookie headers" do
689
+ EventMachine.run {
690
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/set_multiple_cookies').get
691
+
692
+ http.errback { failed(http) }
693
+ http.callback {
694
+ http.response_header.status.should == 200
695
+ http.response_header.cookie.size.should == 2
696
+ http.response_header.cookie.first.should == "id=1; expires=Sat, 09 Aug 2031 17:53:39 GMT; path=/;"
697
+ http.response_header.cookie.last.should == "id=2;"
698
+
699
+ EventMachine.stop
700
+ }
701
+ }
702
+ end
703
+
704
+ it "should pass cookie header to server from string" do
705
+ EventMachine.run {
706
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_cookie').get :head => {'cookie' => 'id=2;'}
707
+
708
+ http.errback { failed(http) }
709
+ http.callback {
710
+ http.response.should == "id=2;"
711
+ EventMachine.stop
712
+ }
713
+ }
714
+ end
715
+
716
+ it "should pass cookie header to server from Hash" do
717
+ EventMachine.run {
718
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo_cookie').get :head => {'cookie' => {'id' => 2}}
719
+
720
+ http.errback { failed(http) }
721
+ http.callback {
722
+ http.response.should == "id=2;"
723
+ EventMachine.stop
724
+ }
725
+ }
726
+ end
727
+
728
+ it "should get the body without Content-Length" do
729
+ EventMachine.run {
730
+ @s = StubServer.new("HTTP/1.1 200 OK\r\n\r\nFoo")
731
+
732
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8081/').get
733
+ http.errback { failed(http) }
734
+ http.callback {
735
+ http.response.should match(/Foo/)
736
+ http.response_header['CONTENT_LENGTH'].should be_nil
737
+
738
+ @s.stop
739
+ EventMachine.stop
740
+ }
741
+ }
742
+ end
743
+
744
+ context "when talking to a stub HTTP/1.0 server" do
745
+ it "should get the body without Content-Length" do
746
+
747
+ EventMachine.run {
748
+ @s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo")
749
+
750
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8081/').get
751
+ http.errback { failed(http) }
752
+ http.callback {
753
+ http.response.should match(/Foo/)
754
+ http.response_header['CONTENT_LENGTH'].should be_nil
755
+
756
+ @s.stop
757
+ EventMachine.stop
758
+ }
759
+ }
760
+ end
761
+
762
+ it "should work with \\n instead of \\r\\n" do
763
+ EventMachine.run {
764
+ @s = StubServer.new("HTTP/1.0 200 OK\nContent-Type: text/plain\nContent-Length: 3\nConnection: close\n\nFoo")
765
+
766
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8081/').get
767
+ http.errback { failed(http) }
768
+ http.callback {
769
+ http.response_header.status.should == 200
770
+ http.response_header['CONTENT_TYPE'].should == 'text/plain'
771
+ http.response.should match(/Foo/)
772
+
773
+ @s.stop
774
+ EventMachine.stop
775
+ }
776
+ }
777
+ end
778
+
779
+ it "should handle invalid HTTP response" do
780
+ EventMachine.run {
781
+ @s = StubServer.new("<html></html>")
782
+
783
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8081/').get
784
+ http.callback { failed(http) }
785
+ http.errback {
786
+ http.error.should_not be_nil
787
+ EM.stop
788
+ }
789
+ }
790
+ end
791
+ end
792
+
793
+ it "should stream a file off disk" do
794
+ EventMachine.run {
795
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').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 "streams POST request from disk via Pathname" do
806
+ EventMachine.run {
807
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').post :body => Pathname.new('spec/fixtures/google.ca')
808
+ http.errback { failed(http) }
809
+ http.callback {
810
+ http.response.should match('google')
811
+ EventMachine.stop
812
+ }
813
+ }
814
+ end
815
+
816
+ it "streams POST request from IO object" do
817
+ EventMachine.run {
818
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/').post :body => StringIO.new(File.read('spec/fixtures/google.ca'))
819
+ http.errback { failed(http) }
820
+ http.callback {
821
+ http.response.should match('google')
822
+ EventMachine.stop
823
+ }
824
+ }
825
+ end
826
+
827
+ it "should reconnect if connection was closed between requests" do
828
+ EventMachine.run {
829
+ conn = EM::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/')
830
+ req = conn.get
831
+
832
+ req.callback do
833
+ conn.close('client closing connection')
834
+
835
+ EM.next_tick do
836
+ req = conn.get :path => "/gzip"
837
+ req.callback do
838
+ req.response_header.status.should == 200
839
+ req.response.should match('compressed')
840
+ EventMachine.stop
841
+ end
842
+ end
843
+ end
844
+ }
845
+ end
846
+
847
+ it "should report error if connection was closed by server on client keepalive requests" do
848
+ EventMachine.run {
849
+ conn = EM::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/')
850
+ req = conn.get :keepalive => true
851
+
852
+ req.callback do
853
+ req = conn.get
854
+
855
+ req.callback { failed(http) }
856
+ req.errback do
857
+ req.error.should match('connection closed by server')
858
+ EventMachine.stop
859
+ end
860
+ end
861
+ }
862
+ end
863
+
864
+ it 'should handle malformed Content-Type header repetitions' do
865
+ EventMachine.run {
866
+ response =<<-HTTP.gsub(/^ +/, '').strip
867
+ HTTP/1.0 200 OK
868
+ Content-Type: text/plain; charset=iso-8859-1
869
+ Content-Type: text/plain; charset=utf-8
870
+ Content-Length: 5
871
+ Connection: close
872
+
873
+ Hello
874
+ HTTP
875
+
876
+ @s = StubServer.new(response)
877
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8081/').get
878
+ http.errback { failed(http) }
879
+ http.callback {
880
+ http.content_charset.should == Encoding::ISO_8859_1 if defined? Encoding
881
+ EventMachine.stop
882
+ }
883
+ }
884
+ end
885
+
886
+ it "should allow indifferent access to headers" do
887
+ EventMachine.run {
888
+ response =<<-HTTP.gsub(/^ +/, '').strip
889
+ HTTP/1.0 200 OK
890
+ Content-Type: text/plain; charset=utf-8
891
+ X-Custom-Header: foo
892
+ Content-Length: 5
893
+ Connection: close
894
+
895
+ Hello
896
+ HTTP
897
+
898
+ @s = StubServer.new(response)
899
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8081/').get
900
+ http.errback { failed(http) }
901
+ http.callback {
902
+ http.response_header["Content-Type"].should == "text/plain; charset=utf-8"
903
+ http.response_header["CONTENT_TYPE"].should == "text/plain; charset=utf-8"
904
+
905
+ http.response_header["Content-Length"].should == "5"
906
+ http.response_header["CONTENT_LENGTH"].should == "5"
907
+
908
+ http.response_header["X-Custom-Header"].should == "foo"
909
+ http.response_header["X_CUSTOM_HEADER"].should == "foo"
910
+
911
+ EventMachine.stop
912
+ }
913
+ }
914
+ end
915
+
916
+ it "should close connection on invalid HTTP response" do
917
+ EventMachine.run {
918
+ response =<<-HTTP.gsub(/^ +/, '').strip
919
+ HTTP/1.1 403 Forbidden
920
+ Content-Type: text/plain
921
+ Content-Length: 13
922
+
923
+ Access Denied
924
+
925
+ HTTP/1.1 403 Forbidden
926
+ Content-Type: text/plain
927
+ Content-Length: 13
928
+
929
+ Access Denied
930
+ HTTP
931
+
932
+ @s = StubServer.new(response)
933
+ lambda {
934
+ conn = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8081/')
935
+ req = conn.get
936
+ req.errback { failed(http) }
937
+ req.callback { EM.stop }
938
+ }.should_not raise_error
939
+
940
+ }
941
+ end
942
+
943
+ context "User-Agent" do
944
+ it 'should default to "EventMachine HttpClient"' do
945
+ EventMachine.run {
946
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo-user-agent').get
947
+
948
+ http.errback { failed(http) }
949
+ http.callback {
950
+ http.response.should == '"EventMachine HttpClient"'
951
+ EventMachine.stop
952
+ }
953
+ }
954
+ end
955
+
956
+ it 'should keep header if given empty string' do
957
+ EventMachine.run {
958
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo-user-agent').get(:head => { 'user-agent'=>'' })
959
+
960
+ http.errback { failed(http) }
961
+ http.callback {
962
+ http.response.should == '""'
963
+ EventMachine.stop
964
+ }
965
+ }
966
+ end
967
+
968
+ it 'should ommit header if given nil' do
969
+ EventMachine.run {
970
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://127.0.0.1:8090/echo-user-agent').get(:head => { 'user-agent'=>nil })
971
+
972
+ http.errback { failed(http) }
973
+ http.callback {
974
+ http.response.should == 'nil'
975
+ EventMachine.stop
976
+ }
977
+ }
978
+ end
979
+ end
980
+
981
+ context "IPv6" do
982
+ it "should perform successful GET" do
983
+ EventMachine.run {
984
+ @s = StubServer.new({
985
+ response: "HTTP/1.1 200 OK\r\n\r\nHello IPv6",
986
+ port: 8091,
987
+ host: '::1',
988
+ })
989
+ http = EventMachine::AblyHttpRequest::HttpRequest.new('http://[::1]:8091/').get
990
+
991
+ http.errback { failed(http) }
992
+ http.callback {
993
+ http.response_header.status.should == 200
994
+ http.response.should match(/Hello IPv6/)
995
+ EventMachine.stop
996
+ }
997
+ }
998
+ end
999
+ end
1000
+ end