fast_curl 0.3.0 → 0.3.1
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/ext/fast_curl/extconf.rb +3 -0
- data/ext/fast_curl/fast_curl.c +718 -307
- data/lib/fast_curl/version.rb +1 -1
- metadata +36 -26
data/ext/fast_curl/fast_curl.c
CHANGED
|
@@ -4,15 +4,32 @@
|
|
|
4
4
|
#ifdef HAVE_RUBY_FIBER_SCHEDULER_H
|
|
5
5
|
#include <ruby/fiber/scheduler.h>
|
|
6
6
|
#endif
|
|
7
|
+
|
|
8
|
+
#if defined(HAVE_RUBY_FIBER_SCHEDULER_H) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT) && \
|
|
9
|
+
defined(HAVE_RB_FIBER_SCHEDULER_BLOCK) && defined(HAVE_RB_FIBER_SCHEDULER_UNBLOCK) && \
|
|
10
|
+
defined(HAVE_RB_FIBER_CURRENT)
|
|
11
|
+
#define FAST_CURL_HAVE_FIBER_SCHEDULER 1
|
|
12
|
+
#endif
|
|
7
13
|
#include <curl/curl.h>
|
|
14
|
+
#include <limits.h>
|
|
8
15
|
#include <stdlib.h>
|
|
9
16
|
#include <string.h>
|
|
17
|
+
#include <strings.h>
|
|
18
|
+
#include <time.h>
|
|
19
|
+
|
|
20
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
21
|
+
#define FAST_CURL_NORETURN __attribute__((noreturn))
|
|
22
|
+
#else
|
|
23
|
+
#define FAST_CURL_NORETURN
|
|
24
|
+
#endif
|
|
10
25
|
|
|
11
26
|
#define MAX_RESPONSE_SIZE (100 * 1024 * 1024)
|
|
12
27
|
#define MAX_REDIRECTS 5
|
|
13
28
|
#define MAX_TIMEOUT 300
|
|
14
29
|
#define MAX_RETRIES 10
|
|
15
30
|
#define MAX_REQUESTS 10000
|
|
31
|
+
#define MAX_CONNECTIONS 100
|
|
32
|
+
#define MAX_RETRY_DELAY_MS 30000
|
|
16
33
|
#define DEFAULT_RETRIES 1
|
|
17
34
|
#define DEFAULT_RETRY_DELAY 0
|
|
18
35
|
#define INITIAL_BUF_CAP 8192
|
|
@@ -27,12 +44,31 @@ static const CURLcode DEFAULT_RETRYABLE_CURLE[] = {
|
|
|
27
44
|
#define DEFAULT_RETRYABLE_CURLE_COUNT \
|
|
28
45
|
(int)(sizeof(DEFAULT_RETRYABLE_CURLE) / sizeof(DEFAULT_RETRYABLE_CURLE[0]))
|
|
29
46
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
typedef enum {
|
|
48
|
+
KEY_STATUS,
|
|
49
|
+
KEY_HEADERS,
|
|
50
|
+
KEY_BODY,
|
|
51
|
+
KEY_ERROR_CODE,
|
|
52
|
+
KEY_URL,
|
|
53
|
+
KEY_METHOD,
|
|
54
|
+
KEY_TIMEOUT,
|
|
55
|
+
KEY_CONNECTIONS,
|
|
56
|
+
KEY_COUNT_OPT,
|
|
57
|
+
KEY_RETRIES,
|
|
58
|
+
KEY_RETRY_DELAY,
|
|
59
|
+
KEY_RETRY_CODES,
|
|
60
|
+
KEY_LAST
|
|
61
|
+
} key_id_t;
|
|
62
|
+
|
|
63
|
+
static ID fast_ids[KEY_LAST];
|
|
64
|
+
static VALUE fast_syms[KEY_LAST];
|
|
65
|
+
|
|
66
|
+
#define SYM(key) fast_syms[key]
|
|
67
|
+
|
|
68
|
+
static const char *const KEY_NAMES[KEY_LAST] = {
|
|
69
|
+
"status", "headers", "body", "error_code", "url", "method",
|
|
70
|
+
"timeout", "connections", "count", "retries", "retry_delay", "retry_codes",
|
|
71
|
+
};
|
|
36
72
|
|
|
37
73
|
typedef struct {
|
|
38
74
|
char *data;
|
|
@@ -64,20 +100,31 @@ static inline void buffer_reset(buffer_t *buf) {
|
|
|
64
100
|
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
65
101
|
buffer_t *buf = (buffer_t *)userdata;
|
|
66
102
|
size_t total = size * nmemb;
|
|
67
|
-
|
|
103
|
+
|
|
104
|
+
if (nmemb != 0 && total / nmemb != size)
|
|
105
|
+
return 0;
|
|
106
|
+
if (total > buf->max_size || buf->len > buf->max_size - total)
|
|
68
107
|
return 0;
|
|
108
|
+
|
|
69
109
|
if (buf->len + total >= buf->cap) {
|
|
70
110
|
size_t new_cap = (buf->cap == 0) ? INITIAL_BUF_CAP : buf->cap;
|
|
71
|
-
while (new_cap <= buf->len + total)
|
|
111
|
+
while (new_cap <= buf->len + total) {
|
|
112
|
+
if (new_cap > buf->max_size / 2) {
|
|
113
|
+
new_cap = buf->max_size;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
72
116
|
new_cap *= 2;
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
}
|
|
118
|
+
if (new_cap < buf->len + total)
|
|
119
|
+
return 0;
|
|
120
|
+
|
|
75
121
|
char *new_data = realloc(buf->data, new_cap);
|
|
76
122
|
if (!new_data)
|
|
77
123
|
return 0;
|
|
78
124
|
buf->data = new_data;
|
|
79
125
|
buf->cap = new_cap;
|
|
80
126
|
}
|
|
127
|
+
|
|
81
128
|
memcpy(buf->data + buf->len, ptr, total);
|
|
82
129
|
buf->len += total;
|
|
83
130
|
return total;
|
|
@@ -118,8 +165,22 @@ static void header_list_reset(header_list_t *h) {
|
|
|
118
165
|
static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
119
166
|
header_list_t *h = (header_list_t *)userdata;
|
|
120
167
|
size_t total = size * nmemb;
|
|
121
|
-
|
|
168
|
+
|
|
169
|
+
if (nmemb != 0 && total / nmemb != size)
|
|
170
|
+
return 0;
|
|
171
|
+
|
|
172
|
+
size_t stripped = total;
|
|
173
|
+
while (stripped > 0 && (ptr[stripped - 1] == '\r' || ptr[stripped - 1] == '\n'))
|
|
174
|
+
stripped--;
|
|
175
|
+
|
|
176
|
+
if (stripped >= 5 && memcmp(ptr, "HTTP/", 5) == 0) {
|
|
177
|
+
header_list_reset(h);
|
|
178
|
+
return total;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (stripped == 0)
|
|
122
182
|
return total;
|
|
183
|
+
|
|
123
184
|
if (h->count >= h->cap) {
|
|
124
185
|
int new_cap = (h->cap == 0) ? INITIAL_HEADER_CAP : h->cap * 2;
|
|
125
186
|
header_entry_t *ne = realloc(h->entries, sizeof(header_entry_t) * new_cap);
|
|
@@ -128,18 +189,17 @@ static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userda
|
|
|
128
189
|
h->entries = ne;
|
|
129
190
|
h->cap = new_cap;
|
|
130
191
|
}
|
|
131
|
-
|
|
132
|
-
while (stripped > 0 && (ptr[stripped - 1] == '\r' || ptr[stripped - 1] == '\n'))
|
|
133
|
-
stripped--;
|
|
192
|
+
|
|
134
193
|
char *entry = malloc(stripped + 1);
|
|
135
194
|
if (!entry)
|
|
136
195
|
return 0;
|
|
137
196
|
memcpy(entry, ptr, stripped);
|
|
138
197
|
entry[stripped] = '\0';
|
|
198
|
+
|
|
139
199
|
h->entries[h->count].str = entry;
|
|
140
200
|
h->entries[h->count].len = stripped;
|
|
141
201
|
h->count++;
|
|
142
|
-
return
|
|
202
|
+
return total;
|
|
143
203
|
}
|
|
144
204
|
|
|
145
205
|
typedef struct {
|
|
@@ -149,17 +209,19 @@ typedef struct {
|
|
|
149
209
|
header_list_t headers;
|
|
150
210
|
struct curl_slist *req_headers;
|
|
151
211
|
int done;
|
|
212
|
+
int active;
|
|
152
213
|
CURLcode curl_result;
|
|
153
214
|
long http_status;
|
|
154
215
|
} request_ctx_t;
|
|
155
216
|
|
|
156
217
|
static inline void request_ctx_init(request_ctx_t *ctx, int index) {
|
|
157
|
-
ctx->easy =
|
|
218
|
+
ctx->easy = NULL;
|
|
158
219
|
ctx->index = index;
|
|
159
220
|
buffer_init(&ctx->body);
|
|
160
221
|
header_list_init(&ctx->headers);
|
|
161
222
|
ctx->req_headers = NULL;
|
|
162
223
|
ctx->done = 0;
|
|
224
|
+
ctx->active = 0;
|
|
163
225
|
ctx->curl_result = CURLE_OK;
|
|
164
226
|
ctx->http_status = 0;
|
|
165
227
|
}
|
|
@@ -175,6 +237,16 @@ static void request_ctx_free(request_ctx_t *ctx) {
|
|
|
175
237
|
curl_slist_free_all(ctx->req_headers);
|
|
176
238
|
ctx->req_headers = NULL;
|
|
177
239
|
}
|
|
240
|
+
ctx->active = 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static int request_ctx_prepare_easy(request_ctx_t *ctx) {
|
|
244
|
+
if (!ctx->easy) {
|
|
245
|
+
ctx->easy = curl_easy_init();
|
|
246
|
+
if (!ctx->easy)
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
return 1;
|
|
178
250
|
}
|
|
179
251
|
|
|
180
252
|
static int request_ctx_reset_for_retry(request_ctx_t *ctx) {
|
|
@@ -192,6 +264,7 @@ static int request_ctx_reset_for_retry(request_ctx_t *ctx) {
|
|
|
192
264
|
if (!ctx->easy)
|
|
193
265
|
return 0;
|
|
194
266
|
ctx->done = 0;
|
|
267
|
+
ctx->active = 0;
|
|
195
268
|
ctx->curl_result = CURLE_OK;
|
|
196
269
|
ctx->http_status = 0;
|
|
197
270
|
return 1;
|
|
@@ -205,10 +278,16 @@ typedef struct {
|
|
|
205
278
|
long timeout_ms;
|
|
206
279
|
int max_connections;
|
|
207
280
|
volatile int cancelled;
|
|
281
|
+
|
|
282
|
+
int active_count;
|
|
283
|
+
int pending_pos;
|
|
284
|
+
int pending_count;
|
|
285
|
+
int *pending_indices;
|
|
208
286
|
} multi_session_t;
|
|
209
287
|
|
|
210
288
|
typedef struct {
|
|
211
289
|
int max_retries;
|
|
290
|
+
int retries_explicit;
|
|
212
291
|
long retry_delay_ms;
|
|
213
292
|
int *retry_http_codes;
|
|
214
293
|
int retry_http_count;
|
|
@@ -222,18 +301,61 @@ static int contains_header_injection(const char *str, long len) {
|
|
|
222
301
|
return 0;
|
|
223
302
|
}
|
|
224
303
|
|
|
225
|
-
|
|
304
|
+
static int is_header_token_char(unsigned char c) {
|
|
305
|
+
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
|
|
306
|
+
return 1;
|
|
307
|
+
switch (c) {
|
|
308
|
+
case '!':
|
|
309
|
+
case '#':
|
|
310
|
+
case '$':
|
|
311
|
+
case '%':
|
|
312
|
+
case '&':
|
|
313
|
+
case '\'':
|
|
314
|
+
case '*':
|
|
315
|
+
case '+':
|
|
316
|
+
case '-':
|
|
317
|
+
case '.':
|
|
318
|
+
case '^':
|
|
319
|
+
case '_':
|
|
320
|
+
case '`':
|
|
321
|
+
case '|':
|
|
322
|
+
case '~':
|
|
323
|
+
return 1;
|
|
324
|
+
default:
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
static int is_valid_header_name(const char *str, long len) {
|
|
330
|
+
if (len <= 0)
|
|
331
|
+
return 0;
|
|
332
|
+
for (long i = 0; i < len; i++) {
|
|
333
|
+
if (!is_header_token_char((unsigned char)str[i]))
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static VALUE hash_aref_symbol_or_string(VALUE hash, VALUE sym, ID id) {
|
|
340
|
+
VALUE value = rb_hash_aref(hash, sym);
|
|
341
|
+
if (!NIL_P(value))
|
|
342
|
+
return value;
|
|
343
|
+
|
|
344
|
+
const char *name = rb_id2name(id);
|
|
345
|
+
return name ? rb_hash_aref(hash, rb_str_new_cstr(name)) : Qnil;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
static inline VALUE hash_aref_key(VALUE hash, key_id_t key) {
|
|
349
|
+
return hash_aref_symbol_or_string(hash, fast_syms[key], fast_ids[key]);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
226
353
|
static VALUE current_fiber_scheduler(void) {
|
|
227
354
|
VALUE sched = rb_fiber_scheduler_current();
|
|
228
355
|
if (sched == Qnil || sched == Qfalse)
|
|
229
356
|
return Qnil;
|
|
230
357
|
return sched;
|
|
231
358
|
}
|
|
232
|
-
#else
|
|
233
|
-
static VALUE current_fiber_scheduler(void) {
|
|
234
|
-
return Qnil;
|
|
235
|
-
}
|
|
236
|
-
#endif
|
|
237
359
|
|
|
238
360
|
typedef struct {
|
|
239
361
|
void *(*func)(void *);
|
|
@@ -268,10 +390,25 @@ static void run_via_fiber_worker(VALUE scheduler, void *(*func)(void *), void *a
|
|
|
268
390
|
rb_fiber_scheduler_block(scheduler, ctx.blocker, Qnil);
|
|
269
391
|
rb_funcall(th, rb_intern("join"), 0);
|
|
270
392
|
}
|
|
393
|
+
#endif
|
|
394
|
+
|
|
395
|
+
static void headers_hash_store(VALUE headers_hash, VALUE key, VALUE val) {
|
|
396
|
+
VALUE existing = rb_hash_aref(headers_hash, key);
|
|
397
|
+
|
|
398
|
+
if (NIL_P(existing)) {
|
|
399
|
+
rb_hash_aset(headers_hash, key, val);
|
|
400
|
+
} else if (RB_TYPE_P(existing, T_ARRAY)) {
|
|
401
|
+
rb_ary_push(existing, val);
|
|
402
|
+
} else {
|
|
403
|
+
VALUE values = rb_ary_new_from_args(2, existing, val);
|
|
404
|
+
rb_hash_aset(headers_hash, key, values);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
271
407
|
|
|
272
408
|
static VALUE build_response(request_ctx_t *ctx) {
|
|
273
409
|
long status = 0;
|
|
274
410
|
curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &status);
|
|
411
|
+
|
|
275
412
|
VALUE headers_hash = rb_hash_new();
|
|
276
413
|
for (int i = 0; i < ctx->headers.count; i++) {
|
|
277
414
|
const char *hdr = ctx->headers.entries[i].str;
|
|
@@ -279,38 +416,41 @@ static VALUE build_response(request_ctx_t *ctx) {
|
|
|
279
416
|
const char *colon = memchr(hdr, ':', hdr_len);
|
|
280
417
|
if (!colon)
|
|
281
418
|
continue;
|
|
419
|
+
|
|
282
420
|
VALUE key = rb_str_new(hdr, colon - hdr);
|
|
283
|
-
const char *vs = colon + 1
|
|
421
|
+
const char *vs = colon + 1;
|
|
422
|
+
const char *ve = hdr + hdr_len;
|
|
423
|
+
|
|
284
424
|
while (vs < ve && (*vs == ' ' || *vs == '\t'))
|
|
285
425
|
vs++;
|
|
286
426
|
while (ve > vs && (*(ve - 1) == ' ' || *(ve - 1) == '\t'))
|
|
287
427
|
ve--;
|
|
428
|
+
|
|
288
429
|
VALUE val = rb_str_new(vs, ve - vs);
|
|
289
|
-
|
|
430
|
+
headers_hash_store(headers_hash, key, val);
|
|
290
431
|
}
|
|
432
|
+
|
|
291
433
|
VALUE body_str =
|
|
292
434
|
ctx->body.data ? rb_str_new(ctx->body.data, ctx->body.len) : rb_str_new_cstr("");
|
|
435
|
+
|
|
293
436
|
VALUE result = rb_hash_new();
|
|
294
|
-
rb_hash_aset(result,
|
|
295
|
-
rb_hash_aset(result,
|
|
296
|
-
rb_hash_aset(result,
|
|
437
|
+
rb_hash_aset(result, SYM(KEY_STATUS), LONG2NUM(status));
|
|
438
|
+
rb_hash_aset(result, SYM(KEY_HEADERS), headers_hash);
|
|
439
|
+
rb_hash_aset(result, SYM(KEY_BODY), body_str);
|
|
297
440
|
return result;
|
|
298
441
|
}
|
|
299
442
|
|
|
300
443
|
static VALUE build_error_response(const char *message) {
|
|
301
444
|
VALUE r = rb_hash_new();
|
|
302
|
-
rb_hash_aset(r,
|
|
303
|
-
rb_hash_aset(r,
|
|
304
|
-
rb_hash_aset(r,
|
|
445
|
+
rb_hash_aset(r, SYM(KEY_STATUS), INT2NUM(0));
|
|
446
|
+
rb_hash_aset(r, SYM(KEY_HEADERS), Qnil);
|
|
447
|
+
rb_hash_aset(r, SYM(KEY_BODY), rb_str_new_cstr(message));
|
|
305
448
|
return r;
|
|
306
449
|
}
|
|
307
450
|
|
|
308
451
|
static VALUE build_error_response_with_code(const char *message, int error_code) {
|
|
309
|
-
VALUE r =
|
|
310
|
-
rb_hash_aset(r,
|
|
311
|
-
rb_hash_aset(r, sym_headers, Qnil);
|
|
312
|
-
rb_hash_aset(r, sym_body, rb_str_new_cstr(message));
|
|
313
|
-
rb_hash_aset(r, sym_error_code, INT2NUM(error_code));
|
|
452
|
+
VALUE r = build_error_response(message);
|
|
453
|
+
rb_hash_aset(r, SYM(KEY_ERROR_CODE), INT2NUM(error_code));
|
|
314
454
|
return r;
|
|
315
455
|
}
|
|
316
456
|
|
|
@@ -354,7 +494,7 @@ static CURLcode setup_basic_options(CURL *easy, const char *url_str, long timeou
|
|
|
354
494
|
static CURLcode setup_security_options(CURL *easy) {
|
|
355
495
|
CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L);
|
|
356
496
|
CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYHOST, 2L);
|
|
357
|
-
#
|
|
497
|
+
#if LIBCURL_VERSION_NUM >= 0x075500
|
|
358
498
|
CURL_SETOPT_CHECK(easy, CURLOPT_PROTOCOLS_STR, "http,https");
|
|
359
499
|
CURL_SETOPT_CHECK(easy, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
|
|
360
500
|
#else
|
|
@@ -364,102 +504,165 @@ static CURLcode setup_security_options(CURL *easy) {
|
|
|
364
504
|
return CURLE_OK;
|
|
365
505
|
}
|
|
366
506
|
|
|
367
|
-
static CURLcode
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
507
|
+
static CURLcode set_body(CURL *easy, VALUE body) {
|
|
508
|
+
VALUE body_str = rb_String(body);
|
|
509
|
+
CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDSIZE, (long)RSTRING_LEN(body_str));
|
|
510
|
+
CURL_SETOPT_CHECK(easy, CURLOPT_COPYPOSTFIELDS, StringValuePtr(body_str));
|
|
511
|
+
RB_GC_GUARD(body_str);
|
|
512
|
+
return CURLE_OK;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
typedef struct {
|
|
516
|
+
const char *name;
|
|
517
|
+
const char *custom;
|
|
518
|
+
int post;
|
|
519
|
+
int nobody;
|
|
520
|
+
int allows_body;
|
|
521
|
+
} http_method_t;
|
|
522
|
+
|
|
523
|
+
static const http_method_t HTTP_METHODS[] = {
|
|
524
|
+
{"GET", NULL, 0, 0, 0}, {"POST", NULL, 1, 0, 1}, {"PUT", "PUT", 0, 0, 1},
|
|
525
|
+
{"DELETE", "DELETE", 0, 0, 1}, {"PATCH", "PATCH", 0, 0, 1}, {"HEAD", NULL, 0, 1, 0},
|
|
526
|
+
{"OPTIONS", "OPTIONS", 0, 0, 0},
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
static const http_method_t *find_http_method(const char *name) {
|
|
530
|
+
for (size_t i = 0; i < sizeof(HTTP_METHODS) / sizeof(HTTP_METHODS[0]); i++) {
|
|
531
|
+
if (strcasecmp(HTTP_METHODS[i].name, name) == 0)
|
|
532
|
+
return &HTTP_METHODS[i];
|
|
381
533
|
}
|
|
534
|
+
return NULL;
|
|
535
|
+
}
|
|
382
536
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
CURL_SETOPT_CHECK(easy,
|
|
537
|
+
static CURLcode apply_http_method(CURL *easy, const http_method_t *method) {
|
|
538
|
+
if (method->post)
|
|
539
|
+
CURL_SETOPT_CHECK(easy, CURLOPT_POST, 1L);
|
|
540
|
+
if (method->custom)
|
|
541
|
+
CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, method->custom);
|
|
542
|
+
if (method->nobody)
|
|
543
|
+
CURL_SETOPT_CHECK(easy, CURLOPT_NOBODY, 1L);
|
|
544
|
+
return CURLE_OK;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
static CURLcode setup_method_and_body(CURL *easy, VALUE method_value, VALUE body) {
|
|
548
|
+
const char *name = NIL_P(method_value) ? "GET" : StringValueCStr(method_value);
|
|
549
|
+
const http_method_t *method = find_http_method(name);
|
|
550
|
+
int has_body = !NIL_P(body);
|
|
551
|
+
|
|
552
|
+
if (!method)
|
|
553
|
+
rb_raise(rb_eArgError, "Unsupported HTTP method: %s", name);
|
|
554
|
+
if (has_body && !method->allows_body)
|
|
555
|
+
rb_raise(rb_eArgError, "%s requests must not include a body", method->name);
|
|
556
|
+
|
|
557
|
+
CURLcode res = apply_http_method(easy, method);
|
|
558
|
+
if (res != CURLE_OK)
|
|
559
|
+
return res;
|
|
560
|
+
|
|
561
|
+
if (has_body) {
|
|
562
|
+
res = set_body(easy, body);
|
|
563
|
+
if (res != CURLE_OK)
|
|
564
|
+
return res;
|
|
386
565
|
}
|
|
566
|
+
|
|
567
|
+
RB_GC_GUARD(method_value);
|
|
568
|
+
RB_GC_GUARD(body);
|
|
387
569
|
return CURLE_OK;
|
|
388
570
|
}
|
|
389
571
|
|
|
572
|
+
static void append_request_header(request_ctx_t *ctx, const char *buf) {
|
|
573
|
+
struct curl_slist *new_headers = curl_slist_append(ctx->req_headers, buf);
|
|
574
|
+
if (!new_headers)
|
|
575
|
+
rb_raise(rb_eNoMemError, "failed to allocate request header");
|
|
576
|
+
ctx->req_headers = new_headers;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
static char *alloc_header_line(const char *key, long key_len, const char *value, long value_len,
|
|
580
|
+
char stack_buf[HEADER_LINE_BUF_SIZE]) {
|
|
581
|
+
int has_value = value && value_len > 0;
|
|
582
|
+
long need = key_len + (has_value ? 2 + value_len : 1) + 1;
|
|
583
|
+
char *buf = need > HEADER_LINE_BUF_SIZE ? malloc((size_t)need) : stack_buf;
|
|
584
|
+
|
|
585
|
+
if (!buf)
|
|
586
|
+
rb_raise(rb_eNoMemError, "failed to allocate request header");
|
|
587
|
+
|
|
588
|
+
memcpy(buf, key, (size_t)key_len);
|
|
589
|
+
if (has_value) {
|
|
590
|
+
buf[key_len] = ':';
|
|
591
|
+
buf[key_len + 1] = ' ';
|
|
592
|
+
memcpy(buf + key_len + 2, value, (size_t)value_len);
|
|
593
|
+
buf[key_len + 2 + value_len] = '\0';
|
|
594
|
+
} else {
|
|
595
|
+
buf[key_len] = ';';
|
|
596
|
+
buf[key_len + 1] = '\0';
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return buf;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
static void append_formatted_header(request_ctx_t *ctx, const char *key, long key_len,
|
|
603
|
+
const char *value, long value_len) {
|
|
604
|
+
char stack_buf[HEADER_LINE_BUF_SIZE];
|
|
605
|
+
char *line = alloc_header_line(key, key_len, value, value_len, stack_buf);
|
|
606
|
+
append_request_header(ctx, line);
|
|
607
|
+
if (line != stack_buf)
|
|
608
|
+
free(line);
|
|
609
|
+
}
|
|
610
|
+
|
|
390
611
|
static int header_iter_cb(VALUE key, VALUE val, VALUE arg) {
|
|
391
612
|
request_ctx_t *ctx = (request_ctx_t *)arg;
|
|
392
613
|
VALUE key_str = rb_String(key);
|
|
614
|
+
VALUE val_str = NIL_P(val) ? Qnil : rb_String(val);
|
|
393
615
|
const char *k = RSTRING_PTR(key_str);
|
|
394
616
|
long klen = RSTRING_LEN(key_str);
|
|
617
|
+
const char *v = NULL;
|
|
618
|
+
long vlen = 0;
|
|
395
619
|
|
|
396
|
-
if (contains_header_injection(k, klen))
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (NIL_P(val) || RSTRING_LEN(rb_String(val)) == 0) {
|
|
400
|
-
char sbuf[HEADER_LINE_BUF_SIZE];
|
|
401
|
-
char *buf = sbuf;
|
|
402
|
-
long need = klen + 2;
|
|
403
|
-
if (need > HEADER_LINE_BUF_SIZE)
|
|
404
|
-
buf = malloc(need);
|
|
405
|
-
if (!buf)
|
|
406
|
-
return ST_CONTINUE;
|
|
407
|
-
memcpy(buf, k, klen);
|
|
408
|
-
buf[klen] = ';';
|
|
409
|
-
buf[klen + 1] = '\0';
|
|
410
|
-
ctx->req_headers = curl_slist_append(ctx->req_headers, buf);
|
|
411
|
-
if (buf != sbuf)
|
|
412
|
-
free(buf);
|
|
413
|
-
} else {
|
|
414
|
-
VALUE val_str = rb_String(val);
|
|
415
|
-
const char *v = RSTRING_PTR(val_str);
|
|
416
|
-
long vlen = RSTRING_LEN(val_str);
|
|
620
|
+
if (!is_valid_header_name(k, klen) || contains_header_injection(k, klen))
|
|
621
|
+
rb_raise(rb_eArgError, "Invalid HTTP header name");
|
|
417
622
|
|
|
623
|
+
if (!NIL_P(val_str) && RSTRING_LEN(val_str) > 0) {
|
|
624
|
+
v = RSTRING_PTR(val_str);
|
|
625
|
+
vlen = RSTRING_LEN(val_str);
|
|
418
626
|
if (contains_header_injection(v, vlen))
|
|
419
|
-
|
|
420
|
-
char sbuf[HEADER_LINE_BUF_SIZE];
|
|
421
|
-
char *buf = sbuf;
|
|
422
|
-
long need = klen + 2 + vlen + 1;
|
|
423
|
-
if (need > HEADER_LINE_BUF_SIZE)
|
|
424
|
-
buf = malloc(need);
|
|
425
|
-
if (!buf)
|
|
426
|
-
return ST_CONTINUE;
|
|
427
|
-
|
|
428
|
-
memcpy(buf, k, klen);
|
|
429
|
-
buf[klen] = ':';
|
|
430
|
-
buf[klen + 1] = ' ';
|
|
431
|
-
memcpy(buf + klen + 2, v, vlen);
|
|
432
|
-
buf[klen + 2 + vlen] = '\0';
|
|
433
|
-
ctx->req_headers = curl_slist_append(ctx->req_headers, buf);
|
|
434
|
-
if (buf != sbuf)
|
|
435
|
-
free(buf);
|
|
627
|
+
rb_raise(rb_eArgError, "Invalid HTTP header value");
|
|
436
628
|
}
|
|
629
|
+
|
|
630
|
+
append_formatted_header(ctx, k, klen, v, vlen);
|
|
631
|
+
|
|
632
|
+
RB_GC_GUARD(key_str);
|
|
633
|
+
RB_GC_GUARD(val_str);
|
|
437
634
|
return ST_CONTINUE;
|
|
438
635
|
}
|
|
439
636
|
|
|
440
637
|
static int setup_easy_handle(request_ctx_t *ctx, VALUE request, long timeout_sec) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
VALUE
|
|
444
|
-
VALUE
|
|
638
|
+
Check_Type(request, T_HASH);
|
|
639
|
+
|
|
640
|
+
VALUE url = hash_aref_key(request, KEY_URL);
|
|
641
|
+
VALUE method = hash_aref_key(request, KEY_METHOD);
|
|
642
|
+
VALUE headers = hash_aref_key(request, KEY_HEADERS);
|
|
643
|
+
VALUE body = hash_aref_key(request, KEY_BODY);
|
|
644
|
+
|
|
445
645
|
if (NIL_P(url))
|
|
446
646
|
return 0;
|
|
647
|
+
|
|
447
648
|
const char *url_str = StringValueCStr(url);
|
|
448
649
|
if (!is_valid_url(url_str))
|
|
449
650
|
rb_raise(rb_eArgError, "Invalid URL: %s", url_str);
|
|
450
651
|
|
|
451
|
-
CURLcode res;
|
|
452
|
-
res = setup_basic_options(ctx->easy, url_str, timeout_sec, ctx);
|
|
652
|
+
CURLcode res = setup_basic_options(ctx->easy, url_str, timeout_sec, ctx);
|
|
453
653
|
if (res != CURLE_OK)
|
|
454
654
|
return 0;
|
|
655
|
+
|
|
455
656
|
res = setup_security_options(ctx->easy);
|
|
456
657
|
if (res != CURLE_OK)
|
|
457
658
|
return 0;
|
|
659
|
+
|
|
458
660
|
res = setup_method_and_body(ctx->easy, method, body);
|
|
459
661
|
if (res != CURLE_OK)
|
|
460
662
|
return 0;
|
|
461
663
|
|
|
462
|
-
if (!NIL_P(headers)
|
|
664
|
+
if (!NIL_P(headers)) {
|
|
665
|
+
Check_Type(headers, T_HASH);
|
|
463
666
|
rb_hash_foreach(headers, header_iter_cb, (VALUE)ctx);
|
|
464
667
|
if (ctx->req_headers) {
|
|
465
668
|
res = curl_easy_setopt(ctx->easy, CURLOPT_HTTPHEADER, ctx->req_headers);
|
|
@@ -480,6 +683,7 @@ static void *poll_without_gvl(void *arg) {
|
|
|
480
683
|
multi_session_t *s = (multi_session_t *)arg;
|
|
481
684
|
if (s->cancelled)
|
|
482
685
|
return NULL;
|
|
686
|
+
|
|
483
687
|
int numfds = 0;
|
|
484
688
|
curl_multi_poll(s->multi, NULL, 0, POLL_TIMEOUT_MS, &numfds);
|
|
485
689
|
curl_multi_perform(s->multi, &s->still_running);
|
|
@@ -501,61 +705,171 @@ typedef struct {
|
|
|
501
705
|
int stream;
|
|
502
706
|
} completion_ctx_t;
|
|
503
707
|
|
|
708
|
+
static VALUE build_result_pair(int index, VALUE response) {
|
|
709
|
+
return rb_ary_new_from_args(2, INT2NUM(index), response);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
static int record_immediate_error(completion_ctx_t *cctx, int index, const char *message) {
|
|
713
|
+
if (cctx->stream || cctx->target > 0) {
|
|
714
|
+
VALUE pair = build_result_pair(index, build_error_response(message));
|
|
715
|
+
|
|
716
|
+
if (cctx->stream)
|
|
717
|
+
rb_yield(pair);
|
|
718
|
+
else
|
|
719
|
+
rb_ary_push(cctx->results, pair);
|
|
720
|
+
|
|
721
|
+
cctx->completed++;
|
|
722
|
+
if (cctx->target > 0 && cctx->completed >= cctx->target)
|
|
723
|
+
return 1;
|
|
724
|
+
}
|
|
725
|
+
return 0;
|
|
726
|
+
}
|
|
727
|
+
|
|
504
728
|
static int process_completed(multi_session_t *session, completion_ctx_t *cctx) {
|
|
505
729
|
CURLMsg *msg;
|
|
506
730
|
int msgs_left;
|
|
731
|
+
|
|
507
732
|
while ((msg = curl_multi_info_read(session->multi, &msgs_left))) {
|
|
508
733
|
if (msg->msg != CURLMSG_DONE)
|
|
509
734
|
continue;
|
|
735
|
+
|
|
510
736
|
request_ctx_t *ctx = NULL;
|
|
511
737
|
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **)&ctx);
|
|
512
738
|
if (!ctx || ctx->done)
|
|
513
739
|
continue;
|
|
740
|
+
|
|
741
|
+
if (ctx->active) {
|
|
742
|
+
curl_multi_remove_handle(session->multi, ctx->easy);
|
|
743
|
+
ctx->active = 0;
|
|
744
|
+
if (session->active_count > 0)
|
|
745
|
+
session->active_count--;
|
|
746
|
+
}
|
|
747
|
+
|
|
514
748
|
ctx->done = 1;
|
|
515
749
|
ctx->curl_result = msg->data.result;
|
|
516
750
|
if (msg->data.result == CURLE_OK)
|
|
517
751
|
curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &ctx->http_status);
|
|
518
|
-
|
|
752
|
+
|
|
753
|
+
if (cctx->stream || cctx->target > 0) {
|
|
519
754
|
VALUE response = (msg->data.result == CURLE_OK)
|
|
520
755
|
? build_response(ctx)
|
|
521
756
|
: build_error_response_with_code(
|
|
522
757
|
curl_easy_strerror(msg->data.result), (int)msg->data.result);
|
|
523
|
-
VALUE pair =
|
|
524
|
-
|
|
525
|
-
cctx->
|
|
526
|
-
|
|
527
|
-
|
|
758
|
+
VALUE pair = build_result_pair(ctx->index, response);
|
|
759
|
+
|
|
760
|
+
if (cctx->stream)
|
|
761
|
+
rb_yield(pair);
|
|
762
|
+
else
|
|
763
|
+
rb_ary_push(cctx->results, pair);
|
|
528
764
|
}
|
|
765
|
+
|
|
766
|
+
cctx->completed++;
|
|
529
767
|
if (cctx->target > 0 && cctx->completed >= cctx->target)
|
|
530
768
|
return 1;
|
|
531
769
|
}
|
|
770
|
+
|
|
771
|
+
return 0;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
static int next_pending_index(multi_session_t *session) {
|
|
775
|
+
if (session->pending_pos >= session->pending_count)
|
|
776
|
+
return -1;
|
|
777
|
+
|
|
778
|
+
if (session->pending_indices)
|
|
779
|
+
return session->pending_indices[session->pending_pos++];
|
|
780
|
+
|
|
781
|
+
return session->pending_pos++;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
static int activate_request(multi_session_t *session, VALUE requests, int idx, int *invalid,
|
|
785
|
+
long timeout_sec) {
|
|
786
|
+
request_ctx_t *ctx = &session->requests[idx];
|
|
787
|
+
|
|
788
|
+
if (invalid[idx] || ctx->done)
|
|
789
|
+
return 0;
|
|
790
|
+
|
|
791
|
+
if (!request_ctx_prepare_easy(ctx))
|
|
792
|
+
return 0;
|
|
793
|
+
|
|
794
|
+
if (!setup_easy_handle(ctx, rb_ary_entry(requests, idx), timeout_sec))
|
|
795
|
+
return 0;
|
|
796
|
+
|
|
797
|
+
CURLMcode mc = curl_multi_add_handle(session->multi, ctx->easy);
|
|
798
|
+
if (mc != CURLM_OK)
|
|
799
|
+
return 0;
|
|
800
|
+
|
|
801
|
+
ctx->active = 1;
|
|
802
|
+
ctx->done = 0;
|
|
803
|
+
session->active_count++;
|
|
804
|
+
return 1;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
static int fill_slots(multi_session_t *session, VALUE requests, int *invalid, long timeout_sec,
|
|
808
|
+
completion_ctx_t *cctx) {
|
|
809
|
+
while (session->active_count < session->max_connections) {
|
|
810
|
+
int idx = next_pending_index(session);
|
|
811
|
+
if (idx < 0)
|
|
812
|
+
break;
|
|
813
|
+
|
|
814
|
+
request_ctx_t *ctx = &session->requests[idx];
|
|
815
|
+
|
|
816
|
+
if (!activate_request(session, requests, idx, invalid, timeout_sec)) {
|
|
817
|
+
invalid[idx] = 1;
|
|
818
|
+
ctx->done = 1;
|
|
819
|
+
ctx->active = 0;
|
|
820
|
+
if (record_immediate_error(cctx, idx, "Invalid request configuration"))
|
|
821
|
+
return 1;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
532
825
|
return 0;
|
|
533
826
|
}
|
|
534
827
|
|
|
535
|
-
static
|
|
828
|
+
static int pending_remaining(multi_session_t *session) {
|
|
829
|
+
return session->pending_pos < session->pending_count;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
static void prepare_pending(multi_session_t *session, int *indices, int count) {
|
|
833
|
+
session->pending_indices = indices;
|
|
834
|
+
session->pending_count = count;
|
|
835
|
+
session->pending_pos = 0;
|
|
836
|
+
session->active_count = 0;
|
|
837
|
+
session->still_running = 0;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
static void run_multi_loop(multi_session_t *session, completion_ctx_t *cctx, VALUE requests,
|
|
841
|
+
int *invalid, long timeout_sec, int *indices, int indices_count) {
|
|
842
|
+
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
536
843
|
VALUE scheduler = current_fiber_scheduler();
|
|
844
|
+
#endif
|
|
845
|
+
prepare_pending(session, indices, indices_count);
|
|
537
846
|
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
847
|
+
if (fill_slots(session, requests, invalid, timeout_sec, cctx))
|
|
848
|
+
return;
|
|
849
|
+
|
|
850
|
+
curl_multi_perform(session->multi, &session->still_running);
|
|
851
|
+
if (process_completed(session, cctx))
|
|
852
|
+
return;
|
|
853
|
+
|
|
854
|
+
while (!session->cancelled && (session->active_count > 0 || pending_remaining(session))) {
|
|
855
|
+
if (fill_slots(session, requests, invalid, timeout_sec, cctx))
|
|
856
|
+
return;
|
|
857
|
+
|
|
858
|
+
if (session->active_count == 0)
|
|
859
|
+
break;
|
|
860
|
+
|
|
861
|
+
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
862
|
+
if (scheduler != Qnil)
|
|
543
863
|
run_via_fiber_worker(scheduler, poll_without_gvl, session);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
process_completed(session, cctx);
|
|
548
|
-
} else {
|
|
549
|
-
curl_multi_perform(session->multi, &session->still_running);
|
|
550
|
-
while (session->still_running > 0) {
|
|
551
|
-
if (session->cancelled)
|
|
552
|
-
break;
|
|
864
|
+
else
|
|
865
|
+
#endif
|
|
553
866
|
rb_thread_call_without_gvl(poll_without_gvl, session, unblock_perform, session);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
process_completed(session, cctx);
|
|
867
|
+
|
|
868
|
+
if (process_completed(session, cctx))
|
|
869
|
+
return;
|
|
558
870
|
}
|
|
871
|
+
|
|
872
|
+
process_completed(session, cctx);
|
|
559
873
|
}
|
|
560
874
|
|
|
561
875
|
static int is_default_retryable_curle(CURLcode code) {
|
|
@@ -568,9 +882,11 @@ static int is_default_retryable_curle(CURLcode code) {
|
|
|
568
882
|
static int should_retry(request_ctx_t *ctx, retry_config_t *rc) {
|
|
569
883
|
if (ctx->curl_result != CURLE_OK)
|
|
570
884
|
return is_default_retryable_curle(ctx->curl_result);
|
|
885
|
+
|
|
571
886
|
for (int i = 0; i < rc->retry_http_count; i++)
|
|
572
887
|
if (rc->retry_http_codes[i] == (int)ctx->http_status)
|
|
573
888
|
return 1;
|
|
889
|
+
|
|
574
890
|
return 0;
|
|
575
891
|
}
|
|
576
892
|
|
|
@@ -586,10 +902,11 @@ static void *sleep_without_gvl(void *arg) {
|
|
|
586
902
|
return NULL;
|
|
587
903
|
}
|
|
588
904
|
|
|
589
|
-
/* FIX #2: Fiber path releases GVL via run_via_fiber_worker */
|
|
590
905
|
static void retry_delay_sleep(long delay_ms) {
|
|
591
906
|
if (delay_ms <= 0)
|
|
592
907
|
return;
|
|
908
|
+
|
|
909
|
+
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
593
910
|
VALUE scheduler = current_fiber_scheduler();
|
|
594
911
|
if (scheduler != Qnil) {
|
|
595
912
|
long remaining = delay_ms;
|
|
@@ -599,70 +916,111 @@ static void retry_delay_sleep(long delay_ms) {
|
|
|
599
916
|
run_via_fiber_worker(scheduler, sleep_without_gvl, &sa);
|
|
600
917
|
remaining -= chunk;
|
|
601
918
|
}
|
|
602
|
-
} else
|
|
919
|
+
} else
|
|
920
|
+
#endif
|
|
921
|
+
{
|
|
603
922
|
sleep_arg_t sa = {.delay_ms = delay_ms};
|
|
604
923
|
rb_thread_call_without_gvl(sleep_without_gvl, &sa, (rb_unblock_function_t *)0, NULL);
|
|
605
924
|
}
|
|
606
925
|
}
|
|
607
926
|
|
|
608
|
-
static void
|
|
609
|
-
*timeout = 30;
|
|
610
|
-
*max_conn = 20;
|
|
927
|
+
static void retry_config_init(retry_config_t *retry_cfg) {
|
|
611
928
|
retry_cfg->max_retries = DEFAULT_RETRIES;
|
|
929
|
+
retry_cfg->retries_explicit = 0;
|
|
612
930
|
retry_cfg->retry_delay_ms = DEFAULT_RETRY_DELAY;
|
|
613
931
|
retry_cfg->retry_http_codes = NULL;
|
|
614
932
|
retry_cfg->retry_http_count = 0;
|
|
615
|
-
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
static long parse_long_option(VALUE options, key_id_t key, const char *name, long min, long max,
|
|
936
|
+
long default_value, int *present) {
|
|
937
|
+
VALUE raw = hash_aref_key(options, key);
|
|
938
|
+
long value;
|
|
939
|
+
|
|
940
|
+
if (present)
|
|
941
|
+
*present = !NIL_P(raw);
|
|
942
|
+
if (NIL_P(raw))
|
|
943
|
+
return default_value;
|
|
944
|
+
|
|
945
|
+
value = NUM2LONG(raw);
|
|
946
|
+
if (value < min || value > max)
|
|
947
|
+
rb_raise(rb_eArgError, "%s must be between %ld and %ld", name, min, max);
|
|
948
|
+
|
|
949
|
+
return value;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
static int parse_int_option(VALUE options, key_id_t key, const char *name, int min, int max,
|
|
953
|
+
int default_value, int *present) {
|
|
954
|
+
return (int)parse_long_option(options, key, name, min, max, default_value, present);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
static void parse_retry_codes(VALUE options, retry_config_t *retry_cfg) {
|
|
958
|
+
VALUE codes = hash_aref_key(options, KEY_RETRY_CODES);
|
|
959
|
+
long len_long;
|
|
960
|
+
int len;
|
|
961
|
+
|
|
962
|
+
if (NIL_P(codes))
|
|
616
963
|
return;
|
|
617
964
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (!NIL_P(c)) {
|
|
629
|
-
int v = NUM2INT(c);
|
|
630
|
-
if (v > 100)
|
|
631
|
-
v = 100;
|
|
632
|
-
else if (v <= 0)
|
|
633
|
-
v = 20;
|
|
634
|
-
*max_conn = v;
|
|
635
|
-
}
|
|
636
|
-
VALUE r = rb_hash_aref(options, sym_retries);
|
|
637
|
-
if (!NIL_P(r)) {
|
|
638
|
-
int v = NUM2INT(r);
|
|
639
|
-
if (v < 0)
|
|
640
|
-
v = 0;
|
|
641
|
-
if (v > MAX_RETRIES)
|
|
642
|
-
v = MAX_RETRIES;
|
|
643
|
-
retry_cfg->max_retries = v;
|
|
644
|
-
}
|
|
645
|
-
VALUE rd = rb_hash_aref(options, sym_retry_delay);
|
|
646
|
-
if (!NIL_P(rd)) {
|
|
647
|
-
long v = NUM2LONG(rd);
|
|
648
|
-
if (v < 0)
|
|
649
|
-
v = 0;
|
|
650
|
-
if (v > 30000)
|
|
651
|
-
v = 30000;
|
|
652
|
-
retry_cfg->retry_delay_ms = v;
|
|
653
|
-
}
|
|
654
|
-
VALUE rc = rb_hash_aref(options, sym_retry_codes);
|
|
655
|
-
if (!NIL_P(rc) && rb_obj_is_kind_of(rc, rb_cArray)) {
|
|
656
|
-
int len = (int)RARRAY_LEN(rc);
|
|
657
|
-
if (len > 0) {
|
|
658
|
-
retry_cfg->retry_http_codes = malloc(sizeof(int) * len);
|
|
659
|
-
if (retry_cfg->retry_http_codes) {
|
|
660
|
-
retry_cfg->retry_http_count = len;
|
|
661
|
-
for (int i = 0; i < len; i++)
|
|
662
|
-
retry_cfg->retry_http_codes[i] = NUM2INT(rb_ary_entry(rc, i));
|
|
663
|
-
}
|
|
664
|
-
}
|
|
965
|
+
Check_Type(codes, T_ARRAY);
|
|
966
|
+
len_long = RARRAY_LEN(codes);
|
|
967
|
+
if (len_long > INT_MAX)
|
|
968
|
+
rb_raise(rb_eArgError, "retry_codes is too large");
|
|
969
|
+
|
|
970
|
+
len = (int)len_long;
|
|
971
|
+
for (int i = 0; i < len; i++) {
|
|
972
|
+
int code = NUM2INT(rb_ary_entry(codes, i));
|
|
973
|
+
if (code < 100 || code > 599)
|
|
974
|
+
rb_raise(rb_eArgError, "retry_codes must contain valid HTTP status codes");
|
|
665
975
|
}
|
|
976
|
+
|
|
977
|
+
if (len == 0)
|
|
978
|
+
return;
|
|
979
|
+
|
|
980
|
+
retry_cfg->retry_http_codes = malloc(sizeof(int) * (size_t)len);
|
|
981
|
+
if (!retry_cfg->retry_http_codes)
|
|
982
|
+
rb_raise(rb_eNoMemError, "failed to allocate retry codes");
|
|
983
|
+
|
|
984
|
+
retry_cfg->retry_http_count = len;
|
|
985
|
+
for (int i = 0; i < len; i++)
|
|
986
|
+
retry_cfg->retry_http_codes[i] = NUM2INT(rb_ary_entry(codes, i));
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
static void parse_options(VALUE options, long *timeout, int *max_conn, retry_config_t *retry_cfg) {
|
|
990
|
+
*timeout = 30;
|
|
991
|
+
*max_conn = 20;
|
|
992
|
+
retry_config_init(retry_cfg);
|
|
993
|
+
|
|
994
|
+
if (NIL_P(options))
|
|
995
|
+
return;
|
|
996
|
+
|
|
997
|
+
Check_Type(options, T_HASH);
|
|
998
|
+
*timeout = parse_long_option(options, KEY_TIMEOUT, "timeout", 1, MAX_TIMEOUT, *timeout, NULL);
|
|
999
|
+
*max_conn = parse_int_option(options, KEY_CONNECTIONS, "connections", 1, MAX_CONNECTIONS,
|
|
1000
|
+
*max_conn, NULL);
|
|
1001
|
+
retry_cfg->max_retries = parse_int_option(options, KEY_RETRIES, "retries", 0, MAX_RETRIES,
|
|
1002
|
+
retry_cfg->max_retries, &retry_cfg->retries_explicit);
|
|
1003
|
+
retry_cfg->retry_delay_ms =
|
|
1004
|
+
parse_long_option(options, KEY_RETRY_DELAY, "retry_delay", 0, MAX_RETRY_DELAY_MS,
|
|
1005
|
+
retry_cfg->retry_delay_ms, NULL);
|
|
1006
|
+
parse_retry_codes(options, retry_cfg);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
static void multi_session_init(multi_session_t *session, CURLM *multi, int count, int max_conn,
|
|
1010
|
+
long timeout_sec) {
|
|
1011
|
+
memset(session, 0, sizeof(*session));
|
|
1012
|
+
session->multi = multi;
|
|
1013
|
+
session->count = count;
|
|
1014
|
+
session->timeout_ms = timeout_sec * 1000;
|
|
1015
|
+
session->max_connections = max_conn;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
static void multi_session_configure(CURLM *multi, int max_conn) {
|
|
1019
|
+
curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, (long)max_conn);
|
|
1020
|
+
curl_multi_setopt(multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long)max_conn);
|
|
1021
|
+
#ifdef CURLPIPE_MULTIPLEX
|
|
1022
|
+
curl_multi_setopt(multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
|
1023
|
+
#endif
|
|
666
1024
|
}
|
|
667
1025
|
|
|
668
1026
|
typedef struct {
|
|
@@ -673,30 +1031,41 @@ typedef struct {
|
|
|
673
1031
|
|
|
674
1032
|
static VALUE cleanup_session(VALUE arg) {
|
|
675
1033
|
cleanup_ctx_t *ctx = (cleanup_ctx_t *)arg;
|
|
1034
|
+
|
|
676
1035
|
if (ctx->session->requests) {
|
|
677
1036
|
for (int i = 0; i < ctx->session->count; i++) {
|
|
678
|
-
if (ctx->session->requests[i].easy)
|
|
1037
|
+
if (ctx->session->requests[i].easy && ctx->session->requests[i].active)
|
|
679
1038
|
curl_multi_remove_handle(ctx->session->multi, ctx->session->requests[i].easy);
|
|
680
1039
|
request_ctx_free(&ctx->session->requests[i]);
|
|
681
1040
|
}
|
|
682
1041
|
free(ctx->session->requests);
|
|
683
1042
|
ctx->session->requests = NULL;
|
|
684
1043
|
}
|
|
1044
|
+
|
|
685
1045
|
if (ctx->invalid) {
|
|
686
1046
|
free(ctx->invalid);
|
|
687
1047
|
ctx->invalid = NULL;
|
|
688
1048
|
}
|
|
1049
|
+
|
|
689
1050
|
if (ctx->session->multi) {
|
|
690
1051
|
curl_multi_cleanup(ctx->session->multi);
|
|
691
1052
|
ctx->session->multi = NULL;
|
|
692
1053
|
}
|
|
1054
|
+
|
|
693
1055
|
if (ctx->retry_cfg && ctx->retry_cfg->retry_http_codes) {
|
|
694
1056
|
free(ctx->retry_cfg->retry_http_codes);
|
|
695
1057
|
ctx->retry_cfg->retry_http_codes = NULL;
|
|
696
1058
|
}
|
|
1059
|
+
|
|
697
1060
|
return Qnil;
|
|
698
1061
|
}
|
|
699
1062
|
|
|
1063
|
+
static FAST_CURL_NORETURN void cleanup_and_raise(cleanup_ctx_t *cleanup, VALUE exception,
|
|
1064
|
+
const char *message) {
|
|
1065
|
+
cleanup_session((VALUE)cleanup);
|
|
1066
|
+
rb_raise(exception, "%s", message);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
700
1069
|
typedef struct {
|
|
701
1070
|
VALUE requests;
|
|
702
1071
|
VALUE options;
|
|
@@ -715,113 +1084,168 @@ static VALUE internal_execute_body(VALUE arg) {
|
|
|
715
1084
|
int *invalid = ea->invalid;
|
|
716
1085
|
retry_config_t *retry_cfg = ea->retry_cfg;
|
|
717
1086
|
long timeout_sec = ea->timeout_sec;
|
|
718
|
-
int count = session->count
|
|
1087
|
+
int count = session->count;
|
|
1088
|
+
int target = ea->target;
|
|
1089
|
+
int stream = ea->stream;
|
|
719
1090
|
|
|
720
|
-
int
|
|
721
|
-
for (int i = 0; i < count; i++) {
|
|
722
|
-
VALUE req = rb_ary_entry(requests, i);
|
|
1091
|
+
for (int i = 0; i < count; i++)
|
|
723
1092
|
request_ctx_init(&session->requests[i], i);
|
|
724
|
-
if (!setup_easy_handle(&session->requests[i], req, timeout_sec)) {
|
|
725
|
-
session->requests[i].done = 1;
|
|
726
|
-
invalid[i] = 1;
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
CURLMcode mc = curl_multi_add_handle(session->multi, session->requests[i].easy);
|
|
730
|
-
if (mc != CURLM_OK) {
|
|
731
|
-
session->requests[i].done = 1;
|
|
732
|
-
invalid[i] = 1;
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
valid_requests++;
|
|
736
|
-
}
|
|
737
|
-
if (valid_requests == 0)
|
|
738
|
-
session->still_running = 0;
|
|
739
1093
|
|
|
740
1094
|
completion_ctx_t cctx;
|
|
741
|
-
cctx.results = stream ? Qnil : rb_ary_new2(count);
|
|
1095
|
+
cctx.results = stream ? Qnil : ((target > 0) ? rb_ary_new2(target) : rb_ary_new2(count));
|
|
742
1096
|
cctx.completed = 0;
|
|
743
1097
|
cctx.target = target;
|
|
744
1098
|
cctx.stream = stream;
|
|
745
|
-
|
|
1099
|
+
|
|
1100
|
+
if (!stream && target <= 0) {
|
|
746
1101
|
for (int i = 0; i < count; i++)
|
|
747
1102
|
rb_ary_store(cctx.results, i, Qnil);
|
|
748
1103
|
}
|
|
749
1104
|
|
|
750
|
-
run_multi_loop(session, &cctx);
|
|
1105
|
+
run_multi_loop(session, &cctx, requests, invalid, timeout_sec, NULL, count);
|
|
751
1106
|
|
|
752
|
-
if (!stream && retry_cfg->max_retries > 0) {
|
|
753
|
-
int prev_all_failed = 0;
|
|
1107
|
+
if (!stream && target <= 0 && retry_cfg->max_retries > 0) {
|
|
754
1108
|
for (int attempt = 0; attempt < retry_cfg->max_retries; attempt++) {
|
|
1109
|
+
int *retry_indices = malloc(sizeof(int) * (size_t)count);
|
|
1110
|
+
if (!retry_indices)
|
|
1111
|
+
rb_raise(rb_eNoMemError, "failed to allocate retry index array");
|
|
1112
|
+
|
|
755
1113
|
int retry_count = 0;
|
|
756
|
-
int *ri = malloc(sizeof(int) * count);
|
|
757
|
-
if (!ri)
|
|
758
|
-
break;
|
|
759
1114
|
for (int i = 0; i < count; i++) {
|
|
760
1115
|
if (invalid[i] || !session->requests[i].done)
|
|
761
1116
|
continue;
|
|
762
1117
|
if (should_retry(&session->requests[i], retry_cfg))
|
|
763
|
-
|
|
1118
|
+
retry_indices[retry_count++] = i;
|
|
764
1119
|
}
|
|
1120
|
+
|
|
765
1121
|
if (retry_count == 0) {
|
|
766
|
-
free(
|
|
767
|
-
break;
|
|
768
|
-
}
|
|
769
|
-
int done_count = 0;
|
|
770
|
-
for (int i = 0; i < count; i++)
|
|
771
|
-
if (!invalid[i] && session->requests[i].done)
|
|
772
|
-
done_count++;
|
|
773
|
-
int all_failed = (retry_count == done_count);
|
|
774
|
-
if (all_failed && prev_all_failed) {
|
|
775
|
-
free(ri);
|
|
1122
|
+
free(retry_indices);
|
|
776
1123
|
break;
|
|
777
1124
|
}
|
|
778
|
-
|
|
1125
|
+
|
|
779
1126
|
retry_delay_sleep(retry_cfg->retry_delay_ms);
|
|
1127
|
+
|
|
1128
|
+
int runnable_count = 0;
|
|
780
1129
|
for (int r = 0; r < retry_count; r++) {
|
|
781
|
-
int idx =
|
|
1130
|
+
int idx = retry_indices[r];
|
|
782
1131
|
request_ctx_t *rc = &session->requests[idx];
|
|
783
|
-
|
|
1132
|
+
|
|
784
1133
|
if (!request_ctx_reset_for_retry(rc)) {
|
|
785
|
-
rc->done = 1;
|
|
786
1134
|
invalid[idx] = 1;
|
|
787
|
-
continue;
|
|
788
|
-
}
|
|
789
|
-
VALUE req = rb_ary_entry(requests, idx);
|
|
790
|
-
if (!setup_easy_handle(rc, req, timeout_sec)) {
|
|
791
1135
|
rc->done = 1;
|
|
792
|
-
invalid[idx] = 1;
|
|
793
1136
|
continue;
|
|
794
1137
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
rc->done = 1;
|
|
798
|
-
invalid[idx] = 1;
|
|
799
|
-
}
|
|
1138
|
+
|
|
1139
|
+
retry_indices[runnable_count++] = idx;
|
|
800
1140
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1141
|
+
|
|
1142
|
+
if (runnable_count > 0) {
|
|
1143
|
+
cctx.completed = 0;
|
|
1144
|
+
run_multi_loop(session, &cctx, requests, invalid, timeout_sec, retry_indices,
|
|
1145
|
+
runnable_count);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
free(retry_indices);
|
|
804
1149
|
}
|
|
805
1150
|
}
|
|
806
1151
|
|
|
807
|
-
if (!stream) {
|
|
1152
|
+
if (!stream && target <= 0) {
|
|
808
1153
|
for (int i = 0; i < count; i++) {
|
|
809
1154
|
request_ctx_t *rc = &session->requests[i];
|
|
810
1155
|
VALUE response;
|
|
1156
|
+
|
|
811
1157
|
if (invalid[i]) {
|
|
812
1158
|
response = build_error_response("Invalid request configuration");
|
|
1159
|
+
} else if (!rc->done) {
|
|
1160
|
+
response = build_error_response("Request was not completed");
|
|
813
1161
|
} else if (rc->curl_result == CURLE_OK) {
|
|
814
1162
|
response = build_response(rc);
|
|
815
1163
|
} else {
|
|
816
1164
|
response = build_error_response_with_code(curl_easy_strerror(rc->curl_result),
|
|
817
1165
|
(int)rc->curl_result);
|
|
818
1166
|
}
|
|
819
|
-
|
|
1167
|
+
|
|
1168
|
+
rb_ary_store(cctx.results, i, build_result_pair(i, response));
|
|
820
1169
|
}
|
|
821
1170
|
}
|
|
1171
|
+
|
|
822
1172
|
return stream ? Qnil : cctx.results;
|
|
823
1173
|
}
|
|
824
1174
|
|
|
1175
|
+
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
1176
|
+
typedef struct {
|
|
1177
|
+
execute_args_t *ea;
|
|
1178
|
+
VALUE scheduler;
|
|
1179
|
+
VALUE blocker;
|
|
1180
|
+
VALUE fiber;
|
|
1181
|
+
VALUE thread;
|
|
1182
|
+
VALUE result;
|
|
1183
|
+
VALUE exception;
|
|
1184
|
+
int state;
|
|
1185
|
+
int finished;
|
|
1186
|
+
} scheduler_execute_ctx_t;
|
|
1187
|
+
|
|
1188
|
+
static VALUE scheduler_execute_thread(void *arg) {
|
|
1189
|
+
scheduler_execute_ctx_t *ctx = (scheduler_execute_ctx_t *)arg;
|
|
1190
|
+
|
|
1191
|
+
ctx->result = rb_protect(internal_execute_body, (VALUE)ctx->ea, &ctx->state);
|
|
1192
|
+
if (ctx->state) {
|
|
1193
|
+
ctx->exception = rb_errinfo();
|
|
1194
|
+
rb_set_errinfo(Qnil);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
ctx->finished = 1;
|
|
1198
|
+
rb_fiber_scheduler_unblock(ctx->scheduler, ctx->blocker, ctx->fiber);
|
|
1199
|
+
return Qnil;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
static void cancel_scheduler_execute(scheduler_execute_ctx_t *ctx) {
|
|
1203
|
+
multi_session_t *session = ctx->ea->session;
|
|
1204
|
+
|
|
1205
|
+
session->cancelled = 1;
|
|
1206
|
+
#ifdef HAVE_CURL_MULTI_WAKEUP
|
|
1207
|
+
if (session->multi)
|
|
1208
|
+
curl_multi_wakeup(session->multi);
|
|
1209
|
+
#endif
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
static VALUE scheduler_execute_wait(VALUE arg) {
|
|
1213
|
+
scheduler_execute_ctx_t *ctx = (scheduler_execute_ctx_t *)arg;
|
|
1214
|
+
|
|
1215
|
+
if (!ctx->finished)
|
|
1216
|
+
rb_fiber_scheduler_block(ctx->scheduler, ctx->blocker, Qnil);
|
|
1217
|
+
|
|
1218
|
+
rb_funcall(ctx->thread, rb_intern("join"), 0);
|
|
1219
|
+
|
|
1220
|
+
if (ctx->state)
|
|
1221
|
+
rb_exc_raise(ctx->exception);
|
|
1222
|
+
|
|
1223
|
+
return ctx->result;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
static VALUE scheduler_execute_ensure(VALUE arg) {
|
|
1227
|
+
scheduler_execute_ctx_t *ctx = (scheduler_execute_ctx_t *)arg;
|
|
1228
|
+
|
|
1229
|
+
if (!NIL_P(ctx->thread)) {
|
|
1230
|
+
if (!ctx->finished)
|
|
1231
|
+
cancel_scheduler_execute(ctx);
|
|
1232
|
+
rb_funcall(ctx->thread, rb_intern("join"), 0);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
return Qnil;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
static VALUE execute_with_fiber_scheduler(VALUE arg) {
|
|
1239
|
+
scheduler_execute_ctx_t *ctx = (scheduler_execute_ctx_t *)arg;
|
|
1240
|
+
|
|
1241
|
+
ctx->blocker = rb_obj_alloc(rb_cObject);
|
|
1242
|
+
ctx->fiber = rb_fiber_current();
|
|
1243
|
+
ctx->thread = rb_thread_create(scheduler_execute_thread, ctx);
|
|
1244
|
+
|
|
1245
|
+
return rb_ensure(scheduler_execute_wait, arg, scheduler_execute_ensure, arg);
|
|
1246
|
+
}
|
|
1247
|
+
#endif
|
|
1248
|
+
|
|
825
1249
|
static VALUE internal_execute(VALUE requests, VALUE options, int target, int stream) {
|
|
826
1250
|
Check_Type(requests, T_ARRAY);
|
|
827
1251
|
|
|
@@ -832,55 +1256,46 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
832
1256
|
rb_raise(rb_eArgError, "too many requests (%ld), maximum is %d", count_long, MAX_REQUESTS);
|
|
833
1257
|
if (count_long > INT_MAX)
|
|
834
1258
|
rb_raise(rb_eArgError, "request count overflows int");
|
|
1259
|
+
|
|
835
1260
|
int count = (int)count_long;
|
|
836
1261
|
|
|
1262
|
+
if (target > 0 && target > count)
|
|
1263
|
+
target = count;
|
|
1264
|
+
|
|
837
1265
|
long timeout_sec;
|
|
838
1266
|
int max_conn;
|
|
839
1267
|
retry_config_t retry_cfg;
|
|
840
1268
|
parse_options(options, &timeout_sec, &max_conn, &retry_cfg);
|
|
841
1269
|
|
|
842
1270
|
if (stream || target > 0) {
|
|
843
|
-
if (retry_cfg.max_retries > 0 && stream)
|
|
844
|
-
rb_warn(
|
|
845
|
-
|
|
846
|
-
if (retry_cfg.max_retries > 0 && target > 0)
|
|
847
|
-
rb_warn(
|
|
848
|
-
|
|
1271
|
+
if (retry_cfg.retries_explicit && retry_cfg.max_retries > 0 && stream)
|
|
1272
|
+
rb_warn(
|
|
1273
|
+
"FastCurl: retries are not supported in stream_execute, ignoring retries option");
|
|
1274
|
+
if (retry_cfg.retries_explicit && retry_cfg.max_retries > 0 && target > 0)
|
|
1275
|
+
rb_warn(
|
|
1276
|
+
"FastCurl: retries are not supported in first_execute, ignoring retries option");
|
|
849
1277
|
retry_cfg.max_retries = 0;
|
|
850
1278
|
}
|
|
851
1279
|
|
|
852
1280
|
multi_session_t session;
|
|
853
|
-
|
|
854
|
-
session
|
|
855
|
-
session.timeout_ms = timeout_sec * 1000;
|
|
856
|
-
session.max_connections = max_conn;
|
|
857
|
-
session.cancelled = 0;
|
|
858
|
-
session.requests = NULL;
|
|
859
|
-
|
|
860
|
-
curl_multi_setopt(session.multi, CURLMOPT_MAXCONNECTS, (long)max_conn);
|
|
861
|
-
curl_multi_setopt(session.multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long)max_conn);
|
|
862
|
-
#ifdef CURLPIPE_MULTIPLEX
|
|
863
|
-
curl_multi_setopt(session.multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
|
864
|
-
#endif
|
|
1281
|
+
int *invalid = NULL;
|
|
1282
|
+
multi_session_init(&session, curl_multi_init(), count, max_conn, timeout_sec);
|
|
865
1283
|
|
|
866
|
-
session.
|
|
867
|
-
if (!session.requests) {
|
|
868
|
-
curl_multi_cleanup(session.multi);
|
|
869
|
-
if (retry_cfg.retry_http_codes)
|
|
870
|
-
free(retry_cfg.retry_http_codes);
|
|
871
|
-
rb_raise(rb_eNoMemError, "failed to allocate request contexts");
|
|
872
|
-
}
|
|
1284
|
+
cleanup_ctx_t cleanup = {.session = &session, .invalid = NULL, .retry_cfg = &retry_cfg};
|
|
873
1285
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
free(session.requests);
|
|
877
|
-
curl_multi_cleanup(session.multi);
|
|
878
|
-
if (retry_cfg.retry_http_codes)
|
|
879
|
-
free(retry_cfg.retry_http_codes);
|
|
880
|
-
rb_raise(rb_eNoMemError, "failed to allocate tracking array");
|
|
881
|
-
}
|
|
1286
|
+
if (!session.multi)
|
|
1287
|
+
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to initialize curl multi handle");
|
|
882
1288
|
|
|
883
|
-
|
|
1289
|
+
multi_session_configure(session.multi, max_conn);
|
|
1290
|
+
|
|
1291
|
+
session.requests = calloc((size_t)count, sizeof(request_ctx_t));
|
|
1292
|
+
if (!session.requests)
|
|
1293
|
+
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to allocate request contexts");
|
|
1294
|
+
|
|
1295
|
+
invalid = calloc((size_t)count, sizeof(int));
|
|
1296
|
+
cleanup.invalid = invalid;
|
|
1297
|
+
if (!invalid)
|
|
1298
|
+
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to allocate tracking array");
|
|
884
1299
|
execute_args_t ea = {
|
|
885
1300
|
.requests = requests,
|
|
886
1301
|
.options = options,
|
|
@@ -891,6 +1306,27 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
891
1306
|
.retry_cfg = &retry_cfg,
|
|
892
1307
|
.timeout_sec = timeout_sec,
|
|
893
1308
|
};
|
|
1309
|
+
|
|
1310
|
+
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
1311
|
+
VALUE scheduler = current_fiber_scheduler();
|
|
1312
|
+
if (scheduler != Qnil && !stream) {
|
|
1313
|
+
scheduler_execute_ctx_t scheduler_ctx = {
|
|
1314
|
+
.ea = &ea,
|
|
1315
|
+
.scheduler = scheduler,
|
|
1316
|
+
.blocker = Qnil,
|
|
1317
|
+
.fiber = Qnil,
|
|
1318
|
+
.thread = Qnil,
|
|
1319
|
+
.result = Qnil,
|
|
1320
|
+
.exception = Qnil,
|
|
1321
|
+
.state = 0,
|
|
1322
|
+
.finished = 0,
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
return rb_ensure(execute_with_fiber_scheduler, (VALUE)&scheduler_ctx, cleanup_session,
|
|
1326
|
+
(VALUE)&cleanup);
|
|
1327
|
+
}
|
|
1328
|
+
#endif
|
|
1329
|
+
|
|
894
1330
|
return rb_ensure(internal_execute_body, (VALUE)&ea, cleanup_session, (VALUE)&cleanup);
|
|
895
1331
|
}
|
|
896
1332
|
|
|
@@ -903,64 +1339,39 @@ static VALUE rb_fast_curl_execute(int argc, VALUE *argv, VALUE self) {
|
|
|
903
1339
|
static VALUE rb_fast_curl_first_execute(int argc, VALUE *argv, VALUE self) {
|
|
904
1340
|
VALUE requests, options;
|
|
905
1341
|
rb_scan_args(argc, argv, "1:", &requests, &options);
|
|
1342
|
+
|
|
906
1343
|
int count = 1;
|
|
907
1344
|
if (!NIL_P(options)) {
|
|
908
|
-
|
|
1345
|
+
Check_Type(options, T_HASH);
|
|
1346
|
+
VALUE c = hash_aref_key(options, KEY_COUNT_OPT);
|
|
909
1347
|
if (!NIL_P(c))
|
|
910
1348
|
count = NUM2INT(c);
|
|
911
1349
|
}
|
|
1350
|
+
|
|
1351
|
+
if (count <= 0)
|
|
1352
|
+
rb_raise(rb_eArgError, "count must be positive");
|
|
1353
|
+
|
|
912
1354
|
return internal_execute(requests, options, count, 0);
|
|
913
1355
|
}
|
|
914
1356
|
|
|
915
1357
|
static VALUE rb_fast_curl_stream_execute(int argc, VALUE *argv, VALUE self) {
|
|
916
1358
|
VALUE requests, options;
|
|
917
1359
|
rb_scan_args(argc, argv, "1:", &requests, &options);
|
|
1360
|
+
|
|
918
1361
|
if (!rb_block_given_p())
|
|
919
1362
|
rb_raise(rb_eArgError, "stream_execute requires a block");
|
|
1363
|
+
|
|
920
1364
|
return internal_execute(requests, options, -1, 1);
|
|
921
1365
|
}
|
|
922
1366
|
|
|
923
1367
|
void Init_fast_curl(void) {
|
|
924
1368
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
925
1369
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
id_method = rb_intern("method");
|
|
932
|
-
id_timeout = rb_intern("timeout");
|
|
933
|
-
id_connections = rb_intern("connections");
|
|
934
|
-
id_count = rb_intern("count");
|
|
935
|
-
id_keys = rb_intern("keys");
|
|
936
|
-
id_retries = rb_intern("retries");
|
|
937
|
-
id_retry_delay = rb_intern("retry_delay");
|
|
938
|
-
id_retry_codes = rb_intern("retry_codes");
|
|
939
|
-
|
|
940
|
-
sym_status = ID2SYM(id_status);
|
|
941
|
-
rb_gc_register_address(&sym_status);
|
|
942
|
-
sym_headers = ID2SYM(id_headers);
|
|
943
|
-
rb_gc_register_address(&sym_headers);
|
|
944
|
-
sym_body = ID2SYM(id_body);
|
|
945
|
-
rb_gc_register_address(&sym_body);
|
|
946
|
-
sym_error_code = ID2SYM(id_error_code);
|
|
947
|
-
rb_gc_register_address(&sym_error_code);
|
|
948
|
-
sym_url = ID2SYM(id_url);
|
|
949
|
-
rb_gc_register_address(&sym_url);
|
|
950
|
-
sym_method = ID2SYM(id_method);
|
|
951
|
-
rb_gc_register_address(&sym_method);
|
|
952
|
-
sym_timeout = ID2SYM(id_timeout);
|
|
953
|
-
rb_gc_register_address(&sym_timeout);
|
|
954
|
-
sym_connections = ID2SYM(id_connections);
|
|
955
|
-
rb_gc_register_address(&sym_connections);
|
|
956
|
-
sym_count = ID2SYM(id_count);
|
|
957
|
-
rb_gc_register_address(&sym_count);
|
|
958
|
-
sym_retries = ID2SYM(id_retries);
|
|
959
|
-
rb_gc_register_address(&sym_retries);
|
|
960
|
-
sym_retry_delay = ID2SYM(id_retry_delay);
|
|
961
|
-
rb_gc_register_address(&sym_retry_delay);
|
|
962
|
-
sym_retry_codes = ID2SYM(id_retry_codes);
|
|
963
|
-
rb_gc_register_address(&sym_retry_codes);
|
|
1370
|
+
for (int i = 0; i < KEY_LAST; i++) {
|
|
1371
|
+
fast_ids[i] = rb_intern(KEY_NAMES[i]);
|
|
1372
|
+
fast_syms[i] = ID2SYM(fast_ids[i]);
|
|
1373
|
+
rb_gc_register_address(&fast_syms[i]);
|
|
1374
|
+
}
|
|
964
1375
|
|
|
965
1376
|
VALUE mFastCurl = rb_define_module("FastCurl");
|
|
966
1377
|
|