wreq 1.2.11 → 1.2.13
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/Cargo.lock +88 -146
- data/Cargo.toml +10 -7
- data/crates/wreq-util/src/emulate/profile/chrome/http2.rs +1 -1
- data/crates/wreq-util/src/emulate/profile/firefox/http2.rs +2 -2
- data/crates/wreq-util/src/emulate/profile/opera/http2.rs +1 -1
- data/docs/fork-safety.md +51 -0
- data/docs/interrupt-handling.md +134 -0
- data/examples/tls_info.rb +27 -0
- data/lib/wreq.rb +37 -0
- data/lib/wreq_ruby/body.rb +9 -0
- data/lib/wreq_ruby/client.rb +24 -1
- data/lib/wreq_ruby/cookie.rb +9 -0
- data/lib/wreq_ruby/error.rb +15 -0
- data/lib/wreq_ruby/response.rb +44 -0
- data/lib/wreq_ruby/tls.rb +73 -0
- data/src/arch.rs +174 -0
- data/src/client/body/stream.rs +28 -22
- data/src/client/req.rs +119 -122
- data/src/client/resp.rs +120 -47
- data/src/client.rs +103 -44
- data/src/cookie.rs +47 -11
- data/src/error.rs +28 -0
- data/src/lib.rs +3 -1
- data/src/macros.rs +2 -3
- data/src/rt.rs +46 -25
- data/src/tls.rs +51 -0
- data/test/fork_test.rb +106 -0
- data/test/scripts/fork_safety.rb +133 -0
- data/test/scripts/prefork_runtime.rb +95 -0
- data/test/support/tls_server.rb +95 -0
- data/test/tls_info_test.rb +59 -0
- metadata +11 -1
data/docs/fork-safety.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Fork safety
|
|
2
|
+
|
|
3
|
+
## Prefork checklist
|
|
4
|
+
|
|
5
|
+
- `require "wreq"` may run in the parent before workers fork.
|
|
6
|
+
- Create each `Wreq::Client`, `Wreq::Jar`, and `Wreq::BodySender` in the worker
|
|
7
|
+
that will use it.
|
|
8
|
+
- Keep each `Wreq::Response` in the process that received it.
|
|
9
|
+
- Do not start requests or push streaming body data in the parent before workers
|
|
10
|
+
fork.
|
|
11
|
+
- If the parent must use wreq-ruby first, start workers with `spawn` or `exec`
|
|
12
|
+
instead of `fork`.
|
|
13
|
+
|
|
14
|
+
wreq-ruby checks process ownership whenever it exposes guarded native state. It
|
|
15
|
+
does not copy, reset, or rebuild inherited objects.
|
|
16
|
+
|
|
17
|
+
## Loading before fork
|
|
18
|
+
|
|
19
|
+
wreq-ruby creates its process-wide Tokio runtime on the first operation that
|
|
20
|
+
needs it. Requiring the gem does not initialize the runtime, so a prefork server
|
|
21
|
+
may load wreq-ruby during boot. Each worker can then create its own runtime on
|
|
22
|
+
its first request without an `after_fork!` hook.
|
|
23
|
+
|
|
24
|
+
Create clients and other native-backed objects inside the worker. Each client,
|
|
25
|
+
response, body sender, and cookie jar belongs to the process that created it.
|
|
26
|
+
Using an inherited object raises `Wreq::ForkError`, even when the parent never
|
|
27
|
+
started the runtime. wreq-ruby does not rebuild these objects.
|
|
28
|
+
|
|
29
|
+
## Forking after runtime initialization
|
|
30
|
+
|
|
31
|
+
Once the parent starts an HTTP operation or otherwise uses the Tokio runtime, a
|
|
32
|
+
forked child must not reuse it. Tokio's worker threads do not survive `fork`, and
|
|
33
|
+
the inherited connection pool may refer to those missing threads.
|
|
34
|
+
|
|
35
|
+
Operations that need the inherited runtime raise `Wreq::ForkError`. This
|
|
36
|
+
includes requests through new or existing clients, module request methods, and
|
|
37
|
+
streaming request writes. Constructing a new client, body sender, or cookie jar
|
|
38
|
+
does not use the runtime, but runtime-backed operations remain unavailable in
|
|
39
|
+
that child. Retrying them raises the same error.
|
|
40
|
+
|
|
41
|
+
An inherited `Wreq::Response` cannot be used at all. This includes status,
|
|
42
|
+
headers, socket addresses, TLS information, and body methods. Values copied out
|
|
43
|
+
before the fork, such as a `Wreq::StatusCode` or `Wreq::TlsInfo`, are separate
|
|
44
|
+
objects and do not retain access to the response.
|
|
45
|
+
|
|
46
|
+
The parent remains usable. Native objects collected in the child do not destroy
|
|
47
|
+
state owned by the parent process.
|
|
48
|
+
|
|
49
|
+
Use a spawn- or exec-based worker when the parent must perform HTTP work before
|
|
50
|
+
workers start. Requiring the extension again cannot replace an inherited
|
|
51
|
+
runtime.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Interrupt handling policy
|
|
2
|
+
|
|
3
|
+
wreq-ruby must not construct or raise Ruby's built-in `Interrupt` to report a
|
|
4
|
+
request cancellation. This rule applies to the Rust extension and to Ruby
|
|
5
|
+
wrappers in this repository. A pull request that turns a wreq-owned
|
|
6
|
+
cancellation into the built-in class must not be merged.
|
|
7
|
+
|
|
8
|
+
Represent native cancellation as a Rust value until the Ruby-owned calling
|
|
9
|
+
thread has reacquired the GVL. Then map a wreq-owned request cancellation to
|
|
10
|
+
`Wreq::InterruptError`:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
Wreq::InterruptError < Interrupt
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Keep this class outside `StandardError`. A broad transport rescue such as
|
|
17
|
+
`rescue StandardError` must not swallow an interruption.
|
|
18
|
+
|
|
19
|
+
## Why `Interrupt` is reserved
|
|
20
|
+
|
|
21
|
+
Ruby documents `Interrupt` as the exception raised for an interrupt signal,
|
|
22
|
+
usually when the user presses Control-C. Its hierarchy is:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Exception
|
|
26
|
+
└── SignalException
|
|
27
|
+
└── Interrupt
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`Interrupt` is not a `StandardError`. Ruby's default `rescue` catches
|
|
31
|
+
`StandardError`, so it does not catch `Interrupt` or `Wreq::InterruptError`.
|
|
32
|
+
Code that explicitly uses `rescue Interrupt` catches both because
|
|
33
|
+
`Wreq::InterruptError` is a subclass.
|
|
34
|
+
|
|
35
|
+
The exact built-in class therefore carries Ruby-level control-flow meaning. If
|
|
36
|
+
wreq creates that class for its own cancellation, callers cannot tell whether
|
|
37
|
+
Ruby delivered an interrupt or the HTTP library cancelled a request. A
|
|
38
|
+
library-specific subclass preserves that distinction while keeping the
|
|
39
|
+
interruption outside ordinary transport errors.
|
|
40
|
+
|
|
41
|
+
## Required behavior
|
|
42
|
+
|
|
43
|
+
| Event | wreq-ruby behavior |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| Ruby raises its built-in `Interrupt`, including an exception supplied through `Thread#raise` | Propagate the original exception. Do not replace or wrap it. |
|
|
46
|
+
| `Thread#kill`, `Thread#terminate`, or `Thread#exit` stops a thread | Let Ruby perform the fatal thread termination. The native unblock callback may request cancellation, but wreq must not translate the event into `Interrupt`. |
|
|
47
|
+
| wreq's native cancellation path finishes without a pending Ruby exception | Raise `Wreq::InterruptError`. |
|
|
48
|
+
| A connection, timeout, protocol, or other transport operation fails | Raise the matching wreq transport error under `StandardError`. |
|
|
49
|
+
|
|
50
|
+
Ruby's implementation also makes an important distinction here.
|
|
51
|
+
`Thread#raise` queues the exception chosen by the caller. `Thread#kill` queues
|
|
52
|
+
Ruby's internal fatal thread-kill event instead of an `Interrupt` object, and
|
|
53
|
+
its termination is asynchronous. Once a no-GVL callback returns, Ruby handles
|
|
54
|
+
that fatal event after reacquiring the GVL and before the native call can return
|
|
55
|
+
normally to wreq's error mapper.
|
|
56
|
+
|
|
57
|
+
## Native no-GVL boundary
|
|
58
|
+
|
|
59
|
+
There are two separate rules at this boundary:
|
|
60
|
+
|
|
61
|
+
1. A Tokio worker, other Rust background thread, no-GVL callback, or UBF must
|
|
62
|
+
not construct or raise any Ruby exception.
|
|
63
|
+
2. Rust code running on the Ruby-owned calling thread with the GVL may construct
|
|
64
|
+
Ruby exceptions, but it must not turn a wreq-owned cancellation into Ruby's
|
|
65
|
+
built-in `Interrupt`.
|
|
66
|
+
|
|
67
|
+
Requests run through `rb_thread_call_without_gvl`. Ruby's C API documents this
|
|
68
|
+
sequence:
|
|
69
|
+
|
|
70
|
+
1. Handle pending interrupts.
|
|
71
|
+
2. Release the GVL.
|
|
72
|
+
3. Run the native callback.
|
|
73
|
+
4. Reacquire the GVL.
|
|
74
|
+
5. Handle interrupts received while the callback was running.
|
|
75
|
+
|
|
76
|
+
Ruby may call the unblock function, or UBF, when another thread interacts with
|
|
77
|
+
the blocked thread. The UBF is a request to stop the native operation. It does
|
|
78
|
+
not identify which Ruby exception, if any, is pending.
|
|
79
|
+
|
|
80
|
+
The UBF in [`src/gvl.rs`](../src/gvl.rs) must only signal cancellation. It must
|
|
81
|
+
not call Ruby APIs or raise an exception while the GVL is released. The request
|
|
82
|
+
future returns its result as a Rust value. Only after the no-GVL call returns
|
|
83
|
+
to the Ruby-owned thread with the GVL may [`src/rt.rs`](../src/rt.rs) map a
|
|
84
|
+
wreq-owned cancellation to the `Wreq::InterruptError` defined in
|
|
85
|
+
[`src/error.rs`](../src/error.rs).
|
|
86
|
+
|
|
87
|
+
Keep cancellation conversion centralized in `rt::block_on`. Request, response,
|
|
88
|
+
and body operations may call `block_on`, but they must not construct their own
|
|
89
|
+
Ruby cancellation exception. `block_on` returns a future's native error
|
|
90
|
+
unchanged so the caller can convert it after the GVL has been reacquired.
|
|
91
|
+
|
|
92
|
+
These forms are forbidden for wreq-owned cancellation:
|
|
93
|
+
|
|
94
|
+
```rust
|
|
95
|
+
MagnusError::new(ruby.exception_interrupt(), "request interrupted")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
raise Interrupt, "request interrupted"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Using `exception_interrupt` as the parent when defining
|
|
103
|
+
`Wreq::InterruptError` is still required. Using it as the class passed to
|
|
104
|
+
`MagnusError::new` is not.
|
|
105
|
+
|
|
106
|
+
## Review checklist
|
|
107
|
+
|
|
108
|
+
- Reject direct construction or raising of Ruby's built-in `Interrupt` for a
|
|
109
|
+
wreq-owned cancellation.
|
|
110
|
+
- Keep `Wreq::InterruptError` as a direct subclass of `Interrupt`.
|
|
111
|
+
- Keep Ruby API calls and exception construction out of the no-GVL callback
|
|
112
|
+
and UBF.
|
|
113
|
+
- Preserve an exception supplied by Ruby through `Thread#raise`.
|
|
114
|
+
- Do not turn `Thread#kill`, `Thread#terminate`, or `Thread#exit` into a new
|
|
115
|
+
exception.
|
|
116
|
+
- Test the real cancellation path, the exception hierarchy, and the
|
|
117
|
+
`StandardError` boundary when changing this code.
|
|
118
|
+
|
|
119
|
+
## Ruby references
|
|
120
|
+
|
|
121
|
+
- [Ruby `Interrupt`](https://docs.ruby-lang.org/en/3.4/Interrupt.html) explains
|
|
122
|
+
that the class represents an interrupt signal, usually Control-C, and
|
|
123
|
+
inherits from `SignalException`.
|
|
124
|
+
- [Ruby's built-in exception hierarchy](https://docs.ruby-lang.org/en/4.0/Exception.html#class-Exception-label-Built-In+Exception+Class+Hierarchy)
|
|
125
|
+
shows that `SignalException` and `StandardError` are separate branches.
|
|
126
|
+
- [`Thread#raise`](https://docs.ruby-lang.org/en/4.0/Thread.html#method-i-raise)
|
|
127
|
+
raises the caller-supplied exception in another thread.
|
|
128
|
+
- [`Thread#kill`](https://docs.ruby-lang.org/en/4.0/Thread.html#method-i-kill)
|
|
129
|
+
documents asynchronous termination and its `terminate` and `exit` aliases.
|
|
130
|
+
- [`rb_thread_call_without_gvl`](https://docs.ruby-lang.org/capi/en/master/d6/dfb/include_2ruby_2thread_8h.html)
|
|
131
|
+
documents interrupt checks, GVL reacquisition, UBF cancellation, and the
|
|
132
|
+
restriction on Ruby API calls from no-GVL callbacks.
|
|
133
|
+
- [Issue #111](https://github.com/SearchApi/wreq-ruby/issues/111) contains the
|
|
134
|
+
original error-hierarchy discussion.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "openssl"
|
|
5
|
+
require_relative "../lib/wreq"
|
|
6
|
+
|
|
7
|
+
url = ARGV.fetch(0, "https://example.com")
|
|
8
|
+
client = Wreq::Client.new(tls_info: true)
|
|
9
|
+
response = client.get(url)
|
|
10
|
+
tls_info = response.tls_info
|
|
11
|
+
response.close
|
|
12
|
+
|
|
13
|
+
abort "TLS information is unavailable for #{url}" unless tls_info
|
|
14
|
+
|
|
15
|
+
p tls_info
|
|
16
|
+
|
|
17
|
+
if (der = tls_info.peer_certificate)
|
|
18
|
+
certificate = OpenSSL::X509::Certificate.new(der)
|
|
19
|
+
puts "Subject: #{certificate.subject}"
|
|
20
|
+
puts "Issuer: #{certificate.issuer}"
|
|
21
|
+
puts "Valid from: #{certificate.not_before}"
|
|
22
|
+
puts "Valid until: #{certificate.not_after}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
chain = tls_info.peer_certificate_chain
|
|
26
|
+
chain_size = chain ? chain.length : "unavailable"
|
|
27
|
+
puts "Certificate chain: #{chain_size}"
|
data/lib/wreq.rb
CHANGED
|
@@ -12,12 +12,40 @@ require_relative "wreq_ruby/http"
|
|
|
12
12
|
require_relative "wreq_ruby/emulate"
|
|
13
13
|
require_relative "wreq_ruby/client"
|
|
14
14
|
require_relative "wreq_ruby/response"
|
|
15
|
+
require_relative "wreq_ruby/tls"
|
|
15
16
|
require_relative "wreq_ruby/body"
|
|
16
17
|
require_relative "wreq_ruby/header"
|
|
17
18
|
require_relative "wreq_ruby/error"
|
|
18
19
|
require_relative "wreq_ruby/cookie"
|
|
19
20
|
|
|
20
21
|
unless defined?(Wreq)
|
|
22
|
+
# An HTTP client backed by a lazily initialized, process-wide Tokio runtime.
|
|
23
|
+
#
|
|
24
|
+
# Loading wreq-ruby before `fork` is supported. The parent must not send a
|
|
25
|
+
# request or perform another operation that starts the runtime before workers
|
|
26
|
+
# are forked. Create clients and begin HTTP work inside each worker so it gets
|
|
27
|
+
# its own runtime and connection pool. Clients, responses, body senders, and
|
|
28
|
+
# cookie jars belong to the process that created them and must be recreated
|
|
29
|
+
# in the worker. wreq-ruby does not rebuild inherited objects.
|
|
30
|
+
#
|
|
31
|
+
# Accessing an inherited native-backed object raises Wreq::ForkError even if
|
|
32
|
+
# the parent did not start the runtime. If the parent did start it, the child
|
|
33
|
+
# also cannot perform new runtime-backed operations. Retrying does not replace
|
|
34
|
+
# either kind of inherited state. Use `spawn` or `exec`, or move the parent's
|
|
35
|
+
# HTTP work until after the workers have been forked.
|
|
36
|
+
#
|
|
37
|
+
# @example Preload the extension, then start HTTP work in the worker
|
|
38
|
+
# require "wreq"
|
|
39
|
+
#
|
|
40
|
+
# Process.fork do
|
|
41
|
+
# client = Wreq::Client.new
|
|
42
|
+
# response = client.get("https://example.com")
|
|
43
|
+
# puts response.status
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# @note Fork safety Create clients, cookie jars, and body senders inside the
|
|
47
|
+
# worker that uses them. Do not carry responses across `fork`.
|
|
48
|
+
# @see https://github.com/SearchApi/wreq-ruby/blob/main/docs/fork-safety.md
|
|
21
49
|
module Wreq
|
|
22
50
|
# Current wreq gem version.
|
|
23
51
|
# @return [String]
|
|
@@ -60,6 +88,7 @@ unless defined?(Wreq)
|
|
|
60
88
|
# @return [Wreq::Response] HTTP response
|
|
61
89
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
62
90
|
# value cannot be converted, validated, or built
|
|
91
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
63
92
|
def self.request(method, url, **options)
|
|
64
93
|
end
|
|
65
94
|
|
|
@@ -93,6 +122,7 @@ unless defined?(Wreq)
|
|
|
93
122
|
# @return [Wreq::Response] HTTP response
|
|
94
123
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
95
124
|
# value cannot be converted, validated, or built
|
|
125
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
96
126
|
def self.get(url, **options)
|
|
97
127
|
end
|
|
98
128
|
|
|
@@ -126,6 +156,7 @@ unless defined?(Wreq)
|
|
|
126
156
|
# @return [Wreq::Response] HTTP response
|
|
127
157
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
128
158
|
# value cannot be converted, validated, or built
|
|
159
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
129
160
|
def self.head(url, **options)
|
|
130
161
|
end
|
|
131
162
|
|
|
@@ -159,6 +190,7 @@ unless defined?(Wreq)
|
|
|
159
190
|
# @return [Wreq::Response] HTTP response
|
|
160
191
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
161
192
|
# value cannot be converted, validated, or built
|
|
193
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
162
194
|
def self.post(url, **options)
|
|
163
195
|
end
|
|
164
196
|
|
|
@@ -192,6 +224,7 @@ unless defined?(Wreq)
|
|
|
192
224
|
# @return [Wreq::Response] HTTP response
|
|
193
225
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
194
226
|
# value cannot be converted, validated, or built
|
|
227
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
195
228
|
def self.put(url, **options)
|
|
196
229
|
end
|
|
197
230
|
|
|
@@ -225,6 +258,7 @@ unless defined?(Wreq)
|
|
|
225
258
|
# @return [Wreq::Response] HTTP response
|
|
226
259
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
227
260
|
# value cannot be converted, validated, or built
|
|
261
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
228
262
|
def self.delete(url, **options)
|
|
229
263
|
end
|
|
230
264
|
|
|
@@ -258,6 +292,7 @@ unless defined?(Wreq)
|
|
|
258
292
|
# @return [Wreq::Response] HTTP response
|
|
259
293
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
260
294
|
# value cannot be converted, validated, or built
|
|
295
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
261
296
|
def self.options(url, **options)
|
|
262
297
|
end
|
|
263
298
|
|
|
@@ -291,6 +326,7 @@ unless defined?(Wreq)
|
|
|
291
326
|
# @return [Wreq::Response] HTTP response
|
|
292
327
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
293
328
|
# value cannot be converted, validated, or built
|
|
329
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
294
330
|
def self.trace(url, **options)
|
|
295
331
|
end
|
|
296
332
|
|
|
@@ -324,6 +360,7 @@ unless defined?(Wreq)
|
|
|
324
360
|
# @return [Wreq::Response] HTTP response
|
|
325
361
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
326
362
|
# value cannot be converted, validated, or built
|
|
363
|
+
# @raise [Wreq::ForkError] if the child inherited an initialized wreq-ruby runtime
|
|
327
364
|
def self.patch(url, **options)
|
|
328
365
|
end
|
|
329
366
|
end
|
data/lib/wreq_ruby/body.rb
CHANGED
|
@@ -17,6 +17,12 @@ unless defined?(Wreq)
|
|
|
17
17
|
#
|
|
18
18
|
# A sender can be attached to one request. Closing it prevents further writes but
|
|
19
19
|
# retains queued chunks so a request attached afterward can still drain them.
|
|
20
|
+
# Creating a sender does not initialize Tokio. An inherited sender raises
|
|
21
|
+
# Wreq::ForkError before its channel is accessed. A new sender can be
|
|
22
|
+
# created in a child, but pushing data also requires a usable runtime.
|
|
23
|
+
#
|
|
24
|
+
# @note Fork safety Create each sender in the worker that writes to it.
|
|
25
|
+
# Do not pass a sender through `fork`.
|
|
20
26
|
class BodySender
|
|
21
27
|
# Create a bounded request-body sender.
|
|
22
28
|
#
|
|
@@ -33,6 +39,7 @@ unless defined?(Wreq)
|
|
|
33
39
|
# @param data [String] binary chunk
|
|
34
40
|
# @return [nil]
|
|
35
41
|
# @raise [IOError] if the sender or receiving side is closed
|
|
42
|
+
# @raise [Wreq::ForkError] if the sender or runtime belongs to the parent process
|
|
36
43
|
def push(data)
|
|
37
44
|
end
|
|
38
45
|
|
|
@@ -41,6 +48,7 @@ unless defined?(Wreq)
|
|
|
41
48
|
# This operation is idempotent.
|
|
42
49
|
#
|
|
43
50
|
# @return [nil]
|
|
51
|
+
# @raise [Wreq::ForkError] if the sender belongs to the parent process
|
|
44
52
|
def close
|
|
45
53
|
end
|
|
46
54
|
|
|
@@ -50,6 +58,7 @@ unless defined?(Wreq)
|
|
|
50
58
|
# the receiving side.
|
|
51
59
|
#
|
|
52
60
|
# @return [Boolean]
|
|
61
|
+
# @raise [Wreq::ForkError] if the sender belongs to the parent process
|
|
53
62
|
def closed?
|
|
54
63
|
end
|
|
55
64
|
end
|
data/lib/wreq_ruby/client.rb
CHANGED
|
@@ -17,6 +17,15 @@ unless defined?(Wreq)
|
|
|
17
17
|
# native conversion, such as TypeError or Wreq::BuilderError. Request
|
|
18
18
|
# validation finishes before network I/O.
|
|
19
19
|
#
|
|
20
|
+
# A client belongs to the process that created it. An inherited client
|
|
21
|
+
# raises Wreq::ForkError before its connection pool is accessed. Loading
|
|
22
|
+
# the gem before fork is supported, but clients must be created inside the
|
|
23
|
+
# worker. If the parent already started the runtime, new clients can be
|
|
24
|
+
# constructed in the child but cannot send requests.
|
|
25
|
+
#
|
|
26
|
+
# @note Fork safety Create each client in the worker that uses it. An
|
|
27
|
+
# inherited client is never rebuilt automatically.
|
|
28
|
+
#
|
|
20
29
|
# @example Basic usage
|
|
21
30
|
# client = Wreq::Client.new
|
|
22
31
|
# # Use client for HTTP requests
|
|
@@ -130,6 +139,11 @@ unless defined?(Wreq)
|
|
|
130
139
|
# including self-signed or expired ones. Should only be disabled
|
|
131
140
|
# for testing purposes.
|
|
132
141
|
#
|
|
142
|
+
# @param tls_info [Boolean, nil] Retain peer certificate data for HTTPS
|
|
143
|
+
# responses. When true, {Wreq::Response#tls_info} may return a
|
|
144
|
+
# {Wreq::TlsInfo} object. Disabled by default because retaining
|
|
145
|
+
# certificate data uses additional memory.
|
|
146
|
+
#
|
|
133
147
|
# @param no_proxy [Boolean, nil] Disable use of any configured proxy
|
|
134
148
|
# for this client, even if proxy settings are detected from the
|
|
135
149
|
# environment.
|
|
@@ -165,7 +179,7 @@ unless defined?(Wreq)
|
|
|
165
179
|
# value cannot be converted or validated.
|
|
166
180
|
# @raise [Wreq::BuilderError, Wreq::TlsError] if the native client cannot
|
|
167
181
|
# be initialized.
|
|
168
|
-
#
|
|
182
|
+
# @raise [Wreq::ForkError] if :cookie_provider belongs to a parent process.
|
|
169
183
|
# @example Minimal client
|
|
170
184
|
# client = Wreq::Client.new
|
|
171
185
|
#
|
|
@@ -280,6 +294,7 @@ unless defined?(Wreq)
|
|
|
280
294
|
# or unavailable on the current platform
|
|
281
295
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
282
296
|
# value cannot be converted, validated, or built
|
|
297
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
283
298
|
def request(method, url, **options)
|
|
284
299
|
end
|
|
285
300
|
|
|
@@ -313,6 +328,7 @@ unless defined?(Wreq)
|
|
|
313
328
|
# @return [Wreq::Response] HTTP response
|
|
314
329
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
315
330
|
# value cannot be converted, validated, or built
|
|
331
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
316
332
|
def get(url, **options)
|
|
317
333
|
end
|
|
318
334
|
|
|
@@ -346,6 +362,7 @@ unless defined?(Wreq)
|
|
|
346
362
|
# @return [Wreq::Response] HTTP response
|
|
347
363
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
348
364
|
# value cannot be converted, validated, or built
|
|
365
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
349
366
|
def head(url, **options)
|
|
350
367
|
end
|
|
351
368
|
|
|
@@ -379,6 +396,7 @@ unless defined?(Wreq)
|
|
|
379
396
|
# @return [Wreq::Response] HTTP response
|
|
380
397
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
381
398
|
# value cannot be converted, validated, or built
|
|
399
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
382
400
|
def post(url, **options)
|
|
383
401
|
end
|
|
384
402
|
|
|
@@ -412,6 +430,7 @@ unless defined?(Wreq)
|
|
|
412
430
|
# @return [Wreq::Response] HTTP response
|
|
413
431
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
414
432
|
# value cannot be converted, validated, or built
|
|
433
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
415
434
|
def put(url, **options)
|
|
416
435
|
end
|
|
417
436
|
|
|
@@ -445,6 +464,7 @@ unless defined?(Wreq)
|
|
|
445
464
|
# @return [Wreq::Response] HTTP response
|
|
446
465
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
447
466
|
# value cannot be converted, validated, or built
|
|
467
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
448
468
|
def delete(url, **options)
|
|
449
469
|
end
|
|
450
470
|
|
|
@@ -478,6 +498,7 @@ unless defined?(Wreq)
|
|
|
478
498
|
# @return [Wreq::Response] HTTP response
|
|
479
499
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
480
500
|
# value cannot be converted, validated, or built
|
|
501
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
481
502
|
def options(url, **options)
|
|
482
503
|
end
|
|
483
504
|
|
|
@@ -511,6 +532,7 @@ unless defined?(Wreq)
|
|
|
511
532
|
# @return [Wreq::Response] HTTP response
|
|
512
533
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
513
534
|
# value cannot be converted, validated, or built
|
|
535
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
514
536
|
def trace(url, **options)
|
|
515
537
|
end
|
|
516
538
|
|
|
@@ -544,6 +566,7 @@ unless defined?(Wreq)
|
|
|
544
566
|
# @return [Wreq::Response] HTTP response
|
|
545
567
|
# @raise [TypeError, ArgumentError, Wreq::BuilderError] if a known option
|
|
546
568
|
# value cannot be converted, validated, or built
|
|
569
|
+
# @raise [Wreq::ForkError] if the client or runtime belongs to the parent process
|
|
547
570
|
def patch(url, **options)
|
|
548
571
|
end
|
|
549
572
|
end
|
data/lib/wreq_ruby/cookie.rb
CHANGED
|
@@ -158,6 +158,11 @@ unless defined?(Wreq)
|
|
|
158
158
|
# Stores cookies for reuse across requests.
|
|
159
159
|
#
|
|
160
160
|
# Pass a Jar to Wreq::Client as `cookie_provider` to share its cookies.
|
|
161
|
+
# A jar belongs to the process that created it and cannot be inherited
|
|
162
|
+
# across `fork`.
|
|
163
|
+
#
|
|
164
|
+
# @note Fork safety Create a new jar in each worker. wreq-ruby does not
|
|
165
|
+
# copy cookies from an inherited jar.
|
|
161
166
|
class Jar
|
|
162
167
|
# Creates an empty cookie jar.
|
|
163
168
|
# @return [Wreq::Jar]
|
|
@@ -166,6 +171,7 @@ unless defined?(Wreq)
|
|
|
166
171
|
|
|
167
172
|
# Returns all stored cookies.
|
|
168
173
|
# @return [Array<Wreq::Cookie>]
|
|
174
|
+
# @raise [Wreq::ForkError] if the jar belongs to the parent process
|
|
169
175
|
def get_all
|
|
170
176
|
end
|
|
171
177
|
|
|
@@ -174,6 +180,7 @@ unless defined?(Wreq)
|
|
|
174
180
|
# @param url [String] URL that scopes the cookie
|
|
175
181
|
# @return [void]
|
|
176
182
|
# @raise [TypeError] if cookie is neither a String nor Wreq::Cookie
|
|
183
|
+
# @raise [Wreq::ForkError] if the jar belongs to the parent process
|
|
177
184
|
def add(cookie, url)
|
|
178
185
|
end
|
|
179
186
|
|
|
@@ -181,11 +188,13 @@ unless defined?(Wreq)
|
|
|
181
188
|
# @param name [String]
|
|
182
189
|
# @param url [String]
|
|
183
190
|
# @return [void]
|
|
191
|
+
# @raise [Wreq::ForkError] if the jar belongs to the parent process
|
|
184
192
|
def remove(name, url)
|
|
185
193
|
end
|
|
186
194
|
|
|
187
195
|
# Clear all cookies from the jar.
|
|
188
196
|
# @return [void]
|
|
197
|
+
# @raise [Wreq::ForkError] if the jar belongs to the parent process
|
|
189
198
|
def clear
|
|
190
199
|
end
|
|
191
200
|
end
|
data/lib/wreq_ruby/error.rb
CHANGED
|
@@ -11,6 +11,21 @@ unless defined?(Wreq)
|
|
|
11
11
|
# Memory allocation failed.
|
|
12
12
|
class MemoryError < StandardError; end
|
|
13
13
|
|
|
14
|
+
# The child process tried to use native state created by its parent.
|
|
15
|
+
#
|
|
16
|
+
# Tokio worker threads do not survive fork. Inherited connection pools,
|
|
17
|
+
# locks, channels, and response state are also unsafe to use. wreq-ruby
|
|
18
|
+
# raises this error before exposing them. Loading the gem before fork is
|
|
19
|
+
# supported, but native-backed objects must be created in each worker.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# client = Wreq::Client.new
|
|
23
|
+
# Process.fork do
|
|
24
|
+
# client.get("https://example.com") # Raises in the child.
|
|
25
|
+
# end
|
|
26
|
+
# @see https://github.com/SearchApi/wreq-ruby/blob/main/docs/fork-safety.md
|
|
27
|
+
class ForkError < RuntimeError; end
|
|
28
|
+
|
|
14
29
|
# Network connection errors
|
|
15
30
|
|
|
16
31
|
# Connection to the server failed.
|