typhoeus 0.2.0 → 0.2.1

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 (49) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.markdown +14 -1
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +2 -0
  5. data/LICENSE +20 -0
  6. data/README.textile +39 -5
  7. data/Rakefile +8 -5
  8. data/VERSION +1 -1
  9. data/examples/file.rb +12 -0
  10. data/examples/times.rb +40 -0
  11. data/ext/typhoeus/.gitignore +2 -1
  12. data/ext/typhoeus/native.c +1 -0
  13. data/ext/typhoeus/native.h +1 -0
  14. data/ext/typhoeus/typhoeus_easy.c +32 -7
  15. data/ext/typhoeus/typhoeus_easy.h +1 -0
  16. data/ext/typhoeus/typhoeus_form.c +59 -0
  17. data/ext/typhoeus/typhoeus_form.h +13 -0
  18. data/ext/typhoeus/typhoeus_multi.c +15 -29
  19. data/lib/typhoeus.rb +1 -0
  20. data/lib/typhoeus/easy.rb +70 -48
  21. data/lib/typhoeus/form.rb +47 -0
  22. data/lib/typhoeus/hydra.rb +40 -7
  23. data/lib/typhoeus/hydra/connect_options.rb +19 -3
  24. data/lib/typhoeus/multi.rb +7 -5
  25. data/lib/typhoeus/remote.rb +1 -1
  26. data/lib/typhoeus/remote_proxy_object.rb +2 -0
  27. data/lib/typhoeus/request.rb +15 -3
  28. data/lib/typhoeus/response.rb +16 -1
  29. data/spec/fixtures/placeholder.gif +0 -0
  30. data/spec/fixtures/placeholder.txt +1 -0
  31. data/spec/fixtures/placeholder.ukn +0 -0
  32. data/spec/servers/app.rb +9 -0
  33. data/spec/spec.opts +1 -0
  34. data/spec/spec_helper.rb +3 -0
  35. data/spec/typhoeus/easy_spec.rb +55 -6
  36. data/spec/typhoeus/filter_spec.rb +2 -2
  37. data/spec/typhoeus/form_spec.rb +106 -0
  38. data/spec/typhoeus/hydra_mock_spec.rb +1 -1
  39. data/spec/typhoeus/hydra_spec.rb +108 -38
  40. data/spec/typhoeus/multi_spec.rb +1 -1
  41. data/spec/typhoeus/normalized_header_hash_spec.rb +1 -1
  42. data/spec/typhoeus/remote_method_spec.rb +2 -2
  43. data/spec/typhoeus/remote_proxy_object_spec.rb +1 -1
  44. data/spec/typhoeus/remote_spec.rb +1 -1
  45. data/spec/typhoeus/request_spec.rb +31 -2
  46. data/spec/typhoeus/response_spec.rb +13 -1
  47. data/spec/typhoeus/utils_spec.rb +1 -1
  48. data/typhoeus.gemspec +23 -6
  49. metadata +39 -19
@@ -0,0 +1,13 @@
1
+ #ifndef TYPHOEUS_FORM
2
+ #define TYPHOEUS_FORM
3
+
4
+ #include <native.h>
5
+
6
+ typedef struct {
7
+ struct curl_httppost *first;
8
+ struct curl_httppost *last;
9
+ } CurlForm;
10
+
11
+ void init_typhoeus_form();
12
+
13
+ #endif
@@ -16,12 +16,15 @@ static VALUE multi_add_handle(VALUE self, VALUE easy) {
16
16
 
17
17
  mcode = curl_multi_add_handle(curl_multi->multi, curl_easy->curl);
18
18
  if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
19
- rb_raise((VALUE)mcode, "An error occured adding the handle");
19
+ rb_raise(rb_eRuntimeError, "An error occured adding the handle: %d: %s", mcode, curl_multi_strerror(mcode));
20
20
  }
21
21
 
22
22
  curl_easy_setopt(curl_easy->curl, CURLOPT_PRIVATE, easy);
23
23
  curl_multi->active++;
24
24
 
25
+ VALUE easy_handles = rb_iv_get(self, "@easy_handles");
26
+ rb_ary_push(easy_handles, easy);
27
+
25
28
  if (mcode == CURLM_CALL_MULTI_PERFORM) {
26
29
  curl_multi_perform(curl_multi->multi, &(curl_multi->running));
27
30
  }
@@ -43,6 +46,9 @@ static VALUE multi_remove_handle(VALUE self, VALUE easy) {
43
46
  curl_multi->active--;
44
47
  curl_multi_remove_handle(curl_multi->multi, curl_easy->curl);
45
48
 
49
+ VALUE easy_handles = rb_iv_get(self, "@easy_handles");
50
+ rb_ary_delete(easy_handles, easy);
51
+
46
52
  return easy;
47
53
  }
48
54
 
@@ -65,36 +71,14 @@ static void multi_read_info(VALUE self, CURLM *multi_handle) {
65
71
  if (easy_handle) {
66
72
  ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &easy);
67
73
  if (ecode != 0) {
68
- rb_raise(ecode, "error getting easy object");
74
+ rb_raise(rb_eRuntimeError, "error getting easy object: %d: %s", ecode, curl_easy_strerror(ecode));
69
75
  }
70
76
 
71
77
  long response_code = -1;
72
78
  curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
73
79
 
74
- // TODO: find out what the real problem is here and fix it.
75
- // this next bit is a horrible hack. For some reason my tests against a local server on my laptop
76
- // fail intermittently and return this result number. However, it will succeed if you try it a few
77
- // more times. Also noteworthy is that this doens't happen when hitting an external server. WTF?!
78
-
79
- // Sandofsky says:
80
- // This is caused by OS X first attempting to resolve using IPV6.
81
- // Hack solution: connect to yourself with 127.0.0.1, not localhost
82
- // http://curl.haxx.se/mail/tracker-2009-09/0018.html
83
- if (result == 7) {
84
- VALUE max_retries = rb_funcall(easy, rb_intern("max_retries?"), 0);
85
- if (max_retries != Qtrue) {
86
- multi_remove_handle(self, easy);
87
- multi_add_handle(self, easy);
88
- CurlMulti *curl_multi;
89
- Data_Get_Struct(self, CurlMulti, curl_multi);
90
- curl_multi_perform(curl_multi->multi, &(curl_multi->running));
91
-
92
- rb_funcall(easy, rb_intern("increment_retries"), 0);
93
-
94
- continue;
95
- }
96
- }
97
80
  multi_remove_handle(self, easy);
81
+ rb_iv_set(easy, "@curl_return_code", INT2FIX(result));
98
82
 
99
83
  if (result != 0) {
100
84
  rb_funcall(easy, rb_intern("failure"), 0);
@@ -118,7 +102,7 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
118
102
  } while (mcode == CURLM_CALL_MULTI_PERFORM);
119
103
 
120
104
  if (mcode != CURLM_OK) {
121
- rb_raise((VALUE)mcode, "an error occured while running perform");
105
+ rb_raise(rb_eRuntimeError, "an error occured while running perform: %d: %s", mcode, curl_multi_strerror(mcode));
122
106
  }
123
107
 
124
108
  multi_read_info( self, multi_handle );
@@ -128,6 +112,8 @@ static VALUE fire_and_forget(VALUE self) {
128
112
  CurlMulti *curl_multi;
129
113
  Data_Get_Struct(self, CurlMulti, curl_multi);
130
114
  rb_curl_multi_run( self, curl_multi->multi, &(curl_multi->running) );
115
+
116
+ return Qnil;
131
117
  }
132
118
 
133
119
  static VALUE multi_perform(VALUE self) {
@@ -150,7 +136,7 @@ static VALUE multi_perform(VALUE self) {
150
136
  /* get the curl suggested time out */
151
137
  mcode = curl_multi_timeout(curl_multi->multi, &timeout);
152
138
  if (mcode != CURLM_OK) {
153
- rb_raise((VALUE)mcode, "an error occured getting the timeout");
139
+ rb_raise(rb_eRuntimeError, "an error occured getting the timeout: %d: %s", mcode, curl_multi_strerror(mcode));
154
140
  }
155
141
 
156
142
  if (timeout == 0) { /* no delay */
@@ -167,7 +153,7 @@ static VALUE multi_perform(VALUE self) {
167
153
  /* load the fd sets from the multi handle */
168
154
  mcode = curl_multi_fdset(curl_multi->multi, &fdread, &fdwrite, &fdexcep, &maxfd);
169
155
  if (mcode != CURLM_OK) {
170
- rb_raise((VALUE)mcode, "an error occured getting the fdset");
156
+ rb_raise(rb_eRuntimeError, "an error occured getting the fdset: %d: %s", mcode, curl_multi_strerror(mcode));
171
157
  }
172
158
 
173
159
  rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
@@ -185,7 +171,7 @@ static VALUE active_handle_count(VALUE self) {
185
171
  CurlMulti *curl_multi;
186
172
  Data_Get_Struct(self, CurlMulti, curl_multi);
187
173
 
188
- return INT2NUM(curl_multi->active);
174
+ return INT2FIX(curl_multi->active);
189
175
  }
190
176
 
191
177
  static VALUE multi_cleanup(VALUE self) {
@@ -5,6 +5,7 @@ require 'digest/sha2'
5
5
  require 'typhoeus/utils'
6
6
  require 'typhoeus/normalized_header_hash'
7
7
  require 'typhoeus/easy'
8
+ require 'typhoeus/form'
8
9
  require 'typhoeus/multi'
9
10
  require 'typhoeus/native'
10
11
  require 'typhoeus/filter'
@@ -1,6 +1,6 @@
1
1
  module Typhoeus
2
2
  class Easy
3
- attr_reader :response_body, :response_header, :method, :headers, :url, :params
3
+ attr_reader :response_body, :response_header, :method, :headers, :url, :params, :curl_return_code
4
4
  attr_accessor :start_time
5
5
 
6
6
  # These integer codes are available in curl/curl.h
@@ -28,6 +28,9 @@ module Typhoeus
28
28
  :CURLOPT_USERPWD => 10000 + 5,
29
29
  :CURLOPT_VERBOSE => 41,
30
30
  :CURLOPT_PROXY => 10004,
31
+ :CURLOPT_PROXYUSERPWD => 10000 + 6,
32
+ :CURLOPT_PROXYTYPE => 101,
33
+ :CURLOPT_PROXYAUTH => 111,
31
34
  :CURLOPT_VERIFYPEER => 64,
32
35
  :CURLOPT_NOBODY => 44,
33
36
  :CURLOPT_ENCODING => 10000 + 102,
@@ -37,13 +40,19 @@ module Typhoeus
37
40
  :CURLOPT_SSLKEYTYPE => 10088,
38
41
  :CURLOPT_KEYPASSWD => 10026,
39
42
  :CURLOPT_CAINFO => 10065,
40
- :CURLOPT_CAPATH => 10097
43
+ :CURLOPT_CAPATH => 10097,
41
44
  }
42
45
  INFO_VALUES = {
43
- :CURLINFO_RESPONSE_CODE => 2097154,
44
- :CURLINFO_TOTAL_TIME => 3145731,
45
- :CURLINFO_HTTPAUTH_AVAIL => 0x200000 + 23,
46
- :CURLINFO_EFFECTIVE_URL => 0x100000 + 1
46
+ :CURLINFO_RESPONSE_CODE => 2097154,
47
+ :CURLINFO_TOTAL_TIME => 3145731,
48
+ :CURLINFO_HTTPAUTH_AVAIL => 0x200000 + 23,
49
+ :CURLINFO_EFFECTIVE_URL => 0x100000 + 1,
50
+ :CURLINFO_NAMELOOKUP_TIME => 0x300000 + 4,
51
+ :CURLINFO_CONNECT_TIME => 0x300000 + 5,
52
+ :CURLINFO_PRETRANSFER_TIME => 0x300000 + 6,
53
+ :CURLINFO_STARTTRANSFER_TIME => 0x300000 + 17,
54
+ :CURLINFO_APPCONNECT_TIME => 0x300000 + 33,
55
+
47
56
  }
48
57
  AUTH_TYPES = {
49
58
  :CURLAUTH_BASIC => 1,
@@ -53,6 +62,14 @@ module Typhoeus
53
62
  :CURLAUTH_DIGEST_IE => 16,
54
63
  :CURLAUTH_AUTO => 16 | 8 | 4 | 2 | 1
55
64
  }
65
+ PROXY_TYPES = {
66
+ :CURLPROXY_HTTP => 0,
67
+ :CURLPROXY_HTTP_1_0 => 1,
68
+ :CURLPROXY_SOCKS4 => 4,
69
+ :CURLPROXY_SOCKS5 => 5,
70
+ :CURLPROXY_SOCKS4A => 6,
71
+ }
72
+
56
73
 
57
74
  def initialize
58
75
  @method = :get
@@ -67,7 +84,13 @@ module Typhoeus
67
84
  end
68
85
 
69
86
  def proxy=(proxy)
70
- set_option(OPTION_VALUES[:CURLOPT_PROXY], proxy)
87
+ set_option(OPTION_VALUES[:CURLOPT_PROXY], proxy[:server])
88
+ set_option(OPTION_VALUES[:CURLOPT_PROXYTYPE], proxy[:type]) if proxy[:type]
89
+ end
90
+
91
+ def proxy_auth=(authinfo)
92
+ set_option(OPTION_VALUES[:CURLOPT_PROXYUSERPWD], "#{authinfo[:username]}:#{authinfo[:password]}")
93
+ set_option(OPTION_VALUES[:CURLOPT_PROXYAUTH], authinfo[:method]) if authinfo[:method]
71
94
  end
72
95
 
73
96
  def auth=(authinfo)
@@ -87,6 +110,26 @@ module Typhoeus
87
110
  get_info_double(INFO_VALUES[:CURLINFO_TOTAL_TIME])
88
111
  end
89
112
 
113
+ def start_transfer_time
114
+ get_info_double(INFO_VALUES[:CURLINFO_STARTTRANSFER_TIME])
115
+ end
116
+
117
+ def app_connect_time
118
+ get_info_double(INFO_VALUES[:CURLINFO_APPCONNECT_TIME])
119
+ end
120
+
121
+ def pretransfer_time
122
+ get_info_double(INFO_VALUES[:CURLINFO_PRETRANSFER_TIME])
123
+ end
124
+
125
+ def connect_time
126
+ get_info_double(INFO_VALUES[:CURLINFO_CONNECT_TIME])
127
+ end
128
+
129
+ def name_lookup_time
130
+ get_info_double(INFO_VALUES[:CURLINFO_NAMELOOKUP_TIME])
131
+ end
132
+
90
133
  def effective_url
91
134
  get_info_string(INFO_VALUES[:CURLINFO_EFFECTIVE_URL])
92
135
  end
@@ -106,7 +149,7 @@ module Typhoeus
106
149
  def max_redirects=(redirects)
107
150
  set_option(OPTION_VALUES[:CURLOPT_MAXREDIRS], redirects)
108
151
  end
109
-
152
+
110
153
  def connect_timeout=(milliseconds)
111
154
  @connect_timeout = milliseconds
112
155
  set_option(OPTION_VALUES[:CURLOPT_NOSIGNAL], 1)
@@ -120,7 +163,7 @@ module Typhoeus
120
163
  end
121
164
 
122
165
  def timed_out?
123
- @timeout && total_time_taken > @timeout && response_code == 0
166
+ curl_return_code == 28
124
167
  end
125
168
 
126
169
  def supports_zlib?
@@ -174,24 +217,18 @@ module Typhoeus
174
217
  set_option(OPTION_VALUES[:CURLOPT_COPYPOSTFIELDS], data)
175
218
  end
176
219
 
220
+ def params
221
+ @form.nil? ? {} : @form.params
222
+ end
223
+
177
224
  def params=(params)
178
- @params = params
179
- params_string = params.keys.collect do |k|
180
- value = params[k]
181
- if value.is_a? Hash
182
- value.keys.collect {|sk| Typhoeus::Utils.escape("#{k}[#{sk}]") + "=" + Typhoeus::Utils.escape(value[sk].to_s)}
183
- elsif value.is_a? Array
184
- key = Typhoeus::Utils.escape(k.to_s)
185
- value.collect { |v| "#{key}=#{Typhoeus::Utils.escape(v.to_s)}" }.join('&')
186
- else
187
- "#{Typhoeus::Utils.escape(k.to_s)}=#{Typhoeus::Utils.escape(params[k].to_s)}"
188
- end
189
- end.flatten.join("&")
225
+ @form = Typhoeus::Form.new(params)
190
226
 
191
227
  if method == :post
192
- self.post_data = params_string
228
+ @form.process!
229
+ set_option(OPTION_VALUES[:CURLOPT_HTTPPOST], @form)
193
230
  else
194
- self.url = "#{url}?#{params_string}"
231
+ self.url = "#{url}?#{@form.to_s}"
195
232
  end
196
233
  end
197
234
 
@@ -205,7 +242,7 @@ module Typhoeus
205
242
  # Set SSL certificate type
206
243
  # " The string should be the format of your certificate. Supported formats are "PEM" and "DER" "
207
244
  def ssl_cert_type=(cert_type)
208
- raise "Invalid ssl cert type : '#{cert_type}'..." if cert_type and !%w(PEM DER).include?(cert_type)
245
+ raise "Invalid ssl cert type : '#{cert_type}'..." if cert_type and !%w(PEM DER).include?(cert_type)
209
246
  set_option(OPTION_VALUES[:CURLOPT_SSLCERTTYPE], cert_type)
210
247
  end
211
248
 
@@ -244,17 +281,20 @@ module Typhoeus
244
281
  end
245
282
 
246
283
  def set_option(option, value)
247
- if value.class == String
248
- easy_setopt_string(option, value)
249
- elsif value
250
- easy_setopt_long(option, value)
284
+ case value
285
+ when String
286
+ easy_setopt_string(option, value)
287
+ when Typhoeus::Form
288
+ easy_setopt_form(option, value)
289
+ else
290
+ easy_setopt_long(option, value) if value
251
291
  end
252
292
  end
253
293
 
254
294
  def perform
255
295
  set_headers()
256
296
  easy_perform()
257
- resp_code = response_code()
297
+ resp_code = response_code()
258
298
  if resp_code >= 200 && resp_code <= 299
259
299
  success
260
300
  else
@@ -283,7 +323,7 @@ module Typhoeus
283
323
  @success = block
284
324
  end
285
325
 
286
- # gets called when finished and response code is 300-599
326
+ # gets called when finished and response code is 300-599 or curl returns an error code
287
327
  def failure
288
328
  @failure.call(self) if @failure
289
329
  end
@@ -296,25 +336,7 @@ module Typhoeus
296
336
  @failure = block
297
337
  end
298
338
 
299
- def retries
300
- @retries ||= 0
301
- end
302
-
303
- def increment_retries
304
- @retries ||= 0
305
- @retries += 1
306
- end
307
-
308
- def max_retries
309
- @max_retries ||= 40
310
- end
311
-
312
- def max_retries?
313
- retries >= max_retries
314
- end
315
-
316
339
  def reset
317
- @retries = 0
318
340
  @response_code = 0
319
341
  @response_header = ""
320
342
  @response_body = ""
@@ -0,0 +1,47 @@
1
+ require 'mime/types'
2
+
3
+ module Typhoeus
4
+ class Form
5
+ attr_accessor :params
6
+
7
+ def initialize(params = {})
8
+ @params = params
9
+ end
10
+
11
+ def process!
12
+ params.each do |key, value|
13
+ case value
14
+ when Hash
15
+ value.keys.each {|sub_key| formadd_param("#{key}[#{sub_key}]", value[sub_key].to_s)}
16
+ when Array
17
+ value.each {|v| formadd_param(key.to_s, v.to_s)}
18
+ when File
19
+ filename = File.basename(value.path)
20
+ types = MIME::Types.type_for(filename)
21
+ formadd_file(
22
+ key.to_s,
23
+ filename,
24
+ types.empty? ? 'application/octet-stream' : types[0].to_s,
25
+ File.expand_path(value.path)
26
+ )
27
+ else
28
+ formadd_param(key.to_s, value.to_s)
29
+ end
30
+ end
31
+ end
32
+
33
+ def to_s
34
+ params.keys.sort_by{|k|k.to_s}.collect do |k|
35
+ value = params[k]
36
+ if value.is_a? Hash
37
+ value.keys.sort_by{|sk|sk.to_s}.collect {|sk| Typhoeus::Utils.escape("#{k}[#{sk}]") + "=" + Typhoeus::Utils.escape(value[sk].to_s)}
38
+ elsif value.is_a? Array
39
+ key = Typhoeus::Utils.escape(k.to_s)
40
+ value.collect { |v| "#{key}=#{Typhoeus::Utils.escape(v.to_s)}" }.join('&')
41
+ else
42
+ "#{Typhoeus::Utils.escape(k.to_s)}=#{Typhoeus::Utils.escape(params[k].to_s)}"
43
+ end
44
+ end.flatten.join("&")
45
+ end
46
+ end
47
+ end
@@ -32,6 +32,17 @@ module Typhoeus
32
32
  @hydra = val
33
33
  end
34
34
 
35
+ #
36
+ # Abort the run on a best-effort basis.
37
+ #
38
+ # It won't abort the current burst of @max_concurrency requests,
39
+ # however it won't fire the rest of the queued requests so the run
40
+ # will be aborted as soon as possible...
41
+ #
42
+ def abort
43
+ @queued_requests.clear
44
+ end
45
+
35
46
  def clear_cache_callbacks
36
47
  @cache_setter = nil
37
48
  @cache_getter = nil
@@ -82,8 +93,11 @@ module Typhoeus
82
93
  end
83
94
 
84
95
  @multi.perform
96
+ ensure
97
+ @multi.reset_easy_handles{|easy| release_easy_object(easy)}
85
98
  @memoized_requests = {}
86
99
  @retrieved_from_cache = {}
100
+ @running_requests = 0
87
101
  end
88
102
 
89
103
  def disable_memoization
@@ -131,6 +145,19 @@ module Typhoeus
131
145
  auth[:method] = Typhoeus::Easy::AUTH_TYPES["CURLAUTH_#{request.auth_method.to_s.upcase}".to_sym] if request.auth_method
132
146
  easy.auth = auth
133
147
  end
148
+
149
+ if request.proxy
150
+ proxy = { :server => request.proxy }
151
+ proxy[:type] = Typhoeus::Easy::PROXY_TYPES["CURLPROXY_#{request.proxy_type.to_s.upcase}".to_sym] if request.proxy_type
152
+ easy.proxy = proxy if request.proxy
153
+ end
154
+
155
+ if request.proxy_username || request.proxy_password
156
+ auth = { :username => request.proxy_username, :password => request.proxy_password }
157
+ auth[:method] = Typhoeus::Easy::AUTH_TYPES["CURLAUTH_#{request.proxy_auth_method.to_s.upcase}".to_sym] if request.proxy_auth_method
158
+ easy.proxy_auth = auth
159
+ end
160
+
134
161
  easy.url = request.url
135
162
  easy.method = request.method
136
163
  easy.params = request.params if request.method == :post && !request.params.nil?
@@ -140,7 +167,6 @@ module Typhoeus
140
167
  easy.connect_timeout = request.connect_timeout if request.connect_timeout
141
168
  easy.follow_location = request.follow_location if request.follow_location
142
169
  easy.max_redirects = request.max_redirects if request.max_redirects
143
- easy.proxy = request.proxy if request.proxy
144
170
  easy.disable_ssl_peer_verification if request.disable_ssl_peer_verification
145
171
  easy.ssl_cert = request.ssl_cert
146
172
  easy.ssl_cert_type = request.ssl_cert_type
@@ -200,12 +226,19 @@ module Typhoeus
200
226
  private :handle_request
201
227
 
202
228
  def response_from_easy(easy, request)
203
- Response.new(:code => easy.response_code,
204
- :headers => easy.response_header,
205
- :body => easy.response_body,
206
- :time => easy.total_time_taken,
207
- :effective_url => easy.effective_url,
208
- :request => request)
229
+ Response.new(:code => easy.response_code,
230
+ :headers => easy.response_header,
231
+ :body => easy.response_body,
232
+ :time => easy.total_time_taken,
233
+ :start_transfer_time => easy.start_transfer_time,
234
+ :app_connect_time => easy.app_connect_time,
235
+ :pretransfer_time => easy.pretransfer_time,
236
+ :connect_time => easy.connect_time,
237
+ :name_lookup_time => easy.name_lookup_time,
238
+ :effective_url => easy.effective_url,
239
+ :curl_return_code => easy.curl_return_code,
240
+ :curl_error_message => easy.curl_error_message,
241
+ :request => request)
209
242
  end
210
243
  private :response_from_easy
211
244
  end