em-http-request-0.3.0/ 0000755 0001750 0001750 00000000000 11772656255 014102 5 ustar tfheen tfheen em-http-request-0.3.0/em-http-request.gemspec 0000644 0001750 0001750 00000002244 11772656255 020515 0 ustar tfheen tfheen # -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "em-http/version" Gem::Specification.new do |s| s.name = "em-http-request" s.version = EventMachine::HttpRequest::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Ilya Grigorik"] s.email = ["ilya@igvita.com"] s.homepage = "http://github.com/igrigorik/em-http-request" s.summary = "EventMachine based, async HTTP Request client" s.description = s.summary s.rubyforge_project = "em-http-request" s.add_dependency "eventmachine", ">= 0.12.9" s.add_dependency "addressable", ">= 2.0.0" s.add_dependency "escape_utils" s.add_development_dependency "rspec" s.add_development_dependency "rake" s.add_development_dependency "em-websocket" s.add_development_dependency "rack" s.add_development_dependency "mongrel", "~> 1.2.0.pre2" s.extensions = ["ext/buffer/extconf.rb", "ext/http11_client/extconf.rb"] s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] end em-http-request-0.3.0/spec/ 0000755 0001750 0001750 00000000000 11772656255 015034 5 ustar tfheen tfheen em-http-request-0.3.0/spec/stub_server.rb 0000644 0001750 0001750 00000000627 11772656255 017731 0 ustar tfheen tfheen class StubServer module Server def receive_data(data) send_data @response close_connection_after_writing end def response=(response) @response = response end end def initialize(response, port=8081) @sig = EventMachine::start_server("127.0.0.1", port, Server) { |s| s.response = response } end def stop EventMachine.stop_server @sig end end em-http-request-0.3.0/spec/request_spec.rb 0000644 0001750 0001750 00000074204 11772656255 020072 0 ustar tfheen tfheen require 'helper' require 'stallion' require 'stub_server' describe EventMachine::HttpRequest do def failed(http=nil) EventMachine.stop http ? fail(http.error) : fail end it "should fail GET on DNS timeout" do EventMachine.run { EventMachine.heartbeat_interval = 0.1 http = EventMachine::HttpRequest.new('http://127.1.1.1/').get :timeout => 1 http.callback { failed(http) } http.errback { http.response_header.status.should == 0 EventMachine.stop } } end it "should fail GET on invalid host" do EventMachine.run { EventMachine.heartbeat_interval = 0.1 http = EventMachine::HttpRequest.new('http://somethinglocal/').get :timeout => 1 http.callback { failed(http) } http.errback { http.response_header.status.should == 0 http.error.should match(/unable to resolve server address/) http.uri.to_s.should match('http://somethinglocal:80/') EventMachine.stop } } end it "should raise error on invalid URL" do EventMachine.run { lambda { EventMachine::HttpRequest.new('random?text').get }.should raise_error EM.stop } end it "should succeed GET on missing path" do EventMachine.run { lambda { EventMachine::HttpRequest.new('http://127.0.0.1:8080').get }.should_not raise_error(ArgumentError) EventMachine.stop } end it "should perform successfull GET" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/Hello/) EventMachine.stop } } end context "host override" do it "should accept optional host" do EventMachine.run { http = EventMachine::HttpRequest.new('http://google.com:8080/').get :host => '127.0.0.1' http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/Hello/) EventMachine.stop } } end it "should reset host on redirect" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1, :host => '127.0.0.1' http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header["CONTENT_ENCODING"].should == "gzip" http.response.should == "compressed" http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip' http.redirects.should == 1 EM.stop } } end it "should redirect with missing content-length" do EventMachine.run { @s = StubServer.new("HTTP/1.0 301 MOVED PERMANENTLY\r\nlocation: http://127.0.0.1:8080/redirect\r\n\r\n") http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get :redirects => 2 http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header["CONTENT_ENCODING"].should == "gzip" http.response.should == "compressed" http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip' http.redirects.should == 2 @s.stop EM.stop } } end it "should follow redirects on HEAD method" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/head').head :redirects => 1 http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/' EM.stop } } end it "should follow redirects on HEAD method (external)" do EventMachine.run { http = EventMachine::HttpRequest.new('http://www.google.com/').head :redirects => 1 http.errback { failed(http) } http.callback { http.response_header.status.should == 200 EM.stop } } end end it "should perform successfull GET with a URI passed as argument" do EventMachine.run { uri = URI.parse('http://127.0.0.1:8080/') http = EventMachine::HttpRequest.new(uri).get http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/Hello/) EventMachine.stop } } end it "should perform successfull HEAD with a URI passed as argument" do EventMachine.run { uri = URI.parse('http://127.0.0.1:8080/') http = EventMachine::HttpRequest.new(uri).head http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should == "" EventMachine.stop } } end # should be no different than a GET it "should perform successfull DELETE with a URI passed as argument" do EventMachine.run { uri = URI.parse('http://127.0.0.1:8080/') http = EventMachine::HttpRequest.new(uri).delete http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should == "" EventMachine.stop } } end it "should return 404 on invalid path" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/fail').get http.errback { failed(http) } http.callback { http.response_header.status.should == 404 EventMachine.stop } } end it "should build query parameters from Hash" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => {:q => 'test'} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/test/) EventMachine.stop } } end it "should pass query parameters string" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :query => "q=test" http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/test/) EventMachine.stop } } end it "should encode an array of query parameters" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get :query => {:hash => ['value1', 'value2']} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/hash\[\]=value1&hash\[\]=value2/) EventMachine.stop } } end # should be no different than a POST it "should perform successfull PUT" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').put :body => "data" http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/data/) EventMachine.stop } } end it "should perform successfull POST" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => "data" http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/data/) EventMachine.stop } } end it "should escape body on POST" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {:stuff => 'string&string'} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should == "stuff=string%26string" EventMachine.stop } } end it "should perform successfull POST with Ruby Hash/Array as params" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :body => {"key1" => 1, "key2" => [2,3]} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/key1=1&key2\[0\]=2&key2\[1\]=3/) EventMachine.stop } } end it "should perform successfull POST with Ruby Hash/Array as params and with the correct content length" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_length').post :body => {"key1" => "data1"} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.to_i.should == 10 EventMachine.stop } } end it "should perform successfull GET with custom header" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'if-none-match' => 'evar!'} http.errback { failed(http) } http.callback { http.response_header.status.should == 304 EventMachine.stop } } end it "should perform a streaming GET" do EventMachine.run { # digg.com uses chunked encoding http = EventMachine::HttpRequest.new('http://digg.com/news').get http.errback { failed(http) } http.callback { http.response_header.status.should == 200 EventMachine.stop } } end it "should perform basic auth" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get :head => {'authorization' => ['user', 'pass']} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 EventMachine.stop } } end it "should send proper OAuth auth header" do EventMachine.run { oauth_header = 'OAuth oauth_nonce="oqwgSYFUD87MHmJJDv7bQqOF2EPnVus7Wkqj5duNByU", b=c, d=e' http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/oauth_auth').get :head => {'authorization' => oauth_header} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should == oauth_header EventMachine.stop } } end context "keepalive" do it "should default to non-keepalive" do EventMachine.run { headers = {'If-Modified-Since' => 'Thu, 05 Aug 2010 22:54:44 GMT'} http = EventMachine::HttpRequest.new('http://www.google.com/images/logos/ps_logo2.png').get :head => headers http.errback { fail } start = Time.now.to_i http.callback { (start - Time.now.to_i).should be_within(1).of(0) EventMachine.stop } } end it "should work with keep-alive servers" do EventMachine.run { http = EventMachine::HttpRequest.new('http://mexicodiario.com/touch.public.json.php').get :keepalive => true http.errback { failed(http) } http.callback { http.response_header.status.should == 200 EventMachine.stop } } end end it "should return ETag and Last-Modified headers" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_query').get http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header.etag.should match('abcdefg') http.response_header.last_modified.should match('Fri, 13 Aug 2010 17:31:21 GMT') EventMachine.stop } } end it "should detect deflate encoding" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate"} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header["CONTENT_ENCODING"].should == "deflate" http.response.should == "compressed" EventMachine.stop } } end it "should detect gzip encoding" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/gzip').get :head => {"accept-encoding" => "gzip, compressed"} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header["CONTENT_ENCODING"].should == "gzip" http.response.should == "compressed" EventMachine.stop } } end it "should timeout after 1 second" do EventMachine.run { t = Time.now.to_i EventMachine.heartbeat_interval = 0.1 http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/timeout').get :timeout => 1 http.errback { (Time.now.to_i - t).should <= 5 EventMachine.stop } http.callback { failed(http) } } end context "redirect" do it "should report last_effective_url" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/' EM.stop } } end it "should follow location redirects" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get :redirects => 1 http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header["CONTENT_ENCODING"].should == "gzip" http.response.should == "compressed" http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip' http.redirects.should == 1 EM.stop } } end it "should default to 0 redirects" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect').get http.errback { failed(http) } http.callback { http.response_header.status.should == 301 http.last_effective_url.to_s.should == 'http://127.0.0.1:8080/gzip' http.redirects.should == 0 EM.stop } } end it "should not invoke redirect logic on failed(http) connections" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get :timeout => 0.1, :redirects => 5 http.callback { failed(http) } http.errback { http.redirects.should == 0 EM.stop } } end it "should normalize redirect urls" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/bad').get :redirects => 1 http.errback { failed(http) } http.callback { http.last_effective_url.to_s.should match('http://127.0.0.1:8080/') http.response.should match('Hello, World!') EM.stop } } end it "should fail gracefully on a missing host in absolute Location header" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/nohost').get :redirects => 1 http.callback { failed(http) } http.errback { http.error.should == 'Location header format error' EM.stop } } end it "should fail gracefully on an invalid host in Location header" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/redirect/badhost').get :redirects => 1 http.callback { failed(http) } http.errback { http.error.should == 'unable to resolve server address' EM.stop } } end end it "should optionally pass the response body progressively" do EventMachine.run { body = '' http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get http.errback { failed(http) } http.stream { |chunk| body += chunk } http.callback { http.response_header.status.should == 200 http.response.should == '' body.should match(/Hello/) EventMachine.stop } } end context "optional header callback" do it "should optionally pass the response headers" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get http.errback { failed(http) } http.headers { |hash| hash.should be_an_kind_of Hash hash.should include 'CONNECTION' hash.should include 'CONTENT_LENGTH' } http.callback { http.response_header.status.should == 200 http.response.should match(/Hello/) EventMachine.stop } } end it "should allow to terminate current connection from header callback" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get http.callback { failed(http) } http.headers { |hash| hash.should be_an_kind_of Hash hash.should include 'CONNECTION' hash.should include 'CONTENT_LENGTH' http.close('header callback terminated connection') } http.errback { |e| http.response_header.status.should == 200 http.error.should == 'header callback terminated connection' http.response.should == '' EventMachine.stop } } end end it "should optionally pass the deflate-encoded response body progressively" do EventMachine.run { body = '' http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/deflate').get :head => {"accept-encoding" => "deflate, compressed"} http.errback { failed(http) } http.stream { |chunk| body += chunk } http.callback { http.response_header.status.should == 200 http.response_header["CONTENT_ENCODING"].should == "deflate" http.response.should == '' body.should == "compressed" EventMachine.stop } } end it "should initiate SSL/TLS on HTTPS connections" do EventMachine.run { http = EventMachine::HttpRequest.new('https://mail.google.com:443/mail/').get http.errback { failed(http) } http.callback { http.response_header.status.should == 302 EventMachine.stop } } end it "should accept & return cookie header to user" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/set_cookie').get http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header.cookie.should == "id=1; expires=Tue, 09-Aug-2011 17:53:39 GMT; path=/;" EventMachine.stop } } end it "should pass cookie header to server from string" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => 'id=2;'} http.errback { failed(http) } http.callback { http.response.should == "id=2;" EventMachine.stop } } end it "should pass cookie header to server from Hash" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_cookie').get :head => {'cookie' => {'id' => 2}} http.errback { failed(http) } http.callback { http.response.should == "id=2;" EventMachine.stop } } end context "when talking to a stub HTTP/1.0 server" do it "should get the body without Content-Length" do EventMachine.run { @s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo") http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get http.errback { failed(http) } http.callback { http.response.should match(/Foo/) http.response_header['CONTENT_LENGTH'].should_not == 0 @s.stop EventMachine.stop } } end it "should work with \\n instead of \\r\\n" do EventMachine.run { @s = StubServer.new("HTTP/1.0 200 OK\nContent-Type: text/plain\nContent-Length: 3\nConnection: close\n\nFoo") http = EventMachine::HttpRequest.new('http://127.0.0.1:8081/').get http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response_header['CONTENT_TYPE'].should == 'text/plain' http.response.should match(/Foo/) @s.stop EventMachine.stop } } end end context "body content-type encoding" do it "should not set content type on string in body" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post :body => "data" http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should be_empty EventMachine.stop } } end it "should set content-type automatically when passed a ruby hash/array for body" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post :body => {:a => :b} http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match("application/x-www-form-urlencoded") EventMachine.stop } } end it "should not override content-type when passing in ruby hash/array for body" do EventMachine.run { ct = 'text; charset=utf-8' http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({ :body => {:a => :b}, :head => {'content-type' => ct}}) http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.content_charset.should == Encoding.find('utf-8') http.response_header["CONTENT_TYPE"].should == ct EventMachine.stop } } end it "should default to external encoding on invalid encoding" do EventMachine.run { ct = 'text/html; charset=utf-8lias' http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({ :body => {:a => :b}, :head => {'content-type' => ct}}) http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.content_charset.should == Encoding.find('utf-8') http.response_header["CONTENT_TYPE"].should == ct EventMachine.stop } } end it "should processed escaped content-type" do EventMachine.run { ct = "text/html; charset=\"ISO-8859-4\"" http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/echo_content_type').post({ :body => {:a => :b}, :head => {'content-type' => ct}}) http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.content_charset.should == Encoding.find('ISO-8859-4') http.response_header["CONTENT_TYPE"].should == ct EventMachine.stop } } end end it "should complete a Location: with a relative path" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/relative-location').get http.errback { failed(http) } http.callback { http.response_header['LOCATION'].should == 'http://127.0.0.1:8080/forwarded' EventMachine.stop } } end it "should stream a file off disk" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post :file => 'spec/fixtures/google.ca' http.errback { failed(http) } http.callback { http.response.should match('google') EventMachine.stop } } end it 'should let you pass a block to be called once the client is created' do client = nil EventMachine.run { request = EventMachine::HttpRequest.new('http://127.0.0.1:8080/') http = request.post { |c| c.options[:body] = {:callback_run => 'yes'} client = c } http.errback { failed(http) } http.callback { client.should be_kind_of(EventMachine::HttpClient) http.response_header.status.should == 200 http.response.should match(/callback_run=yes/) EventMachine.stop } } end it "should retrieve multiple cookies" do EventMachine::MockHttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')) EventMachine.run { http = EventMachine::MockHttpRequest.new('http://www.google.ca/').get http.errback { fail } http.callback { c1 = "PREF=ID=11955ae9690fd292:TM=1281823106:LM=1281823106:S=wHdloFqGQ_OLSE92; expires=Mon, 13-Aug-2012 21:58:26 GMT; path=/; domain=.google.ca" c2 = "NID=37=USTdOsxOSMbLjphkJ3S5Ueua3Yc23COXuK_pbztcHx7JoyhomwQySrvebCf3_u8eyrBiLWssVzaZcEOiKGEJbNdy8lRhnq_mfrdz693LaMjNPh__ccW4sgn1ZO6nQltE; expires=Sun, 13-Feb-2011 21:58:26 GMT; path=/; domain=.google.ca; HttpOnly" http.response_header.cookie.should == [c1, c2] EventMachine.stop } } EventMachine::MockHttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1 end context "connections via" do context "direct proxy" do it "should default to skip CONNECT" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => { :host => '127.0.0.1', :port => 8083 } http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match('test') EventMachine.stop } } end it "should send absolute URIs to the proxy server" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/?q=test').get :proxy => { :host => '127.0.0.1', :port => 8083 } http.errback { failed(http) } http.callback { http.response_header.status.should == 200 # The test proxy server gives the requested uri back in this header http.response_header['X_THE_REQUESTED_URI'].should == 'http://127.0.0.1:8080/?q=test' http.response_header['X_THE_REQUESTED_URI'].should_not == '/?q=test' http.response.should match('test') EventMachine.stop } } end it "should include query parameters specified in the options" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get( :proxy => { :host => '127.0.0.1', :port => 8083 }, :query => { 'q' => 'test' } ) http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match('test') EventMachine.stop } } end end context "CONNECT proxy" do it "should work with CONNECT proxy servers" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get({ :proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true} }) http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should == 'Hello, World!' EventMachine.stop } } end it "should proxy POST data" do EventMachine.run { http = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').post({ :body => "data", :proxy => {:host => '127.0.0.1', :port => 8082, :use_connect => true} }) http.errback { failed(http) } http.callback { http.response_header.status.should == 200 http.response.should match(/data/) EventMachine.stop } } end end end context "websocket connection" do # Spec: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55 # # ws.onopen = http.callback # ws.onmessage = http.stream { |msg| } # ws.errback = no connection # it "should invoke errback on failed upgrade" do EventMachine.run { http = EventMachine::HttpRequest.new('ws://127.0.0.1:8080/').get :timeout => 0 http.callback { failed(http) } http.errback { http.response_header.status.should == 200 EventMachine.stop } } end it "should complete websocket handshake and transfer data from client to server and back" do EventMachine.run { MSG = "hello bi-directional data exchange" EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws| ws.onmessage {|msg| ws.send msg} end http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 1 http.errback { failed(http) } http.callback { http.response_header.status.should == 101 http.response_header['CONNECTION'].should match(/Upgrade/) http.response_header['UPGRADE'].should match(/WebSocket/) # push should only be invoked after handshake is complete http.send(MSG) } http.stream { |chunk| chunk.should == MSG EventMachine.stop } } end it "should split multiple messages from websocket server into separate stream callbacks" do EM.run do messages = %w[1 2] recieved = [] EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8085) do |ws| ws.onopen { ws.send messages[0] ws.send messages[1] } end EventMachine.add_timer(0.1) do http = EventMachine::HttpRequest.new('ws://127.0.0.1:8085/').get :timeout => 0 http.errback { failed(http) } http.callback { http.response_header.status.should == 101 } http.stream {|msg| msg.should == messages[recieved.size] recieved.push msg EventMachine.stop if recieved.size == messages.size } end end end end end em-http-request-0.3.0/spec/encoding_spec.rb 0000644 0001750 0001750 00000002606 11772656255 020165 0 ustar tfheen tfheen require 'helper' describe EventMachine::HttpEncoding do include EventMachine::HttpEncoding it "should transform a basic hash into HTTP POST Params" do form_encode_body({:a => "alpha", :b => "beta"}).should == "a=alpha&b=beta" end it "should transform a more complex hash into HTTP POST Params" do form_encode_body({:a => "a", :b => ["c", "d", "e"]}).should == "a=a&b[0]=c&b[1]=d&b[2]=e" end it "should transform a very complex hash into HTTP POST Params" do params = form_encode_body({:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]}) params.should == "a=a&b[0][c]=c&b[0][d]=d&b[1][e]=e&b[1][f]=f" end it "should escape values" do params = form_encode_body({:stuff => 'string&string'}) params.should == "stuff=string%26string" end it "should escape keys" do params = form_encode_body({'bad&str'=> {'key&key' => [:a, :b]}}) params.should == 'bad%26str[key%26key][0]=a&bad%26str[key%26key][1]=b' end it "should escape keys and values" do params = form_encode_body({'bad&str'=> {'key&key' => ['bad+&stuff', '[test]']}}) params.should == "bad%26str[key%26key][0]=bad%2B%26stuff&bad%26str[key%26key][1]=%5Btest%5D" end it "should be fast on long string escapes" do s = Time.now 5000.times { |n| form_encode_body({:a => "{a:'b', d:'f', g:['a','b']}"*50}) } (Time.now - s).should satisfy { |t| t < 1.5 } end end em-http-request-0.3.0/spec/stallion.rb 0000644 0001750 0001750 00000016421 11772656255 017212 0 ustar tfheen tfheen # #-- # Includes portion originally Copyright (C)2008 Michael Fellinger # license See file LICENSE for details # #-- require 'rack' module Stallion class Mount def initialize(name, *methods, &block) @name, @methods, @block = name, methods, block end def ride @block.call end def match?(request) method = request['REQUEST_METHOD'] right_method = @methods.empty? or @methods.include?(method) end end class Stable attr_reader :request, :response def initialize @boxes = {} end def in(path, *methods, &block) mount = Mount.new(path, *methods, &block) @boxes[[path, methods]] = mount mount end def call(request, response) @request, @response = request, response @boxes.each do |(path, methods), mount| if mount.match?(request) mount.ride end end end end STABLES = {} def self.saddle(name = nil) STABLES[name] = stable = Stable.new yield stable end def self.run(options = {}) options = {:Host => "127.0.0.1", :Port => 8080}.merge(options) Rack::Handler::Mongrel.run(Rack::Lint.new(self), options) end def self.call(env) request = Rack::Request.new(env) response = Rack::Response.new STABLES.each do |name, stable| stable.call(request, response) end response.finish end end Stallion.saddle :spec do |stable| stable.in '/' do if stable.request.path_info == '/fail' stable.response.status = 404 elsif stable.request.query_string == 'q=test' stable.response.write 'test' elsif stable.request.path_info == '/echo_query' stable.response["ETag"] = "abcdefg" stable.response["Last-Modified"] = "Fri, 13 Aug 2010 17:31:21 GMT" stable.response.write stable.request.query_string elsif stable.request.path_info == '/echo_content_length' stable.response.write stable.request.content_length elsif stable.request.head? && stable.request.path_info == '/' stable.response.status = 200 elsif stable.request.delete? stable.response.status = 200 elsif stable.request.put? stable.response.write stable.request.body.read elsif stable.request.post? if stable.request.path_info == '/echo_content_type' stable.response["Content-Type"] = stable.request.env["CONTENT_TYPE"] || 'text/html' stable.response.write stable.request.env["CONTENT_TYPE"] else stable.response.write stable.request.body.read end elsif stable.request.path_info == '/set_cookie' stable.response["Set-Cookie"] = "id=1; expires=Tue, 09-Aug-2011 17:53:39 GMT; path=/;" stable.response.write "cookie set" elsif stable.request.path_info == '/echo_cookie' stable.response.write stable.request.env["HTTP_COOKIE"] elsif stable.request.path_info == '/timeout' sleep(10) stable.response.write 'timeout' elsif stable.request.path_info == '/redirect' stable.response.status = 301 stable.response["Location"] = "/gzip" stable.response.write 'redirect' elsif stable.request.path_info == '/redirect/bad' stable.response.status = 301 stable.response["Location"] = "http://127.0.0.1:8080" elsif stable.request.path_info == '/redirect/head' stable.response.status = 301 stable.response["Location"] = "/" elsif stable.request.path_info == '/redirect/nohost' stable.response.status = 301 stable.response["Location"] = "http:/" elsif stable.request.path_info == '/redirect/badhost' stable.response.status = 301 stable.response["Location"] = "http://$$$@$!%&^" elsif stable.request.path_info == '/gzip' io = StringIO.new gzip = Zlib::GzipWriter.new(io) gzip << "compressed" gzip.close stable.response.write io.string stable.response["Content-Encoding"] = "gzip" elsif stable.request.path_info == '/deflate' stable.response.write Zlib::Deflate.deflate("compressed") stable.response["Content-Encoding"] = "deflate" elsif stable.request.env["HTTP_IF_NONE_MATCH"] stable.response.status = 304 elsif stable.request.env["HTTP_AUTHORIZATION"] if stable.request.path_info == '/oauth_auth' stable.response.status = 200 stable.response.write stable.request.env["HTTP_AUTHORIZATION"] else auth = "Basic %s" % Base64.encode64(['user', 'pass'].join(':')).chomp if auth == stable.request.env["HTTP_AUTHORIZATION"] stable.response.status = 200 stable.response.write 'success' else stable.response.status = 401 end end elsif stable.request.path_info == '/relative-location' stable.response.status = 301 stable.response["Location"] = '/forwarded' elsif stable.response.write 'Hello, World!' end end end Thread.new do begin Stallion.run :Host => '127.0.0.1', :Port => 8080 rescue Exception => e print e end end # # Tunneling HTTP Proxy server # Thread.new do server = TCPServer.new('127.0.0.1', 8082) loop do session = server.accept request = "" while (data = session.gets) != "\r\n" request << data end parts = request.split("\r\n") method, destination, http_version = parts.first.split(' ') if method == 'CONNECT' target_host, target_port = destination.split(':') client = TCPSocket.open(target_host, target_port) session.write "HTTP/1.1 200 Connection established\r\nProxy-agent: Whatever\r\n\r\n" session.flush content_length = -1 verb = "" req = "" while data = session.gets if request = data.match(/(\w+).*HTTP\/1\.1/) verb = request[1] end if post = data.match(/Content-Length: (\d+)/) content_length = post[1].to_i end req += data # read POST data if data == "\r\n" and verb == "POST" req += session.read(content_length) end if data == "\r\n" client.write req client.flush client.close_write break end end while data = client.gets session.write data end session.flush client.close end session.close end end # # CONNECT-less HTTP Proxy server # Thread.new do server = TCPServer.new('127.0.0.1', 8083) loop do session = server.accept request = "" while (data = session.gets) != "\r\n" request << data end parts = request.split("\r\n") method, destination, http_version = parts.first.split(' ') if destination =~ /^http:/ uri = Addressable::URI.parse(destination) absolute_path = uri.path + (uri.query ? "?#{uri.query}" : "") client = TCPSocket.open(uri.host, uri.port || 80) client.write "#{method} #{absolute_path} #{http_version}\r\n" parts[1..-1].each do |part| client.write "#{part}\r\n" end client.write "\r\n" client.flush client.close_write # Take the initial line from the upstream response session.write client.gets # What (absolute) uri was requested? Send it back in a header session.write "X-The-Requested-URI: #{destination}\r\n" while data = client.gets session.write data end session.flush client.close end session.close end end sleep(1) em-http-request-0.3.0/spec/multi_spec.rb 0000644 0001750 0001750 00000004667 11772656255 017542 0 ustar tfheen tfheen require 'helper' require 'stallion' describe EventMachine::MultiRequest do it "should submit multiple requests in parallel and return once all of them are complete" do EventMachine.run { # create an instance of multi-request handler, and the requests themselves multi = EventMachine::MultiRequest.new # add multiple requests to the multi-handler multi.add(EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(:query => {:q => 'test'})) multi.add(EventMachine::HttpRequest.new('http://0.0.0.0:8083/').get(:timeout => 1)) multi.callback { # verify successful request multi.responses[:succeeded].size.should == 1 multi.responses[:succeeded].first.response.should match(/test/) # verify invalid requests multi.responses[:failed].size.should == 1 multi.responses[:failed].first.response_header.status.should == 0 EventMachine.stop } } end it "should accept multiple open connections and return once all of them are complete" do EventMachine.run { http1 = EventMachine::HttpRequest.new('http://127.0.0.1:8080/').get(:query => {:q => 'test'}) http2 = EventMachine::HttpRequest.new('http://0.0.0.0:8083/').get(:timeout => 1) multi = EventMachine::MultiRequest.new([http1, http2]) do multi.responses[:succeeded].size.should == 1 multi.responses[:succeeded].first.response.should match(/test/) multi.responses[:failed].size.should == 1 multi.responses[:failed].first.response_header.status.should == 0 EventMachine.stop end } end it "should handle multiple mock requests" do EventMachine::MockHttpRequest.register_file('http://127.0.0.1:8080/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')) EventMachine::MockHttpRequest.register_file('http://0.0.0.0:8083/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')) EventMachine.run { # create an instance of multi-request handler, and the requests themselves multi = EventMachine::MultiRequest.new # add multiple requests to the multi-handler multi.add(EventMachine::MockHttpRequest.new('http://127.0.0.1:8080/').get) multi.add(EventMachine::MockHttpRequest.new('http://0.0.0.0:8083/').get) multi.callback { # verify successful request multi.responses[:succeeded].size.should == 2 EventMachine.stop } } end end em-http-request-0.3.0/spec/mock_spec.rb 0000644 0001750 0001750 00000027232 11772656255 017332 0 ustar tfheen tfheen require 'helper' describe 'em-http mock' do before(:all) do EventMachine::MockHttpRequest.activate! end after(:all) do EventMachine::MockHttpRequest.deactivate! end before(:each) do EventMachine::MockHttpRequest.reset_registry! EventMachine::MockHttpRequest.reset_counts! end it "should serve a fake http request from a proc" do EventMachine::HttpRequest.register('http://www.google.ca:80/', :get) { |req| req.response_header.http_status = 200 req.response_header['SOME_WACKY_HEADER'] = 'WACKY_HEADER_VALUE' req.response = "Well, now this is fun." } EM.run { http = EventMachine::HttpRequest.new('http://www.google.ca/').get http.errback { fail } http.callback { http.response_header.status.should == 200 http.response_header['SOME_WACKY_HEADER'].should == 'WACKY_HEADER_VALUE' http.response.should == "Well, now this is fun." EventMachine.stop } } EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1 end it "should serve a fake http request from a proc with raw data" do EventMachine::HttpRequest.register('http://www.google.ca:80/', :get) { |req| req.receive_data(File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'))) } EM.run { http = EventMachine::HttpRequest.new('http://www.google.ca/').get http.errback { fail } http.callback { http.response_header.status.should == 200 http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'), :encoding => 'ISO-8859-1').split("\r\n\r\n", 2).last http.response.encoding.to_s.should == 'ISO-8859-1' EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1 EventMachine.stop } } end it "should serve a fake http request from a file" do EventMachine::HttpRequest.register_file('http://www.google.ca:80/', :get, {}, File.join(File.dirname(__FILE__), 'fixtures', 'google.ca')) EM.run { http = EventMachine::HttpRequest.new('http://www.google.ca/').get http.errback { fail } http.callback { http.response_header.status.should == 200 http.response.should == File.read(File.join(File.dirname(__FILE__), 'fixtures', 'google.ca'), :encoding => 'ISO-8859-1').split("\r\n\r\n", 2).last http.response.encoding.to_s.should == 'ISO-8859-1' EventMachine::HttpRequest.count('http://www.google.ca:80/', :get, {}).should == 1 EventMachine.stop } } end it "should serve a fake http request from a string" do data = <<-HEREDOC HTTP/1.0 200 OK Date: Mon, 16 Nov 2009 20:39:15 GMT Expires: -1 Cache-Control: private, max-age=0 Content-Type: text/html; charset=ISO-8859-1 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 Set-Cookie: NID=28=lvxxVdiBQkCetu_WFaUxLyB7qPlHXS5OdAGYTqge_laVlCKVN8VYYeVBh4bNZiK_Oan2gm8oP9GA-FrZfMPC3ZMHeNq37MG2JH8AIW9LYucU8brOeuggMEbLNNXuiWg4; expires=Tue, 18-May-2010 20:39:15 GMT; path=/; domain=.google.ca; HttpOnly Server: gws X-XSS-Protection: 0 X-Cache: MISS from . Via: 1.0 .:80 (squid) Connection: close
©2009 - Privacy
![]() | ![]() | ![]() |
![]() | Canada |