typhoeus 0.1.31 → 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 +8 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +30 -0
- data/README.textile +1 -1
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/ext/typhoeus/.gitignore +2 -1
- data/lib/typhoeus.rb +4 -1
- data/lib/typhoeus/easy.rb +4 -4
- data/lib/typhoeus/hydra.rb +25 -55
- 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_mock.rb +131 -0
- data/lib/typhoeus/normalized_header_hash.rb +58 -0
- data/lib/typhoeus/request.rb +39 -8
- data/lib/typhoeus/response.rb +42 -15
- data/lib/typhoeus/utils.rb +24 -0
- data/spec/spec_helper.rb +1 -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 +54 -2
- 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,11 @@
|
|
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
|
+
|
1
9
|
0.1.31
|
2
10
|
------
|
3
11
|
* Fixed bug in setting compression encoding [morhekil]
|
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"})
|
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/ext/typhoeus/.gitignore
CHANGED
data/lib/typhoeus.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
$LOAD_PATH.unshift(File.dirname(__FILE__)) unless $LOAD_PATH.include?(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + "/../ext")
|
2
3
|
|
3
|
-
require 'rack/utils'
|
4
4
|
require 'digest/sha2'
|
5
|
+
require 'typhoeus/utils'
|
6
|
+
require 'typhoeus/normalized_header_hash'
|
5
7
|
require 'typhoeus/easy'
|
6
8
|
require 'typhoeus/multi'
|
7
9
|
require 'typhoeus/native'
|
@@ -12,6 +14,7 @@ require 'typhoeus/remote_proxy_object'
|
|
12
14
|
require 'typhoeus/response'
|
13
15
|
require 'typhoeus/request'
|
14
16
|
require 'typhoeus/hydra'
|
17
|
+
require 'typhoeus/hydra_mock'
|
15
18
|
|
16
19
|
module Typhoeus
|
17
20
|
VERSION = File.read(File.dirname(__FILE__) + "/../VERSION").chomp
|
data/lib/typhoeus/easy.rb
CHANGED
@@ -179,12 +179,12 @@ module Typhoeus
|
|
179
179
|
params_string = params.keys.collect do |k|
|
180
180
|
value = params[k]
|
181
181
|
if value.is_a? Hash
|
182
|
-
value.keys.collect {|sk|
|
182
|
+
value.keys.collect {|sk| Typhoeus::Utils.escape("#{k}[#{sk}]") + "=" + Typhoeus::Utils.escape(value[sk].to_s)}
|
183
183
|
elsif value.is_a? Array
|
184
|
-
key =
|
185
|
-
value.collect { |v| "#{key}=#{
|
184
|
+
key = Typhoeus::Utils.escape(k.to_s)
|
185
|
+
value.collect { |v| "#{key}=#{Typhoeus::Utils.escape(v.to_s)}" }.join('&')
|
186
186
|
else
|
187
|
-
"#{
|
187
|
+
"#{Typhoeus::Utils.escape(k.to_s)}=#{Typhoeus::Utils.escape(params[k].to_s)}"
|
188
188
|
end
|
189
189
|
end.flatten.join("&")
|
190
190
|
|
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)
|
@@ -188,6 +181,9 @@ module Typhoeus
|
|
188
181
|
def handle_request(request, response, live_request = true)
|
189
182
|
request.response = response
|
190
183
|
|
184
|
+
self.class.run_global_hooks_for(:after_request_before_on_complete,
|
185
|
+
request)
|
186
|
+
|
191
187
|
if live_request && request.cache_timeout && @cache_setter
|
192
188
|
@cache_setter.call(request)
|
193
189
|
end
|
@@ -213,30 +209,4 @@ module Typhoeus
|
|
213
209
|
end
|
214
210
|
private :response_from_easy
|
215
211
|
end
|
216
|
-
|
217
|
-
class HydraMock
|
218
|
-
attr_reader :url, :method, :response, :requests
|
219
|
-
|
220
|
-
def initialize(url, method)
|
221
|
-
@url = url
|
222
|
-
@method = method
|
223
|
-
@requests = []
|
224
|
-
end
|
225
|
-
|
226
|
-
def add_request(request)
|
227
|
-
@requests << request
|
228
|
-
end
|
229
|
-
|
230
|
-
def and_return(val)
|
231
|
-
@response = val
|
232
|
-
end
|
233
|
-
|
234
|
-
def matches?(request)
|
235
|
-
if url.kind_of?(String)
|
236
|
-
request.method == method && request.url == url
|
237
|
-
else
|
238
|
-
request.method == method && url =~ request.url
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
212
|
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
|
@@ -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
|