typhoeus 0.1.2

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,28 @@
1
+ module Typhoeus
2
+ class Filter
3
+ attr_reader :method_name
4
+
5
+ def initialize(method_name, options = {})
6
+ @method_name = method_name
7
+ @options = options
8
+ end
9
+
10
+ def apply_filter?(method_name)
11
+ if @options[:only]
12
+ if @options[:only].instance_of? Symbol
13
+ @options[:only] == method_name
14
+ else
15
+ @options[:only].include?(method_name)
16
+ end
17
+ elsif @options[:except]
18
+ if @options[:except].instance_of? Symbol
19
+ @options[:except] != method_name
20
+ else
21
+ !@options[:except].include?(method_name)
22
+ end
23
+ else
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,160 @@
1
+ module Typhoeus
2
+ class Hydra
3
+ def initialize
4
+ @multi = Multi.new
5
+ @easy_pool = []
6
+ @mocks = []
7
+ @memoized_requests = {}
8
+ @retrieved_from_cache = {}
9
+ end
10
+
11
+ def self.hydra
12
+ @hydra ||= new
13
+ end
14
+
15
+ def queue(request)
16
+ return if assign_to_mock(request)
17
+
18
+ if request.method == :get
19
+ if @memoized_requests.has_key? request.url
20
+ if response = @retrieved_from_cache[request.url]
21
+ request.response = response
22
+ request.call_handlers
23
+ else
24
+ @memoized_requests[request.url] << request
25
+ end
26
+ else
27
+ @memoized_requests[request.url] = []
28
+ get_from_cache_or_queue(request)
29
+ end
30
+ else
31
+ get_from_cache_or_queue(request)
32
+ end
33
+ end
34
+
35
+ def run
36
+ @mocks.each do |m|
37
+ m.requests.each do |request|
38
+ m.response.request = request
39
+ handle_request(request, m.response)
40
+ end
41
+ end
42
+ @multi.perform
43
+ @memoized_requests = {}
44
+ @retrieved_from_cache = {}
45
+ end
46
+
47
+ def cache_getter(&block)
48
+ @cache_getter = block
49
+ end
50
+
51
+ def cache_setter(&block)
52
+ @cache_setter = block
53
+ end
54
+
55
+ def on_complete(&block)
56
+ @on_complete = block
57
+ end
58
+
59
+ def on_complete=(proc)
60
+ @on_complete = proc
61
+ end
62
+
63
+ def mock(method, url)
64
+ @mocks << HydraMock.new(url, method)
65
+ @mocks.last
66
+ end
67
+
68
+ def assign_to_mock(request)
69
+ m = @mocks.detect {|mck| mck.method == request.method && mck.url == request.url}
70
+ m && m.add_request(request)
71
+ end
72
+ private :assign_to_mock
73
+
74
+ def get_from_cache_or_queue(request)
75
+ if @cache_getter
76
+ val = @cache_getter.call(request)
77
+ if val
78
+ @retrieved_from_cache[request.url] = val
79
+ handle_request(request, val, false)
80
+ else
81
+ @multi.add(get_easy_object(request))
82
+ end
83
+ else
84
+ @multi.add(get_easy_object(request))
85
+ end
86
+ end
87
+ private :get_from_cache_or_queue
88
+
89
+ def get_easy_object(request)
90
+ easy = @easy_pool.pop || Easy.new
91
+ easy.url = request.url
92
+ easy.method = request.method
93
+ easy.headers = request.headers if request.headers
94
+ easy.request_body = request.body if request.body
95
+ easy.timeout = request.timeout if request.timeout
96
+ easy.on_success do |easy|
97
+ handle_request(request, response_from_easy(easy, request))
98
+ release_easy_object(easy)
99
+ end
100
+ easy.on_failure do |easy|
101
+ handle_request(request, response_from_easy(easy, request))
102
+ release_easy_object(easy)
103
+ end
104
+ easy.set_headers
105
+ easy
106
+ end
107
+ private :get_easy_object
108
+
109
+ def release_easy_object(easy)
110
+ easy.reset
111
+ @easy_pool.push easy
112
+ end
113
+ private :release_easy_object
114
+
115
+ def handle_request(request, response, live_request = true)
116
+ request.response = response
117
+
118
+ if live_request && request.cache_timeout && @cache_setter
119
+ @cache_setter.call(request)
120
+ end
121
+ @on_complete.call(response) if @on_complete
122
+
123
+ request.call_handlers
124
+ if requests = @memoized_requests[request.url]
125
+ requests.each do |r|
126
+ r.response = response
127
+ r.call_handlers
128
+ end
129
+ end
130
+ end
131
+ private :handle_request
132
+
133
+ def response_from_easy(easy, request)
134
+ Response.new(:code => easy.response_code,
135
+ :headers => easy.response_header,
136
+ :body => easy.response_body,
137
+ :time => easy.total_time_taken,
138
+ :request => request)
139
+ end
140
+ private :response_from_easy
141
+ end
142
+
143
+ class HydraMock
144
+ attr_reader :url, :method, :response, :requests
145
+
146
+ def initialize(url, method)
147
+ @url = url
148
+ @method = method
149
+ @requests = []
150
+ end
151
+
152
+ def add_request(request)
153
+ @requests << request
154
+ end
155
+
156
+ def and_return(val)
157
+ @response = val
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,34 @@
1
+ module Typhoeus
2
+ class Multi
3
+ attr_reader :easy_handles
4
+
5
+ def initialize
6
+ reset_easy_handles
7
+ end
8
+
9
+ def remove(easy)
10
+ multi_remove_handle(easy)
11
+ end
12
+
13
+ def add(easy)
14
+ @easy_handles << easy
15
+ multi_add_handle(easy)
16
+ end
17
+
18
+ def perform()
19
+ while active_handle_count > 0 do
20
+ multi_perform
21
+ end
22
+ reset_easy_handles
23
+ end
24
+
25
+ def cleanup()
26
+ multi_cleanup
27
+ end
28
+
29
+ private
30
+ def reset_easy_handles
31
+ @easy_handles = []
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,306 @@
1
+ module Typhoeus
2
+ USER_AGENT = "Typhoeus - http://github.com/pauldix/typhoeus/tree/master"
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ class MockExpectedError < StandardError; end
9
+
10
+ module ClassMethods
11
+ def allow_net_connect
12
+ @allow_net_connect = true if @allow_net_connect.nil?
13
+ @allow_net_connect
14
+ end
15
+
16
+ def allow_net_connect=(value)
17
+ @allow_net_connect = value
18
+ end
19
+
20
+ def mock(method, args = {})
21
+ @remote_mocks ||= {}
22
+ @remote_mocks[method] ||= {}
23
+ args[:code] ||= 200
24
+ args[:body] ||= ""
25
+ args[:headers] ||= ""
26
+ args[:time] ||= 0
27
+ url = args.delete(:url)
28
+ url ||= :catch_all
29
+ params = args.delete(:params)
30
+
31
+ key = mock_key_for(url, params)
32
+
33
+ @remote_mocks[method][key] = args
34
+ end
35
+
36
+ # Returns a key for a given URL and passed in
37
+ # set of Typhoeus options to be used to store/retrieve
38
+ # a corresponding mock.
39
+ def mock_key_for(url, params = nil)
40
+ if url == :catch_all
41
+ url
42
+ else
43
+ key = url
44
+ if params and !params.empty?
45
+ key += flatten_and_sort_hash(params).to_s
46
+ end
47
+ key
48
+ end
49
+ end
50
+
51
+ def flatten_and_sort_hash(params)
52
+ params = params.dup
53
+
54
+ # Flatten any sub-hashes to a single string.
55
+ params.keys.each do |key|
56
+ if params[key].is_a?(Hash)
57
+ params[key] = params[key].sort_by { |k, v| k.to_s.downcase }.to_s
58
+ end
59
+ end
60
+
61
+ params.sort_by { |k, v| k.to_s.downcase }
62
+ end
63
+
64
+ def get_mock(method, url, options)
65
+ return nil unless @remote_mocks
66
+ if @remote_mocks.has_key? method
67
+ extra_response_args = { :requested_http_method => method,
68
+ :requested_url => url,
69
+ :start_time => Time.now }
70
+ mock_key = mock_key_for(url, options[:params])
71
+ if @remote_mocks[method].has_key? mock_key
72
+ get_mock_and_run_handlers(method,
73
+ @remote_mocks[method][mock_key].merge(
74
+ extra_response_args),
75
+ options)
76
+ elsif @remote_mocks[method].has_key? :catch_all
77
+ get_mock_and_run_handlers(method,
78
+ @remote_mocks[method][:catch_all].merge(
79
+ extra_response_args),
80
+ options)
81
+ else
82
+ nil
83
+ end
84
+ else
85
+ nil
86
+ end
87
+ end
88
+
89
+ def enforce_allow_net_connect!(http_verb, url, params = nil)
90
+ if !allow_net_connect
91
+ message = "Real HTTP connections are disabled. Unregistered request: " <<
92
+ "#{http_verb.to_s.upcase} #{url}\n" <<
93
+ " Try: mock(:#{http_verb}, :url => \"#{url}\""
94
+ if params
95
+ message << ",\n :params => #{params.inspect}"
96
+ end
97
+
98
+ message << ")"
99
+
100
+ raise MockExpectedError, message
101
+ end
102
+ end
103
+
104
+ def check_expected_headers!(response_args, options)
105
+ missing_headers = {}
106
+
107
+ response_args[:expected_headers].each do |key, value|
108
+ if options[:headers].nil?
109
+ missing_headers[key] = [value, nil]
110
+ elsif ((options[:headers][key] && value != :anything) &&
111
+ options[:headers][key] != value)
112
+
113
+ missing_headers[key] = [value, options[:headers][key]]
114
+ end
115
+ end
116
+
117
+ unless missing_headers.empty?
118
+ raise headers_error_summary(response_args, options, missing_headers, 'expected')
119
+ end
120
+ end
121
+
122
+ def check_unexpected_headers!(response_args, options)
123
+ bad_headers = {}
124
+ response_args[:unexpected_headers].each do |key, value|
125
+ if (options[:headers][key] && value == :anything) ||
126
+ (options[:headers][key] == value)
127
+ bad_headers[key] = [value, options[:headers][key]]
128
+ end
129
+ end
130
+
131
+ unless bad_headers.empty?
132
+ raise headers_error_summary(response_args, options, bad_headers, 'did not expect')
133
+ end
134
+ end
135
+
136
+ def headers_error_summary(response_args, options, missing_headers, lead_in)
137
+ error = "#{lead_in} the following headers: #{response_args[:expected_headers].inspect}, but received: #{options[:headers].inspect}\n\n"
138
+ error << "Differences:\n"
139
+ error << "------------\n"
140
+ missing_headers.each do |key, values|
141
+ error << " - #{key}: #{lead_in} #{values[0].inspect}, got #{values[1].inspect}\n"
142
+ end
143
+
144
+ error
145
+ end
146
+ private :headers_error_summary
147
+
148
+ def get_mock_and_run_handlers(method, response_args, options)
149
+ response = Response.new(response_args)
150
+
151
+ if response_args.has_key? :expected_body
152
+ raise "#{method} expected body of \"#{response_args[:expected_body]}\" but received #{options[:body]}" if response_args[:expected_body] != options[:body]
153
+ end
154
+
155
+ if response_args.has_key? :expected_headers
156
+ check_expected_headers!(response_args, options)
157
+ end
158
+
159
+ if response_args.has_key? :unexpected_headers
160
+ check_unexpected_headers!(response_args, options)
161
+ end
162
+
163
+ if response.code >= 200 && response.code < 300 && options.has_key?(:on_success)
164
+ response = options[:on_success].call(response)
165
+ elsif options.has_key?(:on_failure)
166
+ response = options[:on_failure].call(response)
167
+ end
168
+
169
+ encode_nil_response(response)
170
+ end
171
+
172
+ [:get, :post, :put, :delete].each do |method|
173
+ line = __LINE__ + 2 # get any errors on the correct line num
174
+ code = <<-SRC
175
+ def #{method.to_s}(url, options = {})
176
+ mock_object = get_mock(:#{method.to_s}, url, options)
177
+ unless mock_object.nil?
178
+ decode_nil_response(mock_object)
179
+ else
180
+ enforce_allow_net_connect!(:#{method.to_s}, url, options[:params])
181
+ remote_proxy_object(url, :#{method.to_s}, options)
182
+ end
183
+ end
184
+ SRC
185
+ module_eval(code, "./lib/typhoeus/remote.rb", line)
186
+ end
187
+
188
+ def remote_proxy_object(url, method, options)
189
+ easy = Typhoeus.get_easy_object
190
+
191
+ easy.url = url
192
+ easy.method = method
193
+ easy.headers = options[:headers] if options.has_key?(:headers)
194
+ easy.headers["User-Agent"] = (options[:user_agent] || Typhoeus::USER_AGENT)
195
+ easy.params = options[:params] if options[:params]
196
+ easy.request_body = options[:body] if options[:body]
197
+ easy.timeout = options[:timeout] if options[:timeout]
198
+ easy.set_headers
199
+
200
+ proxy = Typhoeus::RemoteProxyObject.new(clear_memoized_proxy_objects, easy, options)
201
+ set_memoized_proxy_object(method, url, options, proxy)
202
+ end
203
+
204
+ def remote_defaults(options)
205
+ @remote_defaults ||= {}
206
+ @remote_defaults.merge!(options) if options
207
+ @remote_defaults
208
+ end
209
+
210
+ # If we get subclassed, make sure that child inherits the remote defaults
211
+ # of the parent class.
212
+ def inherited(child)
213
+ child.__send__(:remote_defaults, @remote_defaults)
214
+ end
215
+
216
+ def call_remote_method(method_name, args)
217
+ m = @remote_methods[method_name]
218
+
219
+ base_uri = args.delete(:base_uri) || m.base_uri || ""
220
+
221
+ if args.has_key? :path
222
+ path = args.delete(:path)
223
+ else
224
+ path = m.interpolate_path_with_arguments(args)
225
+ end
226
+ path ||= ""
227
+
228
+ http_method = m.http_method
229
+ url = base_uri + path
230
+ options = m.merge_options(args)
231
+
232
+ # proxy_object = memoized_proxy_object(http_method, url, options)
233
+ # return proxy_object unless proxy_object.nil?
234
+ #
235
+ # if m.cache_responses?
236
+ # object = @cache.get(get_memcache_response_key(method_name, args))
237
+ # if object
238
+ # set_memoized_proxy_object(http_method, url, options, object)
239
+ # return object
240
+ # end
241
+ # end
242
+
243
+ proxy = memoized_proxy_object(http_method, url, options)
244
+ unless proxy
245
+ if m.cache_responses?
246
+ options[:cache] = @cache
247
+ options[:cache_key] = get_memcache_response_key(method_name, args)
248
+ options[:cache_timeout] = m.cache_ttl
249
+ end
250
+ proxy = send(http_method, url, options)
251
+ end
252
+ proxy
253
+ end
254
+
255
+ def set_memoized_proxy_object(http_method, url, options, object)
256
+ @memoized_proxy_objects ||= {}
257
+ @memoized_proxy_objects["#{http_method}_#{url}_#{options.to_s}"] = object
258
+ end
259
+
260
+ def memoized_proxy_object(http_method, url, options)
261
+ @memoized_proxy_objects ||= {}
262
+ @memoized_proxy_objects["#{http_method}_#{url}_#{options.to_s}"]
263
+ end
264
+
265
+ def clear_memoized_proxy_objects
266
+ lambda { @memoized_proxy_objects = {} }
267
+ end
268
+
269
+ def get_memcache_response_key(remote_method_name, args)
270
+ result = "#{remote_method_name.to_s}-#{args.to_s}"
271
+ (Digest::SHA2.new << result).to_s
272
+ end
273
+
274
+ def cache=(cache)
275
+ @cache = cache
276
+ end
277
+
278
+ def define_remote_method(name, args = {})
279
+ @remote_defaults ||= {}
280
+ args[:method] ||= @remote_defaults[:method]
281
+ args[:on_success] ||= @remote_defaults[:on_success]
282
+ args[:on_failure] ||= @remote_defaults[:on_failure]
283
+ args[:base_uri] ||= @remote_defaults[:base_uri]
284
+ args[:path] ||= @remote_defaults[:path]
285
+ m = RemoteMethod.new(args)
286
+
287
+ @remote_methods ||= {}
288
+ @remote_methods[name] = m
289
+
290
+ class_eval <<-SRC
291
+ def self.#{name.to_s}(args = {})
292
+ call_remote_method(:#{name.to_s}, args)
293
+ end
294
+ SRC
295
+ end
296
+
297
+ private
298
+ def encode_nil_response(response)
299
+ response == nil ? :__nil__ : response
300
+ end
301
+
302
+ def decode_nil_response(response)
303
+ response == :__nil__ ? nil : response
304
+ end
305
+ end # ClassMethods
306
+ end