fast_curl 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00f4a847ee360180fa617212693206970a8e7af85ef02e6370d37cb62d2b384e
4
- data.tar.gz: f5633dc59f41b4b31efa6e07eb32bc6a85b146e85126fa68b24b742294df5e8b
3
+ metadata.gz: 32aca3a9aec734c69bc40aa9b84b519aeddc15defc3ad3a9e8f278f63d9f0ffb
4
+ data.tar.gz: 8037377b3d3e5ab02f4171c3f4ae56e6f42552a6cb5c6d7e2f5fd54921e2cc71
5
5
  SHA512:
6
- metadata.gz: b7f2e299c89da40d0a546e8545310430a46f0cca8abafff53e46953871c4c205d8fc53c16e33e65b9ff3a22c951d212f41f562476977200de84136ce0342cacc
7
- data.tar.gz: a5fa06d7e44c0846de3cccd3c955a136fd7ee05e01611f05b940bf174cbd356315d23418ebc40ea77d94667c928ae3f2c7087c227df0ea7a39c0b3a27f52da77
6
+ metadata.gz: 670eefe8b9fa597e947ae0858bda833d35d15914fbd62e3093483cdf8c2c68560c121586c4b01eec75f81d3a4f6d49d04829d5711180178247f051cb510c8cd6
7
+ data.tar.gz: 709792e880465cc09de983246936ce66a843f5b7899e69ac6f5a390cef35b4cdc8baaf1dbda36abe39627cc2fe457e994076329ab746887d000d1a089e649af7
data/README.md CHANGED
@@ -12,9 +12,9 @@ Ultra-fast parallel HTTP client for Ruby. C extension built on libcurl `curl_mul
12
12
 
13
13
  ## Installation
14
14
 
15
- **Requirements**: Ruby >= 3.1, libcurl
15
+ **Requirements**: Ruby >= 2.7, libcurl
16
16
 
17
- > **Why Ruby 3.1?** The C extension uses `rb_fiber_scheduler_current`, `rb_fiber_scheduler_block` and `rb_fiber_scheduler_unblock` to properly yield control to the Fiber Scheduler during I/O. These APIs are stable starting from Ruby 3.1. Without them, there is no correct way for a C extension to cooperate with the scheduler earlier approaches (`rb_thread_schedule`) hold the GVL and block other fibers.
17
+ > **Fiber Scheduler support requires Ruby >= 3.1.** The C extension uses `rb_fiber_scheduler_current`, `rb_fiber_scheduler_block` and `rb_fiber_scheduler_unblock` to yield control to the Fiber Scheduler during I/O; these APIs are stable from Ruby 3.1. On 2.7 and 3.0 the extension builds and runs correctly, but that code is compiled out so a request made inside a scheduler blocks the whole thread and **no sibling fiber runs until it finishes**. Other OS threads are unaffected, since the GVL is still released. If you use `async`, use Ruby >= 3.1.
18
18
 
19
19
  ```ruby
20
20
  gem 'fast_curl'
@@ -50,16 +50,37 @@ end
50
50
 
51
51
  ### POST with body and headers
52
52
 
53
+ Be explicit about the encoding — `json:` and `form:` set the matching
54
+ `Content-Type` for you:
55
+
53
56
  ```ruby
54
57
  FastCurl.post([
55
58
  {
56
59
  url: "https://api.example.com/users",
57
60
  headers: { "Authorization" => "Bearer token" },
58
- body: { name: "John" }
61
+ json: { name: "John" } # application/json
62
+ },
63
+ {
64
+ url: "https://api.example.com/login",
65
+ form: { user: "john", pass: "x" } # application/x-www-form-urlencoded
66
+ },
67
+ {
68
+ url: "https://api.example.com/blob",
69
+ headers: { "Content-Type" => "application/xml" },
70
+ body: "<user/>" # sent as-is
59
71
  }
60
72
  ])
61
73
  ```
62
74
 
75
+ A raw String `body:` without an explicit `Content-Type` is sent as
76
+ `application/octet-stream`. A Hash `body:` is still encoded as JSON.
77
+
78
+ Query parameters can be passed separately:
79
+
80
+ ```ruby
81
+ FastCurl.get([{ url: "https://api.example.com/search", params: { q: "ruby", page: 2 } }])
82
+ ```
83
+
63
84
  ### First N responses (cancel the rest)
64
85
 
65
86
  ```ruby
@@ -78,13 +99,29 @@ FastCurl.stream_get(urls, connections: 50) do |index, response|
78
99
  end
79
100
  ```
80
101
 
81
- ### Retry functionality (v0.2.0+)
102
+ ### Retries
103
+
104
+ **Only idempotent methods (GET, HEAD, PUT, DELETE, OPTIONS) are retried.**
105
+ Several retryable curl errors — `GOT_NOTHING`, `SEND_ERROR`, `RECV_ERROR`,
106
+ `PARTIAL_FILE` — can occur *after* the server has already accepted and processed
107
+ the request, so replaying a `POST` or `PATCH` may duplicate its side effects.
108
+ If you know the endpoint is safe to replay (e.g. it takes an idempotency key),
109
+ opt in with `retry_non_idempotent: true`.
110
+
111
+ `timeout` applies to a single attempt. Use `total_timeout` to bound the whole
112
+ call, including retries and backoff:
113
+
114
+ ```ruby
115
+ FastCurl.get(urls, timeout: 5, retries: 3, total_timeout: 10_000)
116
+ ```
117
+
118
+ Delays use exponential backoff with full jitter, starting from `retry_delay`.
82
119
 
83
120
  ```ruby
84
121
  # Automatic retry on network errors (timeout, connection issues)
85
122
  results = FastCurl.get([
86
123
  { url: "https://unreliable-api.com/data" }
87
- ], retries: 3, retry_delay: 1000) # 3 retries with 1s delay
124
+ ], retries: 3, retry_delay: 1000) # base delay 1s, doubling with jitter
88
125
 
89
126
  # Retry on specific HTTP status codes
90
127
  results = FastCurl.get([
@@ -109,14 +146,35 @@ end
109
146
 
110
147
  ## Response format
111
148
 
149
+ Every response — successful or not — has the same keys:
150
+
112
151
  ```ruby
113
152
  [index, {
114
- status: 200, # HTTP status code (0 on error)
115
- headers: { "Key" => "Value" },
116
- body: "response body"
153
+ status: 200, # HTTP status code, 0 on error
154
+ headers: { "content-type" => "application/json" },
155
+ body: "response body",
156
+ error: nil, # nil, or :curl_error / :invalid_request /
157
+ # :not_completed / :deadline_exceeded
158
+ error_code: nil, # CURLcode when error == :curl_error
159
+ effective_url: "https://...", # final URL after redirects
160
+ attempts: 1 # attempts made, including the first
117
161
  }]
118
162
  ```
119
163
 
164
+ Check `response[:error]` rather than `response[:status] == 200`; a status of `0`
165
+ always means the request never produced an HTTP response.
166
+
167
+ Header names are normalised to lower case (HTTP/2 sends them that way and
168
+ HTTP/1.1 may not), and lookups are case-insensitive:
169
+
170
+ ```ruby
171
+ response[:headers]["Content-Type"] # => "application/json"
172
+ response[:headers]["content-type"] # => "application/json"
173
+ ```
174
+
175
+ Repeated fields fold into one comma-separated String. `set-cookie` cannot be
176
+ folded and is **always** an Array, even for a single cookie.
177
+
120
178
  ## Available methods
121
179
 
122
180
  | Method | Description |
@@ -137,10 +195,32 @@ end
137
195
  | Option | Default | Description |
138
196
  |---|---|---|
139
197
  | `connections` | 20 | Max parallel connections |
140
- | `timeout` | 30 | Per-request timeout in seconds |
141
- | `retries` | 1 | Number of retry attempts (0-10) |
142
- | `retry_delay` | 0 | Delay between retries in milliseconds |
198
+ | `timeout` | 30 | Timeout for a single attempt, in seconds (1-300) |
199
+ | `connect_timeout` | 10000 | Connection phase timeout, in milliseconds |
200
+ | `total_timeout` | none | Wall-clock budget for the whole call, in milliseconds |
201
+ | `retries` | 1 | Retry attempts for idempotent methods (0-10) |
202
+ | `retry_delay` | 100 | Base backoff in milliseconds; doubles with jitter |
143
203
  | `retry_codes` | [] | HTTP status codes to retry on |
204
+ | `retry_non_idempotent` | false | Also retry POST and PATCH |
205
+ | `follow_redirects` | true | Follow `Location` headers |
206
+ | `max_redirects` | 5 | Redirect limit (0-100) |
207
+
208
+ DNS results and TLS sessions are cached process-wide, so repeated calls to the
209
+ same host skip resolution and can resume TLS. TCP connections are pooled only
210
+ within a single call — see Known limitations.
211
+
212
+ ## Known limitations
213
+
214
+ - TCP connections are not reused across separate calls; each call builds its own
215
+ `curl_multi` handle. Sharing libcurl's connection cache across concurrent
216
+ multi handles deadlocks or crashes, so only the DNS and TLS session caches are
217
+ shared.
218
+ - The whole response body is buffered in memory (100 MB cap per response);
219
+ `stream_execute` streams *responses*, not bodies.
220
+ - HTTP/2 multiplexing is enabled, but `connections` caps in-flight requests and
221
+ TCP connections with the same number, so multiplexing cannot be exploited
222
+ beyond that limit.
223
+ - No multipart, cookie jar, proxy or auth helpers yet.
144
224
 
145
225
  ## Performance
146
226
 
@@ -8,6 +8,9 @@ have_header("ruby/fiber/scheduler.h")
8
8
 
9
9
  have_func("curl_multi_wakeup", "curl/curl.h")
10
10
  have_func("rb_fiber_scheduler_current", "ruby.h")
11
+ have_func("rb_fiber_scheduler_block", "ruby.h")
12
+ have_func("rb_fiber_scheduler_unblock", "ruby.h")
13
+ have_func("rb_fiber_current", "ruby.h")
11
14
  have_func("rb_io_wait", "ruby.h")
12
15
 
13
16
  $CFLAGS << " -std=c99 -O2 -Wall -Wextra -Wno-unused-parameter"