typhoeus 0.4.2 → 0.5.0.alpha

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