hhry-typhoeus 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.
- data/CHANGELOG.md +93 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +455 -0
- data/Rakefile +23 -0
- data/lib/typhoeus.rb +57 -0
- data/lib/typhoeus/curl.rb +453 -0
- data/lib/typhoeus/easy.rb +115 -0
- data/lib/typhoeus/easy/auth.rb +14 -0
- data/lib/typhoeus/easy/callbacks.rb +33 -0
- data/lib/typhoeus/easy/ffi_helper.rb +61 -0
- data/lib/typhoeus/easy/infos.rb +86 -0
- data/lib/typhoeus/easy/options.rb +115 -0
- data/lib/typhoeus/easy/proxy.rb +20 -0
- data/lib/typhoeus/easy/ssl.rb +82 -0
- data/lib/typhoeus/filter.rb +28 -0
- data/lib/typhoeus/form.rb +61 -0
- data/lib/typhoeus/header.rb +54 -0
- data/lib/typhoeus/hydra.rb +246 -0
- data/lib/typhoeus/hydra/callbacks.rb +24 -0
- data/lib/typhoeus/hydra/connect_options.rb +61 -0
- data/lib/typhoeus/hydra/stubbing.rb +68 -0
- data/lib/typhoeus/hydra_mock.rb +131 -0
- data/lib/typhoeus/multi.rb +146 -0
- data/lib/typhoeus/param_processor.rb +43 -0
- data/lib/typhoeus/remote.rb +310 -0
- data/lib/typhoeus/remote_method.rb +108 -0
- data/lib/typhoeus/remote_proxy_object.rb +50 -0
- data/lib/typhoeus/request.rb +278 -0
- data/lib/typhoeus/response.rb +122 -0
- data/lib/typhoeus/utils.rb +58 -0
- data/lib/typhoeus/version.rb +3 -0
- metadata +178 -0
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Typhoeus
|
4
|
+
class Request
|
5
|
+
ACCESSOR_OPTIONS = [
|
6
|
+
:method,
|
7
|
+
:params,
|
8
|
+
:body,
|
9
|
+
:headers,
|
10
|
+
:cache_key_basis,
|
11
|
+
:connect_timeout,
|
12
|
+
:timeout,
|
13
|
+
:user_agent,
|
14
|
+
:response,
|
15
|
+
:cache_timeout,
|
16
|
+
:follow_location,
|
17
|
+
:max_redirects,
|
18
|
+
:proxy,
|
19
|
+
:proxy_username,
|
20
|
+
:proxy_password,
|
21
|
+
:disable_ssl_peer_verification,
|
22
|
+
:disable_ssl_host_verification,
|
23
|
+
:interface,
|
24
|
+
:ssl_cert,
|
25
|
+
:ssl_cert_type,
|
26
|
+
:ssl_key,
|
27
|
+
:ssl_key_type,
|
28
|
+
:ssl_key_password,
|
29
|
+
:ssl_cacert,
|
30
|
+
:ssl_capath,
|
31
|
+
:ssl_version,
|
32
|
+
:verbose,
|
33
|
+
:username,
|
34
|
+
:password,
|
35
|
+
:auth_method,
|
36
|
+
:user_agent,
|
37
|
+
:proxy_auth_method,
|
38
|
+
:proxy_type
|
39
|
+
]
|
40
|
+
|
41
|
+
attr_accessor *ACCESSOR_OPTIONS
|
42
|
+
|
43
|
+
# Initialize a new Request
|
44
|
+
#
|
45
|
+
# Options:
|
46
|
+
# * +url+ : Endpoint (URL) of the request
|
47
|
+
# * +options+ : A hash containing options among :
|
48
|
+
# ** +:method+ : :get (default) / :post / :put
|
49
|
+
# ** +:params+ : params as a Hash
|
50
|
+
# ** +:body+
|
51
|
+
# ** +:timeout+ : timeout (ms)
|
52
|
+
# ** +:interface+ : interface or ip address (string)
|
53
|
+
# ** +:connect_timeout+ : connect timeout (ms)
|
54
|
+
# ** +:headers+ : headers as Hash
|
55
|
+
# ** +:cache_timeout+ : cache timeout (ms)
|
56
|
+
# ** +:follow_location
|
57
|
+
# ** +:max_redirects
|
58
|
+
# ** +:proxy
|
59
|
+
# ** +:disable_ssl_peer_verification
|
60
|
+
# ** +:disable_ssl_host_verification
|
61
|
+
# ** +:ssl_cert
|
62
|
+
# ** +:ssl_cert_type
|
63
|
+
# ** +:ssl_key
|
64
|
+
# ** +:ssl_key_type
|
65
|
+
# ** +:ssl_key_password
|
66
|
+
# ** +:ssl_cacert
|
67
|
+
# ** +:ssl_capath
|
68
|
+
# ** +:verbose
|
69
|
+
# ** +:username
|
70
|
+
# ** +:password
|
71
|
+
# ** +:auth_method
|
72
|
+
#
|
73
|
+
def initialize(url, options = {})
|
74
|
+
@url = url
|
75
|
+
@method = options[:method] || :get
|
76
|
+
@params = options[:params]
|
77
|
+
@body = options[:body]
|
78
|
+
@timeout = safe_to_i(options[:timeout])
|
79
|
+
@connect_timeout = safe_to_i(options[:connect_timeout])
|
80
|
+
@interface = options[:interface]
|
81
|
+
@headers = options[:headers] || {}
|
82
|
+
|
83
|
+
if options.key?(:user_agent)
|
84
|
+
@headers['User-Agent'] = options[:user_agent]
|
85
|
+
end
|
86
|
+
|
87
|
+
@cache_timeout = safe_to_i(options[:cache_timeout])
|
88
|
+
@follow_location = options[:follow_location]
|
89
|
+
@max_redirects = options[:max_redirects]
|
90
|
+
@proxy = options[:proxy]
|
91
|
+
@proxy_type = options[:proxy_type]
|
92
|
+
@proxy_username = options[:proxy_username]
|
93
|
+
@proxy_password = options[:proxy_password]
|
94
|
+
@proxy_auth_method = options[:proxy_auth_method]
|
95
|
+
@disable_ssl_peer_verification = options[:disable_ssl_peer_verification]
|
96
|
+
@disable_ssl_host_verification = options[:disable_ssl_host_verification]
|
97
|
+
@ssl_cert = options[:ssl_cert]
|
98
|
+
@ssl_cert_type = options[:ssl_cert_type]
|
99
|
+
@ssl_key = options[:ssl_key]
|
100
|
+
@ssl_key_type = options[:ssl_key_type]
|
101
|
+
@ssl_key_password = options[:ssl_key_password]
|
102
|
+
@ssl_cacert = options[:ssl_cacert]
|
103
|
+
@ssl_capath = options[:ssl_capath]
|
104
|
+
@ssl_version = options[:ssl_version]
|
105
|
+
@verbose = options[:verbose]
|
106
|
+
@username = options[:username]
|
107
|
+
@password = options[:password]
|
108
|
+
@auth_method = options[:auth_method]
|
109
|
+
|
110
|
+
if @method == :post
|
111
|
+
@params = @params ? Typhoeus::Utils.escape_params(@params) : nil
|
112
|
+
@url = url
|
113
|
+
end
|
114
|
+
|
115
|
+
@parsed_uri = URI.parse(@url)
|
116
|
+
|
117
|
+
@on_complete = nil
|
118
|
+
@after_complete = nil
|
119
|
+
@handled_response = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
LOCALHOST_ALIASES = %w[ localhost 127.0.0.1 0.0.0.0 ]
|
123
|
+
|
124
|
+
def localhost?
|
125
|
+
LOCALHOST_ALIASES.include?(parsed_uri.host)
|
126
|
+
end
|
127
|
+
|
128
|
+
def user_agent
|
129
|
+
headers['User-Agent']
|
130
|
+
end
|
131
|
+
|
132
|
+
def url
|
133
|
+
if @method == :post
|
134
|
+
@url
|
135
|
+
else
|
136
|
+
url = "#{@url}?#{params_string}"
|
137
|
+
url += "&#{URI.escape(@body)}" if @body
|
138
|
+
url.gsub("?&", "?").gsub(/\?$/, '')
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def parsed_uri
|
143
|
+
@parsed_uri ||= URI.parse(@url)
|
144
|
+
end
|
145
|
+
|
146
|
+
def host
|
147
|
+
slash_location = @url.index('/', 8)
|
148
|
+
if slash_location
|
149
|
+
@url.slice(0, slash_location)
|
150
|
+
else
|
151
|
+
query_string_location = @url.index('?')
|
152
|
+
return query_string_location ? @url.slice(0, query_string_location) : @url
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def host_domain
|
157
|
+
parsed_uri.host
|
158
|
+
end
|
159
|
+
|
160
|
+
def params_string
|
161
|
+
return nil unless params
|
162
|
+
traversal = Typhoeus::Utils.traverse_params_hash(params)
|
163
|
+
Typhoeus::Utils.traversal_to_param_string(traversal)
|
164
|
+
end
|
165
|
+
|
166
|
+
def on_complete(&block)
|
167
|
+
@on_complete = block
|
168
|
+
end
|
169
|
+
|
170
|
+
def on_complete=(proc)
|
171
|
+
@on_complete = proc
|
172
|
+
end
|
173
|
+
|
174
|
+
def after_complete(&block)
|
175
|
+
@after_complete = block
|
176
|
+
end
|
177
|
+
|
178
|
+
def after_complete=(proc)
|
179
|
+
@after_complete = proc
|
180
|
+
end
|
181
|
+
|
182
|
+
def call_handlers
|
183
|
+
if @on_complete
|
184
|
+
@handled_response = @on_complete.call(response)
|
185
|
+
call_after_complete
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def call_after_complete
|
190
|
+
@after_complete.call(@handled_response) if @after_complete
|
191
|
+
end
|
192
|
+
|
193
|
+
def handled_response=(val)
|
194
|
+
@handled_response = val
|
195
|
+
end
|
196
|
+
|
197
|
+
def handled_response
|
198
|
+
@handled_response || response
|
199
|
+
end
|
200
|
+
|
201
|
+
def inspect
|
202
|
+
result = ":method => #{self.method.inspect},\n" <<
|
203
|
+
"\t:url => #{URI.parse(self.url).to_s}"
|
204
|
+
if self.body and !self.body.empty?
|
205
|
+
result << ",\n\t:body => #{self.body.inspect}"
|
206
|
+
end
|
207
|
+
|
208
|
+
if self.params and !self.params.empty?
|
209
|
+
result << ",\n\t:params => #{self.params.inspect}"
|
210
|
+
end
|
211
|
+
|
212
|
+
if self.headers and !self.headers.empty?
|
213
|
+
result << ",\n\t:headers => #{self.headers.inspect}"
|
214
|
+
end
|
215
|
+
|
216
|
+
result
|
217
|
+
end
|
218
|
+
|
219
|
+
def cache_key
|
220
|
+
Digest::SHA1.hexdigest(cache_key_basis || url)
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.run(url, params)
|
224
|
+
r = new(url, params)
|
225
|
+
Typhoeus::Hydra.hydra.queue r
|
226
|
+
Typhoeus::Hydra.hydra.run
|
227
|
+
r.response
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.get(url, params = {})
|
231
|
+
run(url, params.merge(:method => :get))
|
232
|
+
end
|
233
|
+
|
234
|
+
def self.post(url, params = {})
|
235
|
+
run(url, params.merge(:method => :post))
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.put(url, params = {})
|
239
|
+
run(url, params.merge(:method => :put))
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.delete(url, params = {})
|
243
|
+
run(url, params.merge(:method => :delete))
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.head(url, params = {})
|
247
|
+
run(url, params.merge(:method => :head))
|
248
|
+
end
|
249
|
+
|
250
|
+
protected
|
251
|
+
|
252
|
+
# Return the important data needed to serialize this Request, except the
|
253
|
+
# `on_complete` and `after_complete` handlers, since they cannot be
|
254
|
+
# marshalled.
|
255
|
+
def marshal_dump
|
256
|
+
(instance_variables - ['@on_complete', '@after_complete', :@on_complete, :@after_complete]).map do |name|
|
257
|
+
[name, instance_variable_get(name)]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def marshal_load(attributes)
|
262
|
+
attributes.each { |name, value| instance_variable_set(name, value) }
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.options
|
266
|
+
ACCESSOR_OPTIONS
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
|
271
|
+
def safe_to_i(value)
|
272
|
+
return value if value.is_a?(Fixnum)
|
273
|
+
return nil if value.nil?
|
274
|
+
return nil if value.respond_to?(:empty?) && value.empty?
|
275
|
+
value.to_i
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class Response
|
3
|
+
attr_accessor :request, :mock
|
4
|
+
attr_reader :code, :headers, :body, :time,
|
5
|
+
:requested_url, :requested_remote_method,
|
6
|
+
:requested_http_method, :start_time,
|
7
|
+
:effective_url, :start_transfer_time,
|
8
|
+
:app_connect_time, :pretransfer_time,
|
9
|
+
:connect_time, :name_lookup_time,
|
10
|
+
:curl_return_code, :curl_error_message,
|
11
|
+
:primary_ip
|
12
|
+
|
13
|
+
attr_writer :headers_hash
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
@code = params[:code]
|
17
|
+
@curl_return_code = params[:curl_return_code]
|
18
|
+
@curl_error_message = params[:curl_error_message]
|
19
|
+
@status_message = params[:status_message]
|
20
|
+
@http_version = params[:http_version]
|
21
|
+
@headers = params[:headers]
|
22
|
+
@body = params[:body]
|
23
|
+
@time = params[:time]
|
24
|
+
@requested_url = params[:requested_url]
|
25
|
+
@requested_http_method = params[:requested_http_method]
|
26
|
+
@start_time = params[:start_time]
|
27
|
+
@start_transfer_time = params[:start_transfer_time]
|
28
|
+
@app_connect_time = params[:app_connect_time]
|
29
|
+
@pretransfer_time = params[:pretransfer_time]
|
30
|
+
@connect_time = params[:connect_time]
|
31
|
+
@name_lookup_time = params[:name_lookup_time]
|
32
|
+
@request = params[:request]
|
33
|
+
@effective_url = params[:effective_url]
|
34
|
+
@primary_ip = params[:primary_ip]
|
35
|
+
@mock = params[:mock] || false # default
|
36
|
+
@headers_hash = Header.new(params[:headers_hash]) if params[:headers_hash]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if this is a mock response.
|
40
|
+
def mock?
|
41
|
+
@mock
|
42
|
+
end
|
43
|
+
|
44
|
+
def headers
|
45
|
+
@headers ||= @headers_hash ? construct_header_string : ''
|
46
|
+
end
|
47
|
+
|
48
|
+
def headers_hash
|
49
|
+
@headers_hash ||= begin
|
50
|
+
headers.split("\n").map {|o| o.strip}.inject(Typhoeus::Header.new) do |hash, o|
|
51
|
+
if o.empty? || o =~ /^HTTP\/[\d\.]+/
|
52
|
+
hash
|
53
|
+
else
|
54
|
+
i = o.index(":") || o.size
|
55
|
+
key = o.slice(0, i)
|
56
|
+
value = o.slice(i + 1, o.size)
|
57
|
+
value = value.strip unless value.nil?
|
58
|
+
if hash.key? key
|
59
|
+
hash[key] = [hash[key], value].flatten
|
60
|
+
else
|
61
|
+
hash[key] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def status_message
|
71
|
+
return @status_message if @status_message != nil
|
72
|
+
|
73
|
+
# HTTP servers can choose not to include the explanation to HTTP codes. The RFC
|
74
|
+
# states this (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4):
|
75
|
+
# Except when responding to a HEAD request, the server SHOULD include an entity containing
|
76
|
+
# an explanation of the error situation [...]
|
77
|
+
# This means 'HTTP/1.1 404' is as valid as 'HTTP/1.1 404 Not Found' and we have to handle it.
|
78
|
+
|
79
|
+
# Regexp doc: http://rubular.com/r/eAr1oVYsVa
|
80
|
+
if first_header_line != nil and first_header_line[/\d{3} (.*)$/, 1] != nil
|
81
|
+
@status_message = first_header_line[/\d{3} (.*)$/, 1].chomp
|
82
|
+
else
|
83
|
+
@status_message = nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def http_version
|
88
|
+
@http_version ||= first_header_line ? first_header_line[/HTTP\/(\S+)/, 1] : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def success?
|
92
|
+
@code >= 200 && @code < 300
|
93
|
+
end
|
94
|
+
|
95
|
+
def modified?
|
96
|
+
@code != 304
|
97
|
+
end
|
98
|
+
|
99
|
+
def timed_out?
|
100
|
+
curl_return_code == 28
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def first_header_line
|
106
|
+
@first_header_line ||= @headers.to_s.split("\n").first
|
107
|
+
end
|
108
|
+
|
109
|
+
def construct_header_string
|
110
|
+
lines = ["HTTP/#{http_version} #{code} #{status_message}"]
|
111
|
+
|
112
|
+
@headers_hash.each do |key, values|
|
113
|
+
[values].flatten.each do |value|
|
114
|
+
lines << "#{key}: #{value}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
lines << '' << ''
|
119
|
+
lines.join("\r\n")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
module Utils
|
3
|
+
# Taken from Rack::Utils, 1.2.1 to remove Rack dependency.
|
4
|
+
def escape(s)
|
5
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/u) {
|
6
|
+
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
7
|
+
}.tr(' ', '+')
|
8
|
+
end
|
9
|
+
module_function :escape
|
10
|
+
|
11
|
+
def escape_params(params)
|
12
|
+
traverse_params_hash(params)[:params].inject({}) do |memo, (k, v)|
|
13
|
+
memo[escape(k)] = escape(v)
|
14
|
+
memo
|
15
|
+
end
|
16
|
+
end
|
17
|
+
module_function :escape_params
|
18
|
+
|
19
|
+
# Params are NOT escaped.
|
20
|
+
def traverse_params_hash(hash, result = nil, current_key = nil)
|
21
|
+
result = ParamProcessor.traverse_params_hash hash, result, current_key
|
22
|
+
end
|
23
|
+
module_function :traverse_params_hash
|
24
|
+
|
25
|
+
def traversal_to_param_string(traversal, escape = true)
|
26
|
+
traversal[:params].collect { |param|
|
27
|
+
escape ? "#{Typhoeus::Utils.escape(param[0])}=#{Typhoeus::Utils.escape(param[1])}" : "#{param[0]}=#{param[1]}"
|
28
|
+
}.join('&')
|
29
|
+
end
|
30
|
+
module_function :traversal_to_param_string
|
31
|
+
|
32
|
+
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
33
|
+
# String#bytesize under 1.9.
|
34
|
+
if ''.respond_to?(:bytesize)
|
35
|
+
def bytesize(string)
|
36
|
+
string.bytesize
|
37
|
+
end
|
38
|
+
else
|
39
|
+
def bytesize(string)
|
40
|
+
string.size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
module_function :bytesize
|
44
|
+
|
45
|
+
# Return a byteslice from a string; uses String#[] under Ruby 1.8 and
|
46
|
+
# String#byteslice under 1.9.
|
47
|
+
if ''.respond_to?(:byteslice)
|
48
|
+
def byteslice(string, *args)
|
49
|
+
string.byteslice(*args)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
def byteslice(string, *args)
|
53
|
+
string[*args]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
module_function :byteslice
|
57
|
+
end
|
58
|
+
end
|