astro-em-http-request 0.2.3 → 0.2.6
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/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
|