httpadapter 0.1.1
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 +3 -0
- data/LICENSE +202 -0
- data/README +47 -0
- data/Rakefile +59 -0
- data/lib/httpadapter/adapters/net_http.rb +192 -0
- data/lib/httpadapter/adapters/rack.rb +117 -0
- data/lib/httpadapter/adapters/typhoeus.rb +92 -0
- data/lib/httpadapter/version.rb +26 -0
- data/lib/httpadapter.rb +232 -0
- data/spec/httpadapter/adapters/net_http_request_spec.rb +415 -0
- data/spec/httpadapter/adapters/net_http_response_spec.rb +170 -0
- data/spec/httpadapter/adapters/rack_request_spec.rb +228 -0
- data/spec/httpadapter/adapters/rack_response_spec.rb +158 -0
- data/spec/httpadapter/adapters/typhoeus_request_spec.rb +203 -0
- data/spec/httpadapter/adapters/typhoeus_response_spec.rb +146 -0
- data/spec/httpadapter_spec.rb +224 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +73 -0
- data/tasks/git.rake +40 -0
- data/tasks/metrics.rake +22 -0
- data/tasks/rdoc.rake +26 -0
- data/tasks/rubyforge.rake +103 -0
- data/tasks/spec.rake +69 -0
- data/tasks/yard.rake +26 -0
- data/website/index.html +95 -0
- metadata +207 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
# Copyright (C) 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'httpadapter'
|
16
|
+
require 'typhoeus'
|
17
|
+
require 'typhoeus/request'
|
18
|
+
require 'typhoeus/response'
|
19
|
+
require 'addressable/uri'
|
20
|
+
|
21
|
+
module HTTPAdapter #:nodoc:
|
22
|
+
class TyphoeusRequestAdapter
|
23
|
+
def initialize(request)
|
24
|
+
unless request.kind_of?(Typhoeus::Request)
|
25
|
+
raise TypeError, "Expected Typhoeus::Request, got #{request.class}."
|
26
|
+
end
|
27
|
+
@request = request
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_ary
|
31
|
+
method = @request.method.to_s.upcase
|
32
|
+
uri = @request.url.to_str
|
33
|
+
headers = []
|
34
|
+
@request.headers.each do |header, value|
|
35
|
+
headers << [header, value]
|
36
|
+
end
|
37
|
+
body = @request.body || ""
|
38
|
+
return [method, uri, headers, [body]]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.from_ary(array)
|
42
|
+
method, uri, headers, body = array
|
43
|
+
method = method.to_s.downcase.to_sym
|
44
|
+
uri = Addressable::URI.parse(uri)
|
45
|
+
merged_body = ""
|
46
|
+
body.each do |chunk|
|
47
|
+
merged_body += chunk
|
48
|
+
end
|
49
|
+
request = Typhoeus::Request.new(
|
50
|
+
uri.to_str,
|
51
|
+
:method => method,
|
52
|
+
:headers => Hash[headers],
|
53
|
+
:body => merged_body
|
54
|
+
)
|
55
|
+
return request
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class TyphoeusResponseAdapter
|
60
|
+
def initialize(response)
|
61
|
+
unless response.kind_of?(Typhoeus::Response)
|
62
|
+
raise TypeError, "Expected Typhoeus::Response, got #{response.class}."
|
63
|
+
end
|
64
|
+
@response = response
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_ary
|
68
|
+
status = @response.code.to_i
|
69
|
+
headers = []
|
70
|
+
@response.headers_hash.each do |header, value|
|
71
|
+
headers << [header, value]
|
72
|
+
end
|
73
|
+
body = @response.body || ""
|
74
|
+
return [status, headers, [body]]
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.from_ary(array)
|
78
|
+
status, headers, body = array
|
79
|
+
status = status.to_i
|
80
|
+
merged_body = ""
|
81
|
+
body.each do |chunk|
|
82
|
+
merged_body += chunk
|
83
|
+
end
|
84
|
+
response = Typhoeus::Response.new(
|
85
|
+
:code => status,
|
86
|
+
:headers => headers.inject('') { |a,(h,v)| a << "#{h}: #{v}\r\n"; a },
|
87
|
+
:body => merged_body
|
88
|
+
)
|
89
|
+
return response
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (C) 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Used to prevent the class/module from being loaded more than once
|
16
|
+
unless defined? HTTPAdapter::VERSION
|
17
|
+
module HTTPAdapter #:nodoc:
|
18
|
+
module VERSION #:nodoc:
|
19
|
+
MAJOR = 0
|
20
|
+
MINOR = 1
|
21
|
+
TINY = 1
|
22
|
+
|
23
|
+
STRING = [MAJOR, MINOR, TINY].join('.')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/httpadapter.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
# Copyright (C) 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "httpadapter/version"
|
16
|
+
|
17
|
+
module HTTPAdapter #:nodoc:
|
18
|
+
##
|
19
|
+
# Converts an HTTP request object to a simple tuple.
|
20
|
+
#
|
21
|
+
# @param [Object, #to_ary] request
|
22
|
+
# The request object to be converted. The request may either implement
|
23
|
+
# the <code>#to_ary</code> method directly or alternately, an optional
|
24
|
+
# adapter class may be provided. The adapter must accept the request
|
25
|
+
# object as a parameter and provide the <code>#to_ary</code> method.
|
26
|
+
#
|
27
|
+
# @return [Array] The tuple that the request was converted to.
|
28
|
+
def self.adapt_request(request, adapter=nil)
|
29
|
+
# Temporarily wrap the request if there's an adapter
|
30
|
+
request = adapter.new(request) if adapter
|
31
|
+
if request.respond_to?(:to_ary)
|
32
|
+
converted_request = request.to_ary
|
33
|
+
else
|
34
|
+
# Can't use #to_a because some versions of Ruby define #to_a on Object
|
35
|
+
raise TypeError,
|
36
|
+
"Expected adapter or request to implement #to_ary."
|
37
|
+
end
|
38
|
+
return self.verified_request(converted_request)
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Converts an HTTP response object to a simple tuple.
|
43
|
+
#
|
44
|
+
# @param [Object, #to_ary] response
|
45
|
+
# The response object to be converted. The response may either implement
|
46
|
+
# the <code>#to_ary</code> method directly or alternately, an optional
|
47
|
+
# adapter class may be provided. The adapter must accept the response
|
48
|
+
# object as a parameter and provide the <code>#to_ary</code> method.
|
49
|
+
#
|
50
|
+
# @return [Array] The tuple that the reponse was converted to.
|
51
|
+
def self.adapt_response(response, adapter=nil)
|
52
|
+
# Temporarily wrap the response if there's an adapter
|
53
|
+
response = adapter.new(response) if adapter
|
54
|
+
if response.respond_to?(:to_ary)
|
55
|
+
converted_response = response.to_ary
|
56
|
+
else
|
57
|
+
# Can't use #to_a because some versions of Ruby define #to_a on Object
|
58
|
+
raise TypeError,
|
59
|
+
"Expected adapter or response to implement #to_ary."
|
60
|
+
end
|
61
|
+
return self.verified_response(converted_response)
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Converts a tuple to a specific HTTP implementation's request format.
|
66
|
+
#
|
67
|
+
# @param [Array] request
|
68
|
+
# The request object to be converted. The request object must be a tuple
|
69
|
+
# with a length of 4. The first element must be the request method.
|
70
|
+
# The second element must be the URI. The URI may be relative. The third
|
71
|
+
# element contains the headers. It must respond to <code>#each</code> and
|
72
|
+
# iterate over the header names and values. The fourth element must be
|
73
|
+
# the body. It must respond to <code>#each</code> but may not be a
|
74
|
+
# <code>String</code>. It should emit <code>String</code> objects.
|
75
|
+
# @param [#from_ary] adapter
|
76
|
+
# The adapter object that will convert to a tuple. It must respond to
|
77
|
+
# <code>#from_ary</code>. Typically a reference to a class is used.
|
78
|
+
#
|
79
|
+
# @return [Array] The implementation-specific request object.
|
80
|
+
def self.specialize_request(request, adapter)
|
81
|
+
request = self.verified_request(request)
|
82
|
+
if adapter.respond_to?(:from_ary)
|
83
|
+
return adapter.from_ary(request)
|
84
|
+
else
|
85
|
+
raise TypeError,
|
86
|
+
"Expected adapter to implement .from_ary."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Converts a tuple to a specific HTTP implementation's response format.
|
92
|
+
#
|
93
|
+
# @param [Array] response
|
94
|
+
# The response object to be converted. The response object must be a
|
95
|
+
# tuple with a length of 3. The first element must be the HTTP status
|
96
|
+
# code. The second element contains the headers. It must respond to
|
97
|
+
# <code>#each</code> and iterate over the header names and values. The
|
98
|
+
# third element must be the body. It must respond to <code>#each</code>
|
99
|
+
# but may not be a <code>String</code>. It should emit
|
100
|
+
# <code>String</code> objects. This is essentially the same format that
|
101
|
+
# Rack uses.
|
102
|
+
# @param [#from_ary] adapter
|
103
|
+
# The adapter object that will convert to a tuple. It must respond to
|
104
|
+
# <code>#from_ary</code>. Typically a reference to a class is used.
|
105
|
+
#
|
106
|
+
# @return [Array] The implementation-specific response object.
|
107
|
+
def self.specialize_response(response, adapter)
|
108
|
+
response = self.verified_response(response)
|
109
|
+
if adapter.respond_to?(:from_ary)
|
110
|
+
return adapter.from_ary(response)
|
111
|
+
else
|
112
|
+
raise TypeError,
|
113
|
+
"Expected adapter to implement .from_ary."
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
protected
|
118
|
+
##
|
119
|
+
# Verifies a request tuple matches the specification.
|
120
|
+
#
|
121
|
+
# @param [Array] request
|
122
|
+
# The request object to be verified.
|
123
|
+
#
|
124
|
+
# @return [Array] The tuple, after normalization.
|
125
|
+
def self.verified_request(request)
|
126
|
+
if !request.kind_of?(Array)
|
127
|
+
raise TypeError, "Expected Array, got #{request.class}."
|
128
|
+
end
|
129
|
+
if request.size == 4
|
130
|
+
# Verify that the request object matches the specification
|
131
|
+
method, uri, headers, body = request
|
132
|
+
method = method.to_str if method.respond_to?(:to_str)
|
133
|
+
# Special-casing symbols here
|
134
|
+
method = method.to_s if method.kind_of?(Symbol)
|
135
|
+
if !method.kind_of?(String)
|
136
|
+
raise TypeError,
|
137
|
+
"Expected String, got #{method.class}."
|
138
|
+
end
|
139
|
+
method = method.upcase
|
140
|
+
if uri.respond_to?(:to_str)
|
141
|
+
uri = uri.to_str
|
142
|
+
else
|
143
|
+
raise TypeError, "Expected String, got #{uri.class}."
|
144
|
+
end
|
145
|
+
original_headers, headers = headers, []
|
146
|
+
if original_headers.respond_to?(:each)
|
147
|
+
original_headers.each do |header, value|
|
148
|
+
if header.respond_to?(:to_str)
|
149
|
+
header = header.to_str
|
150
|
+
else
|
151
|
+
raise TypeError, "Expected String, got #{header.class}."
|
152
|
+
end
|
153
|
+
if value.respond_to?(:to_str)
|
154
|
+
value = value.to_str
|
155
|
+
else
|
156
|
+
raise TypeError, "Expected String, got #{value.class}."
|
157
|
+
end
|
158
|
+
headers << [header, value]
|
159
|
+
end
|
160
|
+
else
|
161
|
+
raise TypeError, "Expected headers to respond to #each."
|
162
|
+
end
|
163
|
+
if body.kind_of?(String)
|
164
|
+
raise TypeError,
|
165
|
+
'Body must not be a String; it must respond to #each and ' +
|
166
|
+
'emit String values.'
|
167
|
+
end
|
168
|
+
# Can't verify that all chunks are Strings because #each may be
|
169
|
+
# effectively destructive.
|
170
|
+
if !body.respond_to?(:each)
|
171
|
+
raise TypeError, "Expected body to respond to #each."
|
172
|
+
end
|
173
|
+
else
|
174
|
+
raise TypeError,
|
175
|
+
"Expected tuple of [method, uri, headers, body]."
|
176
|
+
end
|
177
|
+
return [method, uri, headers, body]
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Verifies a response tuple matches the specification.
|
182
|
+
#
|
183
|
+
# @param [Array] response
|
184
|
+
# The response object to be verified.
|
185
|
+
#
|
186
|
+
# @return [Array] The tuple, after normalization.
|
187
|
+
def self.verified_response(response)
|
188
|
+
if !response.kind_of?(Array)
|
189
|
+
raise TypeError, "Expected Array, got #{response.class}."
|
190
|
+
end
|
191
|
+
if response.size == 3
|
192
|
+
# Verify that the response object matches the specification
|
193
|
+
status, headers, body = response
|
194
|
+
status = status.to_i if status.respond_to?(:to_i)
|
195
|
+
if !status.kind_of?(Integer)
|
196
|
+
raise TypeError, "Expected Integer, got #{status.class}."
|
197
|
+
end
|
198
|
+
original_headers, headers = headers, []
|
199
|
+
if original_headers.respond_to?(:each)
|
200
|
+
original_headers.each do |header, value|
|
201
|
+
if header.respond_to?(:to_str)
|
202
|
+
header = header.to_str
|
203
|
+
else
|
204
|
+
raise TypeError, "Expected String, got #{header.class}."
|
205
|
+
end
|
206
|
+
if value.respond_to?(:to_str)
|
207
|
+
value = value.to_str
|
208
|
+
else
|
209
|
+
raise TypeError, "Expected String, got #{value.class}."
|
210
|
+
end
|
211
|
+
headers << [header, value]
|
212
|
+
end
|
213
|
+
else
|
214
|
+
raise TypeError, "Expected headers to respond to #each."
|
215
|
+
end
|
216
|
+
if body.kind_of?(String)
|
217
|
+
raise TypeError,
|
218
|
+
'Body must not be a String; it must respond to #each and ' +
|
219
|
+
'emit String values.'
|
220
|
+
end
|
221
|
+
# Can't verify that all chunks are Strings because #each may be
|
222
|
+
# effectively destructive.
|
223
|
+
if !body.respond_to?(:each)
|
224
|
+
raise TypeError, "Expected body to respond to #each."
|
225
|
+
end
|
226
|
+
else
|
227
|
+
raise TypeError,
|
228
|
+
"Expected tuple of [status, headers, body]."
|
229
|
+
end
|
230
|
+
return [status, headers, body]
|
231
|
+
end
|
232
|
+
end
|