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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +217 -1
  3. data/README.md +338 -160
  4. data/UPGRADE.md +29 -0
  5. data/lib/rack/typhoeus/middleware/params_decoder/helper.rb +67 -14
  6. data/lib/rack/typhoeus/middleware/params_decoder.rb +8 -19
  7. data/lib/rack/typhoeus.rb +2 -0
  8. data/lib/typhoeus/adapters/faraday.rb +89 -28
  9. data/lib/typhoeus/cache/dalli.rb +2 -0
  10. data/lib/typhoeus/cache/rails.rb +3 -1
  11. data/lib/typhoeus/cache/redis.rb +2 -0
  12. data/lib/typhoeus/config.rb +51 -7
  13. data/lib/typhoeus/easy_factory.rb +129 -23
  14. data/lib/typhoeus/errors/invalid_option.rb +23 -0
  15. data/lib/typhoeus/errors/no_response.rb +18 -0
  16. data/lib/typhoeus/errors/no_stub.rb +8 -1
  17. data/lib/typhoeus/errors/typhoeus_error.rb +2 -0
  18. data/lib/typhoeus/errors.rb +4 -0
  19. data/lib/typhoeus/expectation.rb +33 -1
  20. data/lib/typhoeus/hydra/queueable.rb +29 -4
  21. data/lib/typhoeus/hydra.rb +349 -21
  22. data/lib/typhoeus/pool.rb +80 -17
  23. data/lib/typhoeus/railtie.rb +4 -2
  24. data/lib/typhoeus/request/actions.rb +2 -0
  25. data/lib/typhoeus/request/callbacks.rb +11 -4
  26. data/lib/typhoeus/request/marshal.rb +5 -2
  27. data/lib/typhoeus/request/streamable.rb +2 -0
  28. data/lib/typhoeus/request.rb +317 -30
  29. data/lib/typhoeus/response/cacheable.rb +2 -0
  30. data/lib/typhoeus/response/header.rb +234 -14
  31. data/lib/typhoeus/response/informations.rb +75 -12
  32. data/lib/typhoeus/response/status.rb +4 -13
  33. data/lib/typhoeus/response.rb +35 -0
  34. data/lib/typhoeus/version.rb +3 -1
  35. data/lib/typhoeus.rb +89 -26
  36. data/typhoeus.gemspec +15 -3
  37. metadata +19 -22
  38. data/lib/typhoeus/hydra/addable.rb +0 -23
  39. data/lib/typhoeus/hydra/before.rb +0 -31
  40. data/lib/typhoeus/hydra/block_connection.rb +0 -35
  41. data/lib/typhoeus/hydra/cacheable.rb +0 -15
  42. data/lib/typhoeus/hydra/memoizable.rb +0 -56
  43. data/lib/typhoeus/hydra/runnable.rb +0 -19
  44. data/lib/typhoeus/hydra/stubbable.rb +0 -28
  45. data/lib/typhoeus/request/before.rb +0 -30
  46. data/lib/typhoeus/request/block_connection.rb +0 -52
  47. data/lib/typhoeus/request/cacheable.rb +0 -38
  48. data/lib/typhoeus/request/memoizable.rb +0 -38
  49. data/lib/typhoeus/request/operations.rb +0 -40
  50. data/lib/typhoeus/request/responseable.rb +0 -29
  51. data/lib/typhoeus/request/stubbable.rb +0 -30
data/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Typhoeus [![Gem Version](https://badge.fury.io/rb/typhoeus.svg)](https://badge.fury.io/rb/typhoeus) [![CI](https://github.com/typhoeus/typhoeus/actions/workflows/ci.yml/badge.svg)](https://github.com/typhoeus/typhoeus/actions/workflows/ci.yml) [![Experimental](https://github.com/typhoeus/typhoeus/actions/workflows/experimental.yml/badge.svg)](https://github.com/typhoeus/typhoeus/actions/workflows/experimental.yml)
2
2
 
3
- Like a modern code version of the mythical beast with 100 serpent heads, Typhoeus runs HTTP requests in parallel while cleanly encapsulating handling logic.
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.map{ hydra.queue(Typhoeus::Request.new("www.example.com", followlocation: true)) }
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
- Run:
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 yourself as:
38
+ Or install it directly:
28
39
 
29
- ```
40
+ ```sh
30
41
  gem install typhoeus
31
42
  ```
32
43
 
33
- ## Project Tracking
44
+ Typhoeus requires Ruby 2.6 or later and Ethon 0.19.
45
+
46
+ ## Documentation
34
47
 
35
- * [API Documentation](https://rubydoc.info/github/typhoeus/typhoeus) (GitHub master)
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
- The primary interface for Typhoeus is comprised of three classes: Request, Response, and Hydra. Request represents an HTTP request object, response represents an HTTP response, and Hydra manages making parallel HTTP connections.
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
- We can see from this that the first argument is the url. The second is a set of options.
54
- The options are all optional. The default for `:method` is `:get`.
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
- When you want to send URL parameters, you can use `:params` hash to do so. Please note that in case of you should send a request via `x-www-form-urlencoded` parameters, you need to use `:body` hash instead. `params` are for URL parameters and `:body` is for the request body.
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 a proxy url to the list of options:
77
+ Add `proxy` to the options:
61
78
 
62
79
  ```ruby
63
- options = {proxy: 'http://myproxy.org'}
64
- req = Typhoeus::Request.new(url, options)
80
+ options = { proxy: "http://myproxy.org" }
81
+ request = Typhoeus::Request.new(url, options)
65
82
  ```
66
83
 
67
- If your proxy requires authentication, add it with `proxyuserpwd` option key:
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: 'http://proxyurl.com', proxyuserpwd: 'user:password'}
71
- req = Typhoeus::Request.new(url, options)
88
+ options = { proxy: "http://proxyurl.com", proxyuserpwd: "user:password" }
89
+ request = Typhoeus::Request.new(url, options)
72
90
  ```
73
91
 
74
- Note that `proxyuserpwd` is a colon-separated username and password, in the vein of basic auth `userpwd` option.
75
-
92
+ Run the request directly or queue it on a hydra:
76
93
 
77
- You can run the query either on its own or through the hydra:
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
- The response object will be set after the request is run.
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 has some convenience methods for performing single HTTP requests. The arguments are the same as those you pass into the request constructor.
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: "whoo, a 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
- When using POST the content-type is set automatically to 'application/x-www-form-urlencoded'. That's not the case for any other method like PUT, PATCH, HEAD and so on, irrespective of whether you are using body or not. To get the same result as POST, i.e. a hash in the body coming through as params in the receiver, you need to set the content-type as shown below:
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.put("www.example.com/posts/1",
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
- You can query the response object to figure out if you had a successful
125
- request or not. Here’s some example code that you might use to handle errors.
126
- The callbacks are executed right after the request is finished, make sure to define
127
- them before running the request.
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
- # hell yeah
178
+ # Request succeeded
135
179
  elsif response.timed_out?
136
- # aw hell no
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
- # Received a non-successful http response.
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 also works with serial (blocking) requests in the same fashion. Both
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
- A File object can be passed as a param for a POST request to handle uploading
156
- files to the server. Typhoeus will upload the file as the original file name
157
- and use Mime::Types to set the content type.
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
- Typhoeus can stream responses. When you're expecting a large response,
173
- set the `on_body` callback on a request. Typhoeus will yield to the callback
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 'huge.iso', 'wb'
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
- # Note that response.body is ""
227
+ # response.body is empty
191
228
  end
192
229
  request.run
193
230
  ```
194
231
 
195
- If you need to interrupt the stream halfway,
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
- This will properly stop the stream internally and avoid any memory leak which
206
- may happen if you interrupt with something like a `return`, `throw` or `raise`.
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
- Generally, you should be running requests through hydra. Here is how that looks:
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
- third_url = response.body
218
- third_request = Typhoeus::Request.new(third_url)
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 first_request
224
- hydra.queue second_request
225
- hydra.run # this is a blocking call that returns once all requests are complete
275
+ hydra.queue(first_request)
276
+ hydra.queue(second_request)
277
+ hydra.run # blocks until all requests are complete
226
278
  ```
227
279
 
228
- The execution of that code goes something like this. The first and second requests are built and queued. When hydra is run the first and second requests run in parallel. When the first request completes, the third request is then built and queued, in this example based on the result of the first request. The moment it is queued Hydra starts executing it. Meanwhile the second request would continue to run (or it could have completed before the first). Once the third request is done, `hydra.run` returns.
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
- How to get an array of response bodies back after executing a queue:
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
- `hydra.run` is a blocking request. You can also use the `on_complete` callback to handle each request as it completes:
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
- #do_something_with response
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 'faraday'
317
+ require "faraday"
263
318
 
264
- conn = Faraday.new(:url => 'http://httppage.com') do |builder|
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('/first')
272
- response2 = conn.get('/second')
326
+ response1 = conn.get("/first")
327
+ response2 = conn.get("/second")
273
328
 
274
- # these will return nil here since the
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
- # after it has been completed the response information is fully available
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 will also handle how many requests you can make in parallel. Things will get flakey if you try to make too many requests at the same time. The built in limit is 200. When more requests than that are queued up, hydra will save them for later and start the requests as others are finished. You can raise or lower the concurrency limit through the Hydra constructor.
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
- Hydra memoizes requests within a single run call. You have to enable memoization.
297
- This will result in a single request being issued. However, the on_complete handlers of both will be called.
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 Typhoeus::Request.new("www.example.com")
360
+ hydra.queue(Typhoeus::Request.new("www.example.com"))
305
361
  end
306
362
  hydra.run
307
363
  ```
308
364
 
309
- This will result in two requests.
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 Typhoeus::Request.new("www.example.com")
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 includes built in support for caching. In the following example, if there is a cache hit, the cached object is passed to the on_complete handler of the request object.
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
- For use with [Dalli](https://github.com/mperham/dalli):
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
- All three of these adapters take an optional keyword argument `default_ttl`, which sets a default
375
- TTL on cached responses (in seconds), for requests which do not have a cache TTL set.
429
+ Each adapter accepts a `default_ttl` in seconds for requests without a
430
+ `cache_ttl`:
376
431
 
377
- You may also selectively choose not to cache by setting `cache` to `false` on a request or to use
378
- a different adapter.
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 = Cache.new
382
- Typhoeus.get("www.example.com", cache: cache)
439
+ Typhoeus.get("www.example.com", cache: false)
440
+ Typhoeus.get("www.example.com", cache: Cache.new)
383
441
  ```
384
442
 
385
- ### Direct Stubbing
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
- Hydra allows you to stub out specific urls and patterns to avoid hitting
388
- remote servers while testing.
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('www.example.com').and_return(response)
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
- The queued request will hit the stub. You can also specify a regex to match urls.
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
- You may also specify an array for the stub to return sequentially.
470
+ Pass an array to return responses in sequence:
409
471
 
410
472
  ```ruby
411
- Typhoeus.stub('www.example.com').and_return([response1, response2])
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.get('www.example.com') == response1 #=> true
414
- Typhoeus.get('www.example.com') == response2 #=> true
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
- When testing make sure to clear your expectations or the stubs will persist between tests. The following can be included in your spec_helper.rb file to do this automatically.
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
- No exceptions are raised on HTTP timeouts. You can check whether a request timed out with the following method:
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).timed_out?
505
+ response = Typhoeus.get("www.example.com", timeout: 1)
506
+ response.timed_out? #=> true
507
+ response.success? #=> false
433
508
  ```
434
509
 
435
- Timed out responses also have their success? method return false.
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` is the time limit for the entire request in seconds.
440
- `connecttimeout` is the time limit for just the connection phase, again in seconds.
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
- There are two additional more fine grained options `timeout_ms` and
443
- `connecttimeout_ms`. These options offer millisecond precision but are not always available (for instance on linux if `nosignal` is not set to true).
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
- When you pass a floating point `timeout` (or `connecttimeout`) Typhoeus will set `timeout_ms` for you if it has not been defined. The actual timeout values passed to curl will always be rounded up.
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
- DNS timeouts of less than one second are not supported unless curl is compiled with an asynchronous resolver.
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
- The default `timeout` is 0 (zero) which means curl never times out during transfer. The default `connecttimeout` is 300 seconds. A `connecttimeout` of 0 will also result in the default `connecttimeout` of 300 seconds.
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
- Use `followlocation: true`, eg:
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
- The above has a different behavior than setting the header directly in the header hash, eg:
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("www.example.com", cookiefile: "/path/to/file", cookiejar: "/path/to/file")
608
+ Typhoeus::Request.get(
609
+ "www.example.com",
610
+ cookiefile: "/path/to/file",
611
+ cookiejar: "/path/to/file"
612
+ )
483
613
  ```
484
614
 
485
- Here, `cookiefile` is a file to read cookies from, and `cookiejar` is a file to write received cookies to.
486
- If you just want cookies enabled, you need to pass the same filename for both options.
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 CURL options
618
+ ### Other curl options
489
619
 
490
- Are available and documented [here](http://rubydoc.info/github/typhoeus/ethon/Ethon/Easy/Options)
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
- ### SSL
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
- SSL comes built in to libcurl so it’s in Typhoeus as well. If you pass in a
495
- url with "https" it should just work assuming that you have your [cert
496
- bundle](http://curl.haxx.se/docs/caextract.html) in order and the server is
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
- Now, even if you have libcurl built with OpenSSL you may still have a messed
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 you are getting "SSL: certificate subject name does not match target host
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 checking enabled
659
+ # Verify the host name (default)
519
660
  Typhoeus.get("https://www.example.com", ssl_verifyhost: 2)
520
- # host checking disabled
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
- It’s sometimes useful to see verbose output from curl. You can enable it on a per-request basis:
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
- or globally:
675
+ Or globally:
533
676
 
534
677
  ```ruby
535
678
  Typhoeus::Config.verbose = true
536
679
  ```
537
680
 
538
- Just remember that libcurl prints it’s debug output to the console (to
539
- STDERR), so you’ll need to run your scripts from the console to see it.
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
- ### Default User Agent Header
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
- In many cases, all HTTP requests made by an application require the same User-Agent header set. Instead of supplying it on a per-request basis by supplying a custom header, it is possible to override it for all requests using:
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
- Running the specs should be as easy as:
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
- ## LICENSE
740
+ ## License
563
741
 
564
742
  (The MIT License)
565
743