Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to work with the latest version of em-http-request (green specs) #111

Merged
merged 7 commits into from Jul 30, 2011
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -5,6 +5,7 @@ gemspec
group :development do group :development do
gem 'guard-rspec' gem 'guard-rspec'
gem 'rb-fsevent' gem 'rb-fsevent'
gem 'em-synchrony', '0.3.0.beta.1', :require => false
end end


platforms :jruby do platforms :jruby do
Expand Down
168 changes: 109 additions & 59 deletions lib/webmock/http_lib_adapters/em_http_request.rb
@@ -1,59 +1,98 @@
if defined?(EventMachine::HttpRequest) if defined?(EventMachine::HttpClient)


module EventMachine module EventMachine
OriginalHttpRequest = HttpRequest unless const_defined?(:OriginalHttpRequest) OriginalHttpClient = HttpClient unless const_defined?(:OriginalHttpClient)
OriginalHttpConnection = HttpConnection unless const_defined?(:OriginalHttpConnection)

if defined?(Synchrony)
# have to make the callbacks fire on the next tick in order
# to avoid the dreaded "double resume" exception
module HTTPMethods
%w[get head post delete put].each do |type|
class_eval %[
def #{type}(options = {}, &blk)
f = Fiber.current

conn = setup_request(:#{type}, options, &blk)
conn.callback { EM.next_tick { f.resume(conn) } }
conn.errback { EM.next_tick { f.resume(conn) } }

Fiber.yield
end
]
end
end
end


class WebMockHttpRequest < EventMachine::HttpRequest class WebMockHttpConnection < HttpConnection
def webmock_activate_connection(client)
request_signature = client.request_signature


include HttpEncoding if WebMock::StubRegistry.instance.registered_request?(request_signature)
conn = HttpStubConnection.new rand(10000)
post_init


class WebMockHttpClient < EventMachine::HttpClient @deferred = false
@conn = conn


def setup(response, uri, error = nil) conn.parent = self
@last_effective_url = @uri = uri conn.pending_connect_timeout = @connopts.connect_timeout
if error conn.comm_inactivity_timeout = @connopts.inactivity_timeout
on_error(error)
fail(self)
else
EM.next_tick do
receive_data(response)
succeed(self)
end
end
end


def unbind finalize_request(client)
@conn.set_deferred_status :succeeded
elsif WebMock.net_connect_allowed?(request_signature.uri)
real_activate_connection(client)
else
raise WebMock::NetConnectNotAllowedError.new(request_signature)
end end
end
alias_method :real_activate_connection, :activate_connection
alias_method :activate_connection, :webmock_activate_connection
end


def close_connection class WebMockHttpClient < EventMachine::HttpClient
end include HttpEncoding

def uri
@req.uri
end end


def send_request_with_webmock(&block) def setup(response, uri, error = nil)
request_signature = build_request_signature @last_effective_url = @uri = uri
if error
on_error(error)
fail(self)
else
@conn.receive_data(response)
succeed(self)
end
end


def send_request_with_webmock(head, body)
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)


if WebMock::StubRegistry.instance.registered_request?(request_signature) if WebMock::StubRegistry.instance.registered_request?(request_signature)
webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature) webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
WebMock::CallbackRegistry.invoke_callbacks( on_error("WebMock timeout error") if webmock_response.should_timeout
{:lib => :em_http_request}, request_signature, webmock_response) WebMock::CallbackRegistry.invoke_callbacks({:lib => :em_http_request}, request_signature, webmock_response)
client = WebMockHttpClient.new(nil) EM.next_tick {
client.on_error("WebMock timeout error") if webmock_response.should_timeout setup(make_raw_response(webmock_response), @uri,
client.setup(make_raw_response(webmock_response), @uri, webmock_response.should_timeout ? "WebMock timeout error" : nil)
webmock_response.should_timeout ? "WebMock timeout error" : nil) }
client self
elsif WebMock.net_connect_allowed?(request_signature.uri) elsif WebMock.net_connect_allowed?(request_signature.uri)
http = send_request_without_webmock(&block) send_request_without_webmock(head, body)
http.callback { callback {
if WebMock::CallbackRegistry.any_callbacks? if WebMock::CallbackRegistry.any_callbacks?
webmock_response = build_webmock_response(http) webmock_response = build_webmock_response
WebMock::CallbackRegistry.invoke_callbacks( WebMock::CallbackRegistry.invoke_callbacks(
{:lib => :em_http_request, :real_request => true}, request_signature, {:lib => :em_http_request, :real_request => true},
request_signature,
webmock_response) webmock_response)
end end
} }
http self
else else
raise WebMock::NetConnectNotAllowedError.new(request_signature) raise WebMock::NetConnectNotAllowedError.new(request_signature)
end end
Expand All @@ -62,55 +101,63 @@ def send_request_with_webmock(&block)
alias_method :send_request_without_webmock, :send_request alias_method :send_request_without_webmock, :send_request
alias_method :send_request, :send_request_with_webmock alias_method :send_request, :send_request_with_webmock


def request_signature
@request_signature ||= build_request_signature
end


private private


def build_webmock_response(http) def build_webmock_response
webmock_response = WebMock::Response.new webmock_response = WebMock::Response.new
webmock_response.status = [http.response_header.status, http.response_header.http_reason] webmock_response.status = [response_header.status, response_header.http_reason]
webmock_response.headers = http.response_header webmock_response.headers = response_header
webmock_response.body = http.response webmock_response.body = response
webmock_response webmock_response
end end


def build_request_signature def build_request_signature
if @req headers, body = @req.headers, @req.body
options = @req.options
method = @req.method @conn.middleware.select {|m| m.respond_to?(:request) }.each do |m|
uri = @req.uri headers, body = m.request(self, headers, body)
else
options = @options
method = @method
uri = @uri
end end


if options[:authorization] || options['authorization'] method = @req.method
auth = (options[:authorization] || options['authorization']) uri = @req.uri
auth = @req.proxy[:authorization]
query = @req.query

if auth
userinfo = auth.join(':') userinfo = auth.join(':')
userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo) userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo)
options.reject! {|k,v| k.to_s == 'authorization' } #we added it to url userinfo if @req
@req.proxy.reject! {|k,v| t.to_s == 'authorization' }
else
options.reject! {|k,v| k.to_s == 'authorization' } #we added it to url userinfo
end
uri.userinfo = userinfo uri.userinfo = userinfo
end end


uri.query = encode_query(@req.uri, options[:query]).slice(/\?(.*)/, 1) uri.query = encode_query(@req.uri, query).slice(/\?(.*)/, 1)


WebMock::RequestSignature.new( WebMock::RequestSignature.new(
method.downcase.to_sym, method.downcase.to_sym,
uri.to_s, uri.to_s,
:body => (options[:body] || options['body']), :body => body,
:headers => (options[:head] || options['head']) :headers => headers
) )
end end



def make_raw_response(response) def make_raw_response(response)
response.raise_error_if_any response.raise_error_if_any


status, headers, body = response.status, response.headers, response.body status, headers, body = response.status, response.headers, response.body
headers ||= {}


response_string = [] response_string = []
response_string << "HTTP/1.1 #{status[0]} #{status[1]}" response_string << "HTTP/1.1 #{status[0]} #{status[1]}"


headers["Content-Length"] = body.length unless headers["Content-Length"]
headers.each do |header, value| headers.each do |header, value|
value = value.join(", ") if value.is_a?(Array) value = value.join(", ") if value.is_a?(Array)


Expand All @@ -128,17 +175,20 @@ def make_raw_response(response)
end end


def self.activate! def self.activate!
EventMachine.send(:remove_const, :HttpRequest) EventMachine.send(:remove_const, :HttpConnection)
EventMachine.send(:const_set, :HttpRequest, WebMockHttpRequest) EventMachine.send(:const_set, :HttpConnection, WebMockHttpConnection)
EventMachine.send(:remove_const, :HttpClient)
EventMachine.send(:const_set, :HttpClient, WebMockHttpClient)
end end


def self.deactivate! def self.deactivate!
EventMachine.send(:remove_const, :HttpRequest) EventMachine.send(:remove_const, :HttpConnection)
EventMachine.send(:const_set, :HttpRequest, OriginalHttpRequest) EventMachine.send(:const_set, :HttpConnection, OriginalHttpConnection)
EventMachine.send(:remove_const, :HttpClient)
EventMachine.send(:const_set, :HttpClient, OriginalHttpClient)
end end
end end
end end


EventMachine::WebMockHttpRequest.activate! EventMachine::WebMockHttpClient.activate!

end end
83 changes: 83 additions & 0 deletions spec/em_http_request_spec.rb
Expand Up @@ -10,6 +10,52 @@


it_should_behave_like "WebMock" it_should_behave_like "WebMock"


it "should work with request middleware" do
stub_http_request(:get, "www.example.com").with(:body => 'bar')

middleware = Class.new do
def request(client, head, body)
[{}, 'bar']
end
end

EM.run do
conn = EventMachine::HttpRequest.new('http://www.example.com/')

conn.use middleware

http = conn.get(:body => 'foo')

http.callback do
WebMock.should have_requested(:get, "www.example.com").with(:body => 'bar')
EM.stop
end
end
end

it "should work with response middleware" do
stub_http_request(:get, "www.example.com").to_return(:body => 'foo')

middleware = Class.new do
def response(resp)
resp.response = 'bar'
end
end

EM.run do
conn = EventMachine::HttpRequest.new('http://www.example.com/')

conn.use middleware

http = conn.get

http.callback do
http.response.should be == 'bar'
EM.stop
end
end
end

it "should work with streaming" do it "should work with streaming" do
stub_http_request(:get, "www.example.com").to_return(:body => "abc") stub_http_request(:get, "www.example.com").to_return(:body => "abc")
response = "" response = ""
Expand All @@ -35,6 +81,43 @@
http_request(:get, "http://www.example.com/?x=3", :query => "a[]=b&a[]=c").body.should == "abc" http_request(:get, "http://www.example.com/?x=3", :query => "a[]=b&a[]=c").body.should == "abc"
end end


# not pretty, but it works
describe "with synchrony" do
let(:webmock_em_http) { File.expand_path(File.join(File.dirname(__FILE__), "../lib/webmock/http_lib_adapters/em_http_request.rb")) }

before(:each) do
# need to reload the webmock em-http adapter after we require synchrony
EM::WebMockHttpClient.deactivate!
$".delete webmock_em_http
require 'em-synchrony'
require 'em-synchrony/em-http'
require webmock_em_http
end

it "should work" do
stub_request(:post, /.*.testserver.com*/).to_return(:status => 200, :body => 'ok')
lambda {
EM.run do
fiber = Fiber.new do
http = EM::HttpRequest.new("http://www.testserver.com").post :body => "foo=bar&baz=bang", :timeout => 60
EM.stop
end
fiber.resume
end
}.should_not raise_error
end

after(:each) do
EM.send(:remove_const, :Synchrony)
EM.send(:remove_const, :HTTPMethods)
EM::WebMockHttpClient.deactivate!
$".reject! {|path| path.include? "em-http-request"}
$".delete webmock_em_http
require 'em-http-request'
require webmock_em_http
end
end

describe "mocking EM::HttpClient API" do describe "mocking EM::HttpClient API" do
before { stub_http_request(:get, "www.example.com/") } before { stub_http_request(:get, "www.example.com/") }
subject do subject do
Expand Down
11 changes: 7 additions & 4 deletions spec/em_http_request_spec_helper.rb
Expand Up @@ -6,17 +6,19 @@ def failed
end end


def http_request(method, uri, options = {}, &block) def http_request(method, uri, options = {}, &block)
@http = nil
head = options[:headers] || {}
response = nil response = nil
error = nil error = nil
uri = Addressable::URI.heuristic_parse(uri) uri = Addressable::URI.heuristic_parse(uri)
EventMachine.run { EventMachine.run {
request = EventMachine::HttpRequest.new("#{uri.omit(:userinfo).normalize.to_s}") request = EventMachine::HttpRequest.new("#{uri.normalize.to_s}")
http = request.send(:setup_request, method, { http = request.send(method, {
:timeout => 10, :timeout => 10,
:body => options[:body], :body => options[:body],
:query => options[:query], :query => options[:query],
'authorization' => [uri.user, uri.password], :head => head.merge('authorization' => [uri.user, uri.password])
:head => options[:headers]}, &block) }, &block)
http.errback { http.errback {
error = if http.respond_to?(:errors) error = if http.respond_to?(:errors)
http.errors http.errors
Expand All @@ -34,6 +36,7 @@ def http_request(method, uri, options = {}, &block)
}) })
EventMachine.stop EventMachine.stop
} }
@http = http
} }
raise error if error raise error if error
response response
Expand Down