typhoeus 1.6.0 → 1.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +217 -1
- data/README.md +338 -160
- data/UPGRADE.md +29 -0
- data/lib/rack/typhoeus/middleware/params_decoder/helper.rb +67 -14
- data/lib/rack/typhoeus/middleware/params_decoder.rb +8 -19
- data/lib/rack/typhoeus.rb +2 -0
- data/lib/typhoeus/adapters/faraday.rb +89 -28
- data/lib/typhoeus/cache/dalli.rb +2 -0
- data/lib/typhoeus/cache/rails.rb +3 -1
- data/lib/typhoeus/cache/redis.rb +2 -0
- data/lib/typhoeus/config.rb +51 -7
- data/lib/typhoeus/easy_factory.rb +129 -23
- data/lib/typhoeus/errors/invalid_option.rb +23 -0
- data/lib/typhoeus/errors/no_response.rb +18 -0
- data/lib/typhoeus/errors/no_stub.rb +8 -1
- data/lib/typhoeus/errors/typhoeus_error.rb +2 -0
- data/lib/typhoeus/errors.rb +4 -0
- data/lib/typhoeus/expectation.rb +33 -1
- data/lib/typhoeus/hydra/queueable.rb +29 -4
- data/lib/typhoeus/hydra.rb +349 -21
- data/lib/typhoeus/pool.rb +80 -17
- data/lib/typhoeus/railtie.rb +4 -2
- data/lib/typhoeus/request/actions.rb +2 -0
- data/lib/typhoeus/request/callbacks.rb +11 -4
- data/lib/typhoeus/request/marshal.rb +5 -2
- data/lib/typhoeus/request/streamable.rb +2 -0
- data/lib/typhoeus/request.rb +317 -30
- data/lib/typhoeus/response/cacheable.rb +2 -0
- data/lib/typhoeus/response/header.rb +234 -14
- data/lib/typhoeus/response/informations.rb +75 -12
- data/lib/typhoeus/response/status.rb +4 -13
- data/lib/typhoeus/response.rb +35 -0
- data/lib/typhoeus/version.rb +3 -1
- data/lib/typhoeus.rb +89 -26
- data/typhoeus.gemspec +15 -3
- metadata +19 -22
- data/lib/typhoeus/hydra/addable.rb +0 -23
- data/lib/typhoeus/hydra/before.rb +0 -31
- data/lib/typhoeus/hydra/block_connection.rb +0 -35
- data/lib/typhoeus/hydra/cacheable.rb +0 -15
- data/lib/typhoeus/hydra/memoizable.rb +0 -56
- data/lib/typhoeus/hydra/runnable.rb +0 -19
- data/lib/typhoeus/hydra/stubbable.rb +0 -28
- data/lib/typhoeus/request/before.rb +0 -30
- data/lib/typhoeus/request/block_connection.rb +0 -52
- data/lib/typhoeus/request/cacheable.rb +0 -38
- data/lib/typhoeus/request/memoizable.rb +0 -38
- data/lib/typhoeus/request/operations.rb +0 -40
- data/lib/typhoeus/request/responseable.rb +0 -29
- data/lib/typhoeus/request/stubbable.rb +0 -30
data/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Typhoeus [](https://badge.fury.io/rb/typhoeus) [](https://github.com/typhoeus/typhoeus/actions/workflows/ci.yml) [](https://github.com/typhoeus/typhoeus/actions/workflows/experimental.yml)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typhoeus is a Ruby HTTP client built on [libcurl](https://curl.se/libcurl/).
|
|
4
|
+
Run individual requests, or queue many on `Typhoeus::Hydra` to execute them in
|
|
5
|
+
parallel. It supports redirects, timeouts, streaming, caching, memoization,
|
|
6
|
+
stubbing, and callbacks while still exposing libcurl's options.
|
|
7
|
+
|
|
8
|
+
- `Typhoeus::Request` and `Typhoeus::Response` model a single transfer.
|
|
9
|
+
- `Typhoeus::Hydra` queues requests and runs them concurrently.
|
|
10
|
+
- `Typhoeus.get`, `.post`, and the other shortcuts cover common cases.
|
|
4
11
|
|
|
5
12
|
## Example
|
|
6
13
|
|
|
@@ -14,31 +21,40 @@ Parallel requests:
|
|
|
14
21
|
|
|
15
22
|
```ruby
|
|
16
23
|
hydra = Typhoeus::Hydra.new
|
|
17
|
-
10.times
|
|
24
|
+
10.times do
|
|
25
|
+
hydra.queue(Typhoeus::Request.new("www.example.com", followlocation: true))
|
|
26
|
+
end
|
|
18
27
|
hydra.run
|
|
19
28
|
```
|
|
20
29
|
|
|
21
30
|
## Installation
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
|
|
32
|
+
Add the gem to your bundle:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
24
35
|
bundle add typhoeus
|
|
25
36
|
```
|
|
26
37
|
|
|
27
|
-
Or install it
|
|
38
|
+
Or install it directly:
|
|
28
39
|
|
|
29
|
-
```
|
|
40
|
+
```sh
|
|
30
41
|
gem install typhoeus
|
|
31
42
|
```
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
Typhoeus requires Ruby 2.6 or later and Ethon 0.19.
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
- [API documentation](https://rubydoc.info/github/typhoeus/typhoeus) (master)
|
|
49
|
+
- [Changelog](https://github.com/typhoeus/typhoeus/blob/master/CHANGELOG.md)
|
|
36
50
|
|
|
37
51
|
## Usage
|
|
38
52
|
|
|
39
53
|
### Introduction
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
Typhoeus's primary interface consists of three classes: `Request`, `Response`,
|
|
56
|
+
and `Hydra`. A request describes an HTTP call, a response exposes its result,
|
|
57
|
+
and a hydra runs requests in parallel.
|
|
42
58
|
|
|
43
59
|
```ruby
|
|
44
60
|
request = Typhoeus::Request.new(
|
|
@@ -50,33 +66,32 @@ request = Typhoeus::Request.new(
|
|
|
50
66
|
)
|
|
51
67
|
```
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
The first argument is the URL and the second is an options hash. All options
|
|
70
|
+
are optional, and the method defaults to `:get`.
|
|
55
71
|
|
|
56
|
-
|
|
72
|
+
URL parameters go in `:params`. Form-encoded body parameters go in `:body`.
|
|
73
|
+
`:params` are appended to the URL; `:body` becomes the request body.
|
|
57
74
|
|
|
58
75
|
#### Sending requests through the proxy
|
|
59
76
|
|
|
60
|
-
Add
|
|
77
|
+
Add `proxy` to the options:
|
|
61
78
|
|
|
62
79
|
```ruby
|
|
63
|
-
options = {proxy:
|
|
64
|
-
|
|
80
|
+
options = { proxy: "http://myproxy.org" }
|
|
81
|
+
request = Typhoeus::Request.new(url, options)
|
|
65
82
|
```
|
|
66
83
|
|
|
67
|
-
If
|
|
84
|
+
If the proxy requires authentication, set `proxyuserpwd` to `user:password`,
|
|
85
|
+
just like the basic-auth `userpwd` option:
|
|
68
86
|
|
|
69
87
|
```ruby
|
|
70
|
-
options = {proxy:
|
|
71
|
-
|
|
88
|
+
options = { proxy: "http://proxyurl.com", proxyuserpwd: "user:password" }
|
|
89
|
+
request = Typhoeus::Request.new(url, options)
|
|
72
90
|
```
|
|
73
91
|
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
Run the request directly or queue it on a hydra:
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
``` ruby
|
|
94
|
+
```ruby
|
|
80
95
|
request.run
|
|
81
96
|
#=> <Typhoeus::Response ... >
|
|
82
97
|
```
|
|
@@ -87,7 +102,7 @@ hydra.queue(request)
|
|
|
87
102
|
hydra.run
|
|
88
103
|
```
|
|
89
104
|
|
|
90
|
-
|
|
105
|
+
After the request finishes, read the response:
|
|
91
106
|
|
|
92
107
|
```ruby
|
|
93
108
|
response = request.response
|
|
@@ -99,62 +114,88 @@ response.body
|
|
|
99
114
|
|
|
100
115
|
### Making Quick Requests
|
|
101
116
|
|
|
102
|
-
Typhoeus
|
|
117
|
+
Typhoeus provides convenience methods for single requests. They take the same
|
|
118
|
+
URL and options as `Request.new`.
|
|
103
119
|
|
|
104
120
|
```ruby
|
|
105
121
|
Typhoeus.get("www.example.com")
|
|
106
122
|
Typhoeus.head("www.example.com")
|
|
107
|
-
Typhoeus.put("www.example.com/posts/1", body: "
|
|
123
|
+
Typhoeus.put("www.example.com/posts/1", body: "a body")
|
|
108
124
|
Typhoeus.patch("www.example.com/posts/1", body: "a new body")
|
|
109
|
-
Typhoeus.post("www.example.com/posts", body: { title: "test post", content: "this is my test"})
|
|
125
|
+
Typhoeus.post("www.example.com/posts", body: { title: "test post", content: "this is my test" })
|
|
110
126
|
Typhoeus.delete("www.example.com/posts/1")
|
|
111
127
|
Typhoeus.options("www.example.com")
|
|
112
128
|
```
|
|
129
|
+
|
|
113
130
|
#### Sending params in the body with PUT
|
|
114
|
-
|
|
131
|
+
|
|
132
|
+
POST sets `Content-Type` to `application/x-www-form-urlencoded` when the body
|
|
133
|
+
is a hash. Other methods, including PUT, PATCH, and HEAD, do not. To receive a
|
|
134
|
+
PUT hash as form parameters, set the header yourself:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
Typhoeus.put(
|
|
138
|
+
"www.example.com/posts/1",
|
|
139
|
+
headers: { "Content-Type" => "application/x-www-form-urlencoded" },
|
|
140
|
+
body: { title: "test post updated title", content: "this is my updated content" }
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Before callbacks
|
|
145
|
+
|
|
146
|
+
Use `Typhoeus.before` for every request, or `request.before` for a single
|
|
147
|
+
request. Callbacks receive the request, run before it is performed, and can
|
|
148
|
+
modify it. Returning `nil` or `false` skips the request and returns its current
|
|
149
|
+
response; returning a `Typhoeus::Response` skips the request and returns that
|
|
150
|
+
response. The returned response is treated as a mock, so it is not cached.
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
Typhoeus.before { |request| request.options[:timeout] ||= 5 }
|
|
154
|
+
|
|
155
|
+
request = Typhoeus::Request.new("www.example.com")
|
|
156
|
+
request.before { |request| request.options[:headers] ||= {} }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
To skip before callbacks globally, for example when your application does not
|
|
160
|
+
use them:
|
|
161
|
+
|
|
115
162
|
```ruby
|
|
116
|
-
Typhoeus.
|
|
117
|
-
headers: {'Content-Type'=> "application/x-www-form-urlencoded"},
|
|
118
|
-
body: {title:"test post updated title", content: "this is my updated content"}
|
|
119
|
-
)
|
|
163
|
+
Typhoeus::Config.exclude_features = [:before]
|
|
120
164
|
```
|
|
121
165
|
|
|
122
166
|
### Handling HTTP errors
|
|
123
167
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
168
|
+
After the request completes, inspect the response. `success?` covers both
|
|
169
|
+
transport and HTTP failures, `timed_out?` identifies timeouts, and a zero code
|
|
170
|
+
means no HTTP response was received. Define callbacks before running the
|
|
171
|
+
request.
|
|
128
172
|
|
|
129
173
|
```ruby
|
|
130
174
|
request = Typhoeus::Request.new("www.example.com", followlocation: true)
|
|
131
175
|
|
|
132
176
|
request.on_complete do |response|
|
|
133
177
|
if response.success?
|
|
134
|
-
#
|
|
178
|
+
# Request succeeded
|
|
135
179
|
elsif response.timed_out?
|
|
136
|
-
|
|
137
|
-
log("got a time out")
|
|
180
|
+
log("request timed out")
|
|
138
181
|
elsif response.code == 0
|
|
139
|
-
# Could not get an http response, something's wrong.
|
|
140
182
|
log(response.return_message)
|
|
141
183
|
else
|
|
142
|
-
|
|
143
|
-
log("HTTP request failed: " + response.code.to_s)
|
|
184
|
+
log("HTTP request failed: #{response.code}")
|
|
144
185
|
end
|
|
145
186
|
end
|
|
146
187
|
|
|
147
188
|
request.run
|
|
148
189
|
```
|
|
149
190
|
|
|
150
|
-
This
|
|
151
|
-
serial and parallel requests return a Response object.
|
|
191
|
+
This works for blocking and parallel requests; both return a `Response`.
|
|
152
192
|
|
|
153
193
|
### Handling file uploads
|
|
154
194
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
195
|
+
Pass a `File` in the body to upload a file with a POST request. Typhoeus sends
|
|
196
|
+
the original file name and detects the content type with Mime::Types when the
|
|
197
|
+
optional `mime-types` gem is installed. Without that gem, it uses
|
|
198
|
+
`application/octet-stream`.
|
|
158
199
|
|
|
159
200
|
```ruby
|
|
160
201
|
Typhoeus.post(
|
|
@@ -162,38 +203,33 @@ Typhoeus.post(
|
|
|
162
203
|
body: {
|
|
163
204
|
title: "test post",
|
|
164
205
|
content: "this is my test",
|
|
165
|
-
file: File.open("thesis.txt","r")
|
|
206
|
+
file: File.open("thesis.txt", "r")
|
|
166
207
|
}
|
|
167
208
|
)
|
|
168
209
|
```
|
|
169
210
|
|
|
170
211
|
### Streaming the response body
|
|
171
212
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
with chunks of the response, as they're read. When you set an `on_body` callback,
|
|
175
|
-
Typhoeus will not store the complete response.
|
|
213
|
+
Set `on_body` to receive response chunks as they arrive. This is useful for
|
|
214
|
+
large responses, and Typhoeus does not retain the complete body.
|
|
176
215
|
|
|
177
216
|
```ruby
|
|
178
|
-
downloaded_file = File.open
|
|
217
|
+
downloaded_file = File.open("huge.iso", "wb")
|
|
179
218
|
request = Typhoeus::Request.new("www.example.com/huge.iso")
|
|
180
219
|
request.on_headers do |response|
|
|
181
|
-
if response.code != 200
|
|
182
|
-
raise "Request failed"
|
|
183
|
-
end
|
|
220
|
+
raise "Request failed" if response.code != 200
|
|
184
221
|
end
|
|
185
222
|
request.on_body do |chunk|
|
|
186
223
|
downloaded_file.write(chunk)
|
|
187
224
|
end
|
|
188
225
|
request.on_complete do |response|
|
|
189
226
|
downloaded_file.close
|
|
190
|
-
#
|
|
227
|
+
# response.body is empty
|
|
191
228
|
end
|
|
192
229
|
request.run
|
|
193
230
|
```
|
|
194
231
|
|
|
195
|
-
|
|
196
|
-
you can return the `:abort` symbol from the `on_body` block, example:
|
|
232
|
+
Return `:abort` from the `on_body` block to stop the transfer cleanly:
|
|
197
233
|
|
|
198
234
|
```ruby
|
|
199
235
|
request.on_body do |chunk|
|
|
@@ -202,54 +238,71 @@ request.on_body do |chunk|
|
|
|
202
238
|
end
|
|
203
239
|
```
|
|
204
240
|
|
|
205
|
-
|
|
206
|
-
|
|
241
|
+
Returning `:abort` stops the transfer without buffering the rest of the
|
|
242
|
+
response. Leaving the block with `return`, `throw`, or `raise` can leave the
|
|
243
|
+
transfer in an inconsistent state.
|
|
244
|
+
|
|
245
|
+
### Progress callbacks
|
|
246
|
+
|
|
247
|
+
Set `on_progress` to observe the transfer. The callback receives the total and
|
|
248
|
+
current number of downloaded and uploaded bytes:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
request = Typhoeus::Request.new("www.example.com/huge.iso")
|
|
252
|
+
request.on_progress do |dltotal, dlnow, ultotal, ulnow|
|
|
253
|
+
puts "#{dlnow}/#{dltotal} bytes"
|
|
254
|
+
end
|
|
255
|
+
request.run
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Progress callbacks run during the transfer. Use `on_complete` for completion.
|
|
207
259
|
|
|
208
260
|
### Making Parallel Requests
|
|
209
261
|
|
|
210
|
-
|
|
262
|
+
Use `Hydra` to run requests in parallel. `hydra.run` blocks until all queued
|
|
263
|
+
requests finish.
|
|
211
264
|
|
|
212
265
|
```ruby
|
|
213
266
|
hydra = Typhoeus::Hydra.hydra
|
|
214
267
|
|
|
215
268
|
first_request = Typhoeus::Request.new("http://example.com/posts/1")
|
|
216
269
|
first_request.on_complete do |response|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
hydra.queue third_request
|
|
270
|
+
third_request = Typhoeus::Request.new(response.body)
|
|
271
|
+
hydra.queue(third_request)
|
|
220
272
|
end
|
|
221
273
|
second_request = Typhoeus::Request.new("http://example.com/posts/2")
|
|
222
274
|
|
|
223
|
-
hydra.queue
|
|
224
|
-
hydra.queue
|
|
225
|
-
hydra.run #
|
|
275
|
+
hydra.queue(first_request)
|
|
276
|
+
hydra.queue(second_request)
|
|
277
|
+
hydra.run # blocks until all requests are complete
|
|
226
278
|
```
|
|
227
279
|
|
|
228
|
-
|
|
280
|
+
`hydra.run` starts the first two requests. When the first completes, its
|
|
281
|
+
`on_complete` callback queues a third request, which starts immediately while
|
|
282
|
+
the second request continues. `hydra.run` returns when all three finish.
|
|
229
283
|
|
|
230
|
-
|
|
284
|
+
Collect responses from the requests after the run:
|
|
231
285
|
|
|
232
286
|
```ruby
|
|
233
287
|
hydra = Typhoeus::Hydra.new
|
|
234
|
-
requests = 10.times.map
|
|
288
|
+
requests = 10.times.map do
|
|
235
289
|
request = Typhoeus::Request.new("www.example.com", followlocation: true)
|
|
236
290
|
hydra.queue(request)
|
|
237
291
|
request
|
|
238
|
-
|
|
292
|
+
end
|
|
239
293
|
hydra.run
|
|
240
294
|
|
|
241
|
-
responses = requests.map { |request|
|
|
242
|
-
request.response.body
|
|
243
|
-
}
|
|
295
|
+
responses = requests.map { |request| request.response.body }
|
|
244
296
|
```
|
|
245
|
-
|
|
297
|
+
|
|
298
|
+
To handle responses as they complete, use `on_complete` instead:
|
|
246
299
|
|
|
247
300
|
```ruby
|
|
248
301
|
hydra = Typhoeus::Hydra.new
|
|
249
302
|
10.times do
|
|
250
303
|
request = Typhoeus::Request.new("www.example.com", followlocation: true)
|
|
251
304
|
request.on_complete do |response|
|
|
252
|
-
#
|
|
305
|
+
# handle response
|
|
253
306
|
end
|
|
254
307
|
hydra.queue(request)
|
|
255
308
|
end
|
|
@@ -258,34 +311,36 @@ hydra.run
|
|
|
258
311
|
|
|
259
312
|
### Making Parallel Requests with Faraday + Typhoeus
|
|
260
313
|
|
|
314
|
+
Typhoeus ships a Faraday adapter that supports Faraday 1.x and 2.x.
|
|
315
|
+
|
|
261
316
|
```ruby
|
|
262
|
-
require
|
|
317
|
+
require "faraday"
|
|
263
318
|
|
|
264
|
-
conn = Faraday.new(:
|
|
319
|
+
conn = Faraday.new(url: "http://httppage.com") do |builder|
|
|
265
320
|
builder.request :url_encoded
|
|
266
321
|
builder.response :logger
|
|
267
322
|
builder.adapter :typhoeus
|
|
268
323
|
end
|
|
269
324
|
|
|
270
325
|
conn.in_parallel do
|
|
271
|
-
response1 = conn.get(
|
|
272
|
-
response2 = conn.get(
|
|
326
|
+
response1 = conn.get("/first")
|
|
327
|
+
response2 = conn.get("/second")
|
|
273
328
|
|
|
274
|
-
#
|
|
275
|
-
# requests have not been completed
|
|
329
|
+
# Inside in_parallel, the responses are not complete yet.
|
|
276
330
|
response1.body
|
|
277
331
|
response2.body
|
|
278
332
|
end
|
|
279
333
|
|
|
280
|
-
#
|
|
281
|
-
# response1.status, etc
|
|
334
|
+
# After in_parallel returns, the responses are populated.
|
|
282
335
|
response1.body
|
|
283
336
|
response2.body
|
|
284
337
|
```
|
|
285
338
|
|
|
286
339
|
### Specifying Max Concurrency
|
|
287
340
|
|
|
288
|
-
Hydra
|
|
341
|
+
Hydra runs at most 200 requests concurrently by default. Additional queued
|
|
342
|
+
requests wait until a running request finishes. Set a lower or higher limit on
|
|
343
|
+
the constructor:
|
|
289
344
|
|
|
290
345
|
```ruby
|
|
291
346
|
Typhoeus::Hydra.new(max_concurrency: 20)
|
|
@@ -293,34 +348,37 @@ Typhoeus::Hydra.new(max_concurrency: 20)
|
|
|
293
348
|
|
|
294
349
|
### Memoization
|
|
295
350
|
|
|
296
|
-
|
|
297
|
-
|
|
351
|
+
With memoization enabled, Hydra sends a single request for identical GET
|
|
352
|
+
requests in a run and executes each request's `on_complete` callback.
|
|
353
|
+
Memoization is disabled by default:
|
|
298
354
|
|
|
299
355
|
```ruby
|
|
300
356
|
Typhoeus::Config.memoize = true
|
|
301
357
|
|
|
302
358
|
hydra = Typhoeus::Hydra.new(max_concurrency: 1)
|
|
303
359
|
2.times do
|
|
304
|
-
hydra.queue
|
|
360
|
+
hydra.queue(Typhoeus::Request.new("www.example.com"))
|
|
305
361
|
end
|
|
306
362
|
hydra.run
|
|
307
363
|
```
|
|
308
364
|
|
|
309
|
-
|
|
365
|
+
With memoization disabled, Hydra sends both requests:
|
|
310
366
|
|
|
311
367
|
```ruby
|
|
312
368
|
Typhoeus::Config.memoize = false
|
|
313
369
|
|
|
314
370
|
hydra = Typhoeus::Hydra.new(max_concurrency: 1)
|
|
315
371
|
2.times do
|
|
316
|
-
hydra.queue
|
|
372
|
+
hydra.queue(Typhoeus::Request.new("www.example.com"))
|
|
317
373
|
end
|
|
318
374
|
hydra.run
|
|
319
375
|
```
|
|
320
376
|
|
|
321
377
|
### Caching
|
|
322
378
|
|
|
323
|
-
Typhoeus
|
|
379
|
+
Typhoeus can cache responses. A cache store implements `get(request)` and
|
|
380
|
+
`set(request, response)`, and a cache hit is delivered through the request's
|
|
381
|
+
`on_complete` callback.
|
|
324
382
|
|
|
325
383
|
```ruby
|
|
326
384
|
class Cache
|
|
@@ -345,7 +403,8 @@ Typhoeus.get("www.example.com").cached?
|
|
|
345
403
|
#=> true
|
|
346
404
|
```
|
|
347
405
|
|
|
348
|
-
|
|
406
|
+
Typhoeus ships adapters for [Dalli](https://github.com/mperham/dalli), Rails,
|
|
407
|
+
and [Redis](https://github.com/redis/redis-rb):
|
|
349
408
|
|
|
350
409
|
```ruby
|
|
351
410
|
require "typhoeus/cache/dalli"
|
|
@@ -354,16 +413,12 @@ dalli = Dalli::Client.new(...)
|
|
|
354
413
|
Typhoeus::Config.cache = Typhoeus::Cache::Dalli.new(dalli)
|
|
355
414
|
```
|
|
356
415
|
|
|
357
|
-
For use with Rails:
|
|
358
|
-
|
|
359
416
|
```ruby
|
|
360
417
|
require "typhoeus/cache/rails"
|
|
361
418
|
|
|
362
419
|
Typhoeus::Config.cache = Typhoeus::Cache::Rails.new
|
|
363
420
|
```
|
|
364
421
|
|
|
365
|
-
For use with [Redis](https://github.com/redis/redis-rb):
|
|
366
|
-
|
|
367
422
|
```ruby
|
|
368
423
|
require "typhoeus/cache/redis"
|
|
369
424
|
|
|
@@ -371,31 +426,38 @@ redis = Redis.new(...)
|
|
|
371
426
|
Typhoeus::Config.cache = Typhoeus::Cache::Redis.new(redis)
|
|
372
427
|
```
|
|
373
428
|
|
|
374
|
-
|
|
375
|
-
|
|
429
|
+
Each adapter accepts a `default_ttl` in seconds for requests without a
|
|
430
|
+
`cache_ttl`:
|
|
376
431
|
|
|
377
|
-
|
|
378
|
-
|
|
432
|
+
```ruby
|
|
433
|
+
Typhoeus::Config.cache = Typhoeus::Cache::Dalli.new(dalli, default_ttl: 3600)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Disable caching for one request with `cache: false`, or pass a different store:
|
|
379
437
|
|
|
380
438
|
```ruby
|
|
381
|
-
cache
|
|
382
|
-
Typhoeus.get("www.example.com", cache:
|
|
439
|
+
Typhoeus.get("www.example.com", cache: false)
|
|
440
|
+
Typhoeus.get("www.example.com", cache: Cache.new)
|
|
383
441
|
```
|
|
384
442
|
|
|
385
|
-
|
|
443
|
+
The cache key includes the URL and all request options except the cache store
|
|
444
|
+
and `cache_ttl`. The store decides where a response is cached and the TTL
|
|
445
|
+
decides how long it is cached; neither changes which response is cached.
|
|
446
|
+
|
|
447
|
+
### Stubbing
|
|
386
448
|
|
|
387
|
-
|
|
388
|
-
|
|
449
|
+
Stub URLs or patterns to keep tests off the network. A stub matches the
|
|
450
|
+
request's base URL.
|
|
389
451
|
|
|
390
452
|
```ruby
|
|
391
453
|
response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
|
|
392
|
-
Typhoeus.stub(
|
|
454
|
+
Typhoeus.stub("www.example.com").and_return(response)
|
|
393
455
|
|
|
394
456
|
Typhoeus.get("www.example.com") == response
|
|
395
457
|
#=> true
|
|
396
458
|
```
|
|
397
459
|
|
|
398
|
-
|
|
460
|
+
Pass a regexp to match multiple URLs:
|
|
399
461
|
|
|
400
462
|
```ruby
|
|
401
463
|
response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
|
|
@@ -405,16 +467,27 @@ Typhoeus.get("www.example.com") == response
|
|
|
405
467
|
#=> true
|
|
406
468
|
```
|
|
407
469
|
|
|
408
|
-
|
|
470
|
+
Pass an array to return responses in sequence:
|
|
409
471
|
|
|
410
472
|
```ruby
|
|
411
|
-
Typhoeus.stub(
|
|
473
|
+
Typhoeus.stub("www.example.com").and_return([response1, response2])
|
|
474
|
+
|
|
475
|
+
Typhoeus.get("www.example.com") == response1 #=> true
|
|
476
|
+
Typhoeus.get("www.example.com") == response2 #=> true
|
|
477
|
+
```
|
|
412
478
|
|
|
413
|
-
Typhoeus.
|
|
414
|
-
Typhoeus.
|
|
479
|
+
To make a real request while `Typhoeus::Config.block_connection` is enabled,
|
|
480
|
+
wrap it in `Typhoeus.with_connection`. Stubs are bypassed inside the block,
|
|
481
|
+
which lets a stub forward to a real service:
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
Typhoeus::Config.block_connection = true
|
|
485
|
+
Typhoeus.stub("http://elastic:9200") do |request|
|
|
486
|
+
Typhoeus.with_connection { request.run }
|
|
487
|
+
end
|
|
415
488
|
```
|
|
416
489
|
|
|
417
|
-
|
|
490
|
+
Clear expectations between tests, or stubs will leak into later examples:
|
|
418
491
|
|
|
419
492
|
```ruby
|
|
420
493
|
RSpec.configure do |config|
|
|
@@ -426,31 +499,79 @@ end
|
|
|
426
499
|
|
|
427
500
|
### Timeouts
|
|
428
501
|
|
|
429
|
-
|
|
502
|
+
Typhoeus does not raise on HTTP timeouts. Check the response instead:
|
|
430
503
|
|
|
431
504
|
```ruby
|
|
432
|
-
Typhoeus.get("www.example.com", timeout: 1)
|
|
505
|
+
response = Typhoeus.get("www.example.com", timeout: 1)
|
|
506
|
+
response.timed_out? #=> true
|
|
507
|
+
response.success? #=> false
|
|
433
508
|
```
|
|
434
509
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
There are two different timeouts available: [`timeout`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTIMEOUT)
|
|
510
|
+
Two timeouts are available: [`timeout`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTIMEOUT)
|
|
438
511
|
and [`connecttimeout`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT).
|
|
439
|
-
`timeout`
|
|
440
|
-
|
|
512
|
+
`timeout` limits the entire request, while `connecttimeout` limits only the
|
|
513
|
+
connection phase. Both are in seconds.
|
|
514
|
+
|
|
515
|
+
`timeout_ms` and `connecttimeout_ms` offer millisecond precision, but are not
|
|
516
|
+
available on every platform (for example, Linux without `nosignal: true`).
|
|
517
|
+
When a timeout is a float, Typhoeus also sets the corresponding `*_ms` option
|
|
518
|
+
and rounds the seconds value up.
|
|
519
|
+
|
|
520
|
+
DNS timeouts under one second require libcurl built with an asynchronous
|
|
521
|
+
resolver.
|
|
441
522
|
|
|
442
|
-
|
|
443
|
-
|
|
523
|
+
`timeout` defaults to 0, which means no transfer timeout. `connecttimeout`
|
|
524
|
+
defaults to 300 seconds; passing 0 also uses 300 seconds.
|
|
525
|
+
|
|
526
|
+
### Threads and forking
|
|
527
|
+
|
|
528
|
+
Typhoeus delegates handle management to Ethon. Ethon raises
|
|
529
|
+
`Ethon::Errors::ForkSafety` for `Ethon::Easy` and `Ethon::Multi` handles
|
|
530
|
+
inherited across `fork`. Typhoeus clears its easy pool in the child process and
|
|
531
|
+
creates a hydra's native multi handle lazily, so creating or memoizing a hydra
|
|
532
|
+
during boot does not initialize libcurl. Typhoeus cannot reset libcurl's
|
|
533
|
+
process-global state. If libcurl is initialized before forking, for example by
|
|
534
|
+
making a request during boot, use `fork` followed by `exec`, or fork before the
|
|
535
|
+
first request.
|
|
536
|
+
|
|
537
|
+
On macOS, libcurl's global initialization can touch Objective-C and crash a
|
|
538
|
+
forked child with `objc_initializeAfterForkError`. Initialize libcurl in the
|
|
539
|
+
parent before the workers fork:
|
|
540
|
+
|
|
541
|
+
```ruby
|
|
542
|
+
# config/initializers/ethon.rb in Rails, or before_fork in Puma
|
|
543
|
+
require "ethon"
|
|
544
|
+
Ethon::Curl.init
|
|
545
|
+
```
|
|
444
546
|
|
|
445
|
-
|
|
547
|
+
If you cannot initialize libcurl before forking, set
|
|
548
|
+
`OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` in the environment. It skips the
|
|
549
|
+
safety check in the child, so prefer initializing libcurl before the fork.
|
|
446
550
|
|
|
447
|
-
|
|
551
|
+
Typhoeus keeps at most `Typhoeus::Config.max_idle_easies` idle easy handles in
|
|
552
|
+
the pool (20 by default). Handles beyond that limit are cleaned up instead of
|
|
553
|
+
pooled. Set it to `nil` to keep every handle or `0` to disable pooling.
|
|
448
554
|
|
|
449
|
-
|
|
555
|
+
Exceptions raised by `on_body` and `on_complete` callbacks are propagated by
|
|
556
|
+
Ethon. Typhoeus releases the underlying easy handle before re-raising.
|
|
557
|
+
|
|
558
|
+
### Ethon 0.19
|
|
559
|
+
|
|
560
|
+
Typhoeus requires Ethon 0.19. Ethon 0.19 makes several previously
|
|
561
|
+
silent errors visible:
|
|
562
|
+
|
|
563
|
+
- Unknown enum and bitmask option values raise `Ethon::Errors::InvalidValue`,
|
|
564
|
+
and options the loaded libcurl does not know raise
|
|
565
|
+
`Ethon::Errors::UnsupportedOption`. Guard optional options with
|
|
566
|
+
`Ethon::Curl.supports_option?` when you need to support older libcurl builds.
|
|
567
|
+
- Exceptions raised by `on_body` and `on_complete` callbacks are re-raised.
|
|
568
|
+
- Multipart PUT requests are sent as `multipart/form-data`.
|
|
569
|
+
- Pass `errorbuffer: true` to expose libcurl's detailed error as
|
|
570
|
+
`response.error_message`.
|
|
450
571
|
|
|
451
572
|
### Following Redirections
|
|
452
573
|
|
|
453
|
-
|
|
574
|
+
Enable redirects with `followlocation: true`:
|
|
454
575
|
|
|
455
576
|
```ruby
|
|
456
577
|
Typhoeus.get("www.example.com", followlocation: true)
|
|
@@ -458,90 +579,140 @@ Typhoeus.get("www.example.com", followlocation: true)
|
|
|
458
579
|
|
|
459
580
|
### Basic Authentication
|
|
460
581
|
|
|
582
|
+
Pass `userpwd` as `username:password`:
|
|
583
|
+
|
|
461
584
|
```ruby
|
|
462
585
|
Typhoeus::Request.get("www.example.com", userpwd: "user:password")
|
|
463
586
|
```
|
|
464
587
|
|
|
465
588
|
### Compression
|
|
466
589
|
|
|
590
|
+
Use `accept_encoding` to request and decompress a compressed response:
|
|
591
|
+
|
|
467
592
|
```ruby
|
|
468
593
|
Typhoeus.get("www.example.com", accept_encoding: "gzip")
|
|
469
594
|
```
|
|
470
595
|
|
|
471
|
-
|
|
596
|
+
Setting the `Accept-Encoding` header directly does not add libcurl's automatic
|
|
597
|
+
decompression, so the response stays compressed:
|
|
598
|
+
|
|
472
599
|
```ruby
|
|
473
|
-
Typhoeus.get("www.example.com", headers: {"Accept-Encoding" => "gzip"})
|
|
600
|
+
Typhoeus.get("www.example.com", headers: { "Accept-Encoding" => "gzip" })
|
|
474
601
|
```
|
|
475
602
|
|
|
476
|
-
Setting the header hash directly will not include the `--compressed` flag in the libcurl command and therefore libcurl will not decompress the response. If you want the `--compressed` flag to be added automatically, set `:accept_encoding` Typhoeus option.
|
|
477
|
-
|
|
478
|
-
|
|
479
603
|
### Cookies
|
|
480
604
|
|
|
605
|
+
Pass a file to read cookies from and a file to write them to:
|
|
606
|
+
|
|
481
607
|
```ruby
|
|
482
|
-
Typhoeus::Request.get(
|
|
608
|
+
Typhoeus::Request.get(
|
|
609
|
+
"www.example.com",
|
|
610
|
+
cookiefile: "/path/to/file",
|
|
611
|
+
cookiejar: "/path/to/file"
|
|
612
|
+
)
|
|
483
613
|
```
|
|
484
614
|
|
|
485
|
-
|
|
486
|
-
|
|
615
|
+
`cookiefile` is read before the request and `cookiejar` is written after it.
|
|
616
|
+
Use the same path for both if you want one file for reading and writing.
|
|
487
617
|
|
|
488
|
-
### Other
|
|
618
|
+
### Other curl options
|
|
489
619
|
|
|
490
|
-
|
|
620
|
+
Any libcurl option can be passed as a request option. See the
|
|
621
|
+
[Ethon option documentation](http://rubydoc.info/github/typhoeus/ethon/Ethon/Easy/Options).
|
|
491
622
|
|
|
492
|
-
|
|
623
|
+
An empty header value suppresses libcurl's default header. To send a header
|
|
624
|
+
with an empty value, end the key with a semicolon:
|
|
625
|
+
|
|
626
|
+
```ruby
|
|
627
|
+
Typhoeus.get("www.example.com", headers: { "X-Empty;" => "" })
|
|
628
|
+
```
|
|
493
629
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
verifiable. You must also have libcurl built with SSL support enabled. You can
|
|
498
|
-
check that by doing this:
|
|
630
|
+
Typhoeus sends an empty `Expect` header by default to suppress the
|
|
631
|
+
`100-continue` handshake. Pass `nil` to leave the decision to libcurl, which
|
|
632
|
+
adds the header for uploads with an unknown size and for multipart bodies:
|
|
499
633
|
|
|
634
|
+
```ruby
|
|
635
|
+
Typhoeus.post("www.example.com", headers: { "Expect" => nil }, body: data)
|
|
500
636
|
```
|
|
637
|
+
|
|
638
|
+
### SSL
|
|
639
|
+
|
|
640
|
+
SSL is handled by libcurl. HTTPS URLs work when libcurl was built with SSL
|
|
641
|
+
support and the [CA bundle](http://curl.haxx.se/docs/caextract.html) is valid.
|
|
642
|
+
If `SSL_CERT_FILE` or `SSL_CERT_DIR` is set, Typhoeus uses it as the default CA
|
|
643
|
+
bundle or directory; explicit `:cainfo` and `:capath` options take precedence.
|
|
644
|
+
Check libcurl's build with:
|
|
645
|
+
|
|
646
|
+
```sh
|
|
501
647
|
curl --version
|
|
502
648
|
```
|
|
503
649
|
|
|
504
|
-
|
|
505
|
-
up cert bundle or if you’re hitting a non-verifiable SSL server then you’ll
|
|
506
|
-
have to disable peer verification to make SSL work. Like this:
|
|
650
|
+
If a certificate cannot be verified, disable peer verification:
|
|
507
651
|
|
|
508
652
|
```ruby
|
|
509
653
|
Typhoeus.get("https://www.example.com", ssl_verifypeer: false)
|
|
510
654
|
```
|
|
511
655
|
|
|
512
|
-
If
|
|
513
|
-
name" from curl (ex:- you are trying to access to b.c.host.com when the
|
|
514
|
-
certificate subject is \*.host.com). You can disable host verification. Like
|
|
515
|
-
this:
|
|
656
|
+
If the certificate does not match the host, disable host verification:
|
|
516
657
|
|
|
517
658
|
```ruby
|
|
518
|
-
# host
|
|
659
|
+
# Verify the host name (default)
|
|
519
660
|
Typhoeus.get("https://www.example.com", ssl_verifyhost: 2)
|
|
520
|
-
# host
|
|
661
|
+
# Do not verify the host name
|
|
521
662
|
Typhoeus.get("https://www.example.com", ssl_verifyhost: 0)
|
|
522
663
|
```
|
|
523
664
|
|
|
665
|
+
Only disable these checks in controlled environments.
|
|
666
|
+
|
|
524
667
|
### Verbose debug output
|
|
525
668
|
|
|
526
|
-
|
|
669
|
+
Enable verbose output per request:
|
|
527
670
|
|
|
528
671
|
```ruby
|
|
529
672
|
Typhoeus.get("http://example.com", verbose: true)
|
|
530
673
|
```
|
|
531
674
|
|
|
532
|
-
|
|
675
|
+
Or globally:
|
|
533
676
|
|
|
534
677
|
```ruby
|
|
535
678
|
Typhoeus::Config.verbose = true
|
|
536
679
|
```
|
|
537
680
|
|
|
538
|
-
|
|
539
|
-
|
|
681
|
+
Ethon routes libcurl's verbose output through `Ethon.logger` at the debug
|
|
682
|
+
level, so it no longer prints to STDERR directly. Enable debug logging to see
|
|
683
|
+
it:
|
|
540
684
|
|
|
541
|
-
|
|
685
|
+
```ruby
|
|
686
|
+
require "logger"
|
|
687
|
+
Ethon.logger = Logger.new(STDERR)
|
|
688
|
+
Ethon.logger.level = Logger::DEBUG
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### Thread safety
|
|
692
|
+
|
|
693
|
+
`Typhoeus::Request` and `Typhoeus::Hydra` instances are not thread-safe. An
|
|
694
|
+
`Ethon::Easy` handle is checked out of the process-wide pool for the duration
|
|
695
|
+
of a request and checked back in when it completes, so create requests and
|
|
696
|
+
hydras per thread. `Typhoeus::Hydra.hydra` is memoized per thread.
|
|
697
|
+
`Typhoeus.with_connection` only changes the setting for the current thread.
|
|
542
698
|
|
|
543
|
-
|
|
699
|
+
These are process-wide and should be configured during boot rather than while
|
|
700
|
+
requests are running:
|
|
544
701
|
|
|
702
|
+
- `Typhoeus::Config` (`block_connection`, `cache`, `exclude_features`,
|
|
703
|
+
`max_idle_easies`, `memoize`, timeouts, `proxy`, `user_agent`, `verbose`)
|
|
704
|
+
- `Typhoeus.before`, `Typhoeus.on_complete`, `Typhoeus.on_success`,
|
|
705
|
+
`Typhoeus.on_failure`, `Typhoeus.on_progress`, and `Typhoeus.on_headers`
|
|
706
|
+
- `Typhoeus.stub` and `Typhoeus::Expectation.all`
|
|
707
|
+
- the easy pool used by `Typhoeus::Pool`
|
|
708
|
+
|
|
709
|
+
The easy pool belongs to the process that created it. It keeps at most
|
|
710
|
+
`Typhoeus::Config.max_idle_easies` idle handles (20 by default) and is cleared
|
|
711
|
+
after `fork`.
|
|
712
|
+
|
|
713
|
+
### Default User Agent Header
|
|
714
|
+
|
|
715
|
+
Set a User-Agent for every request:
|
|
545
716
|
|
|
546
717
|
```ruby
|
|
547
718
|
Typhoeus::Config.user_agent = "custom user agent"
|
|
@@ -549,17 +720,24 @@ Typhoeus::Config.user_agent = "custom user agent"
|
|
|
549
720
|
|
|
550
721
|
### Running the specs
|
|
551
722
|
|
|
552
|
-
|
|
723
|
+
Install dependencies and run the suite:
|
|
553
724
|
|
|
554
|
-
```
|
|
725
|
+
```sh
|
|
555
726
|
bundle install
|
|
556
727
|
bundle exec rake
|
|
557
728
|
```
|
|
729
|
+
|
|
730
|
+
Run `COVERAGE=true bundle exec rake` to write an HTML report to `coverage/`.
|
|
731
|
+
|
|
732
|
+
Run `bundle exec rake perf` to benchmark the request hot path. It reports
|
|
733
|
+
throughput, allocations, and retained memory for building, queueing, adding,
|
|
734
|
+
and performing requests.
|
|
735
|
+
|
|
558
736
|
## Semantic Versioning
|
|
559
737
|
|
|
560
738
|
This project conforms to [semver](http://semver.org/).
|
|
561
739
|
|
|
562
|
-
##
|
|
740
|
+
## License
|
|
563
741
|
|
|
564
742
|
(The MIT License)
|
|
565
743
|
|