httpadapter 0.2.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|