typhoeus 0.1.6 → 0.2.0

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