typhoeus 0.1.28 → 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.
- data/.gitignore +1 -0
- data/CHANGELOG.markdown +22 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +30 -0
- data/README.textile +29 -8
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/examples/twitter.rb +1 -1
- data/ext/typhoeus/.gitignore +2 -1
- data/lib/typhoeus/easy.rb +21 -9
- data/lib/typhoeus/hydra/callbacks.rb +24 -0
- data/lib/typhoeus/hydra/connect_options.rb +45 -0
- data/lib/typhoeus/hydra/stubbing.rb +52 -0
- data/lib/typhoeus/hydra.rb +33 -55
- data/lib/typhoeus/hydra_mock.rb +131 -0
- data/lib/typhoeus/multi.rb +1 -1
- data/lib/typhoeus/normalized_header_hash.rb +58 -0
- data/lib/typhoeus/request.rb +49 -8
- data/lib/typhoeus/response.rb +42 -15
- data/lib/typhoeus/utils.rb +24 -0
- data/lib/typhoeus.rb +4 -1
- data/spec/servers/app.rb +11 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/typhoeus/easy_spec.rb +14 -1
- data/spec/typhoeus/hydra_mock_spec.rb +300 -0
- data/spec/typhoeus/hydra_spec.rb +215 -70
- data/spec/typhoeus/normalized_header_hash_spec.rb +41 -0
- data/spec/typhoeus/remote_spec.rb +1 -1
- data/spec/typhoeus/request_spec.rb +78 -0
- data/spec/typhoeus/response_spec.rb +86 -10
- data/spec/typhoeus/utils_spec.rb +22 -0
- data/typhoeus.gemspec +18 -8
- metadata +40 -24
- data/ext/typhoeus/Makefile +0 -157
data/.gitignore
CHANGED
data/CHANGELOG.markdown
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
0.2.0
|
|
2
|
+
------------
|
|
3
|
+
* Fix warning in Request#headers from attr_accessor
|
|
4
|
+
* Params with array values were not parsing into the format that rack expects
|
|
5
|
+
[GH-39, smartocci]
|
|
6
|
+
* Removed Rack as a dependency [GH-45]
|
|
7
|
+
* Added integration hooks for VCR!
|
|
8
|
+
|
|
9
|
+
0.1.31
|
|
10
|
+
------
|
|
11
|
+
* Fixed bug in setting compression encoding [morhekil]
|
|
12
|
+
* Exposed authentication control methods through Request interface [morhekil]
|
|
13
|
+
|
|
14
|
+
0.1.30
|
|
15
|
+
-----------
|
|
16
|
+
* Exposed CURLOPT_CONNECTTIMEOUT_MS to Requests [balexis]
|
|
17
|
+
|
|
18
|
+
0.1.29
|
|
19
|
+
------
|
|
20
|
+
* Fixed a memory corruption with using CURLOPT_POSTFIELDS [gravis,
|
|
21
|
+
32531d0821aecc4]
|
|
22
|
+
|
|
1
23
|
0.1.28
|
|
2
24
|
----------------
|
|
3
25
|
* Added SSL cert options for Typhoeus::Easy [GH-25, gravis]
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: http://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
diff-lcs (1.1.2)
|
|
5
|
+
gemcutter (0.6.1)
|
|
6
|
+
git (1.2.5)
|
|
7
|
+
jeweler (1.4.0)
|
|
8
|
+
gemcutter (>= 0.1.0)
|
|
9
|
+
git (>= 1.2.5)
|
|
10
|
+
rubyforge (>= 2.0.0)
|
|
11
|
+
json (1.4.6)
|
|
12
|
+
json_pure (1.4.6)
|
|
13
|
+
rack (1.2.1)
|
|
14
|
+
rspec (1.3.1)
|
|
15
|
+
rubyforge (2.0.4)
|
|
16
|
+
json_pure (>= 1.1.7)
|
|
17
|
+
sinatra (1.1.0)
|
|
18
|
+
rack (~> 1.1)
|
|
19
|
+
tilt (~> 1.1)
|
|
20
|
+
tilt (1.1)
|
|
21
|
+
|
|
22
|
+
PLATFORMS
|
|
23
|
+
ruby
|
|
24
|
+
|
|
25
|
+
DEPENDENCIES
|
|
26
|
+
diff-lcs
|
|
27
|
+
jeweler
|
|
28
|
+
json
|
|
29
|
+
rspec (= 1.3.1)
|
|
30
|
+
sinatra
|
data/README.textile
CHANGED
|
@@ -51,7 +51,7 @@ require 'json'
|
|
|
51
51
|
request = Typhoeus::Request.new("http://www.pauldix.net",
|
|
52
52
|
:body => "this is a request body",
|
|
53
53
|
:method => :post,
|
|
54
|
-
:headers => {:
|
|
54
|
+
:headers => {:Accept => "text/html"},
|
|
55
55
|
:timeout => 100, # milliseconds
|
|
56
56
|
:cache_timeout => 60, # seconds
|
|
57
57
|
:params => {:field1 => "a field"})
|
|
@@ -103,8 +103,8 @@ hydra.queue first_request
|
|
|
103
103
|
hydra.queue second_request
|
|
104
104
|
hydra.run # this is a blocking call that returns once all requests are complete
|
|
105
105
|
|
|
106
|
-
first_request.
|
|
107
|
-
second_request.
|
|
106
|
+
first_request.handled_response # the value returned from the on_complete block
|
|
107
|
+
second_request.handled_response # the value returned from the on_complete block (parsed JSON)
|
|
108
108
|
</pre>
|
|
109
109
|
|
|
110
110
|
The execution of that code goes something like this. The first and second requests are built and queued. When hydra is run the first and second requests run in parallel. When the first request completes, the third request is then built and queued up. The moment it is queued Hydra starts executing it. Meanwhile the second request would continue to run (or it could have completed before the first). Once the third request is done, hydra.run returns.
|
|
@@ -183,9 +183,8 @@ hydra.stub(:get, "http://localhost:3000/users")
|
|
|
183
183
|
*Basic Authentication*
|
|
184
184
|
|
|
185
185
|
<pre>
|
|
186
|
-
require 'base64'
|
|
187
186
|
response = Typhoeus::Request.get("http://twitter.com/statuses/followers.json",
|
|
188
|
-
:
|
|
187
|
+
:username => username, :password => password)
|
|
189
188
|
</pre>
|
|
190
189
|
|
|
191
190
|
*SSL*
|
|
@@ -227,9 +226,9 @@ e = Typhoeus::Request.get("https://example.com/action",
|
|
|
227
226
|
end
|
|
228
227
|
</pre>
|
|
229
228
|
|
|
230
|
-
h2.
|
|
229
|
+
h2. Advanced authentication
|
|
231
230
|
|
|
232
|
-
Thanks for the authentication piece and this description go to Oleg Ivanov (morhekil). The major reason to start this fork was the need to perform NTLM authentication in Ruby. Now you can do it via Typhoeus::Easy interface using the following API.
|
|
231
|
+
Thanks for the authentication piece and this description go to Oleg Ivanov (morhekil). The major reason to start this fork was the need to perform NTLM authentication in Ruby, and other libcurl's authentications method were made possible as a result. Now you can do it via Typhoeus::Easy interface using the following API.
|
|
233
232
|
|
|
234
233
|
<pre>
|
|
235
234
|
e = Typhoeus::Easy.new
|
|
@@ -251,6 +250,22 @@ The following authentication types are available:
|
|
|
251
250
|
* CURLAUTH_GSSNEGOTIATE
|
|
252
251
|
* CURLAUTH_NTLM
|
|
253
252
|
* CURLAUTH_DIGEST_IE
|
|
253
|
+
* CURLAUTH_AUTO
|
|
254
|
+
|
|
255
|
+
The last one (CURLAUTH_AUTO) is really a combination of all previous methods and is provided by Typhoeus for convenience. When you set authentication to auto, Typhoeus will retrieve the given URL first and examine it's headers to confirm what auth types are supported by the server. The it will select the strongest of available auth methods and will send the second request using the selected authentication method.
|
|
256
|
+
|
|
257
|
+
*Authentication via the quick request interface*
|
|
258
|
+
|
|
259
|
+
There's also an easy way to perform any kind of authentication via the quick request interface:
|
|
260
|
+
|
|
261
|
+
<pre>
|
|
262
|
+
e = Typhoeus::Request.get("http://example.com",
|
|
263
|
+
:username => 'username',
|
|
264
|
+
:password => 'password',
|
|
265
|
+
:method => :ntlm)
|
|
266
|
+
</pre>
|
|
267
|
+
|
|
268
|
+
All methods listed above is available in a shorter form - :basic, :digest, :gssnegotiate, :ntlm, :digest_ie, :auto.
|
|
254
269
|
|
|
255
270
|
*Query of available auth types*
|
|
256
271
|
|
|
@@ -266,7 +281,13 @@ e = Typhoeus::Easy.new
|
|
|
266
281
|
e.verbose = 1
|
|
267
282
|
</pre>
|
|
268
283
|
|
|
269
|
-
|
|
284
|
+
or using the quick request:
|
|
285
|
+
|
|
286
|
+
<pre>
|
|
287
|
+
e = Typhoeus::Request.get("http://example.com", :verbose => true)
|
|
288
|
+
</pre>
|
|
289
|
+
|
|
290
|
+
Just remember that libcurl prints it's debug output to the console (to STDERR), so you'll need to run your scripts from the console to see it.
|
|
270
291
|
|
|
271
292
|
h2. Benchmarks
|
|
272
293
|
|
data/Rakefile
CHANGED
|
@@ -11,7 +11,6 @@ begin
|
|
|
11
11
|
gemspec.email = "paul@pauldix.net"
|
|
12
12
|
gemspec.homepage = "http://github.com/pauldix/typhoeus"
|
|
13
13
|
gemspec.authors = ["Paul Dix"]
|
|
14
|
-
gemspec.add_dependency "rack"
|
|
15
14
|
gemspec.add_development_dependency "rspec"
|
|
16
15
|
gemspec.add_development_dependency "jeweler"
|
|
17
16
|
gemspec.add_development_dependency "diff-lcs"
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.2.0
|
data/examples/twitter.rb
CHANGED
data/ext/typhoeus/.gitignore
CHANGED
data/lib/typhoeus/easy.rb
CHANGED
|
@@ -12,9 +12,14 @@ module Typhoeus
|
|
|
12
12
|
:CURLOPT_UPLOAD => 46,
|
|
13
13
|
:CURLOPT_CUSTOMREQUEST => 10036,
|
|
14
14
|
:CURLOPT_POSTFIELDS => 10015,
|
|
15
|
+
:CURLOPT_COPYPOSTFIELDS => 10165,
|
|
15
16
|
:CURLOPT_POSTFIELDSIZE => 60,
|
|
16
17
|
:CURLOPT_USERAGENT => 10018,
|
|
17
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,
|
|
18
23
|
:CURLOPT_NOSIGNAL => 99,
|
|
19
24
|
:CURLOPT_HTTPHEADER => 10023,
|
|
20
25
|
:CURLOPT_FOLLOWLOCATION => 52,
|
|
@@ -25,7 +30,7 @@ module Typhoeus
|
|
|
25
30
|
:CURLOPT_PROXY => 10004,
|
|
26
31
|
:CURLOPT_VERIFYPEER => 64,
|
|
27
32
|
:CURLOPT_NOBODY => 44,
|
|
28
|
-
:CURLOPT_ENCODING => 102,
|
|
33
|
+
:CURLOPT_ENCODING => 10000 + 102,
|
|
29
34
|
:CURLOPT_SSLCERT => 10025,
|
|
30
35
|
:CURLOPT_SSLCERTTYPE => 10086,
|
|
31
36
|
:CURLOPT_SSLKEY => 10087,
|
|
@@ -45,15 +50,16 @@ module Typhoeus
|
|
|
45
50
|
:CURLAUTH_DIGEST => 2,
|
|
46
51
|
:CURLAUTH_GSSNEGOTIATE => 4,
|
|
47
52
|
:CURLAUTH_NTLM => 8,
|
|
48
|
-
:CURLAUTH_DIGEST_IE => 16
|
|
53
|
+
:CURLAUTH_DIGEST_IE => 16,
|
|
54
|
+
:CURLAUTH_AUTO => 16 | 8 | 4 | 2 | 1
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
def initialize
|
|
52
58
|
@method = :get
|
|
53
|
-
@post_dat_set = nil
|
|
54
59
|
@headers = {}
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
# Enable encoding/compression support
|
|
62
|
+
set_option(OPTION_VALUES[:CURLOPT_ENCODING], '')
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def headers=(hash)
|
|
@@ -100,6 +106,12 @@ module Typhoeus
|
|
|
100
106
|
def max_redirects=(redirects)
|
|
101
107
|
set_option(OPTION_VALUES[:CURLOPT_MAXREDIRS], redirects)
|
|
102
108
|
end
|
|
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
|
|
103
115
|
|
|
104
116
|
def timeout=(milliseconds)
|
|
105
117
|
@timeout = milliseconds
|
|
@@ -158,8 +170,8 @@ module Typhoeus
|
|
|
158
170
|
|
|
159
171
|
def post_data=(data)
|
|
160
172
|
@post_data_set = true
|
|
161
|
-
set_option(OPTION_VALUES[:CURLOPT_POSTFIELDS], data)
|
|
162
173
|
set_option(OPTION_VALUES[:CURLOPT_POSTFIELDSIZE], data.length)
|
|
174
|
+
set_option(OPTION_VALUES[:CURLOPT_COPYPOSTFIELDS], data)
|
|
163
175
|
end
|
|
164
176
|
|
|
165
177
|
def params=(params)
|
|
@@ -167,12 +179,12 @@ module Typhoeus
|
|
|
167
179
|
params_string = params.keys.collect do |k|
|
|
168
180
|
value = params[k]
|
|
169
181
|
if value.is_a? Hash
|
|
170
|
-
value.keys.collect {|sk|
|
|
182
|
+
value.keys.collect {|sk| Typhoeus::Utils.escape("#{k}[#{sk}]") + "=" + Typhoeus::Utils.escape(value[sk].to_s)}
|
|
171
183
|
elsif value.is_a? Array
|
|
172
|
-
key =
|
|
173
|
-
value.collect { |v| "#{key}=#{
|
|
184
|
+
key = Typhoeus::Utils.escape(k.to_s)
|
|
185
|
+
value.collect { |v| "#{key}=#{Typhoeus::Utils.escape(v.to_s)}" }.join('&')
|
|
174
186
|
else
|
|
175
|
-
"#{
|
|
187
|
+
"#{Typhoeus::Utils.escape(k.to_s)}=#{Typhoeus::Utils.escape(params[k].to_s)}"
|
|
176
188
|
end
|
|
177
189
|
end.flatten.join("&")
|
|
178
190
|
|
|
@@ -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
|
data/lib/typhoeus/hydra.rb
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
require 'typhoeus/hydra/callbacks'
|
|
2
|
+
require 'typhoeus/hydra/connect_options'
|
|
3
|
+
require 'typhoeus/hydra/stubbing'
|
|
4
|
+
|
|
1
5
|
module Typhoeus
|
|
2
6
|
class Hydra
|
|
7
|
+
include ConnectOptions
|
|
8
|
+
include Stubbing
|
|
9
|
+
extend Callbacks
|
|
10
|
+
|
|
3
11
|
def initialize(options = {})
|
|
4
12
|
@memoize_requests = true
|
|
5
13
|
@multi = Multi.new
|
|
@@ -7,12 +15,13 @@ module Typhoeus
|
|
|
7
15
|
initial_pool_size = options[:initial_pool_size] || 10
|
|
8
16
|
@max_concurrency = options[:max_concurrency] || 200
|
|
9
17
|
initial_pool_size.times { @easy_pool << Easy.new }
|
|
10
|
-
@stubs = []
|
|
11
18
|
@memoized_requests = {}
|
|
12
19
|
@retrieved_from_cache = {}
|
|
13
20
|
@queued_requests = []
|
|
14
21
|
@running_requests = 0
|
|
15
|
-
|
|
22
|
+
|
|
23
|
+
self.stubs = []
|
|
24
|
+
@active_stubs = []
|
|
16
25
|
end
|
|
17
26
|
|
|
18
27
|
def self.hydra
|
|
@@ -28,10 +37,6 @@ module Typhoeus
|
|
|
28
37
|
@cache_getter = nil
|
|
29
38
|
end
|
|
30
39
|
|
|
31
|
-
def clear_stubs
|
|
32
|
-
@stubs = []
|
|
33
|
-
end
|
|
34
|
-
|
|
35
40
|
def fire_and_forget
|
|
36
41
|
@queued_requests.each {|r| queue(r, false)}
|
|
37
42
|
@multi.fire_and_forget
|
|
@@ -40,6 +45,10 @@ module Typhoeus
|
|
|
40
45
|
def queue(request, obey_concurrency_limit = true)
|
|
41
46
|
return if assign_to_stub(request)
|
|
42
47
|
|
|
48
|
+
# At this point, we are running over live HTTP. Make sure we haven't
|
|
49
|
+
# disabled live requests.
|
|
50
|
+
check_allow_net_connect!(request)
|
|
51
|
+
|
|
43
52
|
if @running_requests >= @max_concurrency && obey_concurrency_limit
|
|
44
53
|
@queued_requests << request
|
|
45
54
|
else
|
|
@@ -62,14 +71,14 @@ module Typhoeus
|
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
def run
|
|
65
|
-
while
|
|
66
|
-
@
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end
|
|
74
|
+
while !@active_stubs.empty?
|
|
75
|
+
m = @active_stubs.first
|
|
76
|
+
while request = m.requests.shift
|
|
77
|
+
response = m.response
|
|
78
|
+
response.request = request
|
|
79
|
+
handle_request(request, response)
|
|
72
80
|
end
|
|
81
|
+
@active_stubs.delete(m)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
84
|
@multi.perform
|
|
@@ -97,22 +106,6 @@ module Typhoeus
|
|
|
97
106
|
@on_complete = proc
|
|
98
107
|
end
|
|
99
108
|
|
|
100
|
-
def stub(method, url)
|
|
101
|
-
@stubs << HydraMock.new(url, method)
|
|
102
|
-
@stubs.last
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def assign_to_stub(request)
|
|
106
|
-
m = @stubs.detect {|stub| stub.matches? request}
|
|
107
|
-
if m
|
|
108
|
-
m.add_request(request)
|
|
109
|
-
@stubbed_request_count += 1
|
|
110
|
-
else
|
|
111
|
-
nil
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
private :assign_to_stub
|
|
115
|
-
|
|
116
109
|
def get_from_cache_or_queue(request)
|
|
117
110
|
if @cache_getter
|
|
118
111
|
val = @cache_getter.call(request)
|
|
@@ -132,12 +125,19 @@ module Typhoeus
|
|
|
132
125
|
@running_requests += 1
|
|
133
126
|
|
|
134
127
|
easy = @easy_pool.pop || Easy.new
|
|
128
|
+
easy.verbose = request.verbose
|
|
129
|
+
if request.username || request.password
|
|
130
|
+
auth = { :username => request.username, :password => request.password }
|
|
131
|
+
auth[:method] = Typhoeus::Easy::AUTH_TYPES["CURLAUTH_#{request.auth_method.to_s.upcase}".to_sym] if request.auth_method
|
|
132
|
+
easy.auth = auth
|
|
133
|
+
end
|
|
135
134
|
easy.url = request.url
|
|
136
135
|
easy.method = request.method
|
|
137
136
|
easy.params = request.params if request.method == :post && !request.params.nil?
|
|
138
137
|
easy.headers = request.headers if request.headers
|
|
139
138
|
easy.request_body = request.body if request.body
|
|
140
139
|
easy.timeout = request.timeout if request.timeout
|
|
140
|
+
easy.connect_timeout = request.connect_timeout if request.connect_timeout
|
|
141
141
|
easy.follow_location = request.follow_location if request.follow_location
|
|
142
142
|
easy.max_redirects = request.max_redirects if request.max_redirects
|
|
143
143
|
easy.proxy = request.proxy if request.proxy
|
|
@@ -149,6 +149,7 @@ module Typhoeus
|
|
|
149
149
|
easy.ssl_key_password = request.ssl_key_password
|
|
150
150
|
easy.ssl_cacert = request.ssl_cacert
|
|
151
151
|
easy.ssl_capath = request.ssl_capath
|
|
152
|
+
easy.verbose = request.verbose
|
|
152
153
|
|
|
153
154
|
easy.on_success do |easy|
|
|
154
155
|
queue_next
|
|
@@ -180,6 +181,9 @@ module Typhoeus
|
|
|
180
181
|
def handle_request(request, response, live_request = true)
|
|
181
182
|
request.response = response
|
|
182
183
|
|
|
184
|
+
self.class.run_global_hooks_for(:after_request_before_on_complete,
|
|
185
|
+
request)
|
|
186
|
+
|
|
183
187
|
if live_request && request.cache_timeout && @cache_setter
|
|
184
188
|
@cache_setter.call(request)
|
|
185
189
|
end
|
|
@@ -205,30 +209,4 @@ module Typhoeus
|
|
|
205
209
|
end
|
|
206
210
|
private :response_from_easy
|
|
207
211
|
end
|
|
208
|
-
|
|
209
|
-
class HydraMock
|
|
210
|
-
attr_reader :url, :method, :response, :requests
|
|
211
|
-
|
|
212
|
-
def initialize(url, method)
|
|
213
|
-
@url = url
|
|
214
|
-
@method = method
|
|
215
|
-
@requests = []
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def add_request(request)
|
|
219
|
-
@requests << request
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
def and_return(val)
|
|
223
|
-
@response = val
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def matches?(request)
|
|
227
|
-
if url.kind_of?(String)
|
|
228
|
-
request.method == method && request.url == url
|
|
229
|
-
else
|
|
230
|
-
request.method == method && url =~ request.url
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
212
|
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module Typhoeus
|
|
2
|
+
class HydraMock
|
|
3
|
+
attr_reader :url, :method, :requests, :uri
|
|
4
|
+
|
|
5
|
+
def initialize(url, method, options = {})
|
|
6
|
+
@url = url
|
|
7
|
+
@uri = URI.parse(url) if url.kind_of?(String)
|
|
8
|
+
@method = method
|
|
9
|
+
@requests = []
|
|
10
|
+
@options = options
|
|
11
|
+
if @options[:headers]
|
|
12
|
+
@options[:headers] = Typhoeus::NormalizedHeaderHash.new(@options[:headers])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
@current_response_index = 0
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def body
|
|
19
|
+
@options[:body]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def body?
|
|
23
|
+
@options.has_key?(:body)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def headers
|
|
27
|
+
@options[:headers]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def headers?
|
|
31
|
+
@options.has_key?(:headers)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def add_request(request)
|
|
35
|
+
@requests << request
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def and_return(val)
|
|
39
|
+
if val.respond_to?(:each)
|
|
40
|
+
@responses = val
|
|
41
|
+
else
|
|
42
|
+
@responses = [val]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# make sure to mark them as a mock.
|
|
46
|
+
@responses.each { |r| r.mock = true }
|
|
47
|
+
|
|
48
|
+
val
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def response
|
|
52
|
+
if @current_response_index == (@responses.length - 1)
|
|
53
|
+
@responses.last
|
|
54
|
+
else
|
|
55
|
+
value = @responses[@current_response_index]
|
|
56
|
+
@current_response_index += 1
|
|
57
|
+
value
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def matches?(request)
|
|
62
|
+
if !method_matches?(request) or !url_matches?(request)
|
|
63
|
+
return false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
if body?
|
|
67
|
+
return false unless body_matches?(request)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if headers?
|
|
71
|
+
return false unless headers_match?(request)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
def method_matches?(request)
|
|
79
|
+
self.method == :any or self.method == request.method
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def url_matches?(request)
|
|
83
|
+
if url.kind_of?(String)
|
|
84
|
+
request_uri = URI.parse(request.url)
|
|
85
|
+
request_uri == self.uri
|
|
86
|
+
else
|
|
87
|
+
self.url =~ request.url
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def body_matches?(request)
|
|
92
|
+
!request.body.nil? && !request.body.empty? && request.body == self.body
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def headers_match?(request)
|
|
96
|
+
request_headers = NormalizedHeaderHash.new(request.headers)
|
|
97
|
+
|
|
98
|
+
if empty_headers?(self.headers)
|
|
99
|
+
empty_headers?(request_headers)
|
|
100
|
+
else
|
|
101
|
+
return false if empty_headers?(request_headers)
|
|
102
|
+
|
|
103
|
+
headers.each do |key, value|
|
|
104
|
+
return false unless header_value_matches?(value, request_headers[key])
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def header_value_matches?(mock_value, request_value)
|
|
112
|
+
mock_arr = mock_value.is_a?(Array) ? mock_value : [mock_value]
|
|
113
|
+
request_arr = request_value.is_a?(Array) ? request_value : [request_value]
|
|
114
|
+
|
|
115
|
+
return false unless mock_arr.size == request_arr.size
|
|
116
|
+
mock_arr.all? do |value|
|
|
117
|
+
request_arr.any? { |a| value === a }
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def empty_headers?(headers)
|
|
122
|
+
# We consider the default User-Agent header to be empty since
|
|
123
|
+
# Typhoeus always adds that.
|
|
124
|
+
headers.nil? || headers.empty? || default_typhoeus_headers?(headers)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def default_typhoeus_headers?(headers)
|
|
128
|
+
headers.size == 1 && headers['User-Agent'] == Typhoeus::USER_AGENT
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|