marnen-typhoeus 0.3.4
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.markdown +84 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +20 -0
- data/Rakefile +43 -0
- data/ext/typhoeus/.gitignore +7 -0
- data/ext/typhoeus/extconf.rb +65 -0
- data/ext/typhoeus/native.c +12 -0
- data/ext/typhoeus/native.h +22 -0
- data/ext/typhoeus/typhoeus_easy.c +232 -0
- data/ext/typhoeus/typhoeus_easy.h +20 -0
- data/ext/typhoeus/typhoeus_form.c +59 -0
- data/ext/typhoeus/typhoeus_form.h +13 -0
- data/ext/typhoeus/typhoeus_multi.c +217 -0
- data/ext/typhoeus/typhoeus_multi.h +16 -0
- data/lib/typhoeus.rb +58 -0
- data/lib/typhoeus/.gitignore +1 -0
- data/lib/typhoeus/easy.rb +413 -0
- data/lib/typhoeus/filter.rb +28 -0
- data/lib/typhoeus/form.rb +32 -0
- data/lib/typhoeus/hydra.rb +250 -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 +37 -0
- data/lib/typhoeus/normalized_header_hash.rb +58 -0
- data/lib/typhoeus/remote.rb +306 -0
- data/lib/typhoeus/remote_method.rb +108 -0
- data/lib/typhoeus/remote_proxy_object.rb +50 -0
- data/lib/typhoeus/request.rb +269 -0
- data/lib/typhoeus/response.rb +122 -0
- data/lib/typhoeus/service.rb +20 -0
- data/lib/typhoeus/utils.rb +74 -0
- data/lib/typhoeus/version.rb +3 -0
- data/spec/fixtures/placeholder.gif +0 -0
- data/spec/fixtures/placeholder.txt +1 -0
- data/spec/fixtures/placeholder.ukn +0 -0
- data/spec/fixtures/result_set.xml +60 -0
- data/spec/servers/app.rb +97 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/typhoeus_localhost_server.rb +58 -0
- data/spec/typhoeus/easy_spec.rb +391 -0
- data/spec/typhoeus/filter_spec.rb +35 -0
- data/spec/typhoeus/form_spec.rb +117 -0
- data/spec/typhoeus/hydra_mock_spec.rb +300 -0
- data/spec/typhoeus/hydra_spec.rb +602 -0
- data/spec/typhoeus/multi_spec.rb +74 -0
- data/spec/typhoeus/normalized_header_hash_spec.rb +41 -0
- data/spec/typhoeus/remote_method_spec.rb +141 -0
- data/spec/typhoeus/remote_proxy_object_spec.rb +65 -0
- data/spec/typhoeus/remote_spec.rb +695 -0
- data/spec/typhoeus/request_spec.rb +387 -0
- data/spec/typhoeus/response_spec.rb +192 -0
- data/spec/typhoeus/utils_spec.rb +22 -0
- data/typhoeus.gemspec +35 -0
- metadata +235 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class Filter
|
3
|
+
attr_reader :method_name
|
4
|
+
|
5
|
+
def initialize(method_name, options = {})
|
6
|
+
@method_name = method_name
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply_filter?(method_name)
|
11
|
+
if @options[:only]
|
12
|
+
if @options[:only].instance_of? Symbol
|
13
|
+
@options[:only] == method_name
|
14
|
+
else
|
15
|
+
@options[:only].include?(method_name)
|
16
|
+
end
|
17
|
+
elsif @options[:except]
|
18
|
+
if @options[:except].instance_of? Symbol
|
19
|
+
@options[:except] != method_name
|
20
|
+
else
|
21
|
+
!@options[:except].include?(method_name)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
|
3
|
+
module Typhoeus
|
4
|
+
class Form
|
5
|
+
attr_accessor :params
|
6
|
+
attr_reader :traversal
|
7
|
+
|
8
|
+
def initialize(params = {})
|
9
|
+
@params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def traversal
|
13
|
+
@traversal ||= Typhoeus::Utils.traverse_params_hash(params)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process!
|
17
|
+
# add params
|
18
|
+
traversal[:params].each { |p| formadd_param(p[0], p[1]) }
|
19
|
+
|
20
|
+
# add files
|
21
|
+
traversal[:files].each { |file_args| formadd_file(*file_args) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def multipart?
|
25
|
+
!traversal[:files].empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
Typhoeus::Utils.traversal_to_param_string(traversal, false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'typhoeus/hydra/callbacks'
|
2
|
+
require 'typhoeus/hydra/connect_options'
|
3
|
+
require 'typhoeus/hydra/stubbing'
|
4
|
+
|
5
|
+
module Typhoeus
|
6
|
+
class Hydra
|
7
|
+
include ConnectOptions
|
8
|
+
include Stubbing
|
9
|
+
extend Callbacks
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@memoize_requests = true
|
13
|
+
@multi = Multi.new
|
14
|
+
@easy_pool = []
|
15
|
+
initial_pool_size = options[:initial_pool_size] || 10
|
16
|
+
@max_concurrency = options[:max_concurrency] || 200
|
17
|
+
initial_pool_size.times { @easy_pool << Easy.new }
|
18
|
+
@memoized_requests = {}
|
19
|
+
@retrieved_from_cache = {}
|
20
|
+
@queued_requests = []
|
21
|
+
@running_requests = 0
|
22
|
+
|
23
|
+
self.stubs = []
|
24
|
+
@active_stubs = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.hydra
|
28
|
+
@hydra ||= new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.hydra=(val)
|
32
|
+
@hydra = val
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Abort the run on a best-effort basis.
|
37
|
+
#
|
38
|
+
# It won't abort the current burst of @max_concurrency requests,
|
39
|
+
# however it won't fire the rest of the queued requests so the run
|
40
|
+
# will be aborted as soon as possible...
|
41
|
+
#
|
42
|
+
def abort
|
43
|
+
@queued_requests.clear
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear_cache_callbacks
|
47
|
+
@cache_setter = nil
|
48
|
+
@cache_getter = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def fire_and_forget
|
52
|
+
@queued_requests.each {|r| queue(r, false)}
|
53
|
+
@multi.fire_and_forget
|
54
|
+
end
|
55
|
+
|
56
|
+
def queue(request, obey_concurrency_limit = true)
|
57
|
+
return if assign_to_stub(request)
|
58
|
+
|
59
|
+
# At this point, we are running over live HTTP. Make sure we haven't
|
60
|
+
# disabled live requests.
|
61
|
+
check_allow_net_connect!(request)
|
62
|
+
|
63
|
+
if @running_requests >= @max_concurrency && obey_concurrency_limit
|
64
|
+
@queued_requests << request
|
65
|
+
else
|
66
|
+
if request.method == :get
|
67
|
+
if @memoize_requests && @memoized_requests.has_key?(request.url)
|
68
|
+
if response = @retrieved_from_cache[request.url]
|
69
|
+
request.response = response
|
70
|
+
request.call_handlers
|
71
|
+
else
|
72
|
+
@memoized_requests[request.url] << request
|
73
|
+
end
|
74
|
+
else
|
75
|
+
@memoized_requests[request.url] = [] if @memoize_requests
|
76
|
+
get_from_cache_or_queue(request)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
get_from_cache_or_queue(request)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def run
|
85
|
+
while !@active_stubs.empty?
|
86
|
+
m = @active_stubs.first
|
87
|
+
while request = m.requests.shift
|
88
|
+
response = m.response
|
89
|
+
response.request = request
|
90
|
+
handle_request(request, response)
|
91
|
+
end
|
92
|
+
@active_stubs.delete(m)
|
93
|
+
end
|
94
|
+
|
95
|
+
@multi.perform
|
96
|
+
ensure
|
97
|
+
@multi.reset_easy_handles{|easy| release_easy_object(easy)}
|
98
|
+
@memoized_requests = {}
|
99
|
+
@retrieved_from_cache = {}
|
100
|
+
@running_requests = 0
|
101
|
+
end
|
102
|
+
|
103
|
+
def disable_memoization
|
104
|
+
@memoize_requests = false
|
105
|
+
end
|
106
|
+
|
107
|
+
def cache_getter(&block)
|
108
|
+
@cache_getter = block
|
109
|
+
end
|
110
|
+
|
111
|
+
def cache_setter(&block)
|
112
|
+
@cache_setter = block
|
113
|
+
end
|
114
|
+
|
115
|
+
def on_complete(&block)
|
116
|
+
@on_complete = block
|
117
|
+
end
|
118
|
+
|
119
|
+
def on_complete=(proc)
|
120
|
+
@on_complete = proc
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_from_cache_or_queue(request)
|
124
|
+
if @cache_getter
|
125
|
+
val = @cache_getter.call(request)
|
126
|
+
if val
|
127
|
+
@retrieved_from_cache[request.url] = val
|
128
|
+
queue_next
|
129
|
+
handle_request(request, val, false)
|
130
|
+
else
|
131
|
+
@multi.add(get_easy_object(request))
|
132
|
+
end
|
133
|
+
else
|
134
|
+
@multi.add(get_easy_object(request))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
private :get_from_cache_or_queue
|
138
|
+
|
139
|
+
def get_easy_object(request)
|
140
|
+
@running_requests += 1
|
141
|
+
|
142
|
+
easy = @easy_pool.pop || Easy.new
|
143
|
+
easy.verbose = request.verbose
|
144
|
+
if request.username || request.password
|
145
|
+
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
|
+
easy.auth = auth
|
148
|
+
end
|
149
|
+
|
150
|
+
if request.proxy
|
151
|
+
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
|
+
easy.proxy = proxy if request.proxy
|
154
|
+
end
|
155
|
+
|
156
|
+
if request.proxy_username || request.proxy_password
|
157
|
+
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
|
+
easy.proxy_auth = auth
|
160
|
+
end
|
161
|
+
|
162
|
+
easy.url = request.url
|
163
|
+
easy.method = request.method
|
164
|
+
easy.params = request.params if request.method == :post && !request.params.nil?
|
165
|
+
easy.headers = request.headers if request.headers
|
166
|
+
easy.request_body = request.body if request.body
|
167
|
+
easy.timeout = request.timeout if request.timeout
|
168
|
+
easy.connect_timeout = request.connect_timeout if request.connect_timeout
|
169
|
+
easy.interface = request.interface if request.interface
|
170
|
+
easy.follow_location = request.follow_location if request.follow_location
|
171
|
+
easy.max_redirects = request.max_redirects if request.max_redirects
|
172
|
+
easy.disable_ssl_peer_verification if request.disable_ssl_peer_verification
|
173
|
+
easy.disable_ssl_host_verification if request.disable_ssl_host_verification
|
174
|
+
easy.ssl_cert = request.ssl_cert
|
175
|
+
easy.ssl_cert_type = request.ssl_cert_type
|
176
|
+
easy.ssl_key = request.ssl_key
|
177
|
+
easy.ssl_key_type = request.ssl_key_type
|
178
|
+
easy.ssl_key_password = request.ssl_key_password
|
179
|
+
easy.ssl_cacert = request.ssl_cacert
|
180
|
+
easy.ssl_capath = request.ssl_capath
|
181
|
+
easy.ssl_version = request.ssl_version || :default
|
182
|
+
easy.verbose = request.verbose
|
183
|
+
|
184
|
+
easy.on_success do |easy|
|
185
|
+
queue_next
|
186
|
+
handle_request(request, response_from_easy(easy, request))
|
187
|
+
release_easy_object(easy)
|
188
|
+
end
|
189
|
+
easy.on_failure do |easy|
|
190
|
+
queue_next
|
191
|
+
handle_request(request, response_from_easy(easy, request))
|
192
|
+
release_easy_object(easy)
|
193
|
+
end
|
194
|
+
easy.set_headers
|
195
|
+
easy
|
196
|
+
end
|
197
|
+
private :get_easy_object
|
198
|
+
|
199
|
+
def queue_next
|
200
|
+
@running_requests -= 1
|
201
|
+
queue(@queued_requests.shift) unless @queued_requests.empty?
|
202
|
+
end
|
203
|
+
private :queue_next
|
204
|
+
|
205
|
+
def release_easy_object(easy)
|
206
|
+
easy.reset
|
207
|
+
@easy_pool.push easy
|
208
|
+
end
|
209
|
+
private :release_easy_object
|
210
|
+
|
211
|
+
def handle_request(request, response, live_request = true)
|
212
|
+
request.response = response
|
213
|
+
|
214
|
+
self.class.run_global_hooks_for(:after_request_before_on_complete,
|
215
|
+
request)
|
216
|
+
|
217
|
+
if live_request && request.cache_timeout && @cache_setter
|
218
|
+
@cache_setter.call(request)
|
219
|
+
end
|
220
|
+
@on_complete.call(response) if @on_complete
|
221
|
+
|
222
|
+
request.call_handlers
|
223
|
+
if requests = @memoized_requests[request.url]
|
224
|
+
requests.each do |r|
|
225
|
+
r.response = response
|
226
|
+
r.call_handlers
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
private :handle_request
|
231
|
+
|
232
|
+
def response_from_easy(easy, request)
|
233
|
+
Response.new(:code => easy.response_code,
|
234
|
+
:headers => easy.response_header,
|
235
|
+
:body => easy.response_body,
|
236
|
+
:time => easy.total_time_taken,
|
237
|
+
:start_transfer_time => easy.start_transfer_time,
|
238
|
+
:app_connect_time => easy.app_connect_time,
|
239
|
+
:pretransfer_time => easy.pretransfer_time,
|
240
|
+
:connect_time => easy.connect_time,
|
241
|
+
:name_lookup_time => easy.name_lookup_time,
|
242
|
+
:effective_url => easy.effective_url,
|
243
|
+
:primary_ip => easy.primary_ip,
|
244
|
+
:curl_return_code => easy.curl_return_code,
|
245
|
+
:curl_error_message => easy.curl_error_message,
|
246
|
+
:request => request)
|
247
|
+
end
|
248
|
+
private :response_from_easy
|
249
|
+
end
|
250
|
+
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,61 @@
|
|
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
|
+
return if Typhoeus::Hydra.allow_net_connect?
|
16
|
+
return if Typhoeus::Hydra.ignore_hosts.include?(request.host_domain)
|
17
|
+
|
18
|
+
raise NetConnectNotAllowedError, "Real HTTP requests are not allowed. Unregistered request: #{request.inspect}"
|
19
|
+
end
|
20
|
+
private :check_allow_net_connect!
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def self.extended(base)
|
24
|
+
class << base
|
25
|
+
attr_accessor :allow_net_connect
|
26
|
+
attr_accessor :ignore_localhost
|
27
|
+
end
|
28
|
+
base.allow_net_connect = true
|
29
|
+
base.ignore_localhost = false
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns whether we allow external HTTP connections.
|
33
|
+
# Useful for mocking/tests.
|
34
|
+
#
|
35
|
+
# @return [boolean] true/false
|
36
|
+
def allow_net_connect?
|
37
|
+
allow_net_connect
|
38
|
+
end
|
39
|
+
|
40
|
+
def ignore_localhost?
|
41
|
+
ignore_localhost
|
42
|
+
end
|
43
|
+
|
44
|
+
def ignore_hosts
|
45
|
+
@ignore_hosts ||= []
|
46
|
+
|
47
|
+
if ignore_localhost?
|
48
|
+
@ignore_hosts + Typhoeus::Request::LOCALHOST_ALIASES
|
49
|
+
else
|
50
|
+
@ignore_hosts
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def ignore_hosts=(hosts)
|
55
|
+
@ignore_hosts = hosts
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,68 @@
|
|
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 register_stub_finder(&block)
|
15
|
+
stub_finders << block
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_stub_from_request(request)
|
19
|
+
stub_finders.each do |finder|
|
20
|
+
if response = finder.call(request)
|
21
|
+
mock = HydraMock.new(/.*/, :any)
|
22
|
+
mock.and_return(response)
|
23
|
+
return mock
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
stubs.detect { |stub| stub.matches?(request) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def stub_finders
|
31
|
+
@stub_finders ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.extended(base)
|
35
|
+
class << base
|
36
|
+
attr_accessor :stubs
|
37
|
+
end
|
38
|
+
base.stubs = []
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.included(base)
|
43
|
+
base.extend(SharedMethods)
|
44
|
+
base.class_eval do
|
45
|
+
attr_accessor :stubs
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def assign_to_stub(request)
|
50
|
+
m = find_stub_from_request(request)
|
51
|
+
|
52
|
+
# Fallback to global stubs.
|
53
|
+
m ||= self.class.find_stub_from_request(request)
|
54
|
+
|
55
|
+
if m
|
56
|
+
m.add_request(request)
|
57
|
+
@active_stubs << m
|
58
|
+
m
|
59
|
+
else
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
private :assign_to_stub
|
64
|
+
|
65
|
+
include SharedMethods
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|