Skip to content

Commit 6dcf2d6

Browse files
authored
Merge pull request #449 from seanpdoyle/net-http-requests
Implement `Connection` in terms of `Net::HTTP` Request
2 parents bdb7f20 + 2ea5401 commit 6dcf2d6

File tree

3 files changed

+61
-34
lines changed

3 files changed

+61
-34
lines changed

lib/active_resource/connection.rb

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ module ActiveResource
1111
# This class is used by ActiveResource::Base to interface with REST
1212
# services.
1313
class Connection
14-
HTTP_FORMAT_HEADER_NAMES = { get: "Accept",
14+
HTTP_FORMAT_HEADER_NAMES = {
15+
get: "Accept",
1516
put: "Content-Type",
1617
post: "Content-Type",
1718
patch: "Content-Type",
1819
delete: "Accept",
1920
head: "Accept"
2021
}
22+
HTTP_METHODS = {
23+
get: Net::HTTP::Get,
24+
put: Net::HTTP::Put,
25+
post: Net::HTTP::Post,
26+
patch: Net::HTTP::Patch,
27+
delete: Net::HTTP::Delete,
28+
head: Net::HTTP::Head
29+
}.freeze # :nodoc:
2130

2231
attr_reader :site, :user, :password, :bearer_token, :auth_type, :timeout, :open_timeout, :read_timeout, :proxy, :ssl_options
2332
attr_accessor :format, :logger
@@ -80,50 +89,52 @@ def auth_type=(auth_type)
8089
# Executes a GET request.
8190
# Used to get (find) resources.
8291
def get(path, headers = {})
83-
with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) }
92+
with_auth { request(:get, path, headers) }
8493
end
8594

8695
# Executes a DELETE request (see HTTP protocol documentation if unfamiliar).
8796
# Used to delete resources.
8897
def delete(path, headers = {})
89-
with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
98+
with_auth { request(:delete, path, headers) }
9099
end
91100

92101
# Executes a PATCH request (see HTTP protocol documentation if unfamiliar).
93102
# Used to update resources.
94103
def patch(path, body = "", headers = {})
95-
with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) }
104+
with_auth { request(:patch, path, body.to_s, headers) }
96105
end
97106

98107
# Executes a PUT request (see HTTP protocol documentation if unfamiliar).
99108
# Used to update resources.
100109
def put(path, body = "", headers = {})
101-
with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) }
110+
with_auth { request(:put, path, body.to_s, headers) }
102111
end
103112

104113
# Executes a POST request.
105114
# Used to create new resources.
106115
def post(path, body = "", headers = {})
107-
with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) }
116+
with_auth { request(:post, path, body.to_s, headers) }
108117
end
109118

110119
# Executes a HEAD request.
111120
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
112121
def head(path, headers = {})
113-
with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) }
122+
with_auth { request(:head, path, headers) }
114123
end
115124

116125
private
117126
# Makes a request to the remote service.
118127
def request(method, path, *arguments)
119128
body, headers = arguments
120129
headers, body = body, nil if headers.nil?
130+
request = build_request(method, path, headers)
131+
request.body = body
121132
result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload|
122133
payload[:method] = method
123-
payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
124-
payload[:headers] = headers
134+
payload[:request_uri] = request.uri.to_s
135+
payload[:headers] = request.each_capitalized.to_h
125136
payload[:body] = body
126-
payload[:result] = http.send(method, path, *arguments)
137+
payload[:result] = http.request(request)
127138
end
128139
handle_response(result)
129140
rescue Timeout::Error => e
@@ -218,6 +229,15 @@ def default_header
218229
@default_header ||= {}
219230
end
220231

232+
def build_request(http_method, path, headers)
233+
HTTP_METHODS[http_method].new(site.merge(path)).tap do |request|
234+
headers = build_request_headers(headers, http_method, request.uri)
235+
236+
request.each_name { |name| request.delete(name) }
237+
headers.each_pair { |name, value| request[name] = value }
238+
end
239+
end
240+
221241
# Builds headers for request to remote service.
222242
def build_request_headers(headers, http_method, uri)
223243
authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers.to_hash)

lib/active_resource/http_mock.rb

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class InvalidRequestError < StandardError; end # :nodoc:
7575
#
7676
# When a block is passed to the mock, it ignores the +body+, +status+, and +response_headers+ arguments.
7777
class HttpMock
78+
HTTP_METHODS = Connection::HTTP_METHODS.invert.freeze # :nodoc:
79+
7880
class Responder # :nodoc:
7981
def initialize(responses)
8082
@responses = responses
@@ -270,33 +272,38 @@ def net_connection_disabled?
270272
methods.each do |method|
271273
# def post(path, body, headers, options = {})
272274
# request = ActiveResource::Request.new(:post, path, body, headers, options)
273-
# self.class.requests << request
274-
# if response = self.class.responses.assoc(request)
275-
# response = response[1]
276-
# response = response.call(request) if response.respond_to?(:call)
277275
#
278-
# Response.wrap(response)
279-
# else
280-
# raise InvalidRequestError.new("Could not find a response recorded for #{request.to_s} - Responses recorded are: - #{inspect_responses}")
281-
# end
276+
# process(request)
282277
# end
283278
module_eval <<-EOE, __FILE__, __LINE__ + 1
284279
def #{method}(path, #{'body, ' if has_body}headers, options = {})
285280
request = ActiveResource::Request.new(:#{method}, path, #{has_body ? 'body, ' : 'nil, '}headers, options)
286-
self.class.requests << request
287-
if response = self.class.responses.assoc(request)
288-
response = response[1]
289-
response = response.call(request) if response.respond_to?(:call)
290-
291-
Response.wrap(response)
292-
else
293-
raise InvalidRequestError.new("Could not find a response recorded for \#{request.to_s} - Responses recorded are: \#{inspect_responses}")
294-
end
281+
282+
process(request)
295283
end
296284
EOE
297285
end
298286
end
299287

288+
def request(http) # :nodoc:
289+
request = Request.new(HTTP_METHODS[http.class], http.path, nil, http.each_capitalized.to_h)
290+
request.body = http.body if http.request_body_permitted?
291+
292+
process(request)
293+
end
294+
295+
def process(request) # :nodoc:
296+
self.class.requests << request
297+
if response = self.class.responses.assoc(request)
298+
response = response[1]
299+
response = response.call(request) if response.respond_to?(:call)
300+
301+
Response.wrap(response)
302+
else
303+
raise InvalidRequestError.new("Could not find a response recorded for #{request} - Responses recorded are: #{inspect_responses}")
304+
end
305+
end
306+
300307
def initialize(site) # :nodoc:
301308
@site = site
302309
end
@@ -310,7 +317,7 @@ class Request
310317
attr_accessor :path, :method, :body, :headers
311318

312319
def initialize(method, path, body = nil, headers = {}, options = {})
313-
@method, @path, @body, @headers, @options = method, path, body, headers, options
320+
@method, @path, @body, @headers, @options = method, path, body, headers.transform_keys(&:downcase), options
314321
end
315322

316323
def ==(req)
@@ -339,7 +346,7 @@ def same_path?(req)
339346

340347
def headers_match?(req)
341348
# Ignore format header on equality if it's not defined
342-
format_header = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method]
349+
format_header = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method].downcase
343350
if headers[format_header].present? || req.headers[format_header].blank?
344351
headers == req.headers
345352
else

test/cases/connection_test.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def test_delete_with_header
242242
def test_timeout
243243
@http = mock("new Net::HTTP")
244244
@conn.expects(:http).returns(@http)
245-
@http.expects(:get).raises(Timeout::Error, "execution expired")
245+
@http.expects(:request).raises(Timeout::Error, "execution expired")
246246
assert_raise(ActiveResource::TimeoutError) { @conn.get("/people_timeout.json") }
247247
end
248248

@@ -261,7 +261,7 @@ def test_accept_http_header
261261
@http = mock("new Net::HTTP")
262262
@conn.expects(:http).returns(@http)
263263
path = "/people/1.xml"
264-
@http.expects(:get).with(path, { "Accept" => "application/xhtml+xml" }).returns(ActiveResource::Response.new(@matz, 200, "Content-Type" => "text/xhtml"))
264+
@http.expects(:request).with { |get| get.path == path && get["Accept"] == "application/xhtml+xml" }.returns(ActiveResource::Response.new(@matz, 200, "Content-Type" => "text/xhtml"))
265265
assert_nothing_raised { @conn.get(path, "Accept" => "application/xhtml+xml") }
266266
end
267267

@@ -284,21 +284,21 @@ def test_ssl_options_get_applied_to_https_urls_without_explicitly_setting_ssl_op
284284
def test_ssl_error
285285
http = Net::HTTP.new("")
286286
@conn.expects(:http).returns(http)
287-
http.expects(:get).raises(OpenSSL::SSL::SSLError, "Expired certificate")
287+
http.expects(:request).raises(OpenSSL::SSL::SSLError, "Expired certificate")
288288
assert_raise(ActiveResource::SSLError) { @conn.get("/people/1.json") }
289289
end
290290

291291
def test_handle_econnrefused
292292
http = Net::HTTP.new("")
293293
@conn.expects(:http).returns(http)
294-
http.expects(:get).raises(Errno::ECONNREFUSED, "Failed to open TCP connection")
294+
http.expects(:request).raises(Errno::ECONNREFUSED, "Failed to open TCP connection")
295295
assert_raise(ActiveResource::ConnectionRefusedError) { @conn.get("/people/1.json") }
296296
end
297297

298298
def test_handle_econnrefused_with_backwards_compatible_error
299299
http = Net::HTTP.new("")
300300
@conn.expects(:http).returns(http)
301-
http.expects(:get).raises(Errno::ECONNREFUSED, "Failed to open TCP connection")
301+
http.expects(:request).raises(Errno::ECONNREFUSED, "Failed to open TCP connection")
302302
assert_raise(Errno::ECONNREFUSED) { @conn.get("/people/1.json") }
303303
end
304304

0 commit comments

Comments
 (0)