em-http-request 0.2.10 → 0.2.11
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/LICENSE +58 -58
- data/README.md +157 -0
- data/Rakefile +110 -106
- data/VERSION +1 -1
- data/em-http-request.gemspec +5 -4
- data/examples/fetch.rb +30 -30
- data/examples/fibered-http.rb +38 -38
- data/examples/oauth-tweet.rb +49 -49
- data/examples/websocket-handler.rb +28 -28
- data/examples/websocket-server.rb +8 -8
- data/ext/buffer/extconf.rb +53 -53
- data/ext/http11_client/ext_help.h +14 -14
- data/ext/http11_client/extconf.rb +6 -6
- data/ext/http11_client/http11_client.c +328 -328
- data/ext/http11_client/http11_parser.c +418 -418
- data/ext/http11_client/http11_parser.h +48 -48
- data/ext/http11_client/http11_parser.rl +170 -170
- data/lib/em-http-request.rb +1 -1
- data/lib/em-http.rb +2 -1
- data/lib/em-http/client.rb +58 -26
- data/lib/em-http/core_ext/bytesize.rb +5 -5
- data/lib/em-http/core_ext/hash.rb +53 -53
- data/lib/em-http/decoders.rb +122 -122
- data/lib/em-http/http_options.rb +33 -31
- data/lib/em-http/mock.rb +90 -50
- data/lib/em-http/multi.rb +21 -17
- data/lib/em-http/request.rb +1 -0
- data/spec/fixtures/google.ca +20 -21
- data/spec/hash_spec.rb +24 -24
- data/spec/helper.rb +3 -2
- data/spec/mock_spec.rb +79 -34
- data/spec/multi_spec.rb +27 -10
- data/spec/request_spec.rb +198 -65
- data/spec/spec.opts +7 -0
- data/spec/stallion.rb +59 -2
- data/spec/stub_server.rb +22 -22
- metadata +6 -5
- data/README.rdoc +0 -138
data/lib/em-http/multi.rb
CHANGED
@@ -1,51 +1,55 @@
|
|
1
1
|
module EventMachine
|
2
2
|
|
3
|
-
# EventMachine based Multi request client, based on a streaming HTTPRequest class,
|
3
|
+
# EventMachine based Multi request client, based on a streaming HTTPRequest class,
|
4
4
|
# which allows you to open multiple parallel connections and return only when all
|
5
5
|
# of them finish. (i.e. ideal for parallelizing workloads)
|
6
|
-
#
|
6
|
+
#
|
7
7
|
# == Example
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# EventMachine.run {
|
10
|
-
#
|
10
|
+
#
|
11
11
|
# multi = EventMachine::MultiRequest.new
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# # add multiple requests to the multi-handler
|
14
14
|
# multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
|
15
15
|
# multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
|
16
|
-
#
|
16
|
+
#
|
17
17
|
# multi.callback {
|
18
18
|
# p multi.responses[:succeeded]
|
19
19
|
# p multi.responses[:failed]
|
20
|
-
#
|
20
|
+
#
|
21
21
|
# EventMachine.stop
|
22
22
|
# }
|
23
23
|
# }
|
24
|
-
#
|
25
|
-
|
24
|
+
#
|
25
|
+
|
26
26
|
class MultiRequest
|
27
27
|
include EventMachine::Deferrable
|
28
28
|
|
29
29
|
attr_reader :requests, :responses
|
30
|
-
|
31
|
-
def initialize
|
32
|
-
@requests
|
30
|
+
|
31
|
+
def initialize(conns=[], &block)
|
32
|
+
@requests = []
|
33
33
|
@responses = {:succeeded => [], :failed => []}
|
34
|
+
|
35
|
+
conns.each {|conn| add(conn)}
|
36
|
+
callback(&block) if block_given?
|
34
37
|
end
|
35
|
-
|
38
|
+
|
36
39
|
def add(conn)
|
37
40
|
@requests.push(conn)
|
38
41
|
|
39
42
|
conn.callback { @responses[:succeeded].push(conn); check_progress }
|
40
43
|
conn.errback { @responses[:failed].push(conn); check_progress }
|
41
44
|
end
|
42
|
-
|
45
|
+
|
43
46
|
protected
|
44
|
-
|
47
|
+
|
45
48
|
# invoke callback if all requests have completed
|
46
49
|
def check_progress
|
47
|
-
succeed if (@responses[:succeeded].size +
|
50
|
+
succeed(self) if (@responses[:succeeded].size +
|
51
|
+
@responses[:failed].size) == @requests.size
|
48
52
|
end
|
49
|
-
|
53
|
+
|
50
54
|
end
|
51
55
|
end
|
data/lib/em-http/request.rb
CHANGED
data/spec/fixtures/google.ca
CHANGED
@@ -1,21 +1,20 @@
|
|
1
|
-
HTTP/1.
|
2
|
-
Date:
|
3
|
-
Expires: -1
|
4
|
-
Cache-Control: private, max-age=0
|
5
|
-
Content-Type: text/html; charset=ISO-8859-1
|
6
|
-
Set-Cookie: PREF=ID=
|
7
|
-
Set-Cookie: NID=
|
8
|
-
Server: gws
|
9
|
-
X-XSS-Protection:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
window.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
})();
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Date: Sat, 14 Aug 2010 21:58:26 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=11955ae9690fd292:TM=1281823106:LM=1281823106:S=wHdloFqGQ_OLSE92; expires=Mon, 13-Aug-2012 21:58:26 GMT; path=/; domain=.google.ca
|
7
|
+
Set-Cookie: NID=37=USTdOsxOSMbLjphkJ3S5Ueua3Yc23COXuK_pbztcHx7JoyhomwQySrvebCf3_u8eyrBiLWssVzaZcEOiKGEJbNdy8lRhnq_mfrdz693LaMjNPh__ccW4sgn1ZO6nQltE; expires=Sun, 13-Feb-2011 21:58:26 GMT; path=/; domain=.google.ca; HttpOnly
|
8
|
+
Server: gws
|
9
|
+
X-XSS-Protection: 1; mode=block
|
10
|
+
|
11
|
+
<!doctype html><html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><title>Google</title><script>window.google={kEI:"ghFnTJiGG9WhnQf4o9X8BA",kEXPI:"25913,25976,26060",kCSI:{e:"25913,25976,26060",ei:"ghFnTJiGG9WhnQf4o9X8BA",expi:"25913,25976,26060"},ml:function(){},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,Toolbelt:{}};
|
12
|
+
window.google.sn="webhp";window.google.timers={load:{t:{start:(new Date).getTime()}}};try{}catch(u){}window.google.jsrt_kill=1;
|
13
|
+
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&&
|
14
|
+
window._gjuc())&&setTimeout(_gjp,500)};
|
15
|
+
window._gjp && _gjp()</script><style id=gstyle>body{margin:0}#gog{padding:3px 8px 0}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{width:496px}.tiah{width:458px}input{font-family:inherit}a.gb1,a.gb2,a.gb3,a.gb4{color:#11c !important}#gbar,#guser{font-size:13px;padding-top:1px !important}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{color:#00c !important}body{background:#fff;color:black}input{-moz-box-sizing:content-box}a{color:#11c;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#4272db}a:visited{color:#551a8b}a.gb1,a.gb4{text-decoration:underline}a.gb3:hover{text-decoration:none}#ghead a.gb2:hover{color:#fff!important}.ds{display:-moz-inline-box}.ds{border-bottom:solid 1px #e7e7e7;border-right:solid 1px #e7e7e7;display:inline-block;margin:3px 0 4px;margin-left:4px}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px;}.lsbb{background:#eee;border:solid 1px;border-color:#ccc #999 #999 #ccc;height:30px;display:block}.lsb{background:url(/images/srpr/nav_logo14.png) bottom;font:15px arial,sans-serif;border:none;color:#000;cursor:pointer;height:30px;margin:0;outline:0;vertical-align:top}.lsb:active{background:#ccc}.lst:focus{outline:none}.ftl,#fll a{margin:0 12px}#addlang a{padding:0 3px}.gac_v div{display:none}.gac_v .gac_v2,.gac_bt{display:block!important}</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/srpr/nav_logo14.png'" ><textarea id=csi style=display:none></textarea><div id=ghead><div id=gbar><nobr><b class=gb1>Web</b> <a onclick=gbar.qs(this) href="http://www.google.ca/imghp?hl=en&tab=wi" class=gb1>Images</a> <a onclick=gbar.qs(this) href="http://video.google.ca/?hl=en&tab=wv" class=gb1>Videos</a> <a onclick=gbar.qs(this) href="http://maps.google.ca/maps?hl=en&tab=wl" class=gb1>Maps</a> <a onclick=gbar.qs(this) href="http://news.google.ca/nwshp?hl=en&tab=wn" class=gb1>News</a> <a onclick=gbar.qs(this) href="http://books.google.ca/bkshp?hl=en&tab=wp" class=gb1>Books</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=gb1 style="text-decoration:none"><u>more</u> »</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe><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> | </span><a href="/preferences?hl=en" class=gb4>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></div> <center><br clear=all id=lgpd><div id=lga><table cellpadding=0 cellspacing=0 border=0><tr><td align=right valign=bottom><img src=images/hp0.gif width=158 height=78 alt="Google"></td><td valign=bottom><img src=images/hp1.gif width=50 height=78 alt=""></td><td valign=bottom><img src=images/hp2.gif width=68 height=78 alt=""></td></tr><tr><td class=h align=right valign=top><b></b></td><td valign=top><img src=images/hp3.gif width=50 height=32 alt=""></td><td valign=top class=h><font color=#666666 style=font-size:16px><b>Canada</b></font></td></tr></table><br></div><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"><div class=ds style="height:32px;margin:4px 0"><input autocomplete="off" maxlength=2048 name=q class="lst" title="Google Search" value="" size=57 style="background:#fff;border:1px solid #ccc;border-bottom-color:#999;border-right-color:#999;color:#000;font:18px arial,sans-serif bold;height:25px;margin:0;padding:5px 8px 0 6px;vertical-align:top"></div><br style="line-height:0"><span class=ds><span class=lsbb><input name=btnG type=submit value="Google Search" class=lsb></span></span><span class=ds><span class=lsbb><input name=btnI type=submit value="I'm Feeling Lucky" class=lsb></span></span></td><td nowrap width=25% align=left class=sblc><a href="/advanced_search?hl=en">Advanced Search</a><a href="/language_tools?hl=en">Language Tools</a></td></tr></table></form><div style="font-size:83%;min-height:3.5em"><br><div id=als><font size=-1 id=addlang>Google.ca offered in: <a href="http://www.google.ca/setprefs?sig=0_h3wwdBzWMwIKco5_3UM-S8-NI00=&hl=fr">fran�ais</a></font><br><br></div></div><div id=res></div><span id=footer><center id=fctr><div style="font-size:10pt"><div id=fll style="margin:19px auto 19px auto;text-align:center"><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></div></div><p style="color:#767676;font-size:8pt">© 2010 - <a href="/intl/en/privacy.html">Privacy</a></p></center></span> <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/CgJlbhICY2EgACswRTgBLCswWjgDLCswDjgXLCswFzgHLCswJzgELCswPDgDLCswCjhzQB0sKzAlOMqIASwrMEA4EiwrMEE4BSwrMFQ4ACwrMBg4BSwrMCY4DSyAAheQAhY/shQ1OaVztmo.js";(document.getElementById("xjsd")||document.body).appendChild(a);if(google.timers&&google.timers.load.t)google.timers.load.t.xjsls=(new Date).getTime();},0);
|
16
|
+
;google.neegg=1;google.y.first.push(function(){var form=document.f||document.f||document.gs;google.ac.i(form,form.q,'','','',{o:1,sw:1});google.History&&google.History.initialize('/')});if(google.j&&google.j.en&&google.j.xi){window.setTimeout(google.j.xi,0);google.fade=null;}</script></div><script>(function(){
|
17
|
+
var b,d,e,f;function g(a,c){if(a.removeEventListener){a.removeEventListener("load",c,false);a.removeEventListener("error",c,false)}else{a.detachEvent("onload",c);a.detachEvent("onerror",c)}}function h(a){f=(new Date).getTime();++d;a=a||window.event;var c=a.target||a.srcElement;g(c,h)}var i=document.getElementsByTagName("img");b=i.length;d=0;for(var j=0,k;j<b;++j){k=i[j];if(k.complete||typeof k.src!="string"||!k.src)++d;else if(k.addEventListener){k.addEventListener("load",h,false);k.addEventListener("error",
|
18
|
+
h,false)}else{k.attachEvent("onload",h);k.attachEvent("onerror",h)}}e=b-d;function l(){if(!google.timers.load.t)return;google.timers.load.t.ol=(new Date).getTime();google.timers.load.t.iml=f;google.kCSI.imc=d;google.kCSI.imn=b;google.kCSI.imp=e;google.report&&google.report(google.timers.load,google.kCSI)}if(window.addEventListener)window.addEventListener("load",l,false);else if(window.attachEvent)window.attachEvent("onload",l);google.timers.load.t.prt=(f=(new Date).getTime());
|
19
|
+
})();
|
20
|
+
</script>
|
data/spec/hash_spec.rb
CHANGED
@@ -1,24 +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
|
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
CHANGED
data/spec/mock_spec.rb
CHANGED
@@ -2,26 +2,70 @@ require 'spec/helper'
|
|
2
2
|
|
3
3
|
describe 'em-http mock' do
|
4
4
|
|
5
|
+
before(:all) do
|
6
|
+
EventMachine::MockHttpRequest.activate!
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
EventMachine::MockHttpRequest.deactivate!
|
11
|
+
end
|
12
|
+
|
5
13
|
before(:each) do
|
6
14
|
EventMachine::MockHttpRequest.reset_registry!
|
7
15
|
EventMachine::MockHttpRequest.reset_counts!
|
8
16
|
end
|
17
|
+
|
18
|
+
it "should serve a fake http request from a proc" do
|
19
|
+
EventMachine::HttpRequest.register('http://www.google.ca:80/', :get) { |req|
|
20
|
+
req.response_header.http_status = 200
|
21
|
+
req.response_header['SOME_WACKY_HEADER'] = 'WACKY_HEADER_VALUE'
|
22
|
+
req.response = "Well, now this is fun."
|
23
|
+
}
|
24
|
+
EM.run {
|
25
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get
|
26
|
+
http.errback { fail }
|
27
|
+
http.callback {
|
28
|
+
http.response_header.status.should == 200
|
29
|
+
http.response_header['SOME_WACKY_HEADER'].should == 'WACKY_HEADER_VALUE'
|
30
|
+
http.response.should == "Well, now this is fun."
|
31
|
+
EventMachine.stop
|
32
|
+
}
|
33
|
+
}
|
9
34
|
|
10
|
-
|
11
|
-
|
35
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should serve a fake http request from a proc with raw data" do
|
39
|
+
EventMachine::HttpRequest.register('http://www.google.ca:80/', :get) { |req|
|
40
|
+
req.receive_data(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')))
|
41
|
+
}
|
12
42
|
EM.run {
|
13
|
-
|
14
|
-
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
43
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get
|
15
44
|
http.errback { fail }
|
16
45
|
http.callback {
|
17
|
-
http.
|
46
|
+
http.response_header.status.should == 200
|
47
|
+
http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\r\n\r\n", 2).last
|
48
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
18
49
|
EventMachine.stop
|
19
50
|
}
|
20
51
|
}
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should serve a fake http request from a file" do
|
55
|
+
EventMachine::HttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
|
56
|
+
EM.run {
|
21
57
|
|
22
|
-
|
58
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get
|
59
|
+
http.errback { fail }
|
60
|
+
http.callback {
|
61
|
+
http.response_header.status.should == 200
|
62
|
+
http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\r\n\r\n", 2).last
|
63
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
64
|
+
EventMachine.stop
|
65
|
+
}
|
66
|
+
}
|
23
67
|
end
|
24
|
-
|
68
|
+
|
25
69
|
it "should serve a fake http request from a string" do
|
26
70
|
data = <<-HEREDOC
|
27
71
|
HTTP/1.0 200 OK
|
@@ -46,70 +90,71 @@ window._gjp && _gjp()</script><style>td{line-height:.8em;}.gac_m td{line-height:
|
|
46
90
|
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
91
|
})();
|
48
92
|
HEREDOC
|
49
|
-
EventMachine::
|
93
|
+
EventMachine::HttpRequest.register('http://www.google.ca:80/', :get, {}, data)
|
50
94
|
EventMachine.run {
|
51
|
-
|
52
|
-
http = EventMachine::
|
95
|
+
|
96
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get
|
53
97
|
http.errback { fail }
|
54
98
|
http.callback {
|
55
99
|
http.response.should == data.split("\n\n", 2).last
|
100
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
56
101
|
EventMachine.stop
|
57
102
|
}
|
58
103
|
}
|
59
|
-
|
60
|
-
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
104
|
+
|
61
105
|
end
|
62
106
|
|
63
107
|
it "should serve a fake failing http request" do
|
64
|
-
EventMachine::
|
108
|
+
EventMachine::HttpRequest.register('http://www.google.ca:80/', :get, {}, :fail)
|
65
109
|
error = false
|
66
110
|
|
67
111
|
EventMachine.run {
|
68
|
-
http = EventMachine::
|
112
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get
|
113
|
+
http.callback {
|
114
|
+
EventMachine.stop
|
115
|
+
fail
|
116
|
+
}
|
69
117
|
http.errback {
|
70
|
-
|
118
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
71
119
|
EventMachine.stop
|
72
120
|
}
|
73
121
|
}
|
74
122
|
|
75
|
-
error.should be_true
|
76
|
-
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
77
123
|
end
|
78
|
-
|
124
|
+
|
79
125
|
it "should distinguish the cache by the given headers" do
|
80
|
-
EventMachine::
|
81
|
-
EventMachine::
|
126
|
+
EventMachine::HttpRequest.register_file('http://www.google.ca:80/', :get, {:user_agent => 'BERT'}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
|
127
|
+
EventMachine::HttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
|
82
128
|
EM.run {
|
83
|
-
|
84
|
-
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get
|
129
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get
|
85
130
|
http.errback { fail }
|
86
131
|
http.callback {
|
87
|
-
http.
|
132
|
+
http.response_header.status.should == 200
|
133
|
+
http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\r\n\r\n", 2).last
|
134
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
135
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {:user_agent => 'BERT'}).should == 0
|
88
136
|
EventMachine.stop
|
89
137
|
}
|
90
138
|
}
|
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
|
-
|
139
|
+
|
95
140
|
EM.run {
|
96
|
-
|
97
|
-
http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get({:head => {:user_agent => 'BERT'}})
|
141
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get({:head => {:user_agent => 'BERT'}})
|
98
142
|
http.errback { fail }
|
99
143
|
http.callback {
|
100
|
-
http.
|
144
|
+
http.response_header.status.should == 200
|
145
|
+
http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')).split("\r\n\r\n", 2).last
|
146
|
+
EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {:user_agent => 'BERT'}).should == 1
|
101
147
|
EventMachine.stop
|
102
148
|
}
|
103
149
|
}
|
104
|
-
|
105
|
-
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {:user_agent => 'BERT'}).should == 1
|
150
|
+
|
106
151
|
end
|
107
152
|
|
108
153
|
it "should raise an exception if pass-thru is disabled" do
|
109
|
-
EventMachine::
|
154
|
+
EventMachine::HttpRequest.pass_through_requests = false
|
110
155
|
EventMachine.run {
|
111
156
|
proc {
|
112
|
-
http = EventMachine::
|
157
|
+
http = EventMachine::HttpRequest.new('http://www.google.ca/').get
|
113
158
|
}.should raise_error
|
114
159
|
EventMachine.stop
|
115
160
|
}
|
data/spec/multi_spec.rb
CHANGED
@@ -5,26 +5,43 @@ describe EventMachine::MultiRequest do
|
|
5
5
|
|
6
6
|
it "should submit multiple requests in parallel and return once all of them are complete" do
|
7
7
|
EventMachine.run {
|
8
|
-
|
8
|
+
|
9
9
|
# create an instance of multi-request handler, and the requests themselves
|
10
10
|
multi = EventMachine::MultiRequest.new
|
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
14
|
multi.add(EventMachine::HttpRequest.new('http://0.0.0.0:8083/').get(:timeout => 1))
|
15
|
-
|
15
|
+
|
16
16
|
multi.callback {
|
17
17
|
# verify successful request
|
18
18
|
multi.responses[:succeeded].size.should == 1
|
19
19
|
multi.responses[:succeeded].first.response.should match(/test/)
|
20
|
-
|
20
|
+
|
21
21
|
# verify invalid requests
|
22
22
|
multi.responses[:failed].size.should == 1
|
23
23
|
multi.responses[:failed].first.response_header.status.should == 0
|
24
|
-
|
24
|
+
|
25
25
|
EventMachine.stop
|
26
26
|
}
|
27
|
-
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should accept multiple open connections and return once all of them are complete" do
|
31
|
+
EventMachine.run {
|
32
|
+
http1 = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(:query => {:q => 'test'})
|
33
|
+
http2 = EventMachine::HttpRequest.new('http://0.0.0.0:8083/').get(:timeout => 1)
|
34
|
+
|
35
|
+
multi = EventMachine::MultiRequest.new([http1, http2]) do
|
36
|
+
multi.responses[:succeeded].size.should == 1
|
37
|
+
multi.responses[:succeeded].first.response.should match(/test/)
|
38
|
+
|
39
|
+
multi.responses[:failed].size.should == 1
|
40
|
+
multi.responses[:failed].first.response_header.status.should == 0
|
41
|
+
|
42
|
+
EventMachine.stop
|
43
|
+
end
|
44
|
+
}
|
28
45
|
end
|
29
46
|
|
30
47
|
it "should handle multiple mock requests" do
|
@@ -32,20 +49,20 @@ describe EventMachine::MultiRequest do
|
|
32
49
|
EventMachine::MockHttpRequest.register_file('http://0.0.0.0:8083/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))
|
33
50
|
|
34
51
|
EventMachine.run {
|
35
|
-
|
52
|
+
|
36
53
|
# create an instance of multi-request handler, and the requests themselves
|
37
54
|
multi = EventMachine::MultiRequest.new
|
38
|
-
|
55
|
+
|
39
56
|
# add multiple requests to the multi-handler
|
40
57
|
multi.add(EventMachine::MockHttpRequest.new('http://127.0.0.1:8080/').get)
|
41
58
|
multi.add(EventMachine::MockHttpRequest.new('http://0.0.0.0:8083/').get)
|
42
|
-
|
59
|
+
|
43
60
|
multi.callback {
|
44
61
|
# verify successful request
|
45
62
|
multi.responses[:succeeded].size.should == 2
|
46
63
|
|
47
64
|
EventMachine.stop
|
48
65
|
}
|
49
|
-
}
|
66
|
+
}
|
50
67
|
end
|
51
68
|
end
|
data/spec/request_spec.rb
CHANGED
@@ -29,16 +29,27 @@ describe EventMachine::HttpRequest do
|
|
29
29
|
http.errback {
|
30
30
|
http.response_header.status.should == 0
|
31
31
|
http.error.should match(/unable to resolve server address/)
|
32
|
+
http.uri.to_s.should match('http://somethinglocal:80/')
|
32
33
|
EventMachine.stop
|
33
34
|
}
|
34
35
|
}
|
35
36
|
end
|
36
37
|
|
37
|
-
it "should
|
38
|
+
it "should raise error on invalid URL" do
|
38
39
|
EventMachine.run {
|
39
40
|
lambda {
|
40
|
-
EventMachine::HttpRequest.new('
|
41
|
-
}.should raise_error
|
41
|
+
EventMachine::HttpRequest.new('random?text').get
|
42
|
+
}.should raise_error
|
43
|
+
|
44
|
+
EM.stop
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should succeed GET on missing path" do
|
49
|
+
EventMachine.run {
|
50
|
+
lambda {
|
51
|
+
EventMachine::HttpRequest.new('http://127.0.0.1:8080').get
|
52
|
+
}.should_not raise_error(ArgumentError)
|
42
53
|
|
43
54
|
EventMachine.stop
|
44
55
|
}
|
@@ -57,6 +68,19 @@ describe EventMachine::HttpRequest do
|
|
57
68
|
}
|
58
69
|
end
|
59
70
|
|
71
|
+
it "should accept optional host override" do
|
72
|
+
EventMachine.run {
|
73
|
+
http = EventMachine::HttpRequest.new('http://google.com:8080/').get :host => '127.0.0.1'
|
74
|
+
|
75
|
+
http.errback { failed }
|
76
|
+
http.callback {
|
77
|
+
http.response_header.status.should == 200
|
78
|
+
http.response.should match(/Hello/)
|
79
|
+
EventMachine.stop
|
80
|
+
}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
60
84
|
it "should perform successfull GET with a URI passed as argument" do
|
61
85
|
EventMachine.run {
|
62
86
|
uri = URI.parse('http://127.0.0.1:8080/')
|
@@ -272,6 +296,20 @@ describe EventMachine::HttpRequest do
|
|
272
296
|
}
|
273
297
|
end
|
274
298
|
|
299
|
+
it "should return ETag and Last-Modified headers" do
|
300
|
+
EventMachine.run {
|
301
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get
|
302
|
+
|
303
|
+
http.errback { failed }
|
304
|
+
http.callback {
|
305
|
+
http.response_header.status.should == 200
|
306
|
+
http.response_header.etag.should match('abcdefg')
|
307
|
+
http.response_header.last_modified.should match('Fri, 13 Aug 2010 17:31:21 GMT')
|
308
|
+
EventMachine.stop
|
309
|
+
}
|
310
|
+
}
|
311
|
+
end
|
312
|
+
|
275
313
|
it "should detect deflate encoding" do
|
276
314
|
EventMachine.run {
|
277
315
|
|
@@ -318,58 +356,95 @@ describe EventMachine::HttpRequest do
|
|
318
356
|
}
|
319
357
|
end
|
320
358
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
http.
|
327
|
-
|
359
|
+
context "redirect" do
|
360
|
+
it "should report last_effective_url" do
|
361
|
+
EventMachine.run {
|
362
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get
|
363
|
+
http.errback { failed }
|
364
|
+
http.callback {
|
365
|
+
http.response_header.status.should == 200
|
366
|
+
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/'
|
328
367
|
|
329
|
-
|
368
|
+
EM.stop
|
369
|
+
}
|
330
370
|
}
|
331
|
-
|
332
|
-
end
|
371
|
+
end
|
333
372
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
373
|
+
it "should follow location redirects" do
|
374
|
+
EventMachine.run {
|
375
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1
|
376
|
+
http.errback { failed }
|
377
|
+
http.callback {
|
378
|
+
http.response_header.status.should == 200
|
379
|
+
http.response_header["CONTENT_ENCODING"].should == "gzip"
|
380
|
+
http.response.should == "compressed"
|
381
|
+
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
|
382
|
+
http.redirects.should == 1
|
344
383
|
|
345
|
-
|
384
|
+
EM.stop
|
385
|
+
}
|
346
386
|
}
|
347
|
-
|
348
|
-
end
|
387
|
+
end
|
349
388
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
389
|
+
it "should default to 0 redirects" do
|
390
|
+
EventMachine.run {
|
391
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get
|
392
|
+
http.errback { failed }
|
393
|
+
http.callback {
|
394
|
+
http.response_header.status.should == 301
|
395
|
+
http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip'
|
396
|
+
http.redirects.should == 0
|
358
397
|
|
359
|
-
|
398
|
+
EM.stop
|
399
|
+
}
|
360
400
|
}
|
361
|
-
|
362
|
-
end
|
401
|
+
end
|
363
402
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
403
|
+
it "should not invoke redirect logic on failed connections" do
|
404
|
+
EventMachine.run {
|
405
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get :timeout => 0.1, :redirects => 5
|
406
|
+
http.callback { failed }
|
407
|
+
http.errback {
|
408
|
+
http.redirects.should == 0
|
409
|
+
EM.stop
|
410
|
+
}
|
371
411
|
}
|
372
|
-
|
412
|
+
end
|
413
|
+
|
414
|
+
it "should normalize redirect urls" do
|
415
|
+
EventMachine.run {
|
416
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/bad').get :redirects => 1
|
417
|
+
http.errback { failed }
|
418
|
+
http.callback {
|
419
|
+
http.last_effective_url.to_s.should match('http://127.0.0.1:8080/')
|
420
|
+
http.response.should match('Hello, World!')
|
421
|
+
EM.stop
|
422
|
+
}
|
423
|
+
}
|
424
|
+
end
|
425
|
+
|
426
|
+
it "should fail gracefully on a missing host in absolute Location header" do
|
427
|
+
EventMachine.run {
|
428
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/nohost').get :redirects => 1
|
429
|
+
http.callback { failed }
|
430
|
+
http.errback {
|
431
|
+
http.error.should == 'Location header format error'
|
432
|
+
EM.stop
|
433
|
+
}
|
434
|
+
}
|
435
|
+
end
|
436
|
+
|
437
|
+
it "should fail gracefully on an invalid host in Location header" do
|
438
|
+
pending "validate tld's?"
|
439
|
+
EventMachine.run {
|
440
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/badhost').get :redirects => 1
|
441
|
+
http.callback { failed }
|
442
|
+
http.errback {
|
443
|
+
http.error.should == 'Location header format error'
|
444
|
+
EM.stop
|
445
|
+
}
|
446
|
+
}
|
447
|
+
end
|
373
448
|
end
|
374
449
|
|
375
450
|
it "should optionally pass the response body progressively" do
|
@@ -593,35 +668,93 @@ describe EventMachine::HttpRequest do
|
|
593
668
|
EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1
|
594
669
|
end
|
595
670
|
|
596
|
-
context "connections via
|
671
|
+
context "connections via" do
|
672
|
+
context "direct proxy" do
|
673
|
+
it "should default to skip CONNECT" do
|
674
|
+
EventMachine.run {
|
597
675
|
|
598
|
-
|
599
|
-
|
676
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => {
|
677
|
+
:host => '127.0.0.1', :port => 8083
|
678
|
+
}
|
679
|
+
|
680
|
+
http.errback { p http.inspect; failed }
|
681
|
+
http.callback {
|
682
|
+
http.response_header.status.should == 200
|
683
|
+
http.response.should match('test')
|
684
|
+
EventMachine.stop
|
685
|
+
}
|
686
|
+
}
|
687
|
+
end
|
600
688
|
|
601
|
-
|
689
|
+
it "should send absolute URIs to the proxy server" do
|
690
|
+
EventMachine.run {
|
602
691
|
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
692
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => {
|
693
|
+
:host => '127.0.0.1', :port => 8083
|
694
|
+
}
|
695
|
+
|
696
|
+
http.errback { p http.inspect; failed }
|
697
|
+
http.callback {
|
698
|
+
http.response_header.status.should == 200
|
699
|
+
# The test proxy server gives the requested uri back in this header
|
700
|
+
http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8080/?q=test'
|
701
|
+
http.response_header['X_THE_REQUESTED_URI'].should_not == '/?q=test'
|
702
|
+
http.response.should match('test')
|
703
|
+
EventMachine.stop
|
704
|
+
}
|
608
705
|
}
|
609
|
-
|
706
|
+
end
|
707
|
+
|
708
|
+
it "should include query parameters specified in the options" do
|
709
|
+
EventMachine.run {
|
710
|
+
|
711
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(
|
712
|
+
:proxy => { :host => '127.0.0.1', :port => 8083 },
|
713
|
+
:query => { 'q' => 'test' }
|
714
|
+
)
|
715
|
+
|
716
|
+
http.errback { p http.inspect; failed }
|
717
|
+
http.callback {
|
718
|
+
http.response_header.status.should == 200
|
719
|
+
http.response.should match('test')
|
720
|
+
EventMachine.stop
|
721
|
+
}
|
722
|
+
}
|
723
|
+
end
|
610
724
|
end
|
611
725
|
|
612
|
-
|
613
|
-
|
726
|
+
context "CONNECT proxy" do
|
727
|
+
it "should work with CONNECT proxy servers" do
|
728
|
+
EventMachine.run {
|
614
729
|
|
615
|
-
|
616
|
-
|
730
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({
|
731
|
+
:proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true}
|
732
|
+
})
|
617
733
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
734
|
+
http.errback { p http.inspect; failed }
|
735
|
+
http.callback {
|
736
|
+
http.response_header.status.should == 200
|
737
|
+
http.response.should == 'Hello, World!'
|
738
|
+
EventMachine.stop
|
739
|
+
}
|
623
740
|
}
|
624
|
-
|
741
|
+
end
|
742
|
+
|
743
|
+
it "should proxy POST data" do
|
744
|
+
EventMachine.run {
|
745
|
+
|
746
|
+
http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({
|
747
|
+
:body => "data", :proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true}
|
748
|
+
})
|
749
|
+
|
750
|
+
http.errback { failed }
|
751
|
+
http.callback {
|
752
|
+
http.response_header.status.should == 200
|
753
|
+
http.response.should match(/data/)
|
754
|
+
EventMachine.stop
|
755
|
+
}
|
756
|
+
}
|
757
|
+
end
|
625
758
|
end
|
626
759
|
end
|
627
760
|
|