typhoeus 0.2.4 → 0.3.2
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 +22 -0
- data/Gemfile +1 -9
- data/Gemfile.lock +21 -18
- data/Rakefile +19 -7
- data/ext/typhoeus/typhoeus_multi.c +7 -1
- data/lib/typhoeus.rb +1 -2
- data/lib/typhoeus/easy.rb +25 -5
- data/lib/typhoeus/hydra.rb +5 -1
- data/lib/typhoeus/hydra/stubbing.rb +16 -0
- data/lib/typhoeus/request.rb +32 -4
- data/lib/typhoeus/response.rb +36 -5
- data/lib/typhoeus/utils.rb +5 -4
- data/lib/typhoeus/version.rb +3 -0
- data/spec/servers/app.rb +4 -0
- data/spec/spec_helper.rb +3 -3
- data/spec/typhoeus/easy_spec.rb +46 -10
- data/spec/typhoeus/form_spec.rb +17 -7
- data/spec/typhoeus/hydra_mock_spec.rb +22 -22
- data/spec/typhoeus/hydra_spec.rb +76 -0
- data/spec/typhoeus/request_spec.rb +97 -2
- data/spec/typhoeus/response_spec.rb +41 -0
- data/typhoeus.gemspec +28 -137
- metadata +43 -98
- data/README.textile +0 -372
- data/VERSION +0 -1
- data/benchmarks/profile.rb +0 -25
- data/benchmarks/vs_nethttp.rb +0 -35
- data/examples/file.rb +0 -12
- data/examples/times.rb +0 -40
- data/examples/twitter.rb +0 -21
- data/profilers/valgrind.rb +0 -24
- data/spec/spec.opts +0 -3
data/lib/typhoeus/utils.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
1
3
|
module Typhoeus
|
2
4
|
module Utils
|
3
5
|
# Taken from Rack::Utils, 1.2.1 to remove Rack dependency.
|
@@ -18,11 +20,10 @@ module Typhoeus
|
|
18
20
|
when Hash
|
19
21
|
traverse_params_hash(hash[key], result, new_key)
|
20
22
|
when Array
|
21
|
-
array_key = "#{new_key}[]"
|
22
23
|
hash[key].each do |v|
|
23
|
-
result[:params] << [
|
24
|
+
result[:params] << [new_key, v.to_s]
|
24
25
|
end
|
25
|
-
when File
|
26
|
+
when File, Tempfile
|
26
27
|
filename = File.basename(hash[key].path)
|
27
28
|
types = MIME::Types.type_for(filename)
|
28
29
|
result[:files] << [
|
@@ -41,7 +42,7 @@ module Typhoeus
|
|
41
42
|
|
42
43
|
def traversal_to_param_string(traversal, escape = true)
|
43
44
|
traversal[:params].collect { |param|
|
44
|
-
"#{Typhoeus::Utils.escape(param[0])}=#{Typhoeus::Utils.escape(param[1])}"
|
45
|
+
escape ? "#{Typhoeus::Utils.escape(param[0])}=#{Typhoeus::Utils.escape(param[1])}" : "#{param[0]}=#{param[1]}"
|
45
46
|
}.join('&')
|
46
47
|
end
|
47
48
|
module_function :traversal_to_param_string
|
data/spec/servers/app.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require 'json'
|
3
|
-
require "
|
3
|
+
require "rspec"
|
4
4
|
|
5
5
|
# gem install redgreen for colored test output
|
6
6
|
begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
|
@@ -10,5 +10,5 @@ $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
|
10
10
|
|
11
11
|
require path + '/typhoeus'
|
12
12
|
|
13
|
-
|
14
|
-
end
|
13
|
+
RSpec.configure do |config|
|
14
|
+
end
|
data/spec/typhoeus/easy_spec.rb
CHANGED
@@ -73,13 +73,13 @@ describe Typhoeus::Easy do
|
|
73
73
|
end
|
74
74
|
|
75
75
|
it "should allow you to set the user agent" do
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
JSON.parse(
|
76
|
+
e = Typhoeus::Easy.new
|
77
|
+
e.url = "http://localhost:3002"
|
78
|
+
e.method = :get
|
79
|
+
e.user_agent = "myapp"
|
80
|
+
e.perform
|
81
|
+
e.response_code.should == 200
|
82
|
+
JSON.parse(e.response_body)["HTTP_USER_AGENT"].should == "myapp"
|
83
83
|
end
|
84
84
|
|
85
85
|
it "should provide a timeout in milliseconds" do
|
@@ -110,6 +110,15 @@ describe Typhoeus::Easy do
|
|
110
110
|
e.perform
|
111
111
|
e.response_code.should == 302
|
112
112
|
end
|
113
|
+
|
114
|
+
it "should provide the primary IP address that was used to perform the HTTP request" do
|
115
|
+
e = Typhoeus::Easy.new
|
116
|
+
e.url = "http://localhost:3002"
|
117
|
+
e.method = :get
|
118
|
+
e.perform
|
119
|
+
e.response_code.should == 200
|
120
|
+
e.primary_ip.should == "127.0.0.1"
|
121
|
+
end
|
113
122
|
end
|
114
123
|
|
115
124
|
describe "authentication" do
|
@@ -190,8 +199,7 @@ describe Typhoeus::Easy do
|
|
190
199
|
:foo => 'bar',
|
191
200
|
:username => ['dbalatero', 'dbalatero2']
|
192
201
|
}
|
193
|
-
|
194
|
-
easy.url.should =~ /\?.*username%5B%5D=dbalatero&username%5B%5D=dbalatero2/
|
202
|
+
easy.url.should =~ /\?.*foo=bar&username=dbalatero&username=dbalatero2/
|
195
203
|
end
|
196
204
|
end
|
197
205
|
|
@@ -215,6 +223,24 @@ describe Typhoeus::Easy do
|
|
215
223
|
easy.response_code.should == 200
|
216
224
|
easy.response_body.should include("this is a body!")
|
217
225
|
end
|
226
|
+
|
227
|
+
it "should be able perform put with empty bodies on the same easy handle" do
|
228
|
+
easy = Typhoeus::Easy.new
|
229
|
+
easy.url = "http://localhost:3002"
|
230
|
+
easy.method = :put
|
231
|
+
easy.perform
|
232
|
+
easy.response_code.should == 200
|
233
|
+
JSON.parse(easy.response_body)["REQUEST_METHOD"].should == "PUT"
|
234
|
+
|
235
|
+
easy.reset
|
236
|
+
|
237
|
+
easy.url = "http://localhost:3002"
|
238
|
+
easy.method = :put
|
239
|
+
easy.perform
|
240
|
+
easy.response_code.should == 200
|
241
|
+
JSON.parse(easy.response_body)["REQUEST_METHOD"].should == "PUT"
|
242
|
+
end
|
243
|
+
|
218
244
|
end
|
219
245
|
|
220
246
|
describe "post" do
|
@@ -257,7 +283,7 @@ describe Typhoeus::Easy do
|
|
257
283
|
|
258
284
|
request = JSON.parse(easy.response_body)
|
259
285
|
request['CONTENT_TYPE'].should == 'application/x-www-form-urlencoded'
|
260
|
-
request['rack.request.form_vars'].should == 'a=b&c=d&e
|
286
|
+
request['rack.request.form_vars'].should == 'a=b&c=d&e[f][g]=h'
|
261
287
|
end
|
262
288
|
|
263
289
|
it "should handle a file upload, as multipart" do
|
@@ -311,6 +337,16 @@ describe Typhoeus::Easy do
|
|
311
337
|
easy.response_code.should == 200
|
312
338
|
JSON.parse(easy.response_body)["HTTP_ACCEPT_ENCODING"].should == "deflate, gzip"
|
313
339
|
end
|
340
|
+
|
341
|
+
it "should send valid encoding headers and decode the response after reset" do
|
342
|
+
easy = Typhoeus::Easy.new
|
343
|
+
easy.reset
|
344
|
+
easy.url = "http://localhost:3002/gzipped"
|
345
|
+
easy.method = :get
|
346
|
+
easy.perform
|
347
|
+
easy.response_code.should == 200
|
348
|
+
JSON.parse(easy.response_body)["HTTP_ACCEPT_ENCODING"].should == "deflate, gzip"
|
349
|
+
end
|
314
350
|
|
315
351
|
end
|
316
352
|
end
|
data/spec/typhoeus/form_spec.rb
CHANGED
@@ -36,9 +36,9 @@ describe Typhoeus::Form do
|
|
36
36
|
:name => "John Smith",
|
37
37
|
:age => "29"
|
38
38
|
})
|
39
|
-
form.should_receive(:formadd_param).with("colors
|
40
|
-
form.should_receive(:formadd_param).with("colors
|
41
|
-
form.should_receive(:formadd_param).with("colors
|
39
|
+
form.should_receive(:formadd_param).with("colors", "brown")
|
40
|
+
form.should_receive(:formadd_param).with("colors", "green")
|
41
|
+
form.should_receive(:formadd_param).with("colors", "white")
|
42
42
|
form.should_receive(:formadd_param).with("name", "John Smith")
|
43
43
|
form.should_receive(:formadd_param).with("age", "29")
|
44
44
|
form.process!
|
@@ -62,6 +62,16 @@ describe Typhoeus::Form do
|
|
62
62
|
form.should_receive(:formadd_file).with("text_file", "placeholder.txt", "text/plain", anything)
|
63
63
|
form.process!
|
64
64
|
end
|
65
|
+
|
66
|
+
it "should handle tempfiles (file subclasses)" do
|
67
|
+
tempfile = Tempfile.new('placeholder_temp')
|
68
|
+
form = Typhoeus::Form.new(
|
69
|
+
:file => tempfile
|
70
|
+
)
|
71
|
+
form.should_receive(:formadd_file).with("file", File.basename(tempfile.path), "application/octet-stream", anything)
|
72
|
+
form.process!
|
73
|
+
end
|
74
|
+
|
65
75
|
it "should default to 'application/octet-stream' if no content type can be determined" do
|
66
76
|
pending
|
67
77
|
form = Typhoeus::Form.new(
|
@@ -79,7 +89,7 @@ describe Typhoeus::Form do
|
|
79
89
|
:name => "John Smith",
|
80
90
|
:age => "29"
|
81
91
|
})
|
82
|
-
form.to_s.should == "age=29&name=John
|
92
|
+
form.to_s.should == "age=29&name=John Smith"
|
83
93
|
end
|
84
94
|
|
85
95
|
it "should handle params that are a hash" do
|
@@ -92,16 +102,16 @@ describe Typhoeus::Form do
|
|
92
102
|
:name => "John Smith",
|
93
103
|
:age => "29"
|
94
104
|
})
|
95
|
-
form.to_s.should == "age=29&attributes
|
105
|
+
form.to_s.should == "age=29&attributes[eyes]=brown&attributes[hair]=green&attributes[teeth]=white&name=John Smith"
|
96
106
|
end
|
97
107
|
|
98
|
-
it "should params that have
|
108
|
+
it "should params that have multiple values" do
|
99
109
|
form = Typhoeus::Form.new({
|
100
110
|
:colors => ["brown", "green", "white"],
|
101
111
|
:name => "John Smith",
|
102
112
|
:age => "29"
|
103
113
|
})
|
104
|
-
form.to_s.should == "age=29&colors
|
114
|
+
form.to_s.should == "age=29&colors=brown&colors=green&colors=white&name=John Smith"
|
105
115
|
end
|
106
116
|
end
|
107
117
|
end
|
@@ -98,12 +98,12 @@ describe Typhoeus::HydraMock do
|
|
98
98
|
Typhoeus::Request.new("http://localhost:3000", options.merge(:method => :get))
|
99
99
|
end
|
100
100
|
|
101
|
-
def
|
101
|
+
def hydra(options = {})
|
102
102
|
Typhoeus::HydraMock.new("http://localhost:3000", :get, options)
|
103
103
|
end
|
104
104
|
|
105
105
|
context 'when no :headers option is given' do
|
106
|
-
subject {
|
106
|
+
subject { hydra }
|
107
107
|
|
108
108
|
it "matches regardless of whether or not the request has headers" do
|
109
109
|
subject.matches?(request(:headers => nil)).should be_true
|
@@ -114,7 +114,7 @@ describe Typhoeus::HydraMock do
|
|
114
114
|
|
115
115
|
[nil, {}].each do |value|
|
116
116
|
context "for :headers => #{value.inspect}" do
|
117
|
-
subject {
|
117
|
+
subject { hydra(:headers => value) }
|
118
118
|
|
119
119
|
it "matches when the request has no headers" do
|
120
120
|
subject.matches?(request(:headers => nil)).should be_true
|
@@ -129,7 +129,7 @@ describe Typhoeus::HydraMock do
|
|
129
129
|
|
130
130
|
context 'for :headers => [a hash]' do
|
131
131
|
it 'does not match if the request has no headers' do
|
132
|
-
m =
|
132
|
+
m = hydra(:headers => { 'A' => 'B', 'C' => 'D' })
|
133
133
|
|
134
134
|
m.matches?(request).should be_false
|
135
135
|
m.matches?(request(:headers => nil)).should be_false
|
@@ -137,7 +137,7 @@ describe Typhoeus::HydraMock do
|
|
137
137
|
end
|
138
138
|
|
139
139
|
it 'does not match if the request lacks any of the given headers' do
|
140
|
-
|
140
|
+
hydra(
|
141
141
|
:headers => { 'A' => 'B', 'C' => 'D' }
|
142
142
|
).matches?(request(
|
143
143
|
:headers => { 'A' => 'B' }
|
@@ -145,7 +145,7 @@ describe Typhoeus::HydraMock do
|
|
145
145
|
end
|
146
146
|
|
147
147
|
it 'does not match if any of the specified values are different from the request value' do
|
148
|
-
|
148
|
+
hydra(
|
149
149
|
:headers => { 'A' => 'B', 'C' => 'D' }
|
150
150
|
).matches?(request(
|
151
151
|
:headers => { 'A' => 'B', 'C' => 'E' }
|
@@ -153,39 +153,39 @@ describe Typhoeus::HydraMock do
|
|
153
153
|
end
|
154
154
|
|
155
155
|
it 'matches if the given hash is exactly equal to the request headers' do
|
156
|
-
|
156
|
+
hydra(
|
157
157
|
:headers => { 'A' => 'B', 'C' => 'D' }
|
158
158
|
).matches?(request(
|
159
159
|
:headers => { 'A' => 'B', 'C' => 'D' }
|
160
160
|
)).should be_true
|
161
161
|
end
|
162
162
|
|
163
|
-
it 'matches even if the request has additional headers not specified in the
|
164
|
-
|
163
|
+
it 'matches even if the request has additional headers not specified in the hydra' do
|
164
|
+
hydra(
|
165
165
|
:headers => { 'A' => 'B', 'C' => 'D' }
|
166
166
|
).matches?(request(
|
167
167
|
:headers => { 'A' => 'B', 'C' => 'D', 'E' => 'F' }
|
168
168
|
)).should be_true
|
169
169
|
end
|
170
170
|
|
171
|
-
it 'matches even if the casing of the header keys is different between the
|
172
|
-
|
171
|
+
it 'matches even if the casing of the header keys is different between the hydra and request' do
|
172
|
+
hydra(
|
173
173
|
:headers => { 'A' => 'B', 'c' => 'D' }
|
174
174
|
).matches?(request(
|
175
175
|
:headers => { 'a' => 'B', 'C' => 'D' }
|
176
176
|
)).should be_true
|
177
177
|
end
|
178
178
|
|
179
|
-
it 'matches if the
|
180
|
-
|
179
|
+
it 'matches if the hydraed values are regexes and match the request values' do
|
180
|
+
hydra(
|
181
181
|
:headers => { 'A' => /foo/, }
|
182
182
|
).matches?(request(
|
183
183
|
:headers => { 'A' => 'foo bar' }
|
184
184
|
)).should be_true
|
185
185
|
end
|
186
186
|
|
187
|
-
it 'does not match if the
|
188
|
-
|
187
|
+
it 'does not match if the hydraed values are regexes and do not match the request values' do
|
188
|
+
hydra(
|
189
189
|
:headers => { 'A' => /foo/, }
|
190
190
|
).matches?(request(
|
191
191
|
:headers => { 'A' => 'bar' }
|
@@ -194,15 +194,15 @@ describe Typhoeus::HydraMock do
|
|
194
194
|
|
195
195
|
context 'when a header is specified as an array' do
|
196
196
|
it 'matches when the request header has the same array' do
|
197
|
-
|
197
|
+
hydra(
|
198
198
|
:headers => { 'Accept' => ['text/html', 'text/plain'] }
|
199
199
|
).matches?(request(
|
200
200
|
:headers => { 'Accept' => ['text/html', 'text/plain'] }
|
201
201
|
)).should be_true
|
202
202
|
end
|
203
203
|
|
204
|
-
it 'matches when the request header is a single value and the
|
205
|
-
|
204
|
+
it 'matches when the request header is a single value and the hydra array has the same value' do
|
205
|
+
hydra(
|
206
206
|
:headers => { 'Accept' => ['text/html'] }
|
207
207
|
).matches?(request(
|
208
208
|
:headers => { 'Accept' => 'text/html' }
|
@@ -210,7 +210,7 @@ describe Typhoeus::HydraMock do
|
|
210
210
|
end
|
211
211
|
|
212
212
|
it 'matches even when the request header array is ordered differently' do
|
213
|
-
|
213
|
+
hydra(
|
214
214
|
:headers => { 'Accept' => ['text/html', 'text/plain'] }
|
215
215
|
).matches?(request(
|
216
216
|
:headers => { 'Accept' => ['text/plain', 'text/html'] }
|
@@ -218,7 +218,7 @@ describe Typhoeus::HydraMock do
|
|
218
218
|
end
|
219
219
|
|
220
220
|
it 'does not match when the request header array lacks a value' do
|
221
|
-
|
221
|
+
hydra(
|
222
222
|
:headers => { 'Accept' => ['text/html', 'text/plain'] }
|
223
223
|
).matches?(request(
|
224
224
|
:headers => { 'Accept' => ['text/plain'] }
|
@@ -226,7 +226,7 @@ describe Typhoeus::HydraMock do
|
|
226
226
|
end
|
227
227
|
|
228
228
|
it 'does not match when the request header array has an extra value' do
|
229
|
-
|
229
|
+
hydra(
|
230
230
|
:headers => { 'Accept' => ['text/html', 'text/plain'] }
|
231
231
|
).matches?(request(
|
232
232
|
:headers => { 'Accept' => ['text/html', 'text/plain', 'application/xml'] }
|
@@ -234,7 +234,7 @@ describe Typhoeus::HydraMock do
|
|
234
234
|
end
|
235
235
|
|
236
236
|
it 'does not match when the request header is not an array' do
|
237
|
-
|
237
|
+
hydra(
|
238
238
|
:headers => { 'Accept' => ['text/html', 'text/plain'] }
|
239
239
|
).matches?(request(
|
240
240
|
:headers => { 'Accept' => 'text/html' }
|
data/spec/typhoeus/hydra_spec.rb
CHANGED
@@ -46,6 +46,29 @@ describe Typhoeus::Hydra do
|
|
46
46
|
second.response.body.should include("second")
|
47
47
|
end
|
48
48
|
|
49
|
+
it "runs queued requests in order of queuing" do
|
50
|
+
hydra = Typhoeus::Hydra.new :max_concurrency => 1
|
51
|
+
first = Typhoeus::Request.new("http://localhost:3000/first")
|
52
|
+
second = Typhoeus::Request.new("http://localhost:3001/second")
|
53
|
+
third = Typhoeus::Request.new("http://localhost:3001/third")
|
54
|
+
second.on_complete do |response|
|
55
|
+
first.response.should_not == nil
|
56
|
+
third.response.should == nil
|
57
|
+
end
|
58
|
+
third.on_complete do |response|
|
59
|
+
first.response.should_not == nil
|
60
|
+
second.response.should_not == nil
|
61
|
+
end
|
62
|
+
|
63
|
+
hydra.queue first
|
64
|
+
hydra.queue second
|
65
|
+
hydra.queue third
|
66
|
+
hydra.run
|
67
|
+
first.response.body.should include("first")
|
68
|
+
second.response.body.should include("second")
|
69
|
+
third.response.body.should include("third")
|
70
|
+
end
|
71
|
+
|
49
72
|
it "should store the curl return codes on the reponses" do
|
50
73
|
hydra = Typhoeus::Hydra.new
|
51
74
|
first = Typhoeus::Request.new("http://localhost:3001/?delay=1", :timeout => 100)
|
@@ -165,6 +188,31 @@ describe Typhoeus::Hydra do
|
|
165
188
|
@cache.get(second.cache_key).should be_nil
|
166
189
|
end
|
167
190
|
|
191
|
+
it "continues queued requests after a queued cache hit" do
|
192
|
+
# Set max_concurrency to 1 so that the second and third requests will end
|
193
|
+
# up in the request queue.
|
194
|
+
hydra = Typhoeus::Hydra.new :max_concurrency => 1
|
195
|
+
hydra.cache_getter do |request|
|
196
|
+
@cache.get(request.cache_key) rescue nil
|
197
|
+
end
|
198
|
+
hydra.cache_setter do |request|
|
199
|
+
@cache.set(request.cache_key, request.response, request.cache_timeout)
|
200
|
+
end
|
201
|
+
|
202
|
+
first = Typhoeus::Request.new("http://localhost:3000/first", :params => {:delay => 1})
|
203
|
+
second = Typhoeus::Request.new("http://localhost:3000/second", :params => {:delay => 1})
|
204
|
+
third = Typhoeus::Request.new("http://localhost:3000/third", :params => {:delay => 1})
|
205
|
+
@cache.set(second.cache_key, "second", 60)
|
206
|
+
hydra.queue first
|
207
|
+
hydra.queue second
|
208
|
+
hydra.queue third
|
209
|
+
hydra.run
|
210
|
+
|
211
|
+
first.response.body.should include("first")
|
212
|
+
second.response.should == "second"
|
213
|
+
third.response.body.should include("third")
|
214
|
+
end
|
215
|
+
|
168
216
|
it "has a global on_complete" do
|
169
217
|
foo = nil
|
170
218
|
hydra = Typhoeus::Hydra.new
|
@@ -300,6 +348,34 @@ describe Typhoeus::Hydra::Stubbing do
|
|
300
348
|
|
301
349
|
after(:each) do
|
302
350
|
@stub_target.clear_stubs
|
351
|
+
@stub_target.stub_finders.clear
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'allows users to register a custom stub finder' do
|
355
|
+
@stub_target.register_stub_finder do |request|
|
356
|
+
Typhoeus::Response.new :code => 200, :body => "stub for #{request.url.split('/').last}"
|
357
|
+
end
|
358
|
+
|
359
|
+
request = Typhoeus::Request.new("http://localhost:3000/foo")
|
360
|
+
returned_response = nil
|
361
|
+
request.on_complete { |response| returned_response = response }
|
362
|
+
@hydra.queue(request); @hydra.run
|
363
|
+
|
364
|
+
returned_response.code.should == 200
|
365
|
+
returned_response.body.should == "stub for foo"
|
366
|
+
end
|
367
|
+
|
368
|
+
it 'ignores the custom stub finder if it the block returns nil' do
|
369
|
+
@stub_target.register_stub_finder { |r| }
|
370
|
+
|
371
|
+
@stub_target.stub(:get, "http://localhost:3000/foo",
|
372
|
+
:headers => { 'user-agent' => 'test'}).
|
373
|
+
and_return(@response)
|
374
|
+
|
375
|
+
@hydra.queue(@request)
|
376
|
+
@hydra.run
|
377
|
+
@on_complete_handler_called.should be_true
|
378
|
+
@response.request.should == @request
|
303
379
|
end
|
304
380
|
|
305
381
|
it "should provide a stubs accessor" do
|