httpadapter 0.2.1 → 1.0.0
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.
- data/CHANGELOG +4 -0
- data/README.md +48 -0
- data/lib/httpadapter.rb +87 -75
- data/lib/httpadapter/adapters/mock.rb +9 -6
- data/lib/httpadapter/adapters/net_http.rb +125 -121
- data/lib/httpadapter/adapters/rack.rb +29 -31
- data/lib/httpadapter/adapters/typhoeus.rb +46 -50
- data/lib/httpadapter/connection.rb +0 -2
- data/lib/httpadapter/version.rb +3 -3
- data/spec/httpadapter/adapter_type_checking_spec.rb +170 -0
- data/spec/httpadapter/adapters/mock_adapter_spec.rb +81 -0
- data/spec/httpadapter/adapters/net_http_spec.rb +633 -0
- data/spec/httpadapter/adapters/rack_spec.rb +357 -0
- data/spec/httpadapter/adapters/typhoeus_spec.rb +379 -0
- data/spec/httpadapter_spec.rb +59 -180
- data/tasks/gem.rake +2 -2
- data/tasks/rdoc.rake +1 -1
- data/tasks/spec.rake +4 -1
- data/tasks/yard.rake +1 -1
- metadata +15 -18
- data/README +0 -49
- data/spec/httpadapter/adapters/net_http_request_spec.rb +0 -417
- data/spec/httpadapter/adapters/net_http_response_spec.rb +0 -170
- data/spec/httpadapter/adapters/net_http_transmission_spec.rb +0 -130
- data/spec/httpadapter/adapters/rack_request_spec.rb +0 -239
- data/spec/httpadapter/adapters/rack_response_spec.rb +0 -158
- data/spec/httpadapter/adapters/typhoeus_request_spec.rb +0 -203
- data/spec/httpadapter/adapters/typhoeus_response_spec.rb +0 -146
- data/spec/httpadapter/adapters/typhoeus_transmission_spec.rb +0 -89
data/CHANGELOG
CHANGED
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# HTTPAdapter
|
2
|
+
|
3
|
+
<dl>
|
4
|
+
<dt>Homepage</dt><dd><a href="http://httpadapter.rubyforge.org/">httpadapter.rubyforge.org</a></dd>
|
5
|
+
<dt>Author</dt><dd><a href="mailto:bobaman@google.com">Bob Aman</a></dd>
|
6
|
+
<dt>Copyright</dt><dd>Copyright © 2010 Google, Inc.</dd>
|
7
|
+
<dt>License</dt><dd>Apache 2.0</dd>
|
8
|
+
</dl>
|
9
|
+
|
10
|
+
# Description
|
11
|
+
|
12
|
+
A library for translating HTTP request and response objects for various clients
|
13
|
+
into a common representation.
|
14
|
+
|
15
|
+
# Reference
|
16
|
+
|
17
|
+
- {HTTPAdapter}
|
18
|
+
|
19
|
+
# Adapters
|
20
|
+
|
21
|
+
- {HTTPAdapter::NetHTTPAdapter}
|
22
|
+
- {HTTPAdapter::RackAdapter}
|
23
|
+
- {HTTPAdapter::TyphoeusAdapter}
|
24
|
+
|
25
|
+
# Example Usage
|
26
|
+
|
27
|
+
adapter = HTTPAdapter::NetHTTPAdapter.new
|
28
|
+
response = Net::HTTP.start('www.google.com', 80) { |http| http.get('/') }
|
29
|
+
# => #<Net::HTTPOK 200 OK readbody=true>
|
30
|
+
result = adapter.adapt_response(response)
|
31
|
+
# => [
|
32
|
+
# 200,
|
33
|
+
# [
|
34
|
+
# ["Expires", "-1"],
|
35
|
+
# ["Content-Type", "text/html; charset=ISO-8859-1"],
|
36
|
+
# ["X-Xss-Protection", "1; mode=block"],
|
37
|
+
# ["Server", "gws"],
|
38
|
+
# ["Date", "Thu, 26 Aug 2010 22:13:03 GMT"],
|
39
|
+
# ["Set-Cookie", "<snip>"],
|
40
|
+
# ["Cache-Control", "private, max-age=0"],
|
41
|
+
# ["Transfer-Encoding", "chunked"]
|
42
|
+
# ],
|
43
|
+
# ["<snip>"]
|
44
|
+
# ]
|
45
|
+
|
46
|
+
# Install
|
47
|
+
|
48
|
+
* sudo gem install httpadapter
|
data/lib/httpadapter.rb
CHANGED
@@ -15,83 +15,100 @@
|
|
15
15
|
require 'httpadapter/version'
|
16
16
|
require 'httpadapter/connection'
|
17
17
|
|
18
|
-
|
18
|
+
##
|
19
|
+
# A module which provides methods to aid in conversion of HTTP request and
|
20
|
+
# response objects. It uses tuples as a generic intermediary format.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class StubAdapter
|
24
|
+
# include HTTPAdapter
|
25
|
+
#
|
26
|
+
# def convert_request_to_a(request_obj)
|
27
|
+
# return ['GET', '/', [], [""]] # Stubbed request tuple
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def convert_request_from_a(request_ary)
|
31
|
+
# return Object.new # Stubbed request object
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def convert_response_to_a(response_obj)
|
35
|
+
# return [200, [], ['']] # Stubbed response tuple
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def convert_response_from_a(response_ary)
|
39
|
+
# return Object.new # Stubbed response object
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def fetch_resource(request_ary, connection=nil)
|
43
|
+
# return [200, [], ['']] # Stubbed response tuple from server
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
module HTTPAdapter
|
19
47
|
##
|
20
48
|
# Converts an HTTP request object to a simple tuple.
|
21
49
|
#
|
22
|
-
# @param [Object
|
23
|
-
# The request object to be converted. The
|
24
|
-
# the <code>#
|
25
|
-
#
|
26
|
-
# object as a parameter and provide the <code>#to_ary</code> method.
|
50
|
+
# @param [Object] request
|
51
|
+
# The request object to be converted. The adapter must implement
|
52
|
+
# the <code>#convert_request_to_a</code> method, which takes the request
|
53
|
+
# object as a parameter.
|
27
54
|
#
|
28
55
|
# @return [Array] The tuple that the request was converted to.
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
if request.respond_to?(:to_ary)
|
33
|
-
converted_request = request.to_ary
|
56
|
+
def adapt_request(request_obj)
|
57
|
+
if self.respond_to?(:convert_request_to_a)
|
58
|
+
converted_request = self.convert_request_to_a(request_obj)
|
34
59
|
else
|
35
|
-
# Can't use #to_a because some versions of Ruby define #to_a on Object
|
36
60
|
raise TypeError,
|
37
|
-
|
61
|
+
'Expected adapter to implement #convert_request_to_a.'
|
38
62
|
end
|
39
|
-
return
|
40
|
-
end
|
41
|
-
|
42
|
-
##
|
43
|
-
# Converts an HTTP response object to a simple tuple.
|
44
|
-
#
|
45
|
-
# @param [Object, #to_ary] response
|
46
|
-
# The response object to be converted. The response may either implement
|
47
|
-
# the <code>#to_ary</code> method directly or alternately, an optional
|
48
|
-
# adapter class may be provided. The adapter must accept the response
|
49
|
-
# object as a parameter and provide the <code>#to_ary</code> method.
|
50
|
-
#
|
51
|
-
# @return [Array] The tuple that the reponse was converted to.
|
52
|
-
def self.adapt_response(response, adapter=nil)
|
53
|
-
# Temporarily wrap the response if there's an adapter
|
54
|
-
response = adapter.new(response) if adapter
|
55
|
-
if response.respond_to?(:to_ary)
|
56
|
-
converted_response = response.to_ary
|
57
|
-
else
|
58
|
-
# Can't use #to_a because some versions of Ruby define #to_a on Object
|
59
|
-
raise TypeError,
|
60
|
-
"Expected adapter or response to implement #to_ary."
|
61
|
-
end
|
62
|
-
return self.verified_response(converted_response)
|
63
|
+
return HTTPAdapter.verified_request(converted_request)
|
63
64
|
end
|
64
65
|
|
65
66
|
##
|
66
67
|
# Converts a tuple to a specific HTTP implementation's request format.
|
67
68
|
#
|
68
|
-
# @param [Array]
|
69
|
-
# The request
|
69
|
+
# @param [Array] request_ary
|
70
|
+
# The request array to be converted. The request array must be a tuple
|
70
71
|
# with a length of 4. The first element must be the request method.
|
71
72
|
# The second element must be the URI. The URI may be relative. The third
|
72
73
|
# element contains the headers. It must respond to <code>#each</code> and
|
73
74
|
# iterate over the header names and values. The fourth element must be
|
74
75
|
# the body. It must respond to <code>#each</code> but may not be a
|
75
76
|
# <code>String</code>. It should emit <code>String</code> objects.
|
76
|
-
# @param [#from_ary] adapter
|
77
|
-
# The adapter object that will convert to a tuple. It must respond to
|
78
|
-
# <code>#from_ary</code>. Typically a reference to a class is used.
|
79
77
|
#
|
80
78
|
# @return [Array] The implementation-specific request object.
|
81
|
-
def
|
82
|
-
request =
|
83
|
-
if
|
84
|
-
return
|
79
|
+
def specialize_request(request_ary)
|
80
|
+
request = HTTPAdapter.verified_request(request_ary)
|
81
|
+
if self.respond_to?(:convert_request_from_a)
|
82
|
+
return self.convert_request_from_a(request)
|
83
|
+
else
|
84
|
+
raise TypeError,
|
85
|
+
'Expected adapter to implement #convert_request_from_a.'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Converts an HTTP response object to a simple tuple.
|
91
|
+
#
|
92
|
+
# @param [Object] response
|
93
|
+
# The response object to be converted. The adapter must implement
|
94
|
+
# the <code>#convert_response_to_a</code> method, which takes the response
|
95
|
+
# object as a parameter.
|
96
|
+
#
|
97
|
+
# @return [Array] The tuple that the reponse was converted to.
|
98
|
+
def adapt_response(response_obj)
|
99
|
+
if self.respond_to?(:convert_response_to_a)
|
100
|
+
converted_response = self.convert_response_to_a(response_obj)
|
85
101
|
else
|
86
102
|
raise TypeError,
|
87
|
-
|
103
|
+
'Expected adapter to implement #convert_response_to_a.'
|
88
104
|
end
|
105
|
+
return HTTPAdapter.verified_response(converted_response)
|
89
106
|
end
|
90
107
|
|
91
108
|
##
|
92
109
|
# Converts a tuple to a specific HTTP implementation's response format.
|
93
110
|
#
|
94
|
-
# @param [Array]
|
111
|
+
# @param [Array] response_ary
|
95
112
|
# The response object to be converted. The response object must be a
|
96
113
|
# tuple with a length of 3. The first element must be the HTTP status
|
97
114
|
# code. The second element contains the headers. It must respond to
|
@@ -100,47 +117,41 @@ module HTTPAdapter #:nodoc:
|
|
100
117
|
# but may not be a <code>String</code>. It should emit
|
101
118
|
# <code>String</code> objects. This is essentially the same format that
|
102
119
|
# Rack uses.
|
103
|
-
# @param [#from_ary] adapter
|
104
|
-
# The adapter object that will convert to a tuple. It must respond to
|
105
|
-
# <code>#from_ary</code>. Typically a reference to a class is used.
|
106
120
|
#
|
107
121
|
# @return [Array] The implementation-specific response object.
|
108
|
-
def
|
109
|
-
|
110
|
-
if
|
111
|
-
return
|
122
|
+
def specialize_response(response_ary)
|
123
|
+
response_ary = HTTPAdapter.verified_response(response_ary)
|
124
|
+
if self.respond_to?(:convert_response_from_a)
|
125
|
+
return self.convert_response_from_a(response_ary)
|
112
126
|
else
|
113
|
-
raise TypeError,
|
127
|
+
raise TypeError,
|
128
|
+
'Expected adapter to implement #convert_response_from_a.'
|
114
129
|
end
|
115
130
|
end
|
116
131
|
|
117
132
|
##
|
118
133
|
# Transmits a request.
|
119
134
|
#
|
120
|
-
# @param [Array]
|
121
|
-
# The request that will be sent.
|
122
|
-
# @param [#transmit] adapter
|
123
|
-
# The adapter object that will perform the transmission of the HTTP
|
124
|
-
# request. It must respond to <code>#transmit</code>. Typically a
|
125
|
-
# reference to a class is used.
|
135
|
+
# @param [Array] request_ary
|
136
|
+
# The request tuple that will be sent.
|
126
137
|
# @param [HTTPAdapter::Connection] connection
|
127
138
|
# An object representing a connection. This object represents an open
|
128
139
|
# HTTP connection that is used to make multiple HTTP requests.
|
129
140
|
# @return [Array]
|
130
141
|
# The response given by the server.
|
131
142
|
#
|
132
|
-
# @return [Array]
|
133
|
-
def
|
134
|
-
|
143
|
+
# @return [Array] A tuple representing the response from the server.
|
144
|
+
def transmit(request_ary, connection=nil)
|
145
|
+
request_ary = HTTPAdapter.verified_request(request_ary)
|
135
146
|
if connection && !connection.kind_of?(HTTPAdapter::Connection)
|
136
147
|
raise TypeError,
|
137
148
|
"Expected HTTPAdapter::Connection, got #{connection.class}."
|
138
149
|
end
|
139
|
-
if
|
140
|
-
|
141
|
-
return
|
150
|
+
if self.respond_to?(:fetch_resource)
|
151
|
+
response_ary = self.fetch_resource(request_ary, connection)
|
152
|
+
return HTTPAdapter.verified_response(response_ary)
|
142
153
|
else
|
143
|
-
raise TypeError, 'Expected adapter to implement .
|
154
|
+
raise TypeError, 'Expected adapter to implement .fetch_resource.'
|
144
155
|
end
|
145
156
|
end
|
146
157
|
|
@@ -187,7 +198,7 @@ module HTTPAdapter #:nodoc:
|
|
187
198
|
headers << [header, value]
|
188
199
|
end
|
189
200
|
else
|
190
|
-
raise TypeError,
|
201
|
+
raise TypeError, 'Expected headers to respond to #each.'
|
191
202
|
end
|
192
203
|
if body.kind_of?(String)
|
193
204
|
raise TypeError,
|
@@ -197,11 +208,12 @@ module HTTPAdapter #:nodoc:
|
|
197
208
|
# Can't verify that all chunks are Strings because #each may be
|
198
209
|
# effectively destructive.
|
199
210
|
if !body.respond_to?(:each)
|
200
|
-
raise TypeError,
|
211
|
+
raise TypeError, 'Expected body to respond to #each.'
|
201
212
|
end
|
202
213
|
else
|
203
214
|
raise TypeError,
|
204
|
-
"Expected tuple of [method, uri, headers, body]
|
215
|
+
"Expected tuple of [method, uri, headers, body], " +
|
216
|
+
"got #{request.inspect}."
|
205
217
|
end
|
206
218
|
return [method, uri, headers, body]
|
207
219
|
end
|
@@ -240,7 +252,7 @@ module HTTPAdapter #:nodoc:
|
|
240
252
|
headers << [header, value]
|
241
253
|
end
|
242
254
|
else
|
243
|
-
raise TypeError,
|
255
|
+
raise TypeError, 'Expected headers to respond to #each.'
|
244
256
|
end
|
245
257
|
if body.kind_of?(String)
|
246
258
|
raise TypeError,
|
@@ -250,11 +262,11 @@ module HTTPAdapter #:nodoc:
|
|
250
262
|
# Can't verify that all chunks are Strings because #each may be
|
251
263
|
# effectively destructive.
|
252
264
|
if !body.respond_to?(:each)
|
253
|
-
raise TypeError,
|
265
|
+
raise TypeError, 'Expected body to respond to #each.'
|
254
266
|
end
|
255
267
|
else
|
256
268
|
raise TypeError,
|
257
|
-
"Expected tuple of [status, headers, body]."
|
269
|
+
"Expected tuple of [status, headers, body], got #{response.inspect}."
|
258
270
|
end
|
259
271
|
return [status, headers, body]
|
260
272
|
end
|
@@ -14,22 +14,24 @@
|
|
14
14
|
|
15
15
|
require 'httpadapter'
|
16
16
|
|
17
|
-
module HTTPAdapter
|
17
|
+
module HTTPAdapter
|
18
18
|
##
|
19
19
|
# A simple module for mocking the transmit method on an adapter.
|
20
20
|
#
|
21
21
|
# @example
|
22
22
|
# # Using RSpec, verify that the request being sent includes a user agent.
|
23
|
-
# adapter = HTTPAdapter::MockAdapter.
|
24
|
-
# method, uri, headers, body =
|
23
|
+
# adapter = HTTPAdapter::MockAdapter.create do |request_ary, connection|
|
24
|
+
# method, uri, headers, body = request_ary
|
25
25
|
# headers.should be_any { |k, v| k.downcase == 'user-agent' }
|
26
26
|
# end
|
27
27
|
module MockAdapter
|
28
|
-
def self.
|
29
|
-
|
28
|
+
def self.create(&block)
|
29
|
+
adapter = Class.new do
|
30
|
+
include HTTPAdapter
|
31
|
+
|
30
32
|
@@block = block
|
31
33
|
|
32
|
-
def
|
34
|
+
def fetch_resource(*params)
|
33
35
|
response = @@block.call(*params)
|
34
36
|
if response.respond_to?(:each)
|
35
37
|
return response
|
@@ -38,6 +40,7 @@ module HTTPAdapter #:nodoc:
|
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
43
|
+
return adapter.new
|
41
44
|
end
|
42
45
|
end
|
43
46
|
end
|
@@ -18,7 +18,9 @@ require 'net/http'
|
|
18
18
|
require 'addressable/uri'
|
19
19
|
|
20
20
|
module HTTPAdapter #:nodoc:
|
21
|
-
class
|
21
|
+
class NetHTTPAdapter
|
22
|
+
include HTTPAdapter
|
23
|
+
|
22
24
|
METHOD_MAPPING = {
|
23
25
|
# RFC 2616
|
24
26
|
'OPTIONS' => Net::HTTP::Options,
|
@@ -38,116 +40,6 @@ module HTTPAdapter #:nodoc:
|
|
38
40
|
'UNLOCK' => Net::HTTP::Unlock
|
39
41
|
}
|
40
42
|
|
41
|
-
def initialize(request, options={})
|
42
|
-
unless request.kind_of?(Net::HTTPRequest)
|
43
|
-
raise TypeError, "Expected Net::HTTPRequest, got #{request.class}."
|
44
|
-
end
|
45
|
-
@request = request
|
46
|
-
@uri = Addressable::URI.parse(options[:uri] || request.path || "")
|
47
|
-
if !@uri.host && options[:host]
|
48
|
-
@uri.host = options[:host]
|
49
|
-
if !@uri.scheme && options[:scheme]
|
50
|
-
@uri.scheme = options[:scheme]
|
51
|
-
elsif !@uri.scheme
|
52
|
-
@uri.scheme = 'http'
|
53
|
-
end
|
54
|
-
if !@uri.port && options[:port]
|
55
|
-
@uri.port = options[:port]
|
56
|
-
end
|
57
|
-
@uri.scheme = @uri.normalized_scheme
|
58
|
-
@uri.authority = @uri.normalized_authority
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def to_ary
|
63
|
-
method = @request.method.to_s.upcase
|
64
|
-
uri = @uri.to_str
|
65
|
-
headers = []
|
66
|
-
@request.canonical_each do |header, value|
|
67
|
-
headers << [header, value]
|
68
|
-
end
|
69
|
-
body = @request.body || ""
|
70
|
-
return [method, uri, headers, [body]]
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.from_ary(array)
|
74
|
-
method, uri, headers, body = array
|
75
|
-
method = method.to_s.upcase
|
76
|
-
uri = Addressable::URI.parse(uri)
|
77
|
-
request_class = METHOD_MAPPING[method]
|
78
|
-
unless request_class
|
79
|
-
raise ArgumentError, "Unknown HTTP method: #{method}"
|
80
|
-
end
|
81
|
-
request = request_class.new(uri.request_uri)
|
82
|
-
headers.each do |header, value|
|
83
|
-
request[header] = value
|
84
|
-
if header.downcase == 'Content-Type'.downcase
|
85
|
-
request.content_type = value
|
86
|
-
end
|
87
|
-
end
|
88
|
-
merged_body = ""
|
89
|
-
body.each do |chunk|
|
90
|
-
merged_body += chunk
|
91
|
-
end
|
92
|
-
if merged_body.length > 0
|
93
|
-
request.body = merged_body
|
94
|
-
elsif ['POST', 'PUT'].include?(method)
|
95
|
-
request.content_length = 0
|
96
|
-
end
|
97
|
-
return request
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.transmit(request, connection=nil)
|
101
|
-
method, uri, headers, body = request
|
102
|
-
uri = Addressable::URI.parse(uri)
|
103
|
-
net_http_request = self.from_ary([method, uri, headers, body])
|
104
|
-
net_http_response = nil
|
105
|
-
unless connection
|
106
|
-
http = Net::HTTP.new(uri.host, uri.inferred_port)
|
107
|
-
if uri.normalized_scheme == 'https'
|
108
|
-
require 'net/https'
|
109
|
-
http.use_ssl = true
|
110
|
-
if http.respond_to?(:enable_post_connection_check=)
|
111
|
-
http.enable_post_connection_check = true
|
112
|
-
end
|
113
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
114
|
-
store = OpenSSL::X509::Store.new
|
115
|
-
store.set_default_paths
|
116
|
-
http.cert_store = store
|
117
|
-
context = http.instance_variable_get('@ssl_context')
|
118
|
-
if context && context.respond_to?(:tmp_dh_callback) &&
|
119
|
-
context.tmp_dh_callback == nil
|
120
|
-
context.tmp_dh_callback = lambda do |*args|
|
121
|
-
tmp_dh_key_file = File.expand_path(
|
122
|
-
ENV['TMP_DH_KEY_FILE'] || "~/.dhparams.pem"
|
123
|
-
)
|
124
|
-
if File.exists?(tmp_dh_key_file)
|
125
|
-
OpenSSL::PKey::DH.new(File.read(tmp_dh_key_file))
|
126
|
-
else
|
127
|
-
# Slow, fix with `openssl dhparam -out ~/.dhparams.pem 2048`
|
128
|
-
OpenSSL::PKey::DH.new(512)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
http.start
|
134
|
-
connection = HTTPAdapter::Connection.new(
|
135
|
-
uri.host, uri.inferred_port, http,
|
136
|
-
:open => [:start, [], nil],
|
137
|
-
:close => [:finish, [], nil]
|
138
|
-
)
|
139
|
-
else
|
140
|
-
http = nil
|
141
|
-
end
|
142
|
-
net_http_response = connection.connection.request(net_http_request)
|
143
|
-
if http
|
144
|
-
connection.close
|
145
|
-
end
|
146
|
-
return NetHTTPResponseAdapter.new(net_http_response).to_ary
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
class NetHTTPResponseAdapter
|
151
43
|
STATUS_MESSAGES = {
|
152
44
|
100 => "Continue",
|
153
45
|
101 => "Switching Protocols",
|
@@ -205,25 +97,87 @@ module HTTPAdapter #:nodoc:
|
|
205
97
|
}
|
206
98
|
STATUS_MAPPING = Net::HTTPResponse::CODE_TO_OBJ
|
207
99
|
|
208
|
-
def initialize(
|
209
|
-
|
210
|
-
|
100
|
+
def initialize(&block)
|
101
|
+
@connection_config = block
|
102
|
+
end
|
103
|
+
|
104
|
+
def convert_request_to_a(request_obj)
|
105
|
+
unless request_obj.kind_of?(Net::HTTPRequest)
|
106
|
+
raise TypeError, "Expected Net::HTTPRequest, got #{request_obj.class}."
|
211
107
|
end
|
212
|
-
|
108
|
+
method = request_obj.method.to_s.upcase
|
109
|
+
host_from_header = nil
|
110
|
+
scheme_from_header = nil
|
111
|
+
headers = []
|
112
|
+
request_obj.canonical_each do |header, value|
|
113
|
+
if header.downcase == 'X-Forwarded-Proto'.downcase
|
114
|
+
scheme_from_header = value
|
115
|
+
elsif header.downcase == 'Host'.downcase
|
116
|
+
host_from_header = value
|
117
|
+
end
|
118
|
+
headers << [header, value]
|
119
|
+
end
|
120
|
+
uri = Addressable::URI.parse(request_obj.path || "")
|
121
|
+
uri.host ||= host_from_header
|
122
|
+
if uri.host
|
123
|
+
uri.scheme ||= scheme_from_header || 'http'
|
124
|
+
uri.scheme = uri.normalized_scheme
|
125
|
+
uri.authority = uri.normalized_authority
|
126
|
+
end
|
127
|
+
uri = uri.to_str
|
128
|
+
body = request_obj.body || ""
|
129
|
+
return [method, uri, headers, [body]]
|
213
130
|
end
|
214
131
|
|
215
|
-
def
|
216
|
-
|
132
|
+
def convert_request_from_a(request_ary)
|
133
|
+
method, uri, headers, body = request_ary
|
134
|
+
method = method.to_s.upcase
|
135
|
+
host_from_header = nil
|
136
|
+
uri = Addressable::URI.parse(uri)
|
137
|
+
request_class = METHOD_MAPPING[method]
|
138
|
+
unless request_class
|
139
|
+
raise ArgumentError, "Unknown HTTP method: #{method}"
|
140
|
+
end
|
141
|
+
request = request_class.new(uri.request_uri)
|
142
|
+
headers.each do |header, value|
|
143
|
+
request[header] = value
|
144
|
+
if header.downcase == 'Content-Type'.downcase
|
145
|
+
request.content_type = value
|
146
|
+
elsif header.downcase == 'Host'.downcase
|
147
|
+
host_from_header = value
|
148
|
+
end
|
149
|
+
end
|
150
|
+
if host_from_header == nil && uri.host
|
151
|
+
request['Host'] = uri.host
|
152
|
+
end
|
153
|
+
merged_body = ""
|
154
|
+
body.each do |chunk|
|
155
|
+
merged_body += chunk
|
156
|
+
end
|
157
|
+
if merged_body.length > 0
|
158
|
+
request.body = merged_body
|
159
|
+
elsif ['POST', 'PUT'].include?(method)
|
160
|
+
request.content_length = 0
|
161
|
+
end
|
162
|
+
return request
|
163
|
+
end
|
164
|
+
|
165
|
+
def convert_response_to_a(response_obj)
|
166
|
+
unless response_obj.kind_of?(Net::HTTPResponse)
|
167
|
+
raise TypeError,
|
168
|
+
"Expected Net::HTTPResponse, got #{response_obj.class}."
|
169
|
+
end
|
170
|
+
status = response_obj.code.to_i
|
217
171
|
headers = []
|
218
|
-
|
172
|
+
response_obj.canonical_each do |header, value|
|
219
173
|
headers << [header, value]
|
220
174
|
end
|
221
|
-
body =
|
175
|
+
body = response_obj.body || ""
|
222
176
|
return [status, headers, [body]]
|
223
177
|
end
|
224
178
|
|
225
|
-
def
|
226
|
-
status, headers, body =
|
179
|
+
def convert_response_from_a(response_ary)
|
180
|
+
status, headers, body = response_ary
|
227
181
|
message = STATUS_MESSAGES[status.to_i]
|
228
182
|
response_class = STATUS_MAPPING[status.to_s]
|
229
183
|
unless message && response_class
|
@@ -245,5 +199,55 @@ module HTTPAdapter #:nodoc:
|
|
245
199
|
|
246
200
|
return response
|
247
201
|
end
|
202
|
+
|
203
|
+
def fetch_resource(request_ary, connection=nil)
|
204
|
+
method, uri, headers, body = request_ary
|
205
|
+
uri = Addressable::URI.parse(uri)
|
206
|
+
net_http_request = self.convert_request_from_a(
|
207
|
+
[method, uri, headers, body]
|
208
|
+
)
|
209
|
+
net_http_response = nil
|
210
|
+
unless connection
|
211
|
+
http = Net::HTTP.new(uri.host, uri.inferred_port)
|
212
|
+
if uri.normalized_scheme == 'https'
|
213
|
+
require 'net/https'
|
214
|
+
http.use_ssl = true
|
215
|
+
if http.respond_to?(:enable_post_connection_check=)
|
216
|
+
http.enable_post_connection_check = true
|
217
|
+
end
|
218
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
219
|
+
ca_file = File.expand_path(ENV['CA_FILE'] || '~/.cacert.pem')
|
220
|
+
if File.exists?(ca_file)
|
221
|
+
http.ca_file = ca_file
|
222
|
+
end
|
223
|
+
store = OpenSSL::X509::Store.new
|
224
|
+
store.set_default_paths
|
225
|
+
http.cert_store = store
|
226
|
+
context = http.instance_variable_get('@ssl_context')
|
227
|
+
if context && context.respond_to?(:tmp_dh_callback)
|
228
|
+
# Fix for annoying warning
|
229
|
+
context.tmp_dh_callback ||= lambda {}
|
230
|
+
end
|
231
|
+
end
|
232
|
+
connection = HTTPAdapter::Connection.new(
|
233
|
+
uri.host, uri.inferred_port, http,
|
234
|
+
:open => [:start, [], nil],
|
235
|
+
:close => [:finish, [], nil]
|
236
|
+
)
|
237
|
+
else
|
238
|
+
http = nil
|
239
|
+
end
|
240
|
+
if @connection_config
|
241
|
+
@connection_config.call(connection)
|
242
|
+
end
|
243
|
+
if connection.connection && !connection.connection.active?
|
244
|
+
connection.connection.start
|
245
|
+
end
|
246
|
+
net_http_response = connection.connection.request(net_http_request)
|
247
|
+
if http
|
248
|
+
connection.close
|
249
|
+
end
|
250
|
+
return self.convert_response_to_a(net_http_response)
|
251
|
+
end
|
248
252
|
end
|
249
253
|
end
|