em-http-request 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of em-http-request might be problematic. Click here for more details.

data/lib/em-http/mock.rb CHANGED
@@ -7,8 +7,12 @@ module EventMachine
7
7
 
8
8
  def setup(response, uri)
9
9
  @uri = uri
10
- receive_data(response)
11
- succeed(self)
10
+ if response == :fail
11
+ fail(self)
12
+ else
13
+ receive_data(response)
14
+ succeed(self)
15
+ end
12
16
  end
13
17
 
14
18
  def unbind
@@ -20,11 +24,15 @@ module EventMachine
20
24
  @@registry_count = nil
21
25
 
22
26
  def self.reset_counts!
23
- @@registry_count = Hash.new{|h,k| h[k] = Hash.new(0)}
27
+ @@registry_count = Hash.new do |registry,query|
28
+ registry[query] = Hash.new{|h,k| h[k] = Hash.new(0)}
29
+ end
24
30
  end
25
31
 
26
32
  def self.reset_registry!
27
- @@registry = Hash.new{|h,k| h[k] = {}}
33
+ @@registry = Hash.new do |registry,query|
34
+ registry[query] = Hash.new{|h,k| h[k] = {}}
35
+ end
28
36
  end
29
37
 
30
38
  reset_counts!
@@ -40,34 +48,49 @@ module EventMachine
40
48
  @@pass_through_requests
41
49
  end
42
50
 
43
- def self.register(uri, method, data)
51
+ def self.register(uri, method, headers, data)
44
52
  method = method.to_s.upcase
45
- @@registry[uri][method] = data
53
+ headers = headers.to_s
54
+ @@registry[uri][method][headers] = data
46
55
  end
47
56
 
48
- def self.register_file(uri, method, file)
49
- register(uri, method, File.read(file))
57
+ def self.register_file(uri, method, headers, file)
58
+ register(uri, method, headers, File.read(file))
50
59
  end
51
60
 
52
- def self.count(uri, method)
61
+ def self.count(uri, method, headers)
53
62
  method = method.to_s.upcase
54
- @@registry_count[uri][method]
63
+ headers = headers.to_s
64
+ @@registry_count[uri][method][headers] rescue 0
65
+ end
66
+
67
+ def self.registered?(query, method, headers)
68
+ @@registry[query] and @@registry[query][method] and @@registry[query][method][headers]
69
+ end
70
+
71
+ def self.registered_content(query, method, headers)
72
+ @@registry[query][method][headers]
73
+ end
74
+
75
+ def self.increment_access(query, method, headers)
76
+ @@registry_count[query][method][headers] += 1
55
77
  end
56
78
 
57
79
  alias_method :real_send_request, :send_request
58
80
 
59
81
  protected
60
82
  def send_request(&blk)
61
- query = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}#{encode_query(@uri.path, @options[:query], @uri.query)}"
62
- if s = @@registry[query] and fake = s[@method]
63
- @@registry_count[query][@method] += 1
83
+ query = "#{@req.uri.scheme}://#{@req.uri.host}:#{@req.uri.port}#{encode_query(@req.uri.path, @req.options[:query], @req.uri.query)}"
84
+ headers = @req.options[:head].to_s
85
+ if self.class.registered?(query, @req.method, headers)
86
+ self.class.increment_access(query, @req.method, headers)
64
87
  client = FakeHttpClient.new(nil)
65
- client.setup(fake, @uri)
88
+ client.setup(self.class.registered_content(query, @req.method, headers), @req.uri)
66
89
  client
67
90
  elsif @@pass_through_requests
68
91
  real_send_request
69
92
  else
70
- raise "this request #{query} for method #{@method} isn't registered, and pass_through_requests is current set to false"
93
+ raise "this request #{query} for method #{@req.method} with the headers #{@req.options[:head].inspect} isn't registered, and pass_through_requests is current set to false"
71
94
  end
72
95
  end
73
96
  end
@@ -1,103 +1,79 @@
1
- require 'base64'
2
- require 'addressable/uri'
3
-
4
- module EventMachine
5
-
6
- # EventMachine based HTTP request class with support for streaming consumption
7
- # of the response. Response is parsed with a Ragel-generated whitelist parser
8
- # which supports chunked HTTP encoding.
9
- #
10
- # == Example
11
- #
12
- # EventMachine.run {
13
- # http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}
14
- #
15
- # http.callback {
16
- # p http.response_header.status
17
- # p http.response_header
18
- # p http.response
19
- #
20
- # EventMachine.stop
21
- # }
22
- # }
23
- #
24
-
25
- class HttpRequest
26
-
27
- attr_reader :options, :method
28
-
29
- def initialize(host)
30
- @uri = host.kind_of?(Addressable::URI) ? host : Addressable::URI::parse(host)
31
- end
32
-
33
- # Send an HTTP request and consume the response. Supported options:
34
- #
35
- # head: {Key: Value}
36
- # Specify an HTTP header, e.g. {'Connection': 'close'}
37
- #
38
- # query: {Key: Value}
39
- # Specify query string parameters (auto-escaped)
40
- #
41
- # body: String
42
- # Specify the request body (you must encode it for now)
43
- #
44
- # on_response: Proc
45
- # Called for each response body chunk (you may assume HTTP 200
46
- # OK then)
47
- #
48
-
49
- def get options = {}, &blk; setup_request(:get, options, &blk); end
50
- def head options = {}, &blk; setup_request(:head, options, &blk); end
51
- def delete options = {}, &blk; setup_request(:delete, options, &blk); end
52
- def put options = {}, &blk; setup_request(:put, options, &blk); end
53
- def post options = {}, &blk; setup_request(:post, options, &blk); end
54
-
55
- protected
56
-
57
- def setup_request(method, options, &blk)
58
- raise ArgumentError, "invalid request path" unless /^\// === @uri.path
59
- @options = options
60
-
61
- if proxy = options[:proxy]
62
- @host_to_connect = proxy[:host]
63
- @port_to_connect = proxy[:port]
64
- else
65
- @host_to_connect = @uri.host
66
- @port_to_connect = @uri.port
67
- end
68
-
69
- # default connect & inactivity timeouts
70
- @options[:timeout] = 10 if not @options[:timeout]
71
-
72
- # Make sure the ports are set as Addressable::URI doesn't
73
- # set the port if it isn't there
74
- if @uri.scheme == "https"
75
- @uri.port ||= 443
76
- @port_to_connect ||= 443
77
- else
78
- @uri.port ||= 80
79
- @port_to_connect ||= 80
80
- end
81
-
82
- @method = method.to_s.upcase
83
- send_request(&blk)
84
- end
85
-
86
- def send_request(&blk)
87
- begin
88
- EventMachine.connect(@host_to_connect, @port_to_connect, EventMachine::HttpClient) { |c|
89
- c.uri = @uri
90
- c.method = @method
91
- c.options = @options
92
- c.comm_inactivity_timeout = @options[:timeout]
93
- c.pending_connect_timeout = @options[:timeout]
94
- blk.call(c) unless blk.nil?
95
- }
96
- rescue EventMachine::ConnectionError => e
97
- conn = EventMachine::HttpClient.new("")
98
- conn.on_error(e.message, true)
99
- conn
100
- end
101
- end
102
- end
1
+ require 'base64'
2
+ require 'addressable/uri'
3
+
4
+ module EventMachine
5
+
6
+ # EventMachine based HTTP request class with support for streaming consumption
7
+ # of the response. Response is parsed with a Ragel-generated whitelist parser
8
+ # which supports chunked HTTP encoding.
9
+ #
10
+ # == Example
11
+ #
12
+ # EventMachine.run {
13
+ # http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}
14
+ #
15
+ # http.callback {
16
+ # p http.response_header.status
17
+ # p http.response_header
18
+ # p http.response
19
+ #
20
+ # EventMachine.stop
21
+ # }
22
+ # }
23
+ #
24
+
25
+ class HttpRequest
26
+
27
+ attr_reader :options, :method
28
+
29
+ def initialize(host)
30
+ @uri = host.kind_of?(Addressable::URI) ? host : Addressable::URI::parse(host)
31
+ end
32
+
33
+ # Send an HTTP request and consume the response. Supported options:
34
+ #
35
+ # head: {Key: Value}
36
+ # Specify an HTTP header, e.g. {'Connection': 'close'}
37
+ #
38
+ # query: {Key: Value}
39
+ # Specify query string parameters (auto-escaped)
40
+ #
41
+ # body: String
42
+ # Specify the request body (you must encode it for now)
43
+ #
44
+ # on_response: Proc
45
+ # Called for each response body chunk (you may assume HTTP 200
46
+ # OK then)
47
+ #
48
+
49
+ def get options = {}, &blk; setup_request(:get, options, &blk); end
50
+ def head options = {}, &blk; setup_request(:head, options, &blk); end
51
+ def delete options = {}, &blk; setup_request(:delete,options, &blk); end
52
+ def put options = {}, &blk; setup_request(:put, options, &blk); end
53
+ def post options = {}, &blk; setup_request(:post, options, &blk); end
54
+
55
+ protected
56
+
57
+ def setup_request(method, options, &blk)
58
+ @req = HttpOptions.new(method, @uri, options)
59
+ send_request(&blk)
60
+ end
61
+
62
+ def send_request(&blk)
63
+ begin
64
+ EventMachine.connect(@req.host, @req.port, EventMachine::HttpClient) { |c|
65
+ c.uri = @req.uri
66
+ c.method = @req.method
67
+ c.options = @req.options
68
+ c.comm_inactivity_timeout = @req.options[:timeout]
69
+ c.pending_connect_timeout = @req.options[:timeout]
70
+ blk.call(c) unless blk.nil?
71
+ }
72
+ rescue EventMachine::ConnectionError => e
73
+ conn = EventMachine::HttpClient.new("")
74
+ conn.on_error(e.message, true)
75
+ conn
76
+ end
77
+ end
78
+ end
103
79
  end
data/spec/mock_spec.rb CHANGED
@@ -8,7 +8,7 @@ describe 'em-http mock' do
8
8
  end
9
9
 
10
10
  it "should serve a fake http request from a file" do
11
- EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
11
+ EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
12
12
  EM.run {
13
13
 
14
14
  http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
@@ -19,7 +19,7 @@ describe 'em-http mock' do
19
19
  }
20
20
  }
21
21
 
22
- EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
22
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
23
23
  end
24
24
 
25
25
  it "should serve a fake http request from a string" do
@@ -46,7 +46,7 @@ window._gjp && _gjp()</script><style>td{line-height:.8em;}.gac_m td{line-height:
46
46
  function a(){google.timers.load.t.ol=(new Date).getTime();google.report&&google.timers.load.t.xjs&&google.report(google.timers.load,google.kCSI)}if(window.addEventListener)window.addEventListener("load",a,false);else if(window.attachEvent)window.attachEvent("onload",a);google.timers.load.t.prt=(new Date).getTime();
47
47
  })();
48
48
  HEREDOC
49
- EventMachine::MockHttpRequest.register('http://www.google.ca:80/', :get, data)
49
+ EventMachine::MockHttpRequest.register('http://www.google.ca:80/', :get, {}, data)
50
50
  EventMachine.run {
51
51
 
52
52
  http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
@@ -57,7 +57,52 @@ function a(){google.timers.load.t.ol=(new Date).getTime();google.report&&google.
57
57
  }
58
58
  }
59
59
 
60
- EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
60
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
61
+ end
62
+
63
+ it "should serve a fake failing http request" do
64
+ EventMachine::MockHttpRequest.register('http://www.google.ca:80/', :get, {}, :fail)
65
+ error = false
66
+
67
+ EventMachine.run {
68
+ http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
69
+ http.errback {
70
+ error = true
71
+ EventMachine.stop
72
+ }
73
+ }
74
+
75
+ error.should be_true
76
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
77
+ end
78
+
79
+ it "should distinguish the cache by the given headers" do
80
+ EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, {:user_agent => 'BERT'}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
81
+ EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
82
+ EM.run {
83
+
84
+ http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
85
+ http.errback { fail }
86
+ http.callback {
87
+ http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\n\n", 2).last
88
+ EventMachine.stop
89
+ }
90
+ }
91
+
92
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
93
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {:user_agent => 'BERT'}).should == 0
94
+
95
+ EM.run {
96
+
97
+ http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get({:head => {:user_agent => 'BERT'}})
98
+ http.errback { fail }
99
+ http.callback {
100
+ http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\n\n", 2).last
101
+ EventMachine.stop
102
+ }
103
+ }
104
+
105
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {:user_agent => 'BERT'}).should == 1
61
106
  end
62
107
 
63
108
  it "should raise an exception if pass-thru is disabled" do
data/spec/multi_spec.rb CHANGED
@@ -28,8 +28,8 @@ describe EventMachine::MultiRequest do
28
28
  end
29
29
 
30
30
  it "should handle multiple mock requests" do
31
- EventMachine::MockHttpRequest.register_file('http://127.0.0.1:8080/', :get, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
32
- EventMachine::MockHttpRequest.register_file('http://0.0.0.0:8083/', :get, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
31
+ EventMachine::MockHttpRequest.register_file('http://127.0.0.1:8080/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
32
+ EventMachine::MockHttpRequest.register_file('http://0.0.0.0:8083/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
33
33
 
34
34
  EventMachine.run {
35
35
 
data/spec/request_spec.rb CHANGED
@@ -11,6 +11,7 @@ describe EventMachine::HttpRequest do
11
11
 
12
12
  it "should fail GET on DNS timeout" do
13
13
  EventMachine.run {
14
+ EventMachine.heartbeat_interval = 0.1
14
15
  http = EventMachine::HttpRequest.new('http://127.1.1.1/').get :timeout => 1
15
16
  http.callback { failed }
16
17
  http.errback {
@@ -22,11 +23,12 @@ describe EventMachine::HttpRequest do
22
23
 
23
24
  it "should fail GET on invalid host" do
24
25
  EventMachine.run {
26
+ EventMachine.heartbeat_interval = 0.1
25
27
  http = EventMachine::HttpRequest.new('http://somethinglocal/').get :timeout => 1
26
28
  http.callback { failed }
27
29
  http.errback {
28
30
  http.response_header.status.should == 0
29
- http.errors.should match(/unable to resolve server address/)
31
+ http.error.should match(/unable to resolve server address/)
30
32
  EventMachine.stop
31
33
  }
32
34
  }
@@ -74,7 +76,7 @@ describe EventMachine::HttpRequest do
74
76
  uri = URI.parse('http://127.0.0.1:8080/')
75
77
  http = EventMachine::HttpRequest.new(uri).head
76
78
 
77
- http.errback { failed }
79
+ http.errback { p http; failed }
78
80
  http.callback {
79
81
  http.response_header.status.should == 200
80
82
  http.response.should == ""
@@ -302,19 +304,63 @@ describe EventMachine::HttpRequest do
302
304
  }
303
305
  end
304
306
 
305
- it "should timeout after 10 seconds" do
307
+ it "should timeout after 1 second" do
306
308
  EventMachine.run {
307
309
  t = Time.now.to_i
310
+ EventMachine.heartbeat_interval = 0.1
308
311
  http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/timeout').get :timeout => 1
309
312
 
310
313
  http.errback {
311
- (Time.now.to_i - t).should >= 2
314
+ (Time.now.to_i - t).should <= 5
312
315
  EventMachine.stop
313
316
  }
314
317
  http.callback { failed }
315
318
  }
316
319
  end
317
320
 
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/'
328
+
329
+ EM.stop
330
+ }
331
+ }
332
+ end
333
+
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
344
+
345
+ EM.stop
346
+ }
347
+ }
348
+ end
349
+
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
358
+
359
+ EM.stop
360
+ }
361
+ }
362
+ end
363
+
318
364
  it "should optionally pass the response body progressively" do
319
365
  EventMachine.run {
320
366
  body = ''
@@ -486,8 +532,20 @@ describe EventMachine::HttpRequest do
486
532
  EventMachine.stop
487
533
  }
488
534
  }
489
- end
490
-
535
+ end
536
+
537
+ it "should stream a file off disk" do
538
+ EventMachine.run {
539
+ http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :file => 'spec/fixtures/google.ca'
540
+
541
+ http.errback { failed }
542
+ http.callback {
543
+ http.response.should match('google')
544
+ EventMachine.stop
545
+ }
546
+ }
547
+ end
548
+
491
549
  it 'should let you pass a block to be called once the client is created' do
492
550
  client = nil
493
551
  EventMachine.run {
@@ -501,14 +559,13 @@ describe EventMachine::HttpRequest do
501
559
  client.should be_kind_of(EventMachine::HttpClient)
502
560
  http.response_header.status.should == 200
503
561
  http.response.should match(/callback_run=yes/)
504
- client.normalize_uri.should == Addressable::URI.parse('http://127.0.0.1:8080/')
505
562
  EventMachine.stop
506
563
  }
507
564
  }
508
565
  end
509
566
 
510
567
  it "should retrieve multiple cookies" do
511
- EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
568
+ EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
512
569
  EventMachine.run {
513
570
 
514
571
  http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
@@ -522,7 +579,7 @@ describe EventMachine::HttpRequest do
522
579
  }
523
580
  }
524
581
 
525
- EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
582
+ EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
526
583
  end
527
584
 
528
585
  context "connections via proxy" do
@@ -595,7 +652,7 @@ describe EventMachine::HttpRequest do
595
652
  # push should only be invoked after handshake is complete
596
653
  http.send(MSG)
597
654
  }
598
-
655
+
599
656
  http.stream { |chunk|
600
657
  chunk.should == MSG
601
658
  EventMachine.stop