fblee-typhoeus 0.1.31

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 (45) hide show
  1. data/.gitignore +2 -0
  2. data/CHANGELOG.markdown +27 -0
  3. data/README.textile +333 -0
  4. data/Rakefile +39 -0
  5. data/VERSION +1 -0
  6. data/benchmarks/profile.rb +25 -0
  7. data/benchmarks/vs_nethttp.rb +35 -0
  8. data/examples/twitter.rb +21 -0
  9. data/ext/typhoeus/.gitignore +5 -0
  10. data/ext/typhoeus/Makefile +157 -0
  11. data/ext/typhoeus/extconf.rb +65 -0
  12. data/ext/typhoeus/native.c +11 -0
  13. data/ext/typhoeus/native.h +21 -0
  14. data/ext/typhoeus/typhoeus_easy.c +207 -0
  15. data/ext/typhoeus/typhoeus_easy.h +19 -0
  16. data/ext/typhoeus/typhoeus_multi.c +225 -0
  17. data/ext/typhoeus/typhoeus_multi.h +16 -0
  18. data/lib/typhoeus/.gitignore +1 -0
  19. data/lib/typhoeus/easy.rb +348 -0
  20. data/lib/typhoeus/filter.rb +28 -0
  21. data/lib/typhoeus/hydra.rb +243 -0
  22. data/lib/typhoeus/multi.rb +35 -0
  23. data/lib/typhoeus/remote.rb +306 -0
  24. data/lib/typhoeus/remote_method.rb +108 -0
  25. data/lib/typhoeus/remote_proxy_object.rb +48 -0
  26. data/lib/typhoeus/request.rb +172 -0
  27. data/lib/typhoeus/response.rb +49 -0
  28. data/lib/typhoeus/service.rb +20 -0
  29. data/lib/typhoeus.rb +55 -0
  30. data/profilers/valgrind.rb +24 -0
  31. data/spec/fixtures/result_set.xml +60 -0
  32. data/spec/servers/app.rb +84 -0
  33. data/spec/spec.opts +2 -0
  34. data/spec/spec_helper.rb +11 -0
  35. data/spec/typhoeus/easy_spec.rb +249 -0
  36. data/spec/typhoeus/filter_spec.rb +35 -0
  37. data/spec/typhoeus/hydra_spec.rb +311 -0
  38. data/spec/typhoeus/multi_spec.rb +74 -0
  39. data/spec/typhoeus/remote_method_spec.rb +141 -0
  40. data/spec/typhoeus/remote_proxy_object_spec.rb +65 -0
  41. data/spec/typhoeus/remote_spec.rb +695 -0
  42. data/spec/typhoeus/request_spec.rb +195 -0
  43. data/spec/typhoeus/response_spec.rb +63 -0
  44. data/typhoeus.gemspec +113 -0
  45. metadata +188 -0
@@ -0,0 +1,348 @@
1
+ module Typhoeus
2
+ class Easy
3
+ attr_reader :response_body, :response_header, :method, :headers, :url, :params
4
+ attr_accessor :start_time
5
+
6
+ # These integer codes are available in curl/curl.h
7
+ CURLINFO_STRING = 1048576
8
+ OPTION_VALUES = {
9
+ :CURLOPT_URL => 10002,
10
+ :CURLOPT_HTTPGET => 80,
11
+ :CURLOPT_HTTPPOST => 10024,
12
+ :CURLOPT_UPLOAD => 46,
13
+ :CURLOPT_CUSTOMREQUEST => 10036,
14
+ :CURLOPT_POSTFIELDS => 10015,
15
+ :CURLOPT_COPYPOSTFIELDS => 10165,
16
+ :CURLOPT_POSTFIELDSIZE => 60,
17
+ :CURLOPT_USERAGENT => 10018,
18
+ :CURLOPT_TIMEOUT_MS => 155,
19
+ # Time-out connect operations after this amount of milliseconds.
20
+ # [Only works on unix-style/SIGALRM operating systems. IOW, does
21
+ # not work on Windows.
22
+ :CURLOPT_CONNECTTIMEOUT_MS => 156,
23
+ :CURLOPT_NOSIGNAL => 99,
24
+ :CURLOPT_HTTPHEADER => 10023,
25
+ :CURLOPT_FOLLOWLOCATION => 52,
26
+ :CURLOPT_MAXREDIRS => 68,
27
+ :CURLOPT_HTTPAUTH => 107,
28
+ :CURLOPT_USERPWD => 10000 + 5,
29
+ :CURLOPT_VERBOSE => 41,
30
+ :CURLOPT_COOKIEFILE => 10000 + 31,
31
+ :CURLOPT_PROXY => 10004,
32
+ :CURLOPT_VERIFYPEER => 64,
33
+ :CURLOPT_NOBODY => 44,
34
+ :CURLOPT_ENCODING => 10000 + 102,
35
+ :CURLOPT_SSLCERT => 10025,
36
+ :CURLOPT_SSLCERTTYPE => 10086,
37
+ :CURLOPT_SSLKEY => 10087,
38
+ :CURLOPT_SSLKEYTYPE => 10088,
39
+ :CURLOPT_KEYPASSWD => 10026,
40
+ :CURLOPT_CAINFO => 10065,
41
+ :CURLOPT_CAPATH => 10097
42
+ }
43
+ INFO_VALUES = {
44
+ :CURLINFO_RESPONSE_CODE => 2097154,
45
+ :CURLINFO_TOTAL_TIME => 3145731,
46
+ :CURLINFO_HTTPAUTH_AVAIL => 0x200000 + 23,
47
+ :CURLINFO_EFFECTIVE_URL => 0x100000 + 1
48
+ }
49
+ AUTH_TYPES = {
50
+ :CURLAUTH_BASIC => 1,
51
+ :CURLAUTH_DIGEST => 2,
52
+ :CURLAUTH_GSSNEGOTIATE => 4,
53
+ :CURLAUTH_NTLM => 8,
54
+ :CURLAUTH_DIGEST_IE => 16,
55
+ :CURLAUTH_AUTO => 16 | 8 | 4 | 2 | 1
56
+ }
57
+
58
+ def initialize
59
+ @method = :get
60
+ @headers = {}
61
+
62
+ # Enable encoding/compression support
63
+ set_option(OPTION_VALUES[:CURLOPT_ENCODING], '')
64
+ end
65
+
66
+ def headers=(hash)
67
+ @headers = hash
68
+ end
69
+
70
+ def proxy=(proxy)
71
+ set_option(OPTION_VALUES[:CURLOPT_PROXY], proxy)
72
+ end
73
+
74
+ def auth=(authinfo)
75
+ set_option(OPTION_VALUES[:CURLOPT_USERPWD], "#{authinfo[:username]}:#{authinfo[:password]}")
76
+ set_option(OPTION_VALUES[:CURLOPT_HTTPAUTH], authinfo[:method]) if authinfo[:method]
77
+ end
78
+
79
+ def auth_methods
80
+ get_info_long(INFO_VALUES[:CURLINFO_HTTPAUTH_AVAIL])
81
+ end
82
+
83
+ def verbose=(boolean)
84
+ set_option(OPTION_VALUES[:CURLOPT_VERBOSE], !!boolean ? 1 : 0)
85
+ end
86
+
87
+ def total_time_taken
88
+ get_info_double(INFO_VALUES[:CURLINFO_TOTAL_TIME])
89
+ end
90
+
91
+ def effective_url
92
+ get_info_string(INFO_VALUES[:CURLINFO_EFFECTIVE_URL])
93
+ end
94
+
95
+ def response_code
96
+ get_info_long(INFO_VALUES[:CURLINFO_RESPONSE_CODE])
97
+ end
98
+
99
+ def follow_location=(boolean)
100
+ if boolean
101
+ set_option(OPTION_VALUES[:CURLOPT_FOLLOWLOCATION], 1)
102
+ else
103
+ set_option(OPTION_VALUES[:CURLOPT_FOLLOWLOCATION], 0)
104
+ end
105
+ end
106
+
107
+ def enable_cookies=(boolean)
108
+ if boolean
109
+ set_option(OPTION_VALUES[:CURLOPT_COOKIEFILE], '')
110
+ end
111
+ end
112
+
113
+ def max_redirects=(redirects)
114
+ set_option(OPTION_VALUES[:CURLOPT_MAXREDIRS], redirects)
115
+ end
116
+
117
+ def connect_timeout=(milliseconds)
118
+ @connect_timeout = milliseconds
119
+ set_option(OPTION_VALUES[:CURLOPT_NOSIGNAL], 1)
120
+ set_option(OPTION_VALUES[:CURLOPT_CONNECTTIMEOUT_MS], milliseconds)
121
+ end
122
+
123
+ def timeout=(milliseconds)
124
+ @timeout = milliseconds
125
+ set_option(OPTION_VALUES[:CURLOPT_NOSIGNAL], 1)
126
+ set_option(OPTION_VALUES[:CURLOPT_TIMEOUT_MS], milliseconds)
127
+ end
128
+
129
+ def timed_out?
130
+ @timeout && total_time_taken > @timeout && response_code == 0
131
+ end
132
+
133
+ def supports_zlib?
134
+ !!(curl_version.match(/zlib/))
135
+ end
136
+
137
+ def request_body=(request_body)
138
+ @request_body = request_body
139
+ if @method == :put
140
+ easy_set_request_body(@request_body)
141
+ headers["Transfer-Encoding"] = ""
142
+ headers["Expect"] = ""
143
+ else
144
+ self.post_data = request_body
145
+ end
146
+ end
147
+
148
+ def user_agent=(user_agent)
149
+ set_option(OPTION_VALUES[:CURLOPT_USERAGENT], user_agent)
150
+ end
151
+
152
+ def url=(url)
153
+ @url = url
154
+ set_option(OPTION_VALUES[:CURLOPT_URL], url)
155
+ end
156
+
157
+ def disable_ssl_peer_verification
158
+ set_option(OPTION_VALUES[:CURLOPT_VERIFYPEER], 0)
159
+ end
160
+
161
+ def method=(method)
162
+ @method = method
163
+ if method == :get
164
+ set_option(OPTION_VALUES[:CURLOPT_HTTPGET], 1)
165
+ elsif method == :post
166
+ set_option(OPTION_VALUES[:CURLOPT_HTTPPOST], 1)
167
+ self.post_data = ""
168
+ elsif method == :put
169
+ set_option(OPTION_VALUES[:CURLOPT_UPLOAD], 1)
170
+ self.request_body = "" unless @request_body
171
+ elsif method == :head
172
+ set_option(OPTION_VALUES[:CURLOPT_NOBODY], 1)
173
+ else
174
+ set_option(OPTION_VALUES[:CURLOPT_CUSTOMREQUEST], method.to_s.upcase)
175
+ end
176
+ end
177
+
178
+ def post_data=(data)
179
+ @post_data_set = true
180
+ set_option(OPTION_VALUES[:CURLOPT_POSTFIELDSIZE], data.length)
181
+ set_option(OPTION_VALUES[:CURLOPT_COPYPOSTFIELDS], data)
182
+ end
183
+
184
+ def params=(params)
185
+ @params = params
186
+ params_string = params.keys.collect do |k|
187
+ value = params[k]
188
+ if value.is_a? Hash
189
+ value.keys.collect {|sk| Rack::Utils.escape("#{k}[#{sk}]") + "=" + Rack::Utils.escape(value[sk].to_s)}
190
+ elsif value.is_a? Array
191
+ key = Rack::Utils.escape(k.to_s)
192
+ value.collect { |v| "#{key}=#{Rack::Utils.escape(v.to_s)}" }.join('&')
193
+ else
194
+ "#{Rack::Utils.escape(k.to_s)}=#{Rack::Utils.escape(params[k].to_s)}"
195
+ end
196
+ end.flatten.join("&")
197
+
198
+ if method == :post
199
+ self.post_data = params_string
200
+ else
201
+ self.url = "#{url}?#{params_string}"
202
+ end
203
+ end
204
+
205
+ # Set SSL certificate
206
+ # " The string should be the file name of your certificate. "
207
+ # The default format is "PEM" and can be changed with ssl_cert_type=
208
+ def ssl_cert=(cert)
209
+ set_option(OPTION_VALUES[:CURLOPT_SSLCERT], cert)
210
+ end
211
+
212
+ # Set SSL certificate type
213
+ # " The string should be the format of your certificate. Supported formats are "PEM" and "DER" "
214
+ def ssl_cert_type=(cert_type)
215
+ raise "Invalid ssl cert type : '#{cert_type}'..." if cert_type and !%w(PEM DER).include?(cert_type)
216
+ set_option(OPTION_VALUES[:CURLOPT_SSLCERTTYPE], cert_type)
217
+ end
218
+
219
+ # Set SSL Key file
220
+ # " The string should be the file name of your private key. "
221
+ # The default format is "PEM" and can be changed with ssl_key_type=
222
+ #
223
+ def ssl_key=(key)
224
+ set_option(OPTION_VALUES[:CURLOPT_SSLKEY], key)
225
+ end
226
+
227
+ # Set SSL Key type
228
+ # " The string should be the format of your private key. Supported formats are "PEM", "DER" and "ENG". "
229
+ #
230
+ def ssl_key_type=(key_type)
231
+ raise "Invalid ssl key type : '#{key_type}'..." if key_type and !%w(PEM DER ENG).include?(key_type)
232
+ set_option(OPTION_VALUES[:CURLOPT_SSLKEYTYPE], key_type)
233
+ end
234
+
235
+ def ssl_key_password=(key_password)
236
+ set_option(OPTION_VALUES[:CURLOPT_KEYPASSWD], key_password)
237
+ end
238
+
239
+ # Set SSL CACERT
240
+ # " File holding one or more certificates to verify the peer with. "
241
+ #
242
+ def ssl_cacert=(cacert)
243
+ set_option(OPTION_VALUES[:CURLOPT_CAINFO], cacert)
244
+ end
245
+
246
+ # Set CAPATH
247
+ # " directory holding multiple CA certificates to verify the peer with. The certificate directory must be prepared using the openssl c_rehash utility. "
248
+ #
249
+ def ssl_capath=(capath)
250
+ set_option(OPTION_VALUES[:CURLOPT_CAPATH], capath)
251
+ end
252
+
253
+ def set_option(option, value)
254
+ if value.class == String
255
+ easy_setopt_string(option, value)
256
+ elsif value
257
+ easy_setopt_long(option, value)
258
+ end
259
+ end
260
+
261
+ def perform
262
+ set_headers()
263
+ easy_perform()
264
+ resp_code = response_code()
265
+ if resp_code >= 200 && resp_code <= 299
266
+ success
267
+ else
268
+ failure
269
+ end
270
+ resp_code
271
+ end
272
+
273
+ def set_headers
274
+ headers.each_pair do |key, value|
275
+ easy_add_header("#{key}: #{value}")
276
+ end
277
+ easy_set_headers() unless headers.empty?
278
+ end
279
+
280
+ # gets called when finished and response code is 200-299
281
+ def success
282
+ @success.call(self) if @success
283
+ end
284
+
285
+ def on_success(&block)
286
+ @success = block
287
+ end
288
+
289
+ def on_success=(block)
290
+ @success = block
291
+ end
292
+
293
+ # gets called when finished and response code is 300-599
294
+ def failure
295
+ @failure.call(self) if @failure
296
+ end
297
+
298
+ def on_failure(&block)
299
+ @failure = block
300
+ end
301
+
302
+ def on_failure=(block)
303
+ @failure = block
304
+ end
305
+
306
+ def retries
307
+ @retries ||= 0
308
+ end
309
+
310
+ def increment_retries
311
+ @retries ||= 0
312
+ @retries += 1
313
+ end
314
+
315
+ def max_retries
316
+ @max_retries ||= 40
317
+ end
318
+
319
+ def max_retries?
320
+ retries >= max_retries
321
+ end
322
+
323
+ def reset
324
+ @retries = 0
325
+ @response_code = 0
326
+ @response_header = ""
327
+ @response_body = ""
328
+ easy_reset()
329
+ end
330
+
331
+ def get_info_string(option)
332
+ easy_getinfo_string(option)
333
+ end
334
+
335
+ def get_info_long(option)
336
+ easy_getinfo_long(option)
337
+ end
338
+
339
+ def get_info_double(option)
340
+ easy_getinfo_double(option)
341
+ end
342
+
343
+ def curl_version
344
+ version
345
+ end
346
+
347
+ end
348
+ end
@@ -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,243 @@
1
+ module Typhoeus
2
+ class Hydra
3
+ def initialize(options = {})
4
+ @memoize_requests = true
5
+ @multi = Multi.new
6
+ @easy_pool = []
7
+ initial_pool_size = options[:initial_pool_size] || 10
8
+ @max_concurrency = options[:max_concurrency] || 200
9
+ initial_pool_size.times { @easy_pool << Easy.new }
10
+ @stubs = []
11
+ @memoized_requests = {}
12
+ @retrieved_from_cache = {}
13
+ @queued_requests = []
14
+ @running_requests = 0
15
+ @stubbed_request_count = 0
16
+ end
17
+
18
+ def self.hydra
19
+ @hydra ||= new
20
+ end
21
+
22
+ def self.hydra=(val)
23
+ @hydra = val
24
+ end
25
+
26
+ def clear_cache_callbacks
27
+ @cache_setter = nil
28
+ @cache_getter = nil
29
+ end
30
+
31
+ def clear_stubs
32
+ @stubs = []
33
+ end
34
+
35
+ def fire_and_forget
36
+ @queued_requests.each {|r| queue(r, false)}
37
+ @multi.fire_and_forget
38
+ end
39
+
40
+ def queue(request, obey_concurrency_limit = true)
41
+ return if assign_to_stub(request)
42
+
43
+ if @running_requests >= @max_concurrency && obey_concurrency_limit
44
+ @queued_requests << request
45
+ else
46
+ if request.method == :get
47
+ if @memoize_requests && @memoized_requests.has_key?(request.url)
48
+ if response = @retrieved_from_cache[request.url]
49
+ request.response = response
50
+ request.call_handlers
51
+ else
52
+ @memoized_requests[request.url] << request
53
+ end
54
+ else
55
+ @memoized_requests[request.url] = [] if @memoize_requests
56
+ get_from_cache_or_queue(request)
57
+ end
58
+ else
59
+ get_from_cache_or_queue(request)
60
+ end
61
+ end
62
+ end
63
+
64
+ def run
65
+ while @stubbed_request_count > 0
66
+ @stubs.each do |m|
67
+ while request = m.requests.shift
68
+ @stubbed_request_count -= 1
69
+ m.response.request = request
70
+ handle_request(request, m.response)
71
+ end
72
+ end
73
+ end
74
+
75
+ @multi.perform
76
+ @memoized_requests = {}
77
+ @retrieved_from_cache = {}
78
+ end
79
+
80
+ def disable_memoization
81
+ @memoize_requests = false
82
+ end
83
+
84
+ def cache_getter(&block)
85
+ @cache_getter = block
86
+ end
87
+
88
+ def cache_setter(&block)
89
+ @cache_setter = block
90
+ end
91
+
92
+ def on_complete(&block)
93
+ @on_complete = block
94
+ end
95
+
96
+ def on_complete=(proc)
97
+ @on_complete = proc
98
+ end
99
+
100
+ def stub(method, url)
101
+ @stubs << HydraMock.new(url, method)
102
+ @stubs.last
103
+ end
104
+
105
+ def assign_to_stub(request)
106
+ m = @stubs.detect {|stub| stub.matches? request}
107
+ if m
108
+ m.add_request(request)
109
+ @stubbed_request_count += 1
110
+ else
111
+ nil
112
+ end
113
+ end
114
+ private :assign_to_stub
115
+
116
+ def get_from_cache_or_queue(request)
117
+ if @cache_getter
118
+ val = @cache_getter.call(request)
119
+ if val
120
+ @retrieved_from_cache[request.url] = val
121
+ handle_request(request, val, false)
122
+ else
123
+ @multi.add(get_easy_object(request))
124
+ end
125
+ else
126
+ @multi.add(get_easy_object(request))
127
+ end
128
+ end
129
+ private :get_from_cache_or_queue
130
+
131
+ def get_easy_object(request)
132
+ @running_requests += 1
133
+
134
+ easy = @easy_pool.pop || Easy.new
135
+ easy.verbose = request.verbose
136
+ if request.username || request.password
137
+ auth = { :username => request.username, :password => request.password }
138
+ auth[:method] = Typhoeus::Easy::AUTH_TYPES["CURLAUTH_#{request.auth_method.to_s.upcase}".to_sym] if request.auth_method
139
+ easy.auth = auth
140
+ end
141
+ easy.url = request.url
142
+ easy.method = request.method
143
+ easy.params = request.params if request.method == :post && !request.params.nil?
144
+ easy.headers = request.headers if request.headers
145
+ easy.request_body = request.body if request.body
146
+ easy.timeout = request.timeout if request.timeout
147
+ easy.connect_timeout = request.connect_timeout if request.connect_timeout
148
+ easy.follow_location = request.follow_location if request.follow_location
149
+ easy.enable_cookies = request.enable_cookies if request.enable_cookies
150
+ easy.max_redirects = request.max_redirects if request.max_redirects
151
+ easy.proxy = request.proxy if request.proxy
152
+ easy.disable_ssl_peer_verification if request.disable_ssl_peer_verification
153
+ easy.ssl_cert = request.ssl_cert
154
+ easy.ssl_cert_type = request.ssl_cert_type
155
+ easy.ssl_key = request.ssl_key
156
+ easy.ssl_key_type = request.ssl_key_type
157
+ easy.ssl_key_password = request.ssl_key_password
158
+ easy.ssl_cacert = request.ssl_cacert
159
+ easy.ssl_capath = request.ssl_capath
160
+ easy.verbose = request.verbose
161
+
162
+ easy.on_success do |easy|
163
+ queue_next
164
+ handle_request(request, response_from_easy(easy, request))
165
+ release_easy_object(easy)
166
+ end
167
+ easy.on_failure do |easy|
168
+ queue_next
169
+ handle_request(request, response_from_easy(easy, request))
170
+ release_easy_object(easy)
171
+ end
172
+ easy.set_headers
173
+ easy
174
+ end
175
+ private :get_easy_object
176
+
177
+ def queue_next
178
+ @running_requests -= 1
179
+ queue(@queued_requests.pop) unless @queued_requests.empty?
180
+ end
181
+ private :queue_next
182
+
183
+ def release_easy_object(easy)
184
+ easy.reset
185
+ @easy_pool.push easy
186
+ end
187
+ private :release_easy_object
188
+
189
+ def handle_request(request, response, live_request = true)
190
+ request.response = response
191
+
192
+ if live_request && request.cache_timeout && @cache_setter
193
+ @cache_setter.call(request)
194
+ end
195
+ @on_complete.call(response) if @on_complete
196
+
197
+ request.call_handlers
198
+ if requests = @memoized_requests[request.url]
199
+ requests.each do |r|
200
+ r.response = response
201
+ r.call_handlers
202
+ end
203
+ end
204
+ end
205
+ private :handle_request
206
+
207
+ def response_from_easy(easy, request)
208
+ Response.new(:code => easy.response_code,
209
+ :headers => easy.response_header,
210
+ :body => easy.response_body,
211
+ :time => easy.total_time_taken,
212
+ :effective_url => easy.effective_url,
213
+ :request => request)
214
+ end
215
+ private :response_from_easy
216
+ end
217
+
218
+ class HydraMock
219
+ attr_reader :url, :method, :response, :requests
220
+
221
+ def initialize(url, method)
222
+ @url = url
223
+ @method = method
224
+ @requests = []
225
+ end
226
+
227
+ def add_request(request)
228
+ @requests << request
229
+ end
230
+
231
+ def and_return(val)
232
+ @response = val
233
+ end
234
+
235
+ def matches?(request)
236
+ if url.kind_of?(String)
237
+ request.method == method && request.url == url
238
+ else
239
+ request.method == method && url =~ request.url
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,35 @@
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.set_headers() if easy.headers.empty?
15
+ @easy_handles << easy
16
+ multi_add_handle(easy)
17
+ end
18
+
19
+ def perform()
20
+ while active_handle_count > 0 do
21
+ multi_perform
22
+ end
23
+ reset_easy_handles
24
+ end
25
+
26
+ def cleanup()
27
+ multi_cleanup
28
+ end
29
+
30
+ private
31
+ def reset_easy_handles
32
+ @easy_handles = []
33
+ end
34
+ end
35
+ end