typhoeus 0.3.3 → 0.4.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 (59) hide show
  1. data/{CHANGELOG.markdown → CHANGELOG.md} +21 -12
  2. data/LICENSE +2 -0
  3. data/README.md +455 -0
  4. data/Rakefile +6 -26
  5. data/lib/typhoeus.rb +4 -6
  6. data/lib/typhoeus/curl.rb +453 -0
  7. data/lib/typhoeus/easy.rb +60 -358
  8. data/lib/typhoeus/easy/auth.rb +14 -0
  9. data/lib/typhoeus/easy/callbacks.rb +33 -0
  10. data/lib/typhoeus/easy/ffi_helper.rb +61 -0
  11. data/lib/typhoeus/easy/infos.rb +90 -0
  12. data/lib/typhoeus/easy/options.rb +115 -0
  13. data/lib/typhoeus/easy/proxy.rb +20 -0
  14. data/lib/typhoeus/easy/ssl.rb +82 -0
  15. data/lib/typhoeus/form.rb +30 -1
  16. data/lib/typhoeus/{normalized_header_hash.rb → header.rb} +2 -6
  17. data/lib/typhoeus/hydra.rb +9 -12
  18. data/lib/typhoeus/hydra_mock.rb +2 -2
  19. data/lib/typhoeus/multi.rb +118 -9
  20. data/lib/typhoeus/param_processor.rb +43 -0
  21. data/lib/typhoeus/request.rb +18 -21
  22. data/lib/typhoeus/response.rb +5 -4
  23. data/lib/typhoeus/utils.rb +14 -27
  24. data/lib/typhoeus/version.rb +1 -1
  25. metadata +155 -152
  26. data/Gemfile.lock +0 -37
  27. data/ext/typhoeus/.gitignore +0 -7
  28. data/ext/typhoeus/extconf.rb +0 -65
  29. data/ext/typhoeus/native.c +0 -12
  30. data/ext/typhoeus/native.h +0 -22
  31. data/ext/typhoeus/typhoeus_easy.c +0 -232
  32. data/ext/typhoeus/typhoeus_easy.h +0 -20
  33. data/ext/typhoeus/typhoeus_form.c +0 -59
  34. data/ext/typhoeus/typhoeus_form.h +0 -13
  35. data/ext/typhoeus/typhoeus_multi.c +0 -217
  36. data/ext/typhoeus/typhoeus_multi.h +0 -16
  37. data/lib/typhoeus/.gitignore +0 -1
  38. data/lib/typhoeus/service.rb +0 -20
  39. data/spec/fixtures/placeholder.gif +0 -0
  40. data/spec/fixtures/placeholder.txt +0 -1
  41. data/spec/fixtures/placeholder.ukn +0 -0
  42. data/spec/fixtures/result_set.xml +0 -60
  43. data/spec/servers/app.rb +0 -97
  44. data/spec/spec_helper.rb +0 -19
  45. data/spec/support/typhoeus_localhost_server.rb +0 -58
  46. data/spec/typhoeus/easy_spec.rb +0 -391
  47. data/spec/typhoeus/filter_spec.rb +0 -35
  48. data/spec/typhoeus/form_spec.rb +0 -117
  49. data/spec/typhoeus/hydra_mock_spec.rb +0 -300
  50. data/spec/typhoeus/hydra_spec.rb +0 -602
  51. data/spec/typhoeus/multi_spec.rb +0 -74
  52. data/spec/typhoeus/normalized_header_hash_spec.rb +0 -41
  53. data/spec/typhoeus/remote_method_spec.rb +0 -141
  54. data/spec/typhoeus/remote_proxy_object_spec.rb +0 -65
  55. data/spec/typhoeus/remote_spec.rb +0 -695
  56. data/spec/typhoeus/request_spec.rb +0 -387
  57. data/spec/typhoeus/response_spec.rb +0 -192
  58. data/spec/typhoeus/utils_spec.rb +0 -22
  59. data/typhoeus.gemspec +0 -33
@@ -1,5 +1,5 @@
1
1
  module Typhoeus
2
- class NormalizedHeaderHash < ::Hash
2
+ class Header < ::Hash
3
3
  def initialize(constructor = {})
4
4
  if constructor.is_a?(Hash)
5
5
  super
@@ -17,10 +17,6 @@ module Typhoeus
17
17
  super(convert_key(key))
18
18
  end
19
19
 
20
- [:include?, :has_key?, :member?].each do |method|
21
- alias_method method, :key?
22
- end
23
-
24
20
  def [](key)
25
21
  super(convert_key(key))
26
22
  end
@@ -39,7 +35,7 @@ module Typhoeus
39
35
  alias_method :merge!, :update
40
36
 
41
37
  def dup
42
- self.class.new(self)
38
+ self.class.new(Marshal.load(Marshal.dump(self)))
43
39
  end
44
40
 
45
41
  def merge(hash)
@@ -64,7 +64,7 @@ module Typhoeus
64
64
  @queued_requests << request
65
65
  else
66
66
  if request.method == :get
67
- if @memoize_requests && @memoized_requests.has_key?(request.url)
67
+ if @memoize_requests && @memoized_requests.key?(request.url)
68
68
  if response = @retrieved_from_cache[request.url]
69
69
  request.response = response
70
70
  request.call_handlers
@@ -120,6 +120,8 @@ module Typhoeus
120
120
  @on_complete = proc
121
121
  end
122
122
 
123
+ private
124
+
123
125
  def get_from_cache_or_queue(request)
124
126
  if @cache_getter
125
127
  val = @cache_getter.call(request)
@@ -134,7 +136,6 @@ module Typhoeus
134
136
  @multi.add(get_easy_object(request))
135
137
  end
136
138
  end
137
- private :get_from_cache_or_queue
138
139
 
139
140
  def get_easy_object(request)
140
141
  @running_requests += 1
@@ -143,27 +144,27 @@ module Typhoeus
143
144
  easy.verbose = request.verbose
144
145
  if request.username || request.password
145
146
  auth = { :username => request.username, :password => request.password }
146
- auth[:method] = Typhoeus::Easy::AUTH_TYPES["CURLAUTH_#{request.auth_method.to_s.upcase}".to_sym] if request.auth_method
147
+ auth[:method] = request.auth_method if request.auth_method
147
148
  easy.auth = auth
148
149
  end
149
150
 
150
151
  if request.proxy
151
152
  proxy = { :server => request.proxy }
152
- proxy[:type] = Typhoeus::Easy::PROXY_TYPES["CURLPROXY_#{request.proxy_type.to_s.upcase}".to_sym] if request.proxy_type
153
+ proxy[:type] = request.proxy_type if request.proxy_type
153
154
  easy.proxy = proxy if request.proxy
154
155
  end
155
156
 
156
157
  if request.proxy_username || request.proxy_password
157
158
  auth = { :username => request.proxy_username, :password => request.proxy_password }
158
- auth[:method] = Typhoeus::Easy::AUTH_TYPES["CURLAUTH_#{request.proxy_auth_method.to_s.upcase}".to_sym] if request.proxy_auth_method
159
+ auth[:method] = request.proxy_auth_method if request.proxy_auth_method
159
160
  easy.proxy_auth = auth
160
161
  end
161
162
 
162
163
  easy.url = request.url
163
164
  easy.method = request.method
164
- easy.params = request.params if request.method == :post && !request.params.nil?
165
+ easy.params = request.params if [:post, :put].include?(request.method) && !request.params.nil?
165
166
  easy.headers = request.headers if request.headers
166
- easy.request_body = request.body if request.body
167
+ easy.request_body = request.body if [:post, :put].include?(request.method) && !request.body.nil?
167
168
  easy.timeout = request.timeout if request.timeout
168
169
  easy.connect_timeout = request.connect_timeout if request.connect_timeout
169
170
  easy.interface = request.interface if request.interface
@@ -194,19 +195,16 @@ module Typhoeus
194
195
  easy.set_headers
195
196
  easy
196
197
  end
197
- private :get_easy_object
198
198
 
199
199
  def queue_next
200
200
  @running_requests -= 1
201
201
  queue(@queued_requests.shift) unless @queued_requests.empty?
202
202
  end
203
- private :queue_next
204
203
 
205
204
  def release_easy_object(easy)
206
205
  easy.reset
207
206
  @easy_pool.push easy
208
207
  end
209
- private :release_easy_object
210
208
 
211
209
  def handle_request(request, response, live_request = true)
212
210
  request.response = response
@@ -227,7 +225,6 @@ module Typhoeus
227
225
  end
228
226
  end
229
227
  end
230
- private :handle_request
231
228
 
232
229
  def response_from_easy(easy, request)
233
230
  Response.new(:code => easy.response_code,
@@ -243,8 +240,8 @@ module Typhoeus
243
240
  :primary_ip => easy.primary_ip,
244
241
  :curl_return_code => easy.curl_return_code,
245
242
  :curl_error_message => easy.curl_error_message,
243
+ :redirect_count => easy.redirect_count,
246
244
  :request => request)
247
245
  end
248
- private :response_from_easy
249
246
  end
250
247
  end
@@ -9,7 +9,7 @@ module Typhoeus
9
9
  @requests = []
10
10
  @options = options
11
11
  if @options[:headers]
12
- @options[:headers] = Typhoeus::NormalizedHeaderHash.new(@options[:headers])
12
+ @options[:headers] = Typhoeus::Header.new(@options[:headers])
13
13
  end
14
14
 
15
15
  @current_response_index = 0
@@ -93,7 +93,7 @@ module Typhoeus
93
93
  end
94
94
 
95
95
  def headers_match?(request)
96
- request_headers = NormalizedHeaderHash.new(request.headers)
96
+ request_headers = Header.new(request.headers)
97
97
 
98
98
  if empty_headers?(self.headers)
99
99
  empty_headers?(request_headers)
@@ -3,35 +3,144 @@ module Typhoeus
3
3
  attr_reader :easy_handles
4
4
 
5
5
  def initialize
6
+ Curl.init
7
+
8
+ @handle = Curl.multi_init
9
+ @active = 0
10
+ @running = 0
6
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))
7
21
  end
8
22
 
9
- def remove(easy)
10
- multi_remove_handle(easy) if @easy_handles.include?(easy)
23
+ def self.finalizer(multi)
24
+ proc { Curl.multi_cleanup(multi.handle) }
11
25
  end
12
26
 
13
27
  def add(easy)
14
28
  raise "trying to add easy handle twice" if @easy_handles.include?(easy)
15
29
  easy.set_headers() if easy.headers.empty?
16
- multi_add_handle(easy)
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
17
39
  end
18
40
 
19
- def perform()
20
- while active_handle_count > 0 do
21
- multi_perform
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
22
85
  end
23
86
  reset_easy_handles
24
87
  end
25
88
 
26
- def cleanup()
27
- multi_cleanup
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 = []
28
121
  end
29
122
 
30
123
  def reset_easy_handles
31
124
  @easy_handles.dup.each do |easy|
32
- multi_remove_handle(easy)
125
+ remove(easy)
33
126
  yield easy if block_given?
34
127
  end
35
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
36
145
  end
37
146
  end
@@ -0,0 +1,43 @@
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
@@ -7,6 +7,7 @@ module Typhoeus
7
7
  :params,
8
8
  :body,
9
9
  :headers,
10
+ :cache_key_basis,
10
11
  :connect_timeout,
11
12
  :timeout,
12
13
  :user_agent,
@@ -32,12 +33,10 @@ module Typhoeus
32
33
  :username,
33
34
  :password,
34
35
  :auth_method,
35
- :user_agent,
36
36
  :proxy_auth_method,
37
37
  :proxy_type
38
38
  ]
39
39
 
40
- attr_reader :url
41
40
  attr_accessor *ACCESSOR_OPTIONS
42
41
 
43
42
  # Initialize a new Request
@@ -69,9 +68,9 @@ module Typhoeus
69
68
  # ** +:username
70
69
  # ** +:password
71
70
  # ** +:auth_method
72
- # ** +:user_agent+ : user agent (string) - DEPRECATED
73
71
  #
74
72
  def initialize(url, options = {})
73
+ @url = url
75
74
  @method = options[:method] || :get
76
75
  @params = options[:params]
77
76
  @body = options[:body]
@@ -80,10 +79,6 @@ module Typhoeus
80
79
  @interface = options[:interface]
81
80
  @headers = options[:headers] || {}
82
81
 
83
- if options.has_key?(:user_agent)
84
- self.user_agent = options[:user_agent]
85
- end
86
-
87
82
  @cache_timeout = safe_to_i(options[:cache_timeout])
88
83
  @follow_location = options[:follow_location]
89
84
  @max_redirects = options[:max_redirects]
@@ -107,14 +102,6 @@ module Typhoeus
107
102
  @password = options[:password]
108
103
  @auth_method = options[:auth_method]
109
104
 
110
- if @method == :post
111
- @url = url
112
- else
113
- @url = @params ? "#{url}?#{params_string}" : url
114
- end
115
-
116
- @parsed_uri = URI.parse(@url)
117
-
118
105
  @on_complete = nil
119
106
  @after_complete = nil
120
107
  @handled_response = nil
@@ -123,16 +110,25 @@ module Typhoeus
123
110
  LOCALHOST_ALIASES = %w[ localhost 127.0.0.1 0.0.0.0 ]
124
111
 
125
112
  def localhost?
126
- LOCALHOST_ALIASES.include?(@parsed_uri.host)
113
+ LOCALHOST_ALIASES.include?(parsed_uri.host)
127
114
  end
128
115
 
129
116
  def user_agent
130
117
  headers['User-Agent']
131
118
  end
132
119
 
133
- def user_agent=(value)
134
- puts "DEPRECATED: Typhoeus::Request#user_agent=(value). This will be removed in a later version."
135
- headers['User-Agent'] = value
120
+ def url
121
+ if [:post, :put].include?(@method)
122
+ @url
123
+ else
124
+ url = "#{@url}?#{params_string}"
125
+ url += "&#{URI.escape(@body)}" if @body
126
+ url.gsub("?&", "?").gsub(/\?$/, '')
127
+ end
128
+ end
129
+
130
+ def parsed_uri
131
+ @parsed_uri ||= URI.parse(@url)
136
132
  end
137
133
 
138
134
  def host
@@ -146,10 +142,11 @@ module Typhoeus
146
142
  end
147
143
 
148
144
  def host_domain
149
- @parsed_uri.host
145
+ parsed_uri.host
150
146
  end
151
147
 
152
148
  def params_string
149
+ return nil unless params
153
150
  traversal = Typhoeus::Utils.traverse_params_hash(params)
154
151
  Typhoeus::Utils.traversal_to_param_string(traversal)
155
152
  end
@@ -208,7 +205,7 @@ module Typhoeus
208
205
  end
209
206
 
210
207
  def cache_key
211
- Digest::SHA1.hexdigest(url)
208
+ Digest::SHA1.hexdigest(cache_key_basis || url)
212
209
  end
213
210
 
214
211
  def self.run(url, params)
@@ -8,7 +8,7 @@ module Typhoeus
8
8
  :app_connect_time, :pretransfer_time,
9
9
  :connect_time, :name_lookup_time,
10
10
  :curl_return_code, :curl_error_message,
11
- :primary_ip
11
+ :primary_ip, :redirect_count
12
12
 
13
13
  attr_writer :headers_hash
14
14
 
@@ -32,8 +32,9 @@ module Typhoeus
32
32
  @request = params[:request]
33
33
  @effective_url = params[:effective_url]
34
34
  @primary_ip = params[:primary_ip]
35
+ @redirect_count = params[:redirect_count]
35
36
  @mock = params[:mock] || false # default
36
- @headers_hash = NormalizedHeaderHash.new(params[:headers_hash]) if params[:headers_hash]
37
+ @headers_hash = Header.new(params[:headers_hash]) if params[:headers_hash]
37
38
  end
38
39
 
39
40
  # Returns true if this is a mock response.
@@ -47,7 +48,7 @@ module Typhoeus
47
48
 
48
49
  def headers_hash
49
50
  @headers_hash ||= begin
50
- headers.split("\n").map {|o| o.strip}.inject(Typhoeus::NormalizedHeaderHash.new) do |hash, o|
51
+ headers.split("\n").map {|o| o.strip}.inject(Typhoeus::Header.new) do |hash, o|
51
52
  if o.empty? || o =~ /^HTTP\/[\d\.]+/
52
53
  hash
53
54
  else
@@ -55,7 +56,7 @@ module Typhoeus
55
56
  key = o.slice(0, i)
56
57
  value = o.slice(i + 1, o.size)
57
58
  value = value.strip unless value.nil?
58
- if hash.has_key? key
59
+ if hash.key? key
59
60
  hash[key] = [hash[key], value].flatten
60
61
  else
61
62
  hash[key] = value