em-http-request 0.2.7 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.

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