em-http-request 0.2.10 → 0.2.14

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.
data/spec/request_spec.rb CHANGED
@@ -29,16 +29,27 @@ describe EventMachine::HttpRequest do
29
29
  http.errback {
30
30
  http.response_header.status.should == 0
31
31
  http.error.should match(/unable to resolve server address/)
32
+ http.uri.to_s.should match('http://somethinglocal:80/')
32
33
  EventMachine.stop
33
34
  }
34
35
  }
35
36
  end
36
37
 
37
- it "should fail GET on missing path" do
38
+ it "should raise error on invalid URL" do
38
39
  EventMachine.run {
39
40
  lambda {
40
- EventMachine::HttpRequest.new('http://www.google.com').get
41
- }.should raise_error(ArgumentError)
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)
42
53
 
43
54
  EventMachine.stop
44
55
  }
@@ -57,6 +68,64 @@ describe EventMachine::HttpRequest do
57
68
  }
58
69
  end
59
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 }
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 }
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 follow redirects on HEAD method" do
104
+ EventMachine.run {
105
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/head').head :redirects => 1
106
+ http.errback { failed }
107
+ http.callback {
108
+ http.response_header.status.should == 200
109
+ http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
110
+ EM.stop
111
+ }
112
+ }
113
+ end
114
+
115
+ it "should follow redirects on HEAD method (external)" do
116
+
117
+ EventMachine.run {
118
+ http = EventMachine::HttpRequest.new('http://www.google.com/').head :redirects => 1
119
+ http.errback { failed }
120
+ http.callback {
121
+ http.response_header.status.should == 200
122
+ EM.stop
123
+ }
124
+ }
125
+ end
126
+
127
+ end
128
+
60
129
  it "should perform successfull GET with a URI passed as argument" do
61
130
  EventMachine.run {
62
131
  uri = URI.parse('http://127.0.0.1:8080/')
@@ -178,6 +247,19 @@ describe EventMachine::HttpRequest do
178
247
  }
179
248
  end
180
249
 
250
+ it "should escape body on POST" do
251
+ EventMachine.run {
252
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {:stuff => 'string&string'}
253
+
254
+ http.errback { failed }
255
+ http.callback {
256
+ http.response_header.status.should == 200
257
+ http.response.should == "stuff=string%26string"
258
+ EventMachine.stop
259
+ }
260
+ }
261
+ end
262
+
181
263
  it "should perform successfull POST with Ruby Hash/Array as params" do
182
264
  EventMachine.run {
183
265
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {"key1" => 1, "key2" => [2,3]}
@@ -222,7 +304,7 @@ describe EventMachine::HttpRequest do
222
304
  EventMachine.run {
223
305
 
224
306
  # digg.com uses chunked encoding
225
- http = EventMachine::HttpRequest.new('http://digg.com/').get
307
+ http = EventMachine::HttpRequest.new('http://digg.com/news').get
226
308
 
227
309
  http.errback { failed }
228
310
  http.callback {
@@ -272,6 +354,20 @@ describe EventMachine::HttpRequest do
272
354
  }
273
355
  end
274
356
 
357
+ it "should return ETag and Last-Modified headers" do
358
+ EventMachine.run {
359
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get
360
+
361
+ http.errback { failed }
362
+ http.callback {
363
+ http.response_header.status.should == 200
364
+ http.response_header.etag.should match('abcdefg')
365
+ http.response_header.last_modified.should match('Fri, 13 Aug 2010 17:31:21 GMT')
366
+ EventMachine.stop
367
+ }
368
+ }
369
+ end
370
+
275
371
  it "should detect deflate encoding" do
276
372
  EventMachine.run {
277
373
 
@@ -318,58 +414,94 @@ describe EventMachine::HttpRequest do
318
414
  }
319
415
  end
320
416
 
321
- it "should report last_effective_url" do
322
- EventMachine.run {
323
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
324
- http.errback { failed }
325
- http.callback {
326
- http.response_header.status.should == 200
327
- http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
417
+ context "redirect" do
418
+ it "should report last_effective_url" do
419
+ EventMachine.run {
420
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
421
+ http.errback { failed }
422
+ http.callback {
423
+ http.response_header.status.should == 200
424
+ http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
328
425
 
329
- EM.stop
426
+ EM.stop
427
+ }
330
428
  }
331
- }
332
- end
429
+ end
333
430
 
334
- it "should follow location redirects" do
335
- EventMachine.run {
336
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1
337
- http.errback { failed }
338
- http.callback {
339
- http.response_header.status.should == 200
340
- http.response_header["CONTENT_ENCODING"].should == "gzip"
341
- http.response.should == "compressed"
342
- http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
343
- http.redirects.should == 1
431
+ it "should follow location redirects" do
432
+ EventMachine.run {
433
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1
434
+ http.errback { failed }
435
+ http.callback {
436
+ http.response_header.status.should == 200
437
+ http.response_header["CONTENT_ENCODING"].should == "gzip"
438
+ http.response.should == "compressed"
439
+ http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
440
+ http.redirects.should == 1
344
441
 
345
- EM.stop
442
+ EM.stop
443
+ }
346
444
  }
347
- }
348
- end
445
+ end
349
446
 
350
- it "should default to 0 redirects" do
351
- EventMachine.run {
352
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get
353
- http.errback { failed }
354
- http.callback {
355
- http.response_header.status.should == 301
356
- http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
357
- http.redirects.should == 0
447
+ it "should default to 0 redirects" do
448
+ EventMachine.run {
449
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get
450
+ http.errback { failed }
451
+ http.callback {
452
+ http.response_header.status.should == 301
453
+ http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
454
+ http.redirects.should == 0
358
455
 
359
- EM.stop
456
+ EM.stop
457
+ }
360
458
  }
361
- }
362
- end
459
+ end
363
460
 
364
- it "should not invoke redirect logic on failed connections" do
365
- EventMachine.run {
366
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get :timeout => 0.1, :redirects => 5
367
- http.callback { failed }
368
- http.errback {
369
- http.redirects.should == 0
370
- EM.stop
461
+ it "should not invoke redirect logic on failed connections" do
462
+ EventMachine.run {
463
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get :timeout => 0.1, :redirects => 5
464
+ http.callback { failed }
465
+ http.errback {
466
+ http.redirects.should == 0
467
+ EM.stop
468
+ }
371
469
  }
372
- }
470
+ end
471
+
472
+ it "should normalize redirect urls" do
473
+ EventMachine.run {
474
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/bad').get :redirects => 1
475
+ http.errback { failed }
476
+ http.callback {
477
+ http.last_effective_url.to_s.should match('http://127.0.0.1:8080/')
478
+ http.response.should match('Hello, World!')
479
+ EM.stop
480
+ }
481
+ }
482
+ end
483
+
484
+ it "should fail gracefully on a missing host in absolute Location header" do
485
+ EventMachine.run {
486
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/nohost').get :redirects => 1
487
+ http.callback { failed }
488
+ http.errback {
489
+ http.error.should == 'Location header format error'
490
+ EM.stop
491
+ }
492
+ }
493
+ end
494
+
495
+ it "should fail gracefully on an invalid host in Location header" do
496
+ EventMachine.run {
497
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/badhost').get :redirects => 1
498
+ http.callback { failed }
499
+ http.errback {
500
+ http.error.should == 'unable to resolve server address'
501
+ EM.stop
502
+ }
503
+ }
504
+ end
373
505
  end
374
506
 
375
507
  it "should optionally pass the response body progressively" do
@@ -389,6 +521,49 @@ describe EventMachine::HttpRequest do
389
521
  }
390
522
  end
391
523
 
524
+ context "optional header callback" do
525
+ it "should optionally pass the response headers" do
526
+ EventMachine.run {
527
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
528
+
529
+ http.errback { failed }
530
+ http.headers { |hash|
531
+ hash.should be_an_kind_of Hash
532
+ hash.should include 'CONNECTION'
533
+ hash.should include 'CONTENT_LENGTH'
534
+ }
535
+
536
+ http.callback {
537
+ http.response_header.status.should == 200
538
+ http.response.should match(/Hello/)
539
+ EventMachine.stop
540
+ }
541
+ }
542
+ end
543
+
544
+ it "should allow to terminate current connection from header callback" do
545
+ EventMachine.run {
546
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
547
+
548
+ http.callback { failed }
549
+ http.headers { |hash|
550
+ hash.should be_an_kind_of Hash
551
+ hash.should include 'CONNECTION'
552
+ hash.should include 'CONTENT_LENGTH'
553
+
554
+ http.close('header callback terminated connection')
555
+ }
556
+
557
+ http.errback { |e|
558
+ http.response_header.status.should == 200
559
+ http.error.should == 'header callback terminated connection'
560
+ http.response.should == ''
561
+ EventMachine.stop
562
+ }
563
+ }
564
+ end
565
+ end
566
+
392
567
  it "should optionally pass the deflate-encoded response body progressively" do
393
568
  EventMachine.run {
394
569
  body = ''
@@ -582,8 +757,8 @@ describe EventMachine::HttpRequest do
582
757
  http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
583
758
  http.errback { fail }
584
759
  http.callback {
585
- c1 = "PREF=ID=9454187d21c4a6a6:TM=1258403955:LM=1258403955:S=2-mf1n5oV5yAeT9-; expires=Wed, 16-Nov-2011 20:39:15 GMT; path=/; domain=.google.ca"
586
- c2 = "NID=28=lvxxVdiBQkCetu_WFaUxLyB7qPlHXS5OdAGYTqge_laVlCKVN8VYYeVBh4bNZiK_Oan2gm8oP9GA-FrZfMPC3ZMHeNq37MG2JH8AIW9LYucU8brOeuggMEbLNNXuiWg4; expires=Tue, 18-May-2010 20:39:15 GMT; path=/; domain=.google.ca; HttpOnly"
760
+ c1 = "PREF=ID=11955ae9690fd292:TM=1281823106:LM=1281823106:S=wHdloFqGQ_OLSE92; expires=Mon, 13-Aug-2012 21:58:26 GMT; path=/; domain=.google.ca"
761
+ c2 = "NID=37=USTdOsxOSMbLjphkJ3S5Ueua3Yc23COXuK_pbztcHx7JoyhomwQySrvebCf3_u8eyrBiLWssVzaZcEOiKGEJbNdy8lRhnq_mfrdz693LaMjNPh__ccW4sgn1ZO6nQltE; expires=Sun, 13-Feb-2011 21:58:26 GMT; path=/; domain=.google.ca; HttpOnly"
587
762
  http.response_header.cookie.should == [c1, c2]
588
763
 
589
764
  EventMachine.stop
@@ -593,35 +768,93 @@ describe EventMachine::HttpRequest do
593
768
  EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
594
769
  end
595
770
 
596
- context "connections via proxy" do
771
+ context "connections via" do
772
+ context "direct proxy" do
773
+ it "should default to skip CONNECT" do
774
+ EventMachine.run {
597
775
 
598
- it "should work with proxy servers" do
599
- EventMachine.run {
776
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => {
777
+ :host => '127.0.0.1', :port => 8083
778
+ }
779
+
780
+ http.errback { p http.inspect; failed }
781
+ http.callback {
782
+ http.response_header.status.should == 200
783
+ http.response.should match('test')
784
+ EventMachine.stop
785
+ }
786
+ }
787
+ end
600
788
 
601
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :proxy => {:host => '127.0.0.1', :port => 8082}
789
+ it "should send absolute URIs to the proxy server" do
790
+ EventMachine.run {
602
791
 
603
- http.errback { p http.inspect; failed }
604
- http.callback {
605
- http.response_header.status.should == 200
606
- http.response.should == 'Hello, World!'
607
- EventMachine.stop
792
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => {
793
+ :host => '127.0.0.1', :port => 8083
794
+ }
795
+
796
+ http.errback { p http.inspect; failed }
797
+ http.callback {
798
+ http.response_header.status.should == 200
799
+ # The test proxy server gives the requested uri back in this header
800
+ http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8080/?q=test'
801
+ http.response_header['X_THE_REQUESTED_URI'].should_not == '/?q=test'
802
+ http.response.should match('test')
803
+ EventMachine.stop
804
+ }
608
805
  }
609
- }
806
+ end
807
+
808
+ it "should include query parameters specified in the options" do
809
+ EventMachine.run {
810
+
811
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(
812
+ :proxy => { :host => '127.0.0.1', :port => 8083 },
813
+ :query => { 'q' => 'test' }
814
+ )
815
+
816
+ http.errback { p http.inspect; failed }
817
+ http.callback {
818
+ http.response_header.status.should == 200
819
+ http.response.should match('test')
820
+ EventMachine.stop
821
+ }
822
+ }
823
+ end
610
824
  end
611
825
 
612
- it "should proxy POST data" do
613
- EventMachine.run {
826
+ context "CONNECT proxy" do
827
+ it "should work with CONNECT proxy servers" do
828
+ EventMachine.run {
614
829
 
615
- http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({
616
- :body => "data", :proxy => {:host => '127.0.0.1', :port => 8082}})
830
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({
831
+ :proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true}
832
+ })
617
833
 
618
- http.errback { failed }
619
- http.callback {
620
- http.response_header.status.should == 200
621
- http.response.should match(/data/)
622
- EventMachine.stop
834
+ http.errback { p http.inspect; failed }
835
+ http.callback {
836
+ http.response_header.status.should == 200
837
+ http.response.should == 'Hello, World!'
838
+ EventMachine.stop
839
+ }
623
840
  }
624
- }
841
+ end
842
+
843
+ it "should proxy POST data" do
844
+ EventMachine.run {
845
+
846
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({
847
+ :body => "data", :proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true}
848
+ })
849
+
850
+ http.errback { failed }
851
+ http.callback {
852
+ http.response_header.status.should == 200
853
+ http.response.should match(/data/)
854
+ EventMachine.stop
855
+ }
856
+ }
857
+ end
625
858
  end
626
859
  end
627
860
 
data/spec/spec.opts ADDED
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ specdoc
4
+ --loadby
5
+ mtime
6
+ --reverse
7
+ --backtrace
data/spec/stallion.rb CHANGED
@@ -78,12 +78,14 @@ Stallion.saddle :spec do |stable|
78
78
  stable.response.write 'test'
79
79
 
80
80
  elsif stable.request.path_info == '/echo_query'
81
+ stable.response["ETag"] = "abcdefg"
82
+ stable.response["Last-Modified"] = "Fri, 13 Aug 2010 17:31:21 GMT"
81
83
  stable.response.write stable.request.query_string
82
84
 
83
85
  elsif stable.request.path_info == '/echo_content_length'
84
86
  stable.response.write stable.request.content_length
85
87
 
86
- elsif stable.request.head?
88
+ elsif stable.request.head? && stable.request.path_info == '/'
87
89
  stable.response.status = 200
88
90
 
89
91
  elsif stable.request.delete?
@@ -113,6 +115,23 @@ Stallion.saddle :spec do |stable|
113
115
  elsif stable.request.path_info == '/redirect'
114
116
  stable.response.status = 301
115
117
  stable.response["Location"] = "/gzip"
118
+ stable.response.write 'redirect'
119
+
120
+ elsif stable.request.path_info == '/redirect/bad'
121
+ stable.response.status = 301
122
+ stable.response["Location"] = "http://127.0.0.1:8080"
123
+
124
+ elsif stable.request.path_info == '/redirect/head'
125
+ stable.response.status = 301
126
+ stable.response["Location"] = "/"
127
+
128
+ elsif stable.request.path_info == '/redirect/nohost'
129
+ stable.response.status = 301
130
+ stable.response["Location"] = "http:/"
131
+
132
+ elsif stable.request.path_info == '/redirect/badhost'
133
+ stable.response.status = 301
134
+ stable.response["Location"] = "http://$$$@$!%&^"
116
135
 
117
136
  elsif stable.request.path_info == '/gzip'
118
137
  io = StringIO.new
@@ -164,7 +183,7 @@ Thread.new do
164
183
  end
165
184
 
166
185
  #
167
- # HTTP Proxy server
186
+ # Tunneling HTTP Proxy server
168
187
  #
169
188
  Thread.new do
170
189
  server = TCPServer.new('127.0.0.1', 8082)
@@ -220,4 +239,46 @@ Thread.new do
220
239
  end
221
240
  end
222
241
 
223
- sleep(1)
242
+ #
243
+ # CONNECT-less HTTP Proxy server
244
+ #
245
+ Thread.new do
246
+ server = TCPServer.new('127.0.0.1', 8083)
247
+ loop do
248
+ session = server.accept
249
+ request = ""
250
+ while (data = session.gets) != "\r\n"
251
+ request << data
252
+ end
253
+ parts = request.split("\r\n")
254
+ method, destination, http_version = parts.first.split(' ')
255
+ if destination =~ /^http:/
256
+ uri = Addressable::URI.parse(destination)
257
+ absolute_path = uri.path + (uri.query ? "?#{uri.query}" : "")
258
+ client = TCPSocket.open(uri.host, uri.port || 80)
259
+ client.write "#{method} #{absolute_path} #{http_version}\r\n"
260
+ parts[1..-1].each do |part|
261
+ client.write "#{part}\r\n"
262
+ end
263
+
264
+ client.write "\r\n"
265
+ client.flush
266
+ client.close_write
267
+
268
+ # Take the initial line from the upstream response
269
+ session.write client.gets
270
+
271
+ # What (absolute) uri was requested? Send it back in a header
272
+ session.write "X-The-Requested-URI: #{destination}\r\n"
273
+
274
+ while data = client.gets
275
+ session.write data
276
+ end
277
+ session.flush
278
+ client.close
279
+ end
280
+ session.close
281
+ end
282
+ end
283
+
284
+ sleep(1)
data/spec/stub_server.rb CHANGED
@@ -1,22 +1,22 @@
1
- class StubServer
2
- module Server
3
- def receive_data(data)
4
- send_data @response
5
- close_connection_after_writing
6
- end
7
-
8
- def response=(response)
9
- @response = response
10
- end
11
- end
12
-
13
- def initialize(response, port=8081)
14
- @sig = EventMachine::start_server("127.0.0.1", port, Server) { |s|
15
- s.response = response
16
- }
17
- end
18
-
19
- def stop
20
- EventMachine.stop_server @sig
21
- end
22
- end
1
+ class StubServer
2
+ module Server
3
+ def receive_data(data)
4
+ send_data @response
5
+ close_connection_after_writing
6
+ end
7
+
8
+ def response=(response)
9
+ @response = response
10
+ end
11
+ end
12
+
13
+ def initialize(response, port=8081)
14
+ @sig = EventMachine::start_server("127.0.0.1", port, Server) { |s|
15
+ s.response = response
16
+ }
17
+ end
18
+
19
+ def stop
20
+ EventMachine.stop_server @sig
21
+ end
22
+ end