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.
@@ -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
@@ -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