typhoeus 0.4.2 → 0.5.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGELOG.md +86 -28
  2. data/Gemfile +17 -1
  3. data/README.md +20 -422
  4. data/Rakefile +21 -12
  5. data/lib/typhoeus.rb +58 -41
  6. data/lib/typhoeus/config.rb +14 -0
  7. data/lib/typhoeus/errors.rb +9 -0
  8. data/lib/typhoeus/errors/no_stub.rb +12 -0
  9. data/lib/typhoeus/errors/typhoeus_error.rb +8 -0
  10. data/lib/typhoeus/expectation.rb +126 -0
  11. data/lib/typhoeus/hydra.rb +31 -236
  12. data/lib/typhoeus/hydras/block_connection.rb +33 -0
  13. data/lib/typhoeus/hydras/easy_factory.rb +67 -0
  14. data/lib/typhoeus/hydras/easy_pool.rb +40 -0
  15. data/lib/typhoeus/hydras/memoizable.rb +53 -0
  16. data/lib/typhoeus/hydras/queueable.rb +46 -0
  17. data/lib/typhoeus/hydras/runnable.rb +18 -0
  18. data/lib/typhoeus/hydras/stubbable.rb +27 -0
  19. data/lib/typhoeus/request.rb +68 -243
  20. data/lib/typhoeus/requests/actions.rb +101 -0
  21. data/lib/typhoeus/requests/block_connection.rb +31 -0
  22. data/lib/typhoeus/requests/callbacks.rb +82 -0
  23. data/lib/typhoeus/requests/marshal.rb +21 -0
  24. data/lib/typhoeus/requests/memoizable.rb +36 -0
  25. data/lib/typhoeus/requests/operations.rb +52 -0
  26. data/lib/typhoeus/requests/responseable.rb +29 -0
  27. data/lib/typhoeus/requests/stubbable.rb +29 -0
  28. data/lib/typhoeus/response.rb +24 -118
  29. data/lib/typhoeus/responses/header.rb +50 -0
  30. data/lib/typhoeus/responses/informations.rb +43 -0
  31. data/lib/typhoeus/responses/legacy.rb +27 -0
  32. data/lib/typhoeus/responses/status.rb +78 -0
  33. data/lib/typhoeus/version.rb +3 -1
  34. metadata +34 -141
  35. data/lib/typhoeus/curl.rb +0 -453
  36. data/lib/typhoeus/easy.rb +0 -115
  37. data/lib/typhoeus/easy/auth.rb +0 -14
  38. data/lib/typhoeus/easy/callbacks.rb +0 -33
  39. data/lib/typhoeus/easy/ffi_helper.rb +0 -61
  40. data/lib/typhoeus/easy/infos.rb +0 -90
  41. data/lib/typhoeus/easy/options.rb +0 -115
  42. data/lib/typhoeus/easy/proxy.rb +0 -20
  43. data/lib/typhoeus/easy/ssl.rb +0 -82
  44. data/lib/typhoeus/filter.rb +0 -28
  45. data/lib/typhoeus/form.rb +0 -61
  46. data/lib/typhoeus/header.rb +0 -54
  47. data/lib/typhoeus/hydra/callbacks.rb +0 -24
  48. data/lib/typhoeus/hydra/connect_options.rb +0 -61
  49. data/lib/typhoeus/hydra/stubbing.rb +0 -68
  50. data/lib/typhoeus/hydra_mock.rb +0 -131
  51. data/lib/typhoeus/multi.rb +0 -146
  52. data/lib/typhoeus/param_processor.rb +0 -43
  53. data/lib/typhoeus/remote.rb +0 -306
  54. data/lib/typhoeus/remote_method.rb +0 -108
  55. data/lib/typhoeus/remote_proxy_object.rb +0 -50
  56. data/lib/typhoeus/utils.rb +0 -50
@@ -1,146 +0,0 @@
1
- module Typhoeus
2
- class Multi
3
- attr_reader :easy_handles
4
-
5
- def initialize
6
- Curl.init
7
-
8
- @handle = Curl.multi_init
9
- @active = 0
10
- @running = 0
11
- @easy_handles = []
12
-
13
- @timeout = ::FFI::MemoryPointer.new(:long)
14
- @timeval = Curl::Timeval.new
15
- @fd_read = Curl::FDSet.new
16
- @fd_write = Curl::FDSet.new
17
- @fd_excep = Curl::FDSet.new
18
- @max_fd = ::FFI::MemoryPointer.new(:int)
19
-
20
- ObjectSpace.define_finalizer(self, self.class.finalizer(self))
21
- end
22
-
23
- def self.finalizer(multi)
24
- proc { Curl.multi_cleanup(multi.handle) }
25
- end
26
-
27
- def add(easy)
28
- raise "trying to add easy handle twice" if @easy_handles.include?(easy)
29
- easy.set_headers() if easy.headers.empty?
30
-
31
- code = Curl.multi_add_handle(@handle, easy.handle)
32
- raise RuntimeError.new("An error occured adding the handle: #{code}: #{Curl.multi_strerror(code)}") if code != :call_multi_perform and code != :ok
33
-
34
- do_perform if code == :call_multi_perform
35
-
36
- @active += 1
37
- @easy_handles << easy
38
- easy
39
- end
40
-
41
- def remove(easy)
42
- if @easy_handles.include?(easy)
43
- @active -= 1
44
- Curl.multi_remove_handle(@handle, easy.handle)
45
- @easy_handles.delete(easy)
46
- end
47
- end
48
-
49
- def perform
50
- while @active > 0
51
- run
52
- while @running > 0
53
- # get the curl-suggested timeout
54
- code = Curl.multi_timeout(@handle, @timeout)
55
- raise RuntimeError.new("an error occured getting the timeout: #{code}: #{Curl.multi_strerror(code)}") if code != :ok
56
- timeout = @timeout.read_long
57
- if timeout == 0 # no delay
58
- run
59
- next
60
- elsif timeout < 0
61
- timeout = 1
62
- end
63
-
64
- # load the fd sets from the multi handle
65
- @fd_read.clear
66
- @fd_write.clear
67
- @fd_excep.clear
68
- code = Curl.multi_fdset(@handle, @fd_read, @fd_write, @fd_excep, @max_fd)
69
- raise RuntimeError.new("an error occured getting the fdset: #{code}: #{Curl.multi_strerror(code)}") if code != :ok
70
-
71
- max_fd = @max_fd.read_int
72
- if max_fd == -1
73
- # curl is doing something special so let it run for a moment
74
- sleep(0.001)
75
- else
76
- @timeval[:sec] = timeout / 1000
77
- @timeval[:usec] = (timeout * 1000) % 1000000
78
-
79
- code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval)
80
- raise RuntimeError.new("error on thread select: #{::FFI.errno}") if code < 0
81
- end
82
-
83
- run
84
- end
85
- end
86
- reset_easy_handles
87
- end
88
-
89
- def fire_and_forget
90
- run
91
- end
92
-
93
- # check for finished easy handles and remove from the multi handle
94
- def read_info
95
- msgs_left = ::FFI::MemoryPointer.new(:int)
96
- while not (msg = Curl.multi_info_read(@handle, msgs_left)).null?
97
- next if msg[:code] != :done
98
-
99
- easy = @easy_handles.find {|easy| easy.handle == msg[:easy_handle] }
100
- next if not easy
101
-
102
- response_code = ::FFI::MemoryPointer.new(:long)
103
- response_code.write_long(-1)
104
- Curl.easy_getinfo(easy.handle, :response_code, response_code)
105
- response_code = response_code.read_long
106
- remove(easy)
107
-
108
- easy.curl_return_code = msg[:data][:code]
109
- if easy.curl_return_code != 0 then easy.failure
110
- elsif (200..299).member?(response_code) or response_code == 0 then easy.success
111
- else easy.failure
112
- end
113
- end
114
- end
115
-
116
- def cleanup
117
- Curl.multi_cleanup(@handle)
118
- @active = 0
119
- @running = 0
120
- @easy_handles = []
121
- end
122
-
123
- def reset_easy_handles
124
- @easy_handles.dup.each do |easy|
125
- remove(easy)
126
- yield easy if block_given?
127
- end
128
- end
129
-
130
- private
131
-
132
- # called by perform and fire_and_forget
133
- def run
134
- begin code = do_perform end while code == :call_multi_perform
135
- raise RuntimeError.new("an error occured while running perform: #{code}: #{Curl.multi_strerror(code)}") if code != :ok
136
- read_info
137
- end
138
-
139
- def do_perform
140
- running = ::FFI::MemoryPointer.new(:int)
141
- code = Curl.multi_perform(@handle, running)
142
- @running = running.read_int
143
- code
144
- end
145
- end
146
- end
@@ -1,43 +0,0 @@
1
- require 'tempfile'
2
-
3
- module Typhoeus
4
- class ParamProcessor
5
- class << self
6
- def traverse_params_hash(hash, result = nil, current_key = nil)
7
- result ||= { :files => [], :params => [] }
8
-
9
- hash.keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |key|
10
- new_key = (current_key ? "#{current_key}[#{key}]" : key).to_s
11
- current_value = hash[key]
12
- process_value current_value, :result => result, :new_key => new_key
13
- end
14
- result
15
- end
16
-
17
- def process_value(current_value, options)
18
- result = options[:result]
19
- new_key = options[:new_key]
20
-
21
- case current_value
22
- when Hash
23
- traverse_params_hash(current_value, result, new_key)
24
- when Array
25
- current_value.each do |v|
26
- result[:params] << [new_key, v.to_s]
27
- end
28
- when File, Tempfile
29
- filename = File.basename(current_value.path)
30
- types = MIME::Types.type_for(filename)
31
- result[:files] << [
32
- new_key,
33
- filename,
34
- types.empty? ? 'application/octet-stream' : types[0].to_s,
35
- File.expand_path(current_value.path)
36
- ]
37
- else
38
- result[:params] << [new_key, current_value.to_s]
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,306 +0,0 @@
1
- module Typhoeus
2
- USER_AGENT = "Typhoeus - http://github.com/dbalatero/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