fast_curl 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d01979b9bff516a526b66220807d7d325846eb507815dcfe46810daa14f5f782
4
- data.tar.gz: d9ff4cb707451f4d3516426e84f22798d89ff8b7fdc549addb4e68fc22e55f3b
3
+ metadata.gz: aa97f9eaef2dd36adbff2816b3ab08ec7e924b7a7a23c01c7c38723c6f071ff3
4
+ data.tar.gz: 5c5e02905534b2c54602b9f970fab2d587603f7c31ab6a0ab251bd6a1857932b
5
5
  SHA512:
6
- metadata.gz: 354b9d90daaa884f6bc31819a7377a10946874d945add791c61e5801c4c65fe618bb364ea0cc25e8bc155afddf07c9cb7dc0593accff7be33559a7c71b23cf1d
7
- data.tar.gz: a6a87dae54ce7c8fb52aa5f7f5780df2943767b46a9c87f582fa20bb76475736eac3f4feb360b5cacf5edaad11e6eee2c81ecc7632248ef80b8154a6e6b88c27
6
+ metadata.gz: 54c9d82c53ab24b08129c38cda9a82e06d834cf16f4b2b210f62edd2361c77db84f49eb59e3cbcbfc04d94dc12a758d89bc6e65bed23183132e8365186892716
7
+ data.tar.gz: 354ee56ed5973a66f365f76eb0f2a01264ae3e14e238ff616cd34d6251f10830babda151037a713b7e17573bb209387e05713b19b291c573b31cf11099ecb3b7
@@ -8,646 +8,711 @@
8
8
  #include <stdlib.h>
9
9
  #include <string.h>
10
10
 
11
- #define MAX_RESPONSE_SIZE (100 * 1024 * 1024)
12
- #define MAX_REDIRECTS 5
13
- #define MAX_TIMEOUT 300
11
+ #define MAX_RESPONSE_SIZE (100 * 1024 * 1024)
12
+ #define MAX_REDIRECTS 5
13
+ #define MAX_TIMEOUT 300
14
+ #define INITIAL_BUF_CAP 8192
15
+ #define INITIAL_HEADER_CAP 16
16
+ #define POLL_TIMEOUT_MS 50
17
+ #define FIBER_POLL_TIMEOUT_MS 10
18
+ #define HEADER_LINE_BUF_SIZE 512
19
+
20
+ static ID id_status;
21
+ static ID id_headers;
22
+ static ID id_body;
23
+ static ID id_error_code;
24
+ static ID id_url;
25
+ static ID id_method;
26
+ static ID id_timeout;
27
+ static ID id_connections;
28
+ static ID id_count;
29
+ static ID id_keys;
30
+ static VALUE sym_status;
31
+ static VALUE sym_headers;
32
+ static VALUE sym_body;
33
+ static VALUE sym_error_code;
34
+ static VALUE sym_url;
35
+ static VALUE sym_method;
36
+ static VALUE sym_timeout;
37
+ static VALUE sym_connections;
38
+ static VALUE sym_count;
14
39
 
15
40
  typedef struct {
16
- char *data;
17
- size_t len;
18
- size_t cap;
19
- size_t max_size;
41
+ char *data;
42
+ size_t len;
43
+ size_t cap;
44
+ size_t max_size;
20
45
  } buffer_t;
21
46
 
22
- static void buffer_init(buffer_t *buf) {
23
- buf->data = NULL;
24
- buf->len = 0;
25
- buf->cap = 0;
26
- buf->max_size = MAX_RESPONSE_SIZE;
47
+ static inline void buffer_init(buffer_t *buf) {
48
+ buf->data = NULL;
49
+ buf->len = 0;
50
+ buf->cap = 0;
51
+ buf->max_size = MAX_RESPONSE_SIZE;
27
52
  }
28
53
 
29
- static void buffer_free(buffer_t *buf) {
30
- if (buf->data) {
31
- free(buf->data);
32
- buf->data = NULL;
33
- }
34
- buf->len = 0;
35
- buf->cap = 0;
54
+ static inline void buffer_free(buffer_t *buf) {
55
+ if (buf->data) {
56
+ free(buf->data);
57
+ buf->data = NULL;
58
+ }
59
+ buf->len = 0;
60
+ buf->cap = 0;
36
61
  }
37
62
 
38
- static size_t write_callback(char *ptr, size_t size, size_t nmemb,
39
- void *userdata) {
40
- buffer_t *buf = (buffer_t *)userdata;
41
- size_t total = size * nmemb;
63
+ static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
64
+ buffer_t *buf = (buffer_t *)userdata;
65
+ size_t total = size * nmemb;
42
66
 
43
- if (buf->len + total > buf->max_size) {
44
- return 0;
45
- }
67
+ if (buf->len + total > buf->max_size) {
68
+ return 0;
69
+ }
46
70
 
47
- if (buf->len + total >= buf->cap) {
48
- size_t new_cap = (buf->cap == 0) ? 4096 : buf->cap;
49
- while (new_cap <= buf->len + total)
50
- new_cap *= 2;
71
+ if (buf->len + total >= buf->cap) {
72
+ size_t new_cap = (buf->cap == 0) ? INITIAL_BUF_CAP : buf->cap;
73
+ while (new_cap <= buf->len + total)
74
+ new_cap *= 2;
51
75
 
52
- if (new_cap > buf->max_size) {
53
- new_cap = buf->max_size;
54
- }
76
+ if (new_cap > buf->max_size)
77
+ new_cap = buf->max_size;
55
78
 
56
- char *new_data = realloc(buf->data, new_cap);
57
- if (!new_data)
58
- return 0;
59
- buf->data = new_data;
60
- buf->cap = new_cap;
61
- }
79
+ char *new_data = realloc(buf->data, new_cap);
80
+ if (!new_data)
81
+ return 0;
82
+ buf->data = new_data;
83
+ buf->cap = new_cap;
84
+ }
62
85
 
63
- memcpy(buf->data + buf->len, ptr, total);
64
- buf->len += total;
65
- return total;
86
+ memcpy(buf->data + buf->len, ptr, total);
87
+ buf->len += total;
88
+ return total;
66
89
  }
67
90
 
68
91
  typedef struct {
69
- char **entries;
70
- int count;
71
- int cap;
92
+ char *str;
93
+ size_t len;
94
+ } header_entry_t;
95
+
96
+ typedef struct {
97
+ header_entry_t *entries;
98
+ int count;
99
+ int cap;
72
100
  } header_list_t;
73
101
 
74
- static void header_list_init(header_list_t *h) {
75
- h->entries = NULL;
76
- h->count = 0;
77
- h->cap = 0;
102
+ static inline void header_list_init(header_list_t *h) {
103
+ h->entries = NULL;
104
+ h->count = 0;
105
+ h->cap = 0;
78
106
  }
79
107
 
80
108
  static void header_list_free(header_list_t *h) {
81
- for (int i = 0; i < h->count; i++)
82
- free(h->entries[i]);
83
- free(h->entries);
84
- h->entries = NULL;
85
- h->count = 0;
86
- h->cap = 0;
109
+ for (int i = 0; i < h->count; i++)
110
+ free(h->entries[i].str);
111
+ free(h->entries);
112
+ h->entries = NULL;
113
+ h->count = 0;
114
+ h->cap = 0;
87
115
  }
88
116
 
89
- static size_t header_callback(char *ptr, size_t size, size_t nmemb,
90
- void *userdata) {
91
- header_list_t *h = (header_list_t *)userdata;
92
- size_t total = size * nmemb;
117
+ static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
118
+ header_list_t *h = (header_list_t *)userdata;
119
+ size_t total = size * nmemb;
93
120
 
94
- if (total <= 2)
95
- return total;
121
+ if (total <= 2)
122
+ return total;
96
123
 
97
- if (h->count >= h->cap) {
98
- int new_cap = (h->cap == 0) ? 16 : h->cap * 2;
99
- char **new_entries = realloc(h->entries, sizeof(char *) * new_cap);
100
- if (!new_entries)
101
- return 0;
102
- h->entries = new_entries;
103
- h->cap = new_cap;
104
- }
105
-
106
- char *entry = malloc(total + 1);
107
- if (!entry)
108
- return 0;
109
- memcpy(entry, ptr, total);
124
+ if (h->count >= h->cap) {
125
+ int new_cap = (h->cap == 0) ? INITIAL_HEADER_CAP : h->cap * 2;
126
+ header_entry_t *new_entries = realloc(h->entries, sizeof(header_entry_t) * new_cap);
127
+ if (!new_entries)
128
+ return 0;
129
+ h->entries = new_entries;
130
+ h->cap = new_cap;
131
+ }
110
132
 
111
- while (total > 0 && (entry[total - 1] == '\r' || entry[total - 1] == '\n'))
112
- total--;
113
- entry[total] = '\0';
114
- h->entries[h->count++] = entry;
115
- return size * nmemb;
133
+ size_t stripped = total;
134
+ while (stripped > 0 && (ptr[stripped - 1] == '\r' || ptr[stripped - 1] == '\n'))
135
+ stripped--;
136
+
137
+ char *entry = malloc(stripped + 1);
138
+ if (!entry)
139
+ return 0;
140
+ memcpy(entry, ptr, stripped);
141
+ entry[stripped] = '\0';
142
+
143
+ h->entries[h->count].str = entry;
144
+ h->entries[h->count].len = stripped;
145
+ h->count++;
146
+
147
+ return size * nmemb;
116
148
  }
117
149
 
118
150
  typedef struct {
119
- CURL *easy;
120
- int index;
121
- buffer_t body;
122
- header_list_t headers;
123
- struct curl_slist *req_headers;
124
- int done;
151
+ CURL *easy;
152
+ int index;
153
+ buffer_t body;
154
+ header_list_t headers;
155
+ struct curl_slist *req_headers;
156
+ int done;
125
157
  } request_ctx_t;
126
158
 
127
- static void request_ctx_init(request_ctx_t *ctx, int index) {
128
- ctx->easy = curl_easy_init();
129
- ctx->index = index;
130
- buffer_init(&ctx->body);
131
- header_list_init(&ctx->headers);
132
- ctx->req_headers = NULL;
133
- ctx->done = 0;
159
+ static inline void request_ctx_init(request_ctx_t *ctx, int index) {
160
+ ctx->easy = curl_easy_init();
161
+ ctx->index = index;
162
+ buffer_init(&ctx->body);
163
+ header_list_init(&ctx->headers);
164
+ ctx->req_headers = NULL;
165
+ ctx->done = 0;
134
166
  }
135
167
 
136
168
  static void request_ctx_free(request_ctx_t *ctx) {
137
- if (ctx->easy) {
138
- curl_easy_cleanup(ctx->easy);
139
- ctx->easy = NULL;
140
- }
141
- buffer_free(&ctx->body);
142
- header_list_free(&ctx->headers);
143
- if (ctx->req_headers) {
144
- curl_slist_free_all(ctx->req_headers);
145
- ctx->req_headers = NULL;
146
- }
169
+ if (ctx->easy) {
170
+ curl_easy_cleanup(ctx->easy);
171
+ ctx->easy = NULL;
172
+ }
173
+ buffer_free(&ctx->body);
174
+ header_list_free(&ctx->headers);
175
+ if (ctx->req_headers) {
176
+ curl_slist_free_all(ctx->req_headers);
177
+ ctx->req_headers = NULL;
178
+ }
147
179
  }
148
180
 
149
181
  typedef struct {
150
- CURLM *multi;
151
- request_ctx_t *requests;
152
- int count;
153
- int still_running;
154
- long timeout_ms;
155
- int max_connections;
182
+ CURLM *multi;
183
+ request_ctx_t *requests;
184
+ int count;
185
+ int still_running;
186
+ long timeout_ms;
187
+ int max_connections;
156
188
  } multi_session_t;
157
189
 
158
190
  static VALUE build_response(request_ctx_t *ctx) {
159
- long status = 0;
160
- curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &status);
161
-
162
- VALUE headers_hash = rb_hash_new();
163
- for (int i = 0; i < ctx->headers.count; i++) {
164
- char *colon = strchr(ctx->headers.entries[i], ':');
165
- if (colon) {
166
- VALUE key =
167
- rb_str_new(ctx->headers.entries[i], colon - ctx->headers.entries[i]);
168
- char *val_start = colon + 1;
169
- while (*val_start == ' ' || *val_start == '\t')
170
- val_start++;
171
-
172
- char *val_end = ctx->headers.entries[i] + strlen(ctx->headers.entries[i]);
173
- while (val_end > val_start &&
174
- (*(val_end - 1) == ' ' || *(val_end - 1) == '\t' ||
175
- *(val_end - 1) == '\r' || *(val_end - 1) == '\n')) {
176
- val_end--;
177
- }
178
-
179
- size_t val_len = val_end - val_start;
180
- VALUE val = rb_str_new(val_start, val_len);
181
- rb_hash_aset(headers_hash, key, val);
191
+ long status = 0;
192
+ curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &status);
193
+
194
+ VALUE headers_hash = rb_hash_new();
195
+ for (int i = 0; i < ctx->headers.count; i++) {
196
+ const char *hdr = ctx->headers.entries[i].str;
197
+ size_t hdr_len = ctx->headers.entries[i].len;
198
+
199
+ const char *colon = memchr(hdr, ':', hdr_len);
200
+ if (!colon)
201
+ continue;
202
+
203
+ VALUE key = rb_str_new(hdr, colon - hdr);
204
+
205
+ const char *val_start = colon + 1;
206
+ const char *val_end = hdr + hdr_len;
207
+
208
+ while (val_start < val_end && (*val_start == ' ' || *val_start == '\t'))
209
+ val_start++;
210
+
211
+ while (val_end > val_start && (*(val_end - 1) == ' ' || *(val_end - 1) == '\t'))
212
+ val_end--;
213
+
214
+ VALUE val = rb_str_new(val_start, val_end - val_start);
215
+ rb_hash_aset(headers_hash, key, val);
182
216
  }
183
- }
184
217
 
185
- VALUE body_str = ctx->body.data ? rb_str_new(ctx->body.data, ctx->body.len)
186
- : rb_str_new_cstr("");
218
+ VALUE body_str =
219
+ ctx->body.data ? rb_str_new(ctx->body.data, ctx->body.len) : rb_str_new_cstr("");
187
220
 
188
- VALUE result = rb_hash_new();
189
- rb_hash_aset(result, ID2SYM(rb_intern("status")), LONG2NUM(status));
190
- rb_hash_aset(result, ID2SYM(rb_intern("headers")), headers_hash);
191
- rb_hash_aset(result, ID2SYM(rb_intern("body")), body_str);
221
+ VALUE result = rb_hash_new();
222
+ rb_hash_aset(result, sym_status, LONG2NUM(status));
223
+ rb_hash_aset(result, sym_headers, headers_hash);
224
+ rb_hash_aset(result, sym_body, body_str);
192
225
 
193
- return result;
226
+ return result;
194
227
  }
195
228
 
196
229
  static VALUE build_error_response(const char *message) {
197
- VALUE result = rb_hash_new();
198
- rb_hash_aset(result, ID2SYM(rb_intern("status")), INT2NUM(0));
199
- rb_hash_aset(result, ID2SYM(rb_intern("headers")), Qnil);
200
- rb_hash_aset(result, ID2SYM(rb_intern("body")), rb_str_new_cstr(message));
201
- return result;
230
+ VALUE result = rb_hash_new();
231
+ rb_hash_aset(result, sym_status, INT2NUM(0));
232
+ rb_hash_aset(result, sym_headers, Qnil);
233
+ rb_hash_aset(result, sym_body, rb_str_new_cstr(message));
234
+ return result;
202
235
  }
203
236
 
204
- static int is_valid_url(const char *url);
205
- static VALUE build_error_response_with_code(const char *message,
206
- int error_code);
207
-
208
- #define CURL_SETOPT_CHECK(handle, option, value) \
209
- do { \
210
- CURLcode res = curl_easy_setopt(handle, option, value); \
211
- if (res != CURLE_OK) { \
212
- return res; \
213
- } \
214
- } while (0)
215
-
216
- static CURLcode setup_basic_options(CURL *easy, const char *url_str,
217
- long timeout_sec, request_ctx_t *ctx) {
218
-
219
- CURL_SETOPT_CHECK(easy, CURLOPT_URL, url_str);
220
- CURL_SETOPT_CHECK(easy, CURLOPT_WRITEFUNCTION, write_callback);
221
- CURL_SETOPT_CHECK(easy, CURLOPT_WRITEDATA, &ctx->body);
222
- CURL_SETOPT_CHECK(easy, CURLOPT_HEADERFUNCTION, header_callback);
223
- CURL_SETOPT_CHECK(easy, CURLOPT_HEADERDATA, &ctx->headers);
224
- CURL_SETOPT_CHECK(easy, CURLOPT_TIMEOUT, timeout_sec);
225
- CURL_SETOPT_CHECK(easy, CURLOPT_NOSIGNAL, 1L);
226
- CURL_SETOPT_CHECK(easy, CURLOPT_FOLLOWLOCATION, 1L);
227
- CURL_SETOPT_CHECK(easy, CURLOPT_MAXREDIRS, MAX_REDIRECTS);
228
- CURL_SETOPT_CHECK(easy, CURLOPT_ACCEPT_ENCODING, "");
229
- CURL_SETOPT_CHECK(easy, CURLOPT_PRIVATE, (char *)ctx);
230
-
231
- return CURLE_OK;
237
+ static VALUE build_error_response_with_code(const char *message, int error_code) {
238
+ VALUE result = rb_hash_new();
239
+ rb_hash_aset(result, sym_status, INT2NUM(0));
240
+ rb_hash_aset(result, sym_headers, Qnil);
241
+ rb_hash_aset(result, sym_body, rb_str_new_cstr(message));
242
+ rb_hash_aset(result, sym_error_code, INT2NUM(error_code));
243
+ return result;
232
244
  }
233
245
 
234
- static CURLcode setup_security_options(CURL *easy) {
246
+ static int is_valid_url(const char *url) {
247
+ if (!url)
248
+ return 0;
249
+
250
+ size_t url_len = strlen(url);
251
+
252
+ if (url_len < 8 || url_len > 2048)
253
+ return 0;
254
+
255
+ if (strncmp(url, "https://", 8) == 0)
256
+ return 1;
257
+ if (url_len >= 7 && strncmp(url, "http://", 7) == 0)
258
+ return 1;
259
+
260
+ return 0;
261
+ }
262
+
263
+ #define CURL_SETOPT_CHECK(handle, option, value) \
264
+ do { \
265
+ CURLcode res = curl_easy_setopt(handle, option, value); \
266
+ if (res != CURLE_OK) { \
267
+ return res; \
268
+ } \
269
+ } while (0)
270
+
271
+ static CURLcode setup_basic_options(CURL *easy, const char *url_str, long timeout_sec,
272
+ request_ctx_t *ctx) {
273
+ CURL_SETOPT_CHECK(easy, CURLOPT_URL, url_str);
274
+ CURL_SETOPT_CHECK(easy, CURLOPT_WRITEFUNCTION, write_callback);
275
+ CURL_SETOPT_CHECK(easy, CURLOPT_WRITEDATA, &ctx->body);
276
+ CURL_SETOPT_CHECK(easy, CURLOPT_HEADERFUNCTION, header_callback);
277
+ CURL_SETOPT_CHECK(easy, CURLOPT_HEADERDATA, &ctx->headers);
278
+ CURL_SETOPT_CHECK(easy, CURLOPT_TIMEOUT, timeout_sec);
279
+ CURL_SETOPT_CHECK(easy, CURLOPT_NOSIGNAL, 1L);
280
+ CURL_SETOPT_CHECK(easy, CURLOPT_FOLLOWLOCATION, 1L);
281
+ CURL_SETOPT_CHECK(easy, CURLOPT_MAXREDIRS, MAX_REDIRECTS);
282
+ CURL_SETOPT_CHECK(easy, CURLOPT_ACCEPT_ENCODING, "");
283
+ CURL_SETOPT_CHECK(easy, CURLOPT_PRIVATE, (char *)ctx);
284
+ CURL_SETOPT_CHECK(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
285
+
286
+ return CURLE_OK;
287
+ }
235
288
 
236
- CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L);
237
- CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYHOST, 2L);
289
+ static CURLcode setup_security_options(CURL *easy) {
290
+ CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L);
291
+ CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYHOST, 2L);
238
292
 
239
293
  #ifdef CURLOPT_PROTOCOLS_STR
240
- CURL_SETOPT_CHECK(easy, CURLOPT_PROTOCOLS_STR, "http,https");
241
- CURL_SETOPT_CHECK(easy, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
294
+ CURL_SETOPT_CHECK(easy, CURLOPT_PROTOCOLS_STR, "http,https");
295
+ CURL_SETOPT_CHECK(easy, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
242
296
  #else
243
-
244
- CURL_SETOPT_CHECK(easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
245
- CURL_SETOPT_CHECK(easy, CURLOPT_REDIR_PROTOCOLS,
246
- CURLPROTO_HTTP | CURLPROTO_HTTPS);
297
+ CURL_SETOPT_CHECK(easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
298
+ CURL_SETOPT_CHECK(easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
247
299
  #endif
248
300
 
249
- return CURLE_OK;
301
+ return CURLE_OK;
250
302
  }
251
303
 
252
304
  static CURLcode setup_method_and_body(CURL *easy, VALUE method, VALUE body) {
253
- if (!NIL_P(method)) {
254
- const char *m = StringValueCStr(method);
255
- if (strcmp(m, "POST") == 0) {
256
- CURL_SETOPT_CHECK(easy, CURLOPT_POST, 1L);
257
- } else if (strcmp(m, "PUT") == 0) {
258
- CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, "PUT");
259
- } else if (strcmp(m, "DELETE") == 0) {
260
- CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, "DELETE");
261
- } else if (strcmp(m, "PATCH") == 0) {
262
- CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, "PATCH");
263
- } else if (strcmp(m, "GET") != 0) {
264
-
265
- CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, m);
305
+ if (!NIL_P(method)) {
306
+ const char *m = StringValueCStr(method);
307
+ if (strcmp(m, "POST") == 0) {
308
+ CURL_SETOPT_CHECK(easy, CURLOPT_POST, 1L);
309
+ } else if (strcmp(m, "PUT") == 0) {
310
+ CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, "PUT");
311
+ } else if (strcmp(m, "DELETE") == 0) {
312
+ CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, "DELETE");
313
+ } else if (strcmp(m, "PATCH") == 0) {
314
+ CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, "PATCH");
315
+ } else if (strcmp(m, "GET") != 0) {
316
+ CURL_SETOPT_CHECK(easy, CURLOPT_CUSTOMREQUEST, m);
317
+ }
266
318
  }
267
- }
268
319
 
269
- if (!NIL_P(body)) {
270
- CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDS, StringValuePtr(body));
271
- CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDSIZE, RSTRING_LEN(body));
272
- }
320
+ if (!NIL_P(body)) {
321
+ CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDS, StringValuePtr(body));
322
+ CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDSIZE, RSTRING_LEN(body));
323
+ }
273
324
 
274
- return CURLE_OK;
325
+ return CURLE_OK;
275
326
  }
276
327
 
277
- static int setup_easy_handle(request_ctx_t *ctx, VALUE request,
278
- long timeout_sec) {
279
- VALUE url = rb_hash_aref(request, ID2SYM(rb_intern("url")));
280
- VALUE method = rb_hash_aref(request, ID2SYM(rb_intern("method")));
281
- VALUE headers = rb_hash_aref(request, ID2SYM(rb_intern("headers")));
282
- VALUE body = rb_hash_aref(request, ID2SYM(rb_intern("body")));
328
+ static int header_iter_cb(VALUE key, VALUE val, VALUE arg) {
329
+ request_ctx_t *ctx = (request_ctx_t *)arg;
283
330
 
284
- if (NIL_P(url)) {
285
- return 0;
286
- }
331
+ VALUE key_str = rb_String(key);
332
+ const char *k = RSTRING_PTR(key_str);
333
+ long klen = RSTRING_LEN(key_str);
287
334
 
288
- const char *url_str = StringValueCStr(url);
335
+ if (NIL_P(val) || RSTRING_LEN(rb_String(val)) == 0) {
336
+ char stack_buf[HEADER_LINE_BUF_SIZE];
337
+ char *buf = stack_buf;
338
+ long need = klen + 2;
289
339
 
290
- if (!is_valid_url(url_str)) {
291
- rb_raise(rb_eArgError, "Invalid URL: %s", url_str);
292
- }
340
+ if (need > HEADER_LINE_BUF_SIZE)
341
+ buf = malloc(need);
342
+ if (!buf)
343
+ return ST_CONTINUE;
293
344
 
294
- CURLcode res;
345
+ memcpy(buf, k, klen);
346
+ buf[klen] = ';';
347
+ buf[klen + 1] = '\0';
295
348
 
296
- res = setup_basic_options(ctx->easy, url_str, timeout_sec, ctx);
297
- if (res != CURLE_OK)
298
- return 0;
349
+ ctx->req_headers = curl_slist_append(ctx->req_headers, buf);
299
350
 
300
- res = setup_security_options(ctx->easy);
301
- if (res != CURLE_OK)
302
- return 0;
351
+ if (buf != stack_buf)
352
+ free(buf);
353
+ } else {
354
+ VALUE val_str = rb_String(val);
355
+ const char *v = RSTRING_PTR(val_str);
356
+ long vlen = RSTRING_LEN(val_str);
357
+ char stack_buf[HEADER_LINE_BUF_SIZE];
358
+ char *buf = stack_buf;
359
+ long need = klen + 2 + vlen + 1;
360
+
361
+ if (need > HEADER_LINE_BUF_SIZE)
362
+ buf = malloc(need);
363
+ if (!buf)
364
+ return ST_CONTINUE;
365
+
366
+ memcpy(buf, k, klen);
367
+ buf[klen] = ':';
368
+ buf[klen + 1] = ' ';
369
+ memcpy(buf + klen + 2, v, vlen);
370
+ buf[klen + 2 + vlen] = '\0';
371
+
372
+ ctx->req_headers = curl_slist_append(ctx->req_headers, buf);
373
+
374
+ if (buf != stack_buf)
375
+ free(buf);
376
+ }
303
377
 
304
- res = setup_method_and_body(ctx->easy, method, body);
305
- if (res != CURLE_OK)
306
- return 0;
378
+ return ST_CONTINUE;
379
+ }
380
+
381
+ static int setup_easy_handle(request_ctx_t *ctx, VALUE request, long timeout_sec) {
382
+ VALUE url = rb_hash_aref(request, sym_url);
383
+ VALUE method = rb_hash_aref(request, sym_method);
384
+ VALUE headers = rb_hash_aref(request, sym_headers);
385
+ VALUE body = rb_hash_aref(request, sym_body);
307
386
 
308
- if (!NIL_P(headers) && rb_obj_is_kind_of(headers, rb_cHash)) {
309
- VALUE keys = rb_funcall(headers, rb_intern("keys"), 0);
310
- long hlen = RARRAY_LEN(keys);
311
- for (long i = 0; i < hlen; i++) {
312
- VALUE key = rb_ary_entry(keys, i);
313
- VALUE val = rb_hash_aref(headers, key);
314
- VALUE header_line;
315
-
316
- if (NIL_P(val) || RSTRING_LEN(rb_String(val)) == 0) {
317
- header_line = rb_sprintf("%" PRIsVALUE ";", key);
318
- } else {
319
- header_line = rb_sprintf("%" PRIsVALUE ": %" PRIsVALUE, key, val);
320
- }
321
-
322
- ctx->req_headers =
323
- curl_slist_append(ctx->req_headers, StringValueCStr(header_line));
324
- if (!ctx->req_headers) {
387
+ if (NIL_P(url))
325
388
  return 0;
326
- }
389
+
390
+ const char *url_str = StringValueCStr(url);
391
+
392
+ if (!is_valid_url(url_str)) {
393
+ rb_raise(rb_eArgError, "Invalid URL: %s", url_str);
327
394
  }
328
395
 
329
- res = curl_easy_setopt(ctx->easy, CURLOPT_HTTPHEADER, ctx->req_headers);
396
+ CURLcode res;
397
+
398
+ res = setup_basic_options(ctx->easy, url_str, timeout_sec, ctx);
330
399
  if (res != CURLE_OK)
331
- return 0;
332
- }
400
+ return 0;
333
401
 
334
- return 1;
402
+ res = setup_security_options(ctx->easy);
403
+ if (res != CURLE_OK)
404
+ return 0;
405
+
406
+ res = setup_method_and_body(ctx->easy, method, body);
407
+ if (res != CURLE_OK)
408
+ return 0;
409
+
410
+ if (!NIL_P(headers) && rb_obj_is_kind_of(headers, rb_cHash)) {
411
+ rb_hash_foreach(headers, header_iter_cb, (VALUE)ctx);
412
+
413
+ if (ctx->req_headers) {
414
+ res = curl_easy_setopt(ctx->easy, CURLOPT_HTTPHEADER, ctx->req_headers);
415
+ if (res != CURLE_OK)
416
+ return 0;
417
+ }
418
+ }
419
+
420
+ return 1;
335
421
  }
336
422
 
337
423
  static void *perform_without_gvl(void *arg) {
338
- multi_session_t *session = (multi_session_t *)arg;
339
-
340
- while (session->still_running > 0) {
341
- CURLMcode mc = curl_multi_perform(session->multi, &session->still_running);
342
- if (mc != CURLM_OK)
343
- break;
344
-
345
- if (session->still_running > 0) {
346
- int numfds = 0;
347
- mc = curl_multi_poll(session->multi, NULL, 0, 100, &numfds);
348
- if (mc != CURLM_OK)
349
- break;
424
+ multi_session_t *session = (multi_session_t *)arg;
425
+
426
+ while (session->still_running > 0) {
427
+ CURLMcode mc = curl_multi_perform(session->multi, &session->still_running);
428
+ if (mc != CURLM_OK)
429
+ break;
430
+
431
+ if (session->still_running > 0) {
432
+ int numfds = 0;
433
+ mc = curl_multi_poll(session->multi, NULL, 0, POLL_TIMEOUT_MS, &numfds);
434
+ if (mc != CURLM_OK)
435
+ break;
436
+ }
350
437
  }
351
- }
352
438
 
353
- return NULL;
439
+ return NULL;
354
440
  }
355
441
 
356
442
  static void *poll_without_gvl(void *arg) {
357
- multi_session_t *session = (multi_session_t *)arg;
358
- int numfds = 0;
359
- curl_multi_poll(session->multi, NULL, 0, 100, &numfds);
360
- curl_multi_perform(session->multi, &session->still_running);
361
- return NULL;
443
+ multi_session_t *session = (multi_session_t *)arg;
444
+ int numfds = 0;
445
+ curl_multi_poll(session->multi, NULL, 0, POLL_TIMEOUT_MS, &numfds);
446
+ curl_multi_perform(session->multi, &session->still_running);
447
+ return NULL;
362
448
  }
363
449
 
364
450
  static void unblock_perform(void *arg) {
365
- multi_session_t *session = (multi_session_t *)arg;
366
- (void)session;
451
+ (void)arg;
367
452
  }
368
453
 
369
454
  static int has_fiber_scheduler(void) {
370
455
  #ifdef HAVE_RB_FIBER_SCHEDULER_CURRENT
371
- VALUE scheduler = rb_fiber_scheduler_current();
372
- return scheduler != Qnil && scheduler != Qfalse;
456
+ VALUE scheduler = rb_fiber_scheduler_current();
457
+ return scheduler != Qnil && scheduler != Qfalse;
373
458
  #else
374
- return 0;
459
+ return 0;
375
460
  #endif
376
461
  }
377
462
 
378
463
  typedef struct {
379
- VALUE results;
380
- int completed;
381
- int target;
382
- int stream;
464
+ VALUE results;
465
+ int completed;
466
+ int target;
467
+ int stream;
383
468
  } completion_ctx_t;
384
469
 
385
470
  static int process_completed(multi_session_t *session, completion_ctx_t *cctx) {
386
- CURLMsg *msg;
387
- int msgs_left;
388
-
389
- while ((msg = curl_multi_info_read(session->multi, &msgs_left))) {
390
- if (msg->msg != CURLMSG_DONE)
391
- continue;
392
-
393
- request_ctx_t *ctx = NULL;
394
- curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **)&ctx);
395
- if (!ctx || ctx->done)
396
- continue;
397
- ctx->done = 1;
398
-
399
- VALUE response;
400
- if (msg->data.result == CURLE_OK) {
401
- response = build_response(ctx);
402
- } else {
403
-
404
- response = build_error_response_with_code(
405
- curl_easy_strerror(msg->data.result), (int)msg->data.result);
471
+ CURLMsg *msg;
472
+ int msgs_left;
473
+
474
+ while ((msg = curl_multi_info_read(session->multi, &msgs_left))) {
475
+ if (msg->msg != CURLMSG_DONE)
476
+ continue;
477
+
478
+ request_ctx_t *ctx = NULL;
479
+ curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **)&ctx);
480
+ if (!ctx || ctx->done)
481
+ continue;
482
+ ctx->done = 1;
483
+
484
+ VALUE response;
485
+ if (msg->data.result == CURLE_OK) {
486
+ response = build_response(ctx);
487
+ } else {
488
+ response = build_error_response_with_code(curl_easy_strerror(msg->data.result),
489
+ (int)msg->data.result);
490
+ }
491
+
492
+ VALUE pair = rb_ary_new_from_args(2, INT2NUM(ctx->index), response);
493
+
494
+ if (cctx->stream) {
495
+ rb_yield(pair);
496
+ } else if (!NIL_P(cctx->results)) {
497
+ rb_ary_store(cctx->results, ctx->index, pair);
498
+ }
499
+
500
+ cctx->completed++;
501
+
502
+ if (cctx->target > 0 && cctx->completed >= cctx->target)
503
+ return 1;
406
504
  }
407
505
 
408
- VALUE pair = rb_ary_new_from_args(2, INT2NUM(ctx->index), response);
409
-
410
- if (cctx->stream) {
411
- rb_yield(pair);
412
- } else if (!NIL_P(cctx->results)) {
413
- rb_ary_store(cctx->results, ctx->index, pair);
414
- }
415
-
416
- cctx->completed++;
417
-
418
- if (cctx->target > 0 && cctx->completed >= cctx->target) {
419
- return 1;
420
- }
421
- }
422
-
423
- return 0;
424
- }
425
-
426
- static VALUE build_error_response_with_code(const char *message,
427
- int error_code) {
428
- VALUE result = rb_hash_new();
429
- rb_hash_aset(result, ID2SYM(rb_intern("status")), INT2NUM(0));
430
- rb_hash_aset(result, ID2SYM(rb_intern("headers")), Qnil);
431
- rb_hash_aset(result, ID2SYM(rb_intern("body")), rb_str_new_cstr(message));
432
- rb_hash_aset(result, ID2SYM(rb_intern("error_code")), INT2NUM(error_code));
433
- return result;
434
- }
435
-
436
- static int is_valid_url(const char *url) {
437
- if (!url || strlen(url) == 0)
438
- return 0;
439
-
440
- size_t url_len = strlen(url);
441
-
442
- if (url_len > 2048) {
443
- return 0;
444
- }
445
-
446
- if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0) {
447
- return 0;
448
- }
449
-
450
- if (strncmp(url, "file://", 7) == 0 || strncmp(url, "ftp://", 6) == 0 ||
451
- strncmp(url, "gopher://", 9) == 0 || strncmp(url, "ldap://", 7) == 0 ||
452
- strncmp(url, "dict://", 7) == 0 || strncmp(url, "tftp://", 7) == 0) {
453
506
  return 0;
454
- }
455
-
456
- return 1;
457
507
  }
458
508
 
459
- static VALUE parse_options(VALUE options, long *timeout, int *max_conn) {
460
- *timeout = 30;
461
- *max_conn = 20;
509
+ static void parse_options(VALUE options, long *timeout, int *max_conn) {
510
+ *timeout = 30;
511
+ *max_conn = 20;
462
512
 
463
- if (!NIL_P(options) && rb_obj_is_kind_of(options, rb_cHash)) {
464
- VALUE t = rb_hash_aref(options, ID2SYM(rb_intern("timeout")));
465
- VALUE c = rb_hash_aref(options, ID2SYM(rb_intern("connections")));
513
+ if (NIL_P(options) || !rb_obj_is_kind_of(options, rb_cHash))
514
+ return;
466
515
 
516
+ VALUE t = rb_hash_aref(options, sym_timeout);
467
517
  if (!NIL_P(t)) {
468
- long timeout_val = NUM2LONG(t);
469
-
470
- if (timeout_val > MAX_TIMEOUT) {
471
- timeout_val = MAX_TIMEOUT;
472
- } else if (timeout_val <= 0) {
473
- timeout_val = 30;
474
- }
475
- *timeout = timeout_val;
518
+ long timeout_val = NUM2LONG(t);
519
+ if (timeout_val > MAX_TIMEOUT)
520
+ timeout_val = MAX_TIMEOUT;
521
+ else if (timeout_val <= 0)
522
+ timeout_val = 30;
523
+ *timeout = timeout_val;
476
524
  }
477
525
 
526
+ VALUE c = rb_hash_aref(options, sym_connections);
478
527
  if (!NIL_P(c)) {
479
- int conn_val = NUM2INT(c);
480
-
481
- if (conn_val > 100) {
482
- conn_val = 100;
483
- } else if (conn_val <= 0) {
484
- conn_val = 20;
485
- }
486
- *max_conn = conn_val;
528
+ int conn_val = NUM2INT(c);
529
+ if (conn_val > 100)
530
+ conn_val = 100;
531
+ else if (conn_val <= 0)
532
+ conn_val = 20;
533
+ *max_conn = conn_val;
487
534
  }
488
- }
489
-
490
- return Qnil;
491
535
  }
492
536
 
493
- static VALUE internal_execute(VALUE requests, VALUE options, int target,
494
- int stream) {
495
- Check_Type(requests, T_ARRAY);
496
- int count = (int)RARRAY_LEN(requests);
497
- if (count == 0)
498
- return rb_ary_new();
499
-
500
- long timeout_sec;
501
- int max_conn;
502
- parse_options(options, &timeout_sec, &max_conn);
503
-
504
- multi_session_t session;
505
- session.multi = curl_multi_init();
506
- session.count = count;
507
- session.timeout_ms = timeout_sec * 1000;
508
- session.max_connections = max_conn;
509
-
510
- curl_multi_setopt(session.multi, CURLMOPT_MAXCONNECTS, (long)max_conn);
511
- curl_multi_setopt(session.multi, CURLMOPT_MAX_TOTAL_CONNECTIONS,
512
- (long)max_conn);
513
-
514
- session.requests = calloc(count, sizeof(request_ctx_t));
515
- if (!session.requests) {
516
- curl_multi_cleanup(session.multi);
517
- rb_raise(rb_eNoMemError, "failed to allocate request contexts");
518
- }
537
+ static VALUE internal_execute(VALUE requests, VALUE options, int target, int stream) {
538
+ Check_Type(requests, T_ARRAY);
539
+ int count = (int)RARRAY_LEN(requests);
540
+ if (count == 0)
541
+ return rb_ary_new();
542
+
543
+ long timeout_sec;
544
+ int max_conn;
545
+ parse_options(options, &timeout_sec, &max_conn);
546
+
547
+ multi_session_t session;
548
+ session.multi = curl_multi_init();
549
+ session.count = count;
550
+ session.timeout_ms = timeout_sec * 1000;
551
+ session.max_connections = max_conn;
552
+
553
+ curl_multi_setopt(session.multi, CURLMOPT_MAXCONNECTS, (long)max_conn);
554
+ curl_multi_setopt(session.multi, CURLMOPT_MAX_TOTAL_CONNECTIONS, (long)max_conn);
555
+ #ifdef CURLPIPE_MULTIPLEX
556
+ curl_multi_setopt(session.multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
557
+ #endif
558
+
559
+ session.requests = calloc(count, sizeof(request_ctx_t));
560
+ if (!session.requests) {
561
+ curl_multi_cleanup(session.multi);
562
+ rb_raise(rb_eNoMemError, "failed to allocate request contexts");
563
+ }
519
564
 
520
- int valid_requests = 0;
521
- for (int i = 0; i < count; i++) {
522
- VALUE req = rb_ary_entry(requests, i);
523
- request_ctx_init(&session.requests[i], i);
565
+ int valid_requests = 0;
566
+ for (int i = 0; i < count; i++) {
567
+ VALUE req = rb_ary_entry(requests, i);
568
+ request_ctx_init(&session.requests[i], i);
524
569
 
525
- if (!setup_easy_handle(&session.requests[i], req, timeout_sec)) {
570
+ if (!setup_easy_handle(&session.requests[i], req, timeout_sec)) {
571
+ session.requests[i].done = 1;
572
+ continue;
573
+ }
526
574
 
527
- session.requests[i].done = 1;
528
- continue;
529
- }
575
+ CURLMcode mc = curl_multi_add_handle(session.multi, session.requests[i].easy);
576
+ if (mc != CURLM_OK) {
577
+ session.requests[i].done = 1;
578
+ continue;
579
+ }
530
580
 
531
- CURLMcode mc =
532
- curl_multi_add_handle(session.multi, session.requests[i].easy);
533
- if (mc != CURLM_OK) {
534
- session.requests[i].done = 1;
535
- continue;
581
+ valid_requests++;
536
582
  }
537
583
 
538
- valid_requests++;
539
- }
540
-
541
- if (valid_requests == 0) {
542
- session.still_running = 0;
543
- }
544
-
545
- completion_ctx_t cctx;
546
- cctx.results = stream ? Qnil : rb_ary_new2(count);
547
- cctx.completed = 0;
548
- cctx.target = target;
549
- cctx.stream = stream;
550
-
551
- if (!stream) {
552
- for (int i = 0; i < count; i++)
553
- rb_ary_store(cctx.results, i, Qnil);
554
- }
555
-
556
- if (has_fiber_scheduler()) {
557
- while (session.still_running > 0 || 1) {
558
- CURLMcode mc = curl_multi_perform(session.multi, &session.still_running);
559
- if (mc != CURLM_OK)
560
- break;
561
- if (process_completed(&session, &cctx))
562
- break;
563
- if (session.still_running == 0)
564
- break;
565
-
566
- int numfds = 0;
567
- curl_multi_poll(session.multi, NULL, 0, 1, &numfds);
568
- rb_thread_schedule();
584
+ if (valid_requests == 0)
585
+ session.still_running = 0;
586
+
587
+ completion_ctx_t cctx;
588
+ cctx.results = stream ? Qnil : rb_ary_new2(count);
589
+ cctx.completed = 0;
590
+ cctx.target = target;
591
+ cctx.stream = stream;
592
+
593
+ if (!stream) {
594
+ for (int i = 0; i < count; i++)
595
+ rb_ary_store(cctx.results, i, Qnil);
569
596
  }
570
597
 
571
- process_completed(&session, &cctx);
572
- } else {
573
- if (stream || target > 0) {
574
- curl_multi_perform(session.multi, &session.still_running);
575
- while (session.still_running > 0) {
576
- rb_thread_call_without_gvl(poll_without_gvl, &session, unblock_perform,
577
- &session);
578
- if (process_completed(&session, &cctx))
579
- break;
580
- }
581
- process_completed(&session, &cctx);
598
+ if (has_fiber_scheduler()) {
599
+ for (;;) {
600
+ CURLMcode mc = curl_multi_perform(session.multi, &session.still_running);
601
+ if (mc != CURLM_OK)
602
+ break;
603
+ if (process_completed(&session, &cctx))
604
+ break;
605
+ if (session.still_running == 0)
606
+ break;
607
+
608
+ int numfds = 0;
609
+ curl_multi_poll(session.multi, NULL, 0, FIBER_POLL_TIMEOUT_MS, &numfds);
610
+ rb_thread_schedule();
611
+ }
612
+ process_completed(&session, &cctx);
582
613
  } else {
583
- session.still_running = 1;
584
- curl_multi_perform(session.multi, &session.still_running);
585
- rb_thread_call_without_gvl(perform_without_gvl, &session, unblock_perform,
586
- &session);
587
- process_completed(&session, &cctx);
614
+ if (stream || target > 0) {
615
+ curl_multi_perform(session.multi, &session.still_running);
616
+ while (session.still_running > 0) {
617
+ rb_thread_call_without_gvl(poll_without_gvl, &session, unblock_perform, &session);
618
+ if (process_completed(&session, &cctx))
619
+ break;
620
+ }
621
+ process_completed(&session, &cctx);
622
+ } else {
623
+ session.still_running = 1;
624
+ curl_multi_perform(session.multi, &session.still_running);
625
+ rb_thread_call_without_gvl(perform_without_gvl, &session, unblock_perform, &session);
626
+ process_completed(&session, &cctx);
627
+ }
588
628
  }
589
- }
590
629
 
591
- if (!stream) {
592
- for (int i = 0; i < count; i++) {
593
- if (session.requests[i].done && rb_ary_entry(cctx.results, i) == Qnil) {
594
-
595
- VALUE error_response =
596
- build_error_response("Invalid request configuration");
597
- VALUE pair = rb_ary_new_from_args(2, INT2NUM(i), error_response);
598
- rb_ary_store(cctx.results, i, pair);
599
- }
630
+ if (!stream) {
631
+ for (int i = 0; i < count; i++) {
632
+ if (session.requests[i].done && rb_ary_entry(cctx.results, i) == Qnil) {
633
+ VALUE error_response = build_error_response("Invalid request configuration");
634
+ VALUE pair = rb_ary_new_from_args(2, INT2NUM(i), error_response);
635
+ rb_ary_store(cctx.results, i, pair);
636
+ }
637
+ }
600
638
  }
601
- }
602
639
 
603
- for (int i = 0; i < count; i++) {
604
- curl_multi_remove_handle(session.multi, session.requests[i].easy);
605
- request_ctx_free(&session.requests[i]);
606
- }
607
- free(session.requests);
608
- curl_multi_cleanup(session.multi);
640
+ for (int i = 0; i < count; i++) {
641
+ curl_multi_remove_handle(session.multi, session.requests[i].easy);
642
+ request_ctx_free(&session.requests[i]);
643
+ }
644
+ free(session.requests);
645
+ curl_multi_cleanup(session.multi);
609
646
 
610
- return stream ? Qnil : cctx.results;
647
+ return stream ? Qnil : cctx.results;
611
648
  }
612
649
 
613
650
  static VALUE rb_fast_curl_execute(int argc, VALUE *argv, VALUE self) {
614
- VALUE requests, options;
615
- rb_scan_args(argc, argv, "1:", &requests, &options);
616
- return internal_execute(requests, options, -1, 0);
651
+ VALUE requests, options;
652
+ rb_scan_args(argc, argv, "1:", &requests, &options);
653
+ return internal_execute(requests, options, -1, 0);
617
654
  }
618
655
 
619
656
  static VALUE rb_fast_curl_first_execute(int argc, VALUE *argv, VALUE self) {
620
- VALUE requests, options;
621
- rb_scan_args(argc, argv, "1:", &requests, &options);
622
-
623
- int count = 1;
624
- if (!NIL_P(options)) {
625
- VALUE c = rb_hash_aref(options, ID2SYM(rb_intern("count")));
626
- if (!NIL_P(c))
627
- count = NUM2INT(c);
628
- }
657
+ VALUE requests, options;
658
+ rb_scan_args(argc, argv, "1:", &requests, &options);
659
+
660
+ int count = 1;
661
+ if (!NIL_P(options)) {
662
+ VALUE c = rb_hash_aref(options, sym_count);
663
+ if (!NIL_P(c))
664
+ count = NUM2INT(c);
665
+ }
629
666
 
630
- return internal_execute(requests, options, count, 0);
667
+ return internal_execute(requests, options, count, 0);
631
668
  }
632
669
 
633
670
  static VALUE rb_fast_curl_stream_execute(int argc, VALUE *argv, VALUE self) {
634
- VALUE requests, options;
635
- rb_scan_args(argc, argv, "1:", &requests, &options);
671
+ VALUE requests, options;
672
+ rb_scan_args(argc, argv, "1:", &requests, &options);
636
673
 
637
- if (!rb_block_given_p())
638
- rb_raise(rb_eArgError, "stream_execute requires a block");
674
+ if (!rb_block_given_p())
675
+ rb_raise(rb_eArgError, "stream_execute requires a block");
639
676
 
640
- return internal_execute(requests, options, -1, 1);
677
+ return internal_execute(requests, options, -1, 1);
641
678
  }
642
679
 
643
680
  void Init_fast_curl(void) {
644
- curl_global_init(CURL_GLOBAL_ALL);
645
-
646
- VALUE mFastCurl = rb_define_module("FastCurl");
647
-
648
- rb_define_module_function(mFastCurl, "execute", rb_fast_curl_execute, -1);
649
- rb_define_module_function(mFastCurl, "first_execute",
650
- rb_fast_curl_first_execute, -1);
651
- rb_define_module_function(mFastCurl, "stream_execute",
652
- rb_fast_curl_stream_execute, -1);
681
+ curl_global_init(CURL_GLOBAL_ALL);
682
+
683
+ id_status = rb_intern("status");
684
+ id_headers = rb_intern("headers");
685
+ id_body = rb_intern("body");
686
+ id_error_code = rb_intern("error_code");
687
+ id_url = rb_intern("url");
688
+ id_method = rb_intern("method");
689
+ id_timeout = rb_intern("timeout");
690
+ id_connections = rb_intern("connections");
691
+ id_count = rb_intern("count");
692
+ id_keys = rb_intern("keys");
693
+
694
+ sym_status = ID2SYM(id_status);
695
+ rb_gc_register_address(&sym_status);
696
+ sym_headers = ID2SYM(id_headers);
697
+ rb_gc_register_address(&sym_headers);
698
+ sym_body = ID2SYM(id_body);
699
+ rb_gc_register_address(&sym_body);
700
+ sym_error_code = ID2SYM(id_error_code);
701
+ rb_gc_register_address(&sym_error_code);
702
+ sym_url = ID2SYM(id_url);
703
+ rb_gc_register_address(&sym_url);
704
+ sym_method = ID2SYM(id_method);
705
+ rb_gc_register_address(&sym_method);
706
+ sym_timeout = ID2SYM(id_timeout);
707
+ rb_gc_register_address(&sym_timeout);
708
+ sym_connections = ID2SYM(id_connections);
709
+ rb_gc_register_address(&sym_connections);
710
+ sym_count = ID2SYM(id_count);
711
+ rb_gc_register_address(&sym_count);
712
+
713
+ VALUE mFastCurl = rb_define_module("FastCurl");
714
+
715
+ rb_define_module_function(mFastCurl, "execute", rb_fast_curl_execute, -1);
716
+ rb_define_module_function(mFastCurl, "first_execute", rb_fast_curl_first_execute, -1);
717
+ rb_define_module_function(mFastCurl, "stream_execute", rb_fast_curl_stream_execute, -1);
653
718
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FastCurl
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_curl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - roman-haidarov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-21 00:00:00.000000000 Z
11
+ date: 2026-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json