sandofsky-typhoeus 0.1.3

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,176 @@
1
+ module Typhoeus
2
+ class Hydra
3
+ def initialize
4
+ @multi = Multi.new
5
+ @easy_pool = []
6
+ @stubs = []
7
+ @memoized_requests = {}
8
+ @retrieved_from_cache = {}
9
+ end
10
+
11
+ def self.hydra
12
+ @hydra ||= new
13
+ end
14
+
15
+ def self.hydra=(val)
16
+ @hydra = val
17
+ end
18
+
19
+ def clear_stubs
20
+ @stubs = []
21
+ end
22
+
23
+ def queue(request)
24
+ return if assign_to_stub(request)
25
+
26
+ if request.method == :get
27
+ if @memoized_requests.has_key? request.url
28
+ if response = @retrieved_from_cache[request.url]
29
+ request.response = response
30
+ request.call_handlers
31
+ else
32
+ @memoized_requests[request.url] << request
33
+ end
34
+ else
35
+ @memoized_requests[request.url] = []
36
+ get_from_cache_or_queue(request)
37
+ end
38
+ else
39
+ get_from_cache_or_queue(request)
40
+ end
41
+ end
42
+
43
+ def run
44
+ @stubs.each do |m|
45
+ m.requests.each do |request|
46
+ m.response.request = request
47
+ handle_request(request, m.response)
48
+ end
49
+ end
50
+ @multi.perform
51
+ @memoized_requests = {}
52
+ @retrieved_from_cache = {}
53
+ end
54
+
55
+ def cache_getter(&block)
56
+ @cache_getter = block
57
+ end
58
+
59
+ def cache_setter(&block)
60
+ @cache_setter = block
61
+ end
62
+
63
+ def on_complete(&block)
64
+ @on_complete = block
65
+ end
66
+
67
+ def on_complete=(proc)
68
+ @on_complete = proc
69
+ end
70
+
71
+ def stub(method, url)
72
+ @stubs << HydraMock.new(url, method)
73
+ @stubs.last
74
+ end
75
+
76
+ def assign_to_stub(request)
77
+ m = @stubs.detect {|stub| stub.matches? request}
78
+ m && m.add_request(request)
79
+ end
80
+ private :assign_to_stub
81
+
82
+ def get_from_cache_or_queue(request)
83
+ if @cache_getter
84
+ val = @cache_getter.call(request)
85
+ if val
86
+ @retrieved_from_cache[request.url] = val
87
+ handle_request(request, val, false)
88
+ else
89
+ @multi.add(get_easy_object(request))
90
+ end
91
+ else
92
+ @multi.add(get_easy_object(request))
93
+ end
94
+ end
95
+ private :get_from_cache_or_queue
96
+
97
+ def get_easy_object(request)
98
+ easy = @easy_pool.pop || Easy.new
99
+ easy.url = request.url
100
+ easy.method = request.method
101
+ easy.headers = request.headers if request.headers
102
+ easy.request_body = request.body if request.body
103
+ easy.timeout = request.timeout if request.timeout
104
+ easy.on_success do |easy|
105
+ handle_request(request, response_from_easy(easy, request))
106
+ release_easy_object(easy)
107
+ end
108
+ easy.on_failure do |easy|
109
+ handle_request(request, response_from_easy(easy, request))
110
+ release_easy_object(easy)
111
+ end
112
+ easy.set_headers
113
+ easy
114
+ end
115
+ private :get_easy_object
116
+
117
+ def release_easy_object(easy)
118
+ easy.reset
119
+ @easy_pool.push easy
120
+ end
121
+ private :release_easy_object
122
+
123
+ def handle_request(request, response, live_request = true)
124
+ request.response = response
125
+
126
+ if live_request && request.cache_timeout && @cache_setter
127
+ @cache_setter.call(request)
128
+ end
129
+ @on_complete.call(response) if @on_complete
130
+
131
+ request.call_handlers
132
+ if requests = @memoized_requests[request.url]
133
+ requests.each do |r|
134
+ r.response = response
135
+ r.call_handlers
136
+ end
137
+ end
138
+ end
139
+ private :handle_request
140
+
141
+ def response_from_easy(easy, request)
142
+ Response.new(:code => easy.response_code,
143
+ :headers => easy.response_header,
144
+ :body => easy.response_body,
145
+ :time => easy.total_time_taken,
146
+ :request => request)
147
+ end
148
+ private :response_from_easy
149
+ end
150
+
151
+ class HydraMock
152
+ attr_reader :url, :method, :response, :requests
153
+
154
+ def initialize(url, method)
155
+ @url = url
156
+ @method = method
157
+ @requests = []
158
+ end
159
+
160
+ def add_request(request)
161
+ @requests << request
162
+ end
163
+
164
+ def and_return(val)
165
+ @response = val
166
+ end
167
+
168
+ def matches?(request)
169
+ if url.kind_of?(String)
170
+ request.method == method && request.url == url
171
+ else
172
+ request.method == method && url =~ request.url
173
+ end
174
+ end
175
+ end
176
+ 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