diff --git a/README.md b/README.md index 7d41b7f53..afac495ff 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Supported HTTP libraries * Curb (currently only Curb::Easy) * Typhoeus (currently only Typhoeus::Hydra) * Excon +* HTTP Gem Supported Ruby Interpreters --------------------------- diff --git a/lib/webmock.rb b/lib/webmock.rb index 905145d40..b72230267 100644 --- a/lib/webmock.rb +++ b/lib/webmock.rb @@ -38,6 +38,7 @@ require 'webmock/http_lib_adapters/http_lib_adapter_registry' require 'webmock/http_lib_adapters/http_lib_adapter' require 'webmock/http_lib_adapters/net_http' +require 'webmock/http_lib_adapters/http_gem_adapter' require 'webmock/http_lib_adapters/httpclient_adapter' require 'webmock/http_lib_adapters/patron_adapter' require 'webmock/http_lib_adapters/curb_adapter' diff --git a/lib/webmock/http_lib_adapters/http_gem_adapter.rb b/lib/webmock/http_lib_adapters/http_gem_adapter.rb new file mode 100644 index 000000000..92137d777 --- /dev/null +++ b/lib/webmock/http_lib_adapters/http_gem_adapter.rb @@ -0,0 +1,170 @@ +begin + require "http" +rescue LoadError + # HTTP gem not found +end + + +if defined?(HTTP::Client) + module WebMock + module HttpLibAdapters + class HttpGemAdapter < HttpLibAdapter + + adapter_for :http_gem + + + def self.enable! + ::HTTP.enable_webmock! + end + + + def self.disable! + ::HTTP.disable_webmock! + end + + end + end + end + + + module HTTP + class Request + + def webmock_signature + ::WebMock::RequestSignature.new(method, uri.to_s, { + :headers => headers, + :body => body + }) + end + + end + + + class Response + + def to_webmock + webmock_response = ::WebMock::Response.new + + webmock_response.status = [status, reason] + webmock_response.body = body + webmock_response.headers = headers + + webmock_response + end + + + def self.from_webmock(webmock_response) + status = webmock_response.status.first + headers = webmock_response.headers || {} + body = webmock_response.body + + new(status, "1.1", headers, body) + end + + end + + + class WebMockPerform + + def initialize request, &perform + @request = request + @perform = perform + end + + + def exec + replay || perform || halt + end + + + def request_signature + unless @request_signature + @request_signature = @request.webmock_signature + register_request(@request_signature) + end + + @request_signature + end + + + protected + + + def response_for_request(signature) + ::WebMock::StubRegistry.instance.response_for_request(signature) + end + + + def register_request(signature) + ::WebMock::RequestRegistry.instance.requested_signatures.put(signature) + end + + + def replay + webmock_response = response_for_request(request_signature) + + return unless webmock_response + + raise Errno::ETIMEDOUT if webmock_response.should_timeout + webmock_response.raise_error_if_any + + invoke_callbacks(webmock_response, :real_request => false) + ::HTTP::Response.from_webmock webmock_response + end + + + def perform + return unless ::WebMock.net_connect_allowed?(request_signature.uri) + response = @perform.call + invoke_callbacks(response.to_webmock, :real_request => true) + response + end + + + def halt + raise ::WebMock::NetConnectNotAllowedError.new request_signature + end + + + def invoke_callbacks webmock_response, options = {} + ::WebMock::CallbackRegistry.invoke_callbacks( + options.merge({ :lib => :http_gem }), + request_signature, + webmock_response + ) + end + + end + + + class Client + + alias :__perform__ :perform + + def perform request, options + return __perform__(request, options) unless ::HTTP.webmock_enabled? + WebMockPerform.new(request) { __perform__(request, options) }.exec + end + + end + + + class << self + + def enable_webmock! + @webmock_enabled = true + end + + + def disable_webmock! + @webmock_enabled = false + end + + + def webmock_enabled? + @webmock_enabled + end + + end + end +end diff --git a/spec/acceptance/http_gem/http_gem_spec.rb b/spec/acceptance/http_gem/http_gem_spec.rb new file mode 100644 index 000000000..5dc2c39c2 --- /dev/null +++ b/spec/acceptance/http_gem/http_gem_spec.rb @@ -0,0 +1,54 @@ +# encoding: utf-8 + +require "spec_helper" +require "acceptance/webmock_shared" +require "acceptance/http_gem/http_gem_spec_helper" + +describe "HTTP Gem" do + + include HttpGemSpecHelper + + + include_examples "with WebMock", :no_status_message + + + context "when not following redirects" do + + let(:response) { http_request(:get, "http://example.com") } + let(:headers) { response.headers } + + it "stops on first request" do + stub_simple_request("example.com", 302, "Location" => "www.example.com") + stub_simple_request("www.example.com") + + expect(headers).to include "Host" => "example.com" + end + + end + + + context "when following redirects" do + + let(:response) { http_request(:get, "http://example.com", :follow => true) } + let(:headers) { response.headers } + + + it "returns response of destination" do + stub_simple_request("example.com", 302, "Location" => "www.example.com") + stub_simple_request("www.example.com") + + expect(headers).to include "Host" => "www.example.com" + end + + + it "works with more than one redirect" do + stub_simple_request("example.com", 302, "Location" => "www.example.com") + stub_simple_request("www.example.com", 302, "Location" => "blog.example.com") + stub_simple_request("blog.example.com") + + expect(headers).to include "Host" => "blog.example.com" + end + + end + +end diff --git a/spec/acceptance/http_gem/http_gem_spec_helper.rb b/spec/acceptance/http_gem/http_gem_spec_helper.rb new file mode 100644 index 000000000..b56fa198c --- /dev/null +++ b/spec/acceptance/http_gem/http_gem_spec_helper.rb @@ -0,0 +1,52 @@ +require "ostruct" + + +module HttpGemSpecHelper + + def http_request(method, uri, options = {}, &block) + response = HTTP.request(method, normalize_uri(uri), options).response + + OpenStruct.new({ + :body => response.body, + :headers => normalize_headers(response.headers), + :status => response.status.to_s, + :message => response.reason + }) + end + + + def normalize_uri(uri) + Addressable::URI.heuristic_parse(uri).normalize.to_s + end + + + def normalize_headers headers + WebMock::Util::Headers.normalize_headers(Hash[headers.map { |k, v| + [k, v.is_a?(Array) ? v.join(", ") : v] + }]) + end + + + def stub_simple_request host, status = 200, headers = {} + stub_request(:any, host).to_return({ + :status => status, + :headers => headers.merge({ "Host" => host }) + }) + end + + + def client_timeout_exception_class + Errno::ETIMEDOUT + end + + + def connection_refused_exception_class + Errno::ECONNREFUSED + end + + + def http_library + :http_gem + end + +end diff --git a/webmock.gemspec b/webmock.gemspec index d48a44b4b..82ae37a67 100644 --- a/webmock.gemspec +++ b/webmock.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |s| s.add_dependency 'crack', '>=0.3.2' s.add_development_dependency 'rspec', '~> 2.10' + s.add_development_dependency 'http', '>= 0.5.0' s.add_development_dependency 'httpclient', '>= 2.2.4' s.add_development_dependency 'patron', '>= 0.4.18' unless RUBY_PLATFORM =~ /java/ s.add_development_dependency 'em-http-request', '>= 1.0.2'