z-http-request 0.1.0

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