astro-em-http-request 0.2.3 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +42 -4
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/examples/oauth-tweet.rb +50 -0
- data/ext/http11_client/http11_client.c +27 -1
- data/lib/em-http.rb +3 -5
- data/lib/em-http/client.rb +165 -53
- data/lib/em-http/mock.rb +74 -0
- data/lib/em-http/request.rb +45 -35
- data/spec/fixtures/google.ca +21 -0
- data/spec/hash_spec.rb +24 -0
- data/spec/helper.rb +7 -0
- data/spec/mock_spec.rb +72 -0
- data/{test/test_multi.rb → spec/multi_spec.rb} +3 -3
- data/{test/test_request.rb → spec/request_spec.rb} +192 -30
- data/{test → spec}/stallion.rb +75 -8
- data/{test → spec}/stub_server.rb +0 -0
- metadata +21 -14
- data/test/helper.rb +0 -5
- data/test/test_hash.rb +0 -19
data/lib/em-http/mock.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module EventMachine
|
2
|
+
class MockHttpRequest < EventMachine::HttpRequest
|
3
|
+
|
4
|
+
include HttpEncoding
|
5
|
+
|
6
|
+
class FakeHttpClient < EventMachine::HttpClient
|
7
|
+
|
8
|
+
def setup(response, uri)
|
9
|
+
@uri = uri
|
10
|
+
receive_data(response)
|
11
|
+
succeed(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def unbind
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
@@registry = nil
|
20
|
+
@@registry_count = nil
|
21
|
+
|
22
|
+
def self.reset_counts!
|
23
|
+
@@registry_count = Hash.new{|h,k| h[k] = Hash.new(0)}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.reset_registry!
|
27
|
+
@@registry = Hash.new{|h,k| h[k] = {}}
|
28
|
+
end
|
29
|
+
|
30
|
+
reset_counts!
|
31
|
+
reset_registry!
|
32
|
+
|
33
|
+
@@pass_through_requests = true
|
34
|
+
|
35
|
+
def self.pass_through_requests=(pass_through_requests)
|
36
|
+
@@pass_through_requests = pass_through_requests
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.pass_through_requests
|
40
|
+
@@pass_through_requests
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.register(uri, method, data)
|
44
|
+
method = method.to_s.upcase
|
45
|
+
@@registry[uri][method] = data
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.register_file(uri, method, file)
|
49
|
+
register(uri, method, File.read(file))
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.count(uri, method)
|
53
|
+
method = method.to_s.upcase
|
54
|
+
@@registry_count[uri][method]
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :real_send_request, :send_request
|
58
|
+
|
59
|
+
protected
|
60
|
+
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
|
64
|
+
client = FakeHttpClient.new(nil)
|
65
|
+
client.setup(fake, @uri)
|
66
|
+
client
|
67
|
+
elsif @@pass_through_requests
|
68
|
+
real_send_request
|
69
|
+
else
|
70
|
+
raise "this request #{query} for method #{@method} isn't registered, and pass_through_requests is current set to false"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/em-http/request.rb
CHANGED
@@ -9,7 +9,6 @@ module EventMachine
|
|
9
9
|
#
|
10
10
|
# == Example
|
11
11
|
#
|
12
|
-
#
|
13
12
|
# EventMachine.run {
|
14
13
|
# http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}
|
15
14
|
#
|
@@ -24,6 +23,8 @@ module EventMachine
|
|
24
23
|
#
|
25
24
|
|
26
25
|
class HttpRequest
|
26
|
+
|
27
|
+
attr_reader :options, :method
|
27
28
|
|
28
29
|
def initialize(host, headers = {})
|
29
30
|
@headers = headers
|
@@ -45,44 +46,53 @@ module EventMachine
|
|
45
46
|
# Called for each response body chunk (you may assume HTTP 200
|
46
47
|
# OK then)
|
47
48
|
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# Default : none.
|
55
|
-
#
|
56
|
-
# max_connection_duration: Integer
|
57
|
-
# Explictly specify a maximum number of second for the connection. This is useful to avoid dealing with never ending connections (AtomStream-like)
|
58
|
-
# Default : none.
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
def get options = {}; send_request(:get, options); end
|
63
|
-
def head options = {}; send_request(:head, options); end
|
64
|
-
def post options = {}; send_request(:post, options); end
|
49
|
+
|
50
|
+
def get options = {}, &blk; setup_request(:get, options, &blk); end
|
51
|
+
def head options = {}, &blk; setup_request(:head, options, &blk); end
|
52
|
+
def delete options = {}, &blk; setup_request(:delete, options, &blk); end
|
53
|
+
def put options = {}, &blk; setup_request(:put, options, &blk); end
|
54
|
+
def post options = {}, &blk; setup_request(:post, options, &blk); end
|
65
55
|
|
66
56
|
protected
|
67
57
|
|
68
|
-
def
|
58
|
+
def setup_request(method, options, &blk)
|
69
59
|
raise ArgumentError, "invalid request path" unless /^\// === @uri.path
|
60
|
+
@options = options
|
61
|
+
|
62
|
+
if proxy = options[:proxy]
|
63
|
+
@host_to_connect = proxy[:host]
|
64
|
+
@port_to_connect = proxy[:port]
|
65
|
+
else
|
66
|
+
@host_to_connect = options[:host] || @uri.host
|
67
|
+
@port_to_connect = options[:port] || @uri.port
|
68
|
+
end
|
69
|
+
|
70
|
+
# default connect & inactivity timeouts
|
71
|
+
@options[:timeout] = 10 if not @options[:timeout]
|
70
72
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
# Make sure the ports are set as Addressable::URI doesn't
|
74
|
+
# set the port if it isn't there
|
75
|
+
if @uri.scheme == "https"
|
76
|
+
@uri.port ||= 443
|
77
|
+
@port_to_connect ||= 443
|
78
|
+
else
|
79
|
+
@uri.port ||= 80
|
80
|
+
@port_to_connect ||= 80
|
81
|
+
end
|
82
|
+
|
83
|
+
@method = method.to_s.upcase
|
84
|
+
send_request(&blk)
|
85
|
+
end
|
86
|
+
|
87
|
+
def send_request(&blk)
|
78
88
|
begin
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
EventMachine.connect(
|
89
|
+
raise ArgumentError, "invalid host" unless @host_to_connect
|
90
|
+
raise ArgumentError, "invalid port" unless @port_to_connect
|
91
|
+
|
92
|
+
EventMachine.connect(@host_to_connect, @port_to_connect, EventMachine::HttpClient) { |c|
|
83
93
|
c.uri = @uri
|
84
|
-
c.method = method
|
85
|
-
c.options = options
|
94
|
+
c.method = @method
|
95
|
+
c.options = @options || {}
|
86
96
|
if options.has_key?(:timeout) && options[:timeout]
|
87
97
|
c.comm_inactivity_timeout = options[:timeout]
|
88
98
|
c.pending_connect_timeout = options[:timeout] if c.respond_to?(:pending_connect_timeout)
|
@@ -90,11 +100,11 @@ module EventMachine
|
|
90
100
|
c.comm_inactivity_timeout = 5
|
91
101
|
c.pending_connect_timeout = 5 if c.respond_to?(:pending_connect_timeout)
|
92
102
|
end
|
103
|
+
blk.call(c) unless blk.nil?
|
93
104
|
}
|
94
|
-
rescue
|
95
|
-
raise e unless e.message == "no connection"
|
105
|
+
rescue EventMachine::ConnectionError => e
|
96
106
|
conn = EventMachine::HttpClient.new("")
|
97
|
-
conn.on_error(
|
107
|
+
conn.on_error(e.message, true)
|
98
108
|
conn
|
99
109
|
end
|
100
110
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
HTTP/1.0 200 OK
|
2
|
+
Date: Mon, 16 Nov 2009 20:39:15 GMT
|
3
|
+
Expires: -1
|
4
|
+
Cache-Control: private, max-age=0
|
5
|
+
Content-Type: text/html; charset=ISO-8859-1
|
6
|
+
Set-Cookie: PREF=ID=9454187d21c4a6a6:TM=1258403955:LM=1258403955:S=2-mf1n5oV5yAeT9-; expires=Wed, 16-Nov-2011 20:39:15 GMT; path=/; domain=.google.ca
|
7
|
+
Set-Cookie: NID=28=lvxxVdiBQkCetu_WFaUxLyB7qPlHXS5OdAGYTqge_laVlCKVN8VYYeVBh4bNZiK_Oan2gm8oP9GA-FrZfMPC3ZMHeNq37MG2JH8AIW9LYucU8brOeuggMEbLNNXuiWg4; expires=Tue, 18-May-2010 20:39:15 GMT; path=/; domain=.google.ca; HttpOnly
|
8
|
+
Server: gws
|
9
|
+
X-XSS-Protection: 0
|
10
|
+
X-Cache: MISS from .
|
11
|
+
Via: 1.0 .:80 (squid)
|
12
|
+
Connection: close
|
13
|
+
|
14
|
+
<!doctype html><html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><title>Google</title><script>window.google={kEI:"eLgBS4LROqCQedKVvfUL",kEXPI:"17259,21329,21516,22107",kCSI:{e:"17259,21329,21516,22107",ei:"eLgBS4LROqCQedKVvfUL"},kHL:"en",time:function(){return(new Date).getTime()},log:function(b,d,c){var a=new Image,e=google,g=e.lc,f=e.li;a.onerror=(a.onload=(a.onabort=function(){delete g[f]}));g[f]=a;c=c||"/gen_204?atyp=i&ct="+b+"&cad="+d+"&zx="+google.time();a.src=c;e.li=f+1},lc:[],li:0};
|
15
|
+
window.google.sn="webhp";window.google.timers={load:{t:{start:(new Date).getTime()}}};try{}catch(b){}window.google.jsrt_kill=1;
|
16
|
+
var _gjwl=location;function _gjuc(){var e=_gjwl.href.indexOf("#");if(e>=0){var a=_gjwl.href.substring(e);if(a.indexOf("&q=")>0||a.indexOf("#q=")>=0){a=a.substring(1);if(a.indexOf("#")==-1){for(var c=0;c<a.length;){var d=c;if(a.charAt(d)=="&")++d;var b=a.indexOf("&",d);if(b==-1)b=a.length;var f=a.substring(d,b);if(f.indexOf("fp=")==0){a=a.substring(0,c)+a.substring(b,a.length);b=c}else if(f=="cad=h")return 0;c=b}_gjwl.href="/search?"+a+"&cad=h";return 1}}}return 0}function _gjp(){!(window._gjwl.hash&&
|
17
|
+
window._gjuc())&&setTimeout(_gjp,500)};
|
18
|
+
window._gjp && _gjp()</script><style>td{line-height:.8em;}.gac_m td{line-height:17px;}form{margin-bottom:20px;}body,td,a,p,.h{font-family:arial,sans-serif}.h{color:#36c;font-size:20px}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}em{font-weight:bold;font-style:normal}.lst{font:17px arial,sans-serif;margin-bottom:.2em;vertical-align:bottom;}input{font-family:inherit}.lsb,.gac_sb{font-size:15px;height:1.85em!important;margin:.2em;}#gbar{height:22px}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}#guser{padding-bottom:7px !important;text-align:right}#gbar,#guser{font-size:13px;padding-top:1px !important}@media all{.gb1,.gb3{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb3,a.gb4{color:#00c !important}.gb3{text-decoration:none}</style><script>google.y={};google.x=function(e,g){google.y[e.id]=[e,g];return false};</script></head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onload="document.f.q.focus();if(document.images)new Image().src='/images/nav_logo7.png'" topmargin=3 marginheight=3><textarea id=csi style=display:none></textarea><div id=gbar><nobr><b class=gb1>Web</b> <a href="http://images.google.ca/imghp?hl=en&tab=wi" class=gb1>Images</a> <a href="http://video.google.ca/?hl=en&tab=wv" class=gb1>Videos</a> <a href="http://maps.google.ca/maps?hl=en&tab=wl" class=gb1>Maps</a> <a href="http://news.google.ca/nwshp?hl=en&tab=wn" class=gb1>News</a> <a href="http://groups.google.ca/grphp?hl=en&tab=wg" class=gb1>Groups</a> <a href="http://mail.google.com/mail/?hl=en&tab=wm" class=gb1>Gmail</a> <a href="http://www.google.ca/intl/en/options/" class=gb3><u>more</u> »</a></nobr></div><div id=guser width=100%><nobr><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.ca/ig%3Fhl%3Den%26source%3Diglk&usg=AFQjCNG2Kt7TgMZuV7Fl3FeeTOmTWMvggA" class=gb4>iGoogle</a> | <a href="/preferences?hl=en" class=gb4>Search settings</a> | <a href="https://www.google.com/accounts/Login?hl=en&continue=http://www.google.ca/" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div><center><br clear=all id=lgpd><img alt="Google" height=110 src="/intl/en_ca/images/logo.gif" width=276 id=logo onload="window.lol&&lol()"><br><br><form action="/search" name=f><table cellpadding=0 cellspacing=0><tr valign=top><td width=25%> </td><td align=center nowrap><input name=hl type=hidden value=en><input name=source type=hidden value=hp><input type=hidden name=ie value="ISO-8859-1"><input autocomplete="off" maxlength=2048 name=q size=55 class=lst title="Google Search" value=""><br><input name=btnG type=submit value="Google Search" class=lsb><input name=btnI type=submit value="I'm Feeling Lucky" class=lsb></td><td nowrap width=25% align=left><font size=-2> <a href=/advanced_search?hl=en>Advanced Search</a><br> <a href=/language_tools?hl=en>Language Tools</a></font></td></tr><tr><td align=center colspan=3><font size=-1><span style="text-align:left">Search: <input id=all type=radio name=meta value="" checked><label for=all> the web </label> <input id=cty type=radio name=meta value="cr=countryCA"><label for=cty> pages from Canada </label> </span></font></td></tr></table></form><br><font size=-1>Google.ca offered in: <a href="http://www.google.ca/setprefs?sig=0_XtMil90_yvnNEW8dIglASaHCVhU=&hl=fr">fran?ais</a> </font><br><br><br><font size=-1><a href="/intl/en/ads/">Advertising Programs</a> - <a href="/services/">Business Solutions</a> - <a href="/intl/en/about.html">About Google</a> - <a href="http://www.google.com/ncr">Go to Google.com</a></font><p><font size=-2>©2009 - <a href="/intl/en/privacy.html">Privacy</a></font></p></center><div id=xjsd></div><div id=xjsi><script>if(google.y)google.y.first=[];if(google.y)google.y.first=[];google.dstr=[];google.rein=[];window.setTimeout(function(){var a=document.createElement("script");a.src="/extern_js/f/CgJlbhICY2EgACswCjhBQB0sKzAOOAksKzAYOAQsKzAlOMmIASwrMCY4BywrMCc4Aiw/n_sssePDGvc.js";(document.getElementById("xjsd")||document.body).appendChild(a)},0);
|
19
|
+
;google.y.first.push(function(){google.ac.b=true;google.ac.i(document.f,document.f.q,'','')});google.xjs&&google.j&&google.j.xi&&google.j.xi()</script></div><script>(function(){
|
20
|
+
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();
|
21
|
+
})();
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec/helper'
|
2
|
+
|
3
|
+
describe Hash do
|
4
|
+
|
5
|
+
describe ".to_params" do
|
6
|
+
it "should transform a basic hash into HTTP POST Params" do
|
7
|
+
{:a => "alpha", :b => "beta"}.to_params.split("&").should include "a=alpha"
|
8
|
+
{:a => "alpha", :b => "beta"}.to_params.split("&").should include "b=beta"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should transform a more complex hash into HTTP POST Params" do
|
12
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "a=a"
|
13
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[0]=c"
|
14
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[1]=d"
|
15
|
+
{:a => "a", :b => ["c", "d", "e"]}.to_params.split("&").should include "b[2]=e"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should transform a very complex hash into HTTP POST Params" do
|
19
|
+
params = {:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}.to_params.split("&")
|
20
|
+
params.should include "a=a"
|
21
|
+
params.should include "b[0][d]=d"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/helper.rb
ADDED
data/spec/mock_spec.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec/helper'
|
2
|
+
|
3
|
+
describe 'em-http mock' do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
EventMachine::MockHttpRequest.reset_registry!
|
7
|
+
EventMachine::MockHttpRequest.reset_counts!
|
8
|
+
end
|
9
|
+
|
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'))
|
12
|
+
EM.run {
|
13
|
+
|
14
|
+
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
15
|
+
http.errback { fail }
|
16
|
+
http.callback {
|
17
|
+
http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\n\n", 2).last
|
18
|
+
EventMachine.stop
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should serve a fake http request from a string" do
|
26
|
+
data = <<-HEREDOC
|
27
|
+
HTTP/1.0 200 OK
|
28
|
+
Date: Mon, 16 Nov 2009 20:39:15 GMT
|
29
|
+
Expires: -1
|
30
|
+
Cache-Control: private, max-age=0
|
31
|
+
Content-Type: text/html; charset=ISO-8859-1
|
32
|
+
Set-Cookie: PREF=ID=9454187d21c4a6a6:TM=1258403955:LM=1258403955:S=2-mf1n5oV5yAeT9-; expires=Wed, 16-Nov-2011 20:39:15 GMT; path=/; domain=.google.ca
|
33
|
+
Set-Cookie: NID=28=lvxxVdiBQkCetu_WFaUxLyB7qPlHXS5OdAGYTqge_laVlCKVN8VYYeVBh4bNZiK_Oan2gm8oP9GA-FrZfMPC3ZMHeNq37MG2JH8AIW9LYucU8brOeuggMEbLNNXuiWg4; expires=Tue, 18-May-2010 20:39:15 GMT; path=/; domain=.google.ca; HttpOnly
|
34
|
+
Server: gws
|
35
|
+
X-XSS-Protection: 0
|
36
|
+
X-Cache: MISS from .
|
37
|
+
Via: 1.0 .:80 (squid)
|
38
|
+
Connection: close
|
39
|
+
|
40
|
+
<!doctype html><html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><title>Google</title><script>window.google={kEI:"eLgBS4LROqCQedKVvfUL",kEXPI:"17259,21329,21516,22107",kCSI:{e:"17259,21329,21516,22107",ei:"eLgBS4LROqCQedKVvfUL"},kHL:"en",time:function(){return(new Date).getTime()},log:function(b,d,c){var a=new Image,e=google,g=e.lc,f=e.li;a.onerror=(a.onload=(a.onabort=function(){delete g[f]}));g[f]=a;c=c||"/gen_204?atyp=i&ct="+b+"&cad="+d+"&zx="+google.time();a.src=c;e.li=f+1},lc:[],li:0};
|
41
|
+
window.google.sn="webhp";window.google.timers={load:{t:{start:(new Date).getTime()}}};try{}catch(b){}window.google.jsrt_kill=1;
|
42
|
+
var _gjwl=location;function _gjuc(){var e=_gjwl.href.indexOf("#");if(e>=0){var a=_gjwl.href.substring(e);if(a.indexOf("&q=")>0||a.indexOf("#q=")>=0){a=a.substring(1);if(a.indexOf("#")==-1){for(var c=0;c<a.length;){var d=c;if(a.charAt(d)=="&")++d;var b=a.indexOf("&",d);if(b==-1)b=a.length;var f=a.substring(d,b);if(f.indexOf("fp=")==0){a=a.substring(0,c)+a.substring(b,a.length);b=c}else if(f=="cad=h")return 0;c=b}_gjwl.href="/search?"+a+"&cad=h";return 1}}}return 0}function _gjp(){!(window._gjwl.hash&&
|
43
|
+
window._gjuc())&&setTimeout(_gjp,500)};
|
44
|
+
window._gjp && _gjp()</script><style>td{line-height:.8em;}.gac_m td{line-height:17px;}form{margin-bottom:20px;}body,td,a,p,.h{font-family:arial,sans-serif}.h{color:#36c;font-size:20px}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}em{font-weight:bold;font-style:normal}.lst{font:17px arial,sans-serif;margin-bottom:.2em;vertical-align:bottom;}input{font-family:inherit}.lsb,.gac_sb{font-size:15px;height:1.85em!important;margin:.2em;}#gbar{height:22px}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}#guser{padding-bottom:7px !important;text-align:right}#gbar,#guser{font-size:13px;padding-top:1px !important}@media all{.gb1,.gb3{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb3,a.gb4{color:#00c !important}.gb3{text-decoration:none}</style><script>google.y={};google.x=function(e,g){google.y[e.id]=[e,g];return false};</script></head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onload="document.f.q.focus();if(document.images)new Image().src='/images/nav_logo7.png'" topmargin=3 marginheight=3><textarea id=csi style=display:none></textarea><div id=gbar><nobr><b class=gb1>Web</b> <a href="http://images.google.ca/imghp?hl=en&tab=wi" class=gb1>Images</a> <a href="http://video.google.ca/?hl=en&tab=wv" class=gb1>Videos</a> <a href="http://maps.google.ca/maps?hl=en&tab=wl" class=gb1>Maps</a> <a href="http://news.google.ca/nwshp?hl=en&tab=wn" class=gb1>News</a> <a href="http://groups.google.ca/grphp?hl=en&tab=wg" class=gb1>Groups</a> <a href="http://mail.google.com/mail/?hl=en&tab=wm" class=gb1>Gmail</a> <a href="http://www.google.ca/intl/en/options/" class=gb3><u>more</u> »</a></nobr></div><div id=guser width=100%><nobr><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.ca/ig%3Fhl%3Den%26source%3Diglk&usg=AFQjCNG2Kt7TgMZuV7Fl3FeeTOmTWMvggA" class=gb4>iGoogle</a> | <a href="/preferences?hl=en" class=gb4>Search settings</a> | <a href="https://www.google.com/accounts/Login?hl=en&continue=http://www.google.ca/" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div><center><br clear=all id=lgpd><img alt="Google" height=110 src="/intl/en_ca/images/logo.gif" width=276 id=logo onload="window.lol&&lol()"><br><br><form action="/search" name=f><table cellpadding=0 cellspacing=0><tr valign=top><td width=25%> </td><td align=center nowrap><input name=hl type=hidden value=en><input name=source type=hidden value=hp><input type=hidden name=ie value="ISO-8859-1"><input autocomplete="off" maxlength=2048 name=q size=55 class=lst title="Google Search" value=""><br><input name=btnG type=submit value="Google Search" class=lsb><input name=btnI type=submit value="I'm Feeling Lucky" class=lsb></td><td nowrap width=25% align=left><font size=-2> <a href=/advanced_search?hl=en>Advanced Search</a><br> <a href=/language_tools?hl=en>Language Tools</a></font></td></tr><tr><td align=center colspan=3><font size=-1><span style="text-align:left">Search: <input id=all type=radio name=meta value="" checked><label for=all> the web </label> <input id=cty type=radio name=meta value="cr=countryCA"><label for=cty> pages from Canada </label> </span></font></td></tr></table></form><br><font size=-1>Google.ca offered in: <a href="http://www.google.ca/setprefs?sig=0_XtMil90_yvnNEW8dIglASaHCVhU=&hl=fr">fran?ais</a> </font><br><br><br><font size=-1><a href="/intl/en/ads/">Advertising Programs</a> - <a href="/services/">Business Solutions</a> - <a href="/intl/en/about.html">About Google</a> - <a href="http://www.google.com/ncr">Go to Google.com</a></font><p><font size=-2>©2009 - <a href="/intl/en/privacy.html">Privacy</a></font></p></center><div id=xjsd></div><div id=xjsi><script>if(google.y)google.y.first=[];if(google.y)google.y.first=[];google.dstr=[];google.rein=[];window.setTimeout(function(){var a=document.createElement("script");a.src="/extern_js/f/CgJlbhICY2EgACswCjhBQB0sKzAOOAksKzAYOAQsKzAlOMmIASwrMCY4BywrMCc4Aiw/n_sssePDGvc.js";(document.getElementById("xjsd")||document.body).appendChild(a)},0);
|
45
|
+
;google.y.first.push(function(){google.ac.b=true;google.ac.i(document.f,document.f.q,'','')});google.xjs&&google.j&&google.j.xi&&google.j.xi()</script></div><script>(function(){
|
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
|
+
})();
|
48
|
+
HEREDOC
|
49
|
+
EventMachine::MockHttpRequest.register('http://www.google.ca:80/', :get, data)
|
50
|
+
EventMachine.run {
|
51
|
+
|
52
|
+
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
53
|
+
http.errback { fail }
|
54
|
+
http.callback {
|
55
|
+
http.response.should == data.split("\n\n", 2).last
|
56
|
+
EventMachine.stop
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise an exception if pass-thru is disabled" do
|
64
|
+
EventMachine::MockHttpRequest.pass_through_requests = false
|
65
|
+
EventMachine.run {
|
66
|
+
proc {
|
67
|
+
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
68
|
+
}.should raise_error
|
69
|
+
EventMachine.stop
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'spec/helper'
|
2
|
+
require 'spec/stallion'
|
3
3
|
|
4
4
|
describe EventMachine::MultiRequest do
|
5
5
|
|
@@ -11,7 +11,7 @@ describe EventMachine::MultiRequest do
|
|
11
11
|
|
12
12
|
# add multiple requests to the multi-handler
|
13
13
|
multi.add(EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(:query => {:q => 'test'}))
|
14
|
-
multi.add(EventMachine::HttpRequest.new('http://0.0.0.0/').get(:timeout => 1))
|
14
|
+
multi.add(EventMachine::HttpRequest.new('http://0.0.0.0:8083/').get(:timeout => 1))
|
15
15
|
|
16
16
|
multi.callback {
|
17
17
|
# verify successfull request
|
@@ -1,14 +1,14 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
|
1
|
+
require 'spec/helper'
|
2
|
+
require 'spec/stallion'
|
3
|
+
require 'spec/stub_server'
|
4
|
+
|
5
5
|
describe EventMachine::HttpRequest do
|
6
6
|
|
7
7
|
def failed
|
8
8
|
EventMachine.stop
|
9
9
|
fail
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
it "should fail GET on DNS timeout" do
|
13
13
|
EventMachine.run {
|
14
14
|
http = EventMachine::HttpRequest.new('http://127.1.1.1/').get :timeout => 1
|
@@ -26,7 +26,7 @@ describe EventMachine::HttpRequest do
|
|
26
26
|
http.callback { failed }
|
27
27
|
http.errback {
|
28
28
|
http.response_header.status.should == 0
|
29
|
-
http.errors.should match(/
|
29
|
+
http.errors.should match(/unable to resolve server address/)
|
30
30
|
EventMachine.stop
|
31
31
|
}
|
32
32
|
}
|
@@ -54,7 +54,7 @@ describe EventMachine::HttpRequest do
|
|
54
54
|
}
|
55
55
|
}
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
it "should perform successfull GET with a URI passed as argument" do
|
59
59
|
EventMachine.run {
|
60
60
|
uri = URI.parse('http://127.0.0.1:8080/')
|
@@ -66,7 +66,7 @@ describe EventMachine::HttpRequest do
|
|
66
66
|
http.response.should match(/Hello/)
|
67
67
|
EventMachine.stop
|
68
68
|
}
|
69
|
-
}
|
69
|
+
}
|
70
70
|
end
|
71
71
|
|
72
72
|
it "should perform successfull HEAD with a URI passed as argument" do
|
@@ -80,7 +80,22 @@ describe EventMachine::HttpRequest do
|
|
80
80
|
http.response.should == ""
|
81
81
|
EventMachine.stop
|
82
82
|
}
|
83
|
-
}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
# should be no different than a GET
|
87
|
+
it "should perform successfull DELETE with a URI passed as argument" do
|
88
|
+
EventMachine.run {
|
89
|
+
uri = URI.parse('http://127.0.0.1:8080/')
|
90
|
+
http = EventMachine::HttpRequest.new(uri).delete
|
91
|
+
|
92
|
+
http.errback { failed }
|
93
|
+
http.callback {
|
94
|
+
http.response_header.status.should == 200
|
95
|
+
http.response.should == ""
|
96
|
+
EventMachine.stop
|
97
|
+
}
|
98
|
+
}
|
84
99
|
end
|
85
100
|
|
86
101
|
it "should return 404 on invalid path" do
|
@@ -88,7 +103,7 @@ describe EventMachine::HttpRequest do
|
|
88
103
|
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get
|
89
104
|
|
90
105
|
http.errback { failed }
|
91
|
-
http.callback {
|
106
|
+
http.callback {
|
92
107
|
http.response_header.status.should == 404
|
93
108
|
EventMachine.stop
|
94
109
|
}
|
@@ -134,6 +149,20 @@ describe EventMachine::HttpRequest do
|
|
134
149
|
}
|
135
150
|
end
|
136
151
|
|
152
|
+
# should be no different than a POST
|
153
|
+
it "should perform successfull PUT" do
|
154
|
+
EventMachine.run {
|
155
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').put :body => "data"
|
156
|
+
|
157
|
+
http.errback { failed }
|
158
|
+
http.callback {
|
159
|
+
http.response_header.status.should == 200
|
160
|
+
http.response.should match(/data/)
|
161
|
+
EventMachine.stop
|
162
|
+
}
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
137
166
|
it "should perform successfull POST" do
|
138
167
|
EventMachine.run {
|
139
168
|
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => "data"
|
@@ -154,13 +183,13 @@ describe EventMachine::HttpRequest do
|
|
154
183
|
http.errback { failed }
|
155
184
|
http.callback {
|
156
185
|
http.response_header.status.should == 200
|
157
|
-
|
186
|
+
|
158
187
|
http.response.should match(/key1=1&key2\[0\]=2&key2\[1\]=3/)
|
159
188
|
EventMachine.stop
|
160
189
|
}
|
161
190
|
}
|
162
191
|
end
|
163
|
-
|
192
|
+
|
164
193
|
it "should perform successfull POST with Ruby Hash/Array as params and with the correct content length" do
|
165
194
|
EventMachine.run {
|
166
195
|
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_length').post :body => {"key1" => "data1"}
|
@@ -168,7 +197,7 @@ describe EventMachine::HttpRequest do
|
|
168
197
|
http.errback { failed }
|
169
198
|
http.callback {
|
170
199
|
http.response_header.status.should == 200
|
171
|
-
|
200
|
+
|
172
201
|
http.response.to_i.should == 10
|
173
202
|
EventMachine.stop
|
174
203
|
}
|
@@ -214,6 +243,20 @@ describe EventMachine::HttpRequest do
|
|
214
243
|
}
|
215
244
|
end
|
216
245
|
|
246
|
+
it "should send proper OAuth auth header" do
|
247
|
+
EventMachine.run {
|
248
|
+
oauth_header = 'OAuth oauth_nonce="oqwgSYFUD87MHmJJDv7bQqOF2EPnVus7Wkqj5duNByU", b=c, d=e'
|
249
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/oauth_auth').get :head => {'authorization' => oauth_header}
|
250
|
+
|
251
|
+
http.errback { failed }
|
252
|
+
http.callback {
|
253
|
+
http.response_header.status.should == 200
|
254
|
+
http.response.should == oauth_header
|
255
|
+
EventMachine.stop
|
256
|
+
}
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
217
260
|
it "should work with keep-alive servers" do
|
218
261
|
EventMachine.run {
|
219
262
|
|
@@ -275,7 +318,7 @@ describe EventMachine::HttpRequest do
|
|
275
318
|
it "should optionally pass the response body progressively" do
|
276
319
|
EventMachine.run {
|
277
320
|
body = ''
|
278
|
-
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
321
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
279
322
|
|
280
323
|
http.errback { failed }
|
281
324
|
http.stream { |chunk| body += chunk }
|
@@ -418,7 +461,7 @@ describe EventMachine::HttpRequest do
|
|
418
461
|
}
|
419
462
|
end
|
420
463
|
|
421
|
-
|
464
|
+
it "should not override content-type when passing in ruby hash/array for body" do
|
422
465
|
EventMachine.run {
|
423
466
|
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({
|
424
467
|
:body => {:a => :b}, :head => {'content-type' => 'text'}})
|
@@ -437,34 +480,153 @@ describe EventMachine::HttpRequest do
|
|
437
480
|
EventMachine.run {
|
438
481
|
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/relative-location').get
|
439
482
|
|
483
|
+
http.errback { failed }
|
484
|
+
http.callback {
|
485
|
+
http.response_header['LOCATION'].should == 'http://127.0.0.1:8080/forwarded'
|
486
|
+
EventMachine.stop
|
487
|
+
}
|
488
|
+
}
|
489
|
+
end
|
490
|
+
|
491
|
+
it 'should let you pass a block to be called once the client is created' do
|
492
|
+
client = nil
|
493
|
+
EventMachine.run {
|
494
|
+
request = EventMachine::HttpRequest.new('http://127.0.0.1:8080/')
|
495
|
+
http = request.post { |c|
|
496
|
+
c.options[:body] = {:callback_run => 'yes'}
|
497
|
+
client = c
|
498
|
+
}
|
499
|
+
http.errback { failed }
|
500
|
+
http.callback {
|
501
|
+
client.should be_kind_of(EventMachine::HttpClient)
|
502
|
+
http.response_header.status.should == 200
|
503
|
+
http.response.should match(/callback_run=yes/)
|
504
|
+
client.normalize_uri.should == Addressable::URI.parse('http://127.0.0.1:8080/')
|
505
|
+
EventMachine.stop
|
506
|
+
}
|
507
|
+
}
|
508
|
+
end
|
509
|
+
|
510
|
+
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'))
|
512
|
+
EventMachine.run {
|
513
|
+
|
514
|
+
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
515
|
+
http.errback { fail }
|
516
|
+
http.callback {
|
517
|
+
c1 = "PREF=ID=9454187d21c4a6a6:TM=1258403955:LM=1258403955:S=2-mf1n5oV5yAeT9-; expires=Wed, 16-Nov-2011 20:39:15 GMT; path=/; domain=.google.ca"
|
518
|
+
c2 = "NID=28=lvxxVdiBQkCetu_WFaUxLyB7qPlHXS5OdAGYTqge_laVlCKVN8VYYeVBh4bNZiK_Oan2gm8oP9GA-FrZfMPC3ZMHeNq37MG2JH8AIW9LYucU8brOeuggMEbLNNXuiWg4; expires=Tue, 18-May-2010 20:39:15 GMT; path=/; domain=.google.ca; HttpOnly"
|
519
|
+
http.response_header.cookie.should == [c1, c2]
|
520
|
+
|
521
|
+
EventMachine.stop
|
522
|
+
}
|
523
|
+
}
|
524
|
+
|
525
|
+
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get).should == 1
|
526
|
+
end
|
527
|
+
|
528
|
+
context "connections via proxy" do
|
529
|
+
|
530
|
+
it "should work with proxy servers" do
|
531
|
+
EventMachine.run {
|
532
|
+
|
533
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :proxy => {:host => '127.0.0.1', :port => 8082}
|
534
|
+
|
535
|
+
http.errback { p http.inspect; failed }
|
536
|
+
http.callback {
|
537
|
+
http.response_header.status.should == 200
|
538
|
+
http.response.should == 'Hello, World!'
|
539
|
+
EventMachine.stop
|
540
|
+
}
|
541
|
+
}
|
542
|
+
end
|
543
|
+
|
544
|
+
it "should proxy POST data" do
|
545
|
+
EventMachine.run {
|
546
|
+
|
547
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({
|
548
|
+
:body => "data", :proxy => {:host => '127.0.0.1', :port => 8082}})
|
549
|
+
|
440
550
|
http.errback { failed }
|
441
551
|
http.callback {
|
442
|
-
http.response_header
|
552
|
+
http.response_header.status.should == 200
|
553
|
+
http.response.should match(/data/)
|
443
554
|
EventMachine.stop
|
444
555
|
}
|
445
556
|
}
|
557
|
+
end
|
446
558
|
end
|
447
559
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
560
|
+
context "websocket connection" do
|
561
|
+
# Spec: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55
|
562
|
+
#
|
563
|
+
# ws.onopen = http.callback
|
564
|
+
# ws.onmessage = http.stream { |msg| }
|
565
|
+
# ws.errback = no connection
|
566
|
+
#
|
567
|
+
|
568
|
+
it "should invoke errback on failed upgrade" do
|
569
|
+
EventMachine.run {
|
570
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0
|
571
|
+
|
572
|
+
http.callback { failed }
|
573
|
+
http.errback {
|
574
|
+
http.response_header.status.should == 200
|
453
575
|
EventMachine.stop
|
454
576
|
}
|
455
|
-
http.callback { failed }
|
456
577
|
}
|
457
|
-
|
578
|
+
end
|
458
579
|
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
580
|
+
it "should complete websocket handshake and transfer data from client to server and back" do
|
581
|
+
EventMachine.run {
|
582
|
+
MSG = "hello bi-directional data exchange"
|
583
|
+
|
584
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws|
|
585
|
+
ws.onmessage {|msg| ws.send msg}
|
586
|
+
end
|
587
|
+
|
588
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 1
|
589
|
+
http.errback { failed }
|
590
|
+
http.callback {
|
591
|
+
http.response_header.status.should == 101
|
592
|
+
http.response_header['CONNECTION'].should match(/Upgrade/)
|
593
|
+
http.response_header['UPGRADE'].should match(/WebSocket/)
|
594
|
+
|
595
|
+
# push should only be invoked after handshake is complete
|
596
|
+
http.send(MSG)
|
597
|
+
}
|
598
|
+
|
599
|
+
http.stream { |chunk|
|
600
|
+
chunk.should == MSG
|
464
601
|
EventMachine.stop
|
465
602
|
}
|
466
|
-
http.callback { failed }
|
467
603
|
}
|
468
|
-
|
604
|
+
end
|
469
605
|
|
606
|
+
it "should split multiple messages from websocket server into separate stream callbacks" do
|
607
|
+
EM.run do
|
608
|
+
messages = %w[1 2]
|
609
|
+
recieved = []
|
610
|
+
|
611
|
+
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws|
|
612
|
+
ws.onopen {
|
613
|
+
ws.send messages[0]
|
614
|
+
ws.send messages[1]
|
615
|
+
}
|
616
|
+
end
|
617
|
+
|
618
|
+
EventMachine.add_timer(0.1) do
|
619
|
+
http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 0
|
620
|
+
http.errback { failed }
|
621
|
+
http.callback { http.response_header.status.should == 101 }
|
622
|
+
http.stream {|msg|
|
623
|
+
msg.should == messages[recieved.size]
|
624
|
+
recieved.push msg
|
625
|
+
|
626
|
+
EventMachine.stop if recieved.size == messages.size
|
627
|
+
}
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
470
632
|
end
|