typhoeus 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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