z-http-request 0.1.0

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