typhoeus 0.2.4 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|