curb 1.2.0 → 1.2.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/README.md +70 -1
- data/ext/curb.c +5 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +139 -10
- data/ext/curb_easy.h +1 -0
- data/ext/curb_multi.c +118 -5
- data/ext/curb_multi.h +4 -1
- data/ext/extconf.rb +1 -0
- data/lib/curl/easy.rb +56 -5
- data/tests/tc_curl_multi.rb +29 -0
- metadata +2 -12
- data/tests/test_basic.rb +0 -29
- data/tests/test_fiber_debug.rb +0 -69
- data/tests/test_fiber_simple.rb +0 -65
- data/tests/test_real_url.rb +0 -65
- data/tests/test_simple_fiber.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48135be870a425c76338a773b41dc9438883b30cab622cc8c2d81394b860047d
|
4
|
+
data.tar.gz: e7a439c010c1bdbd3ab26b92f2126a4d518a2c7808d7acc6af2e57a03ec07376
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5c61d835a10a80211b625dd3ef8cf949fdcf30be9584d2a6793d69e309e3cb283097c2502a8912801ab9ee13679f1e71344034a197c54c88872157e66df74a0
|
7
|
+
data.tar.gz: 20fbde1832dc7338dba7f78c08556eceab4532247f41f0bb1c0c15ba11fc0cd2f07aabeaef62525eba24afc95b5e9854dbb3d71f6d6f493097d614ae913818f8
|
data/README.md
CHANGED
@@ -74,11 +74,37 @@ puts "\n=== FTP Directory Listing Example ==="
|
|
74
74
|
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
|
75
75
|
list.username = 'user'
|
76
76
|
list.password = 'password'
|
77
|
-
list.dirlistonly
|
77
|
+
list.set(:dirlistonly, 1)
|
78
78
|
list.perform
|
79
79
|
puts list.body
|
80
80
|
```
|
81
81
|
|
82
|
+
### FTP over HTTP proxy tunnel (NLST/LIST)
|
83
|
+
When listing directories through an HTTP proxy with `proxy_tunnel` (CONNECT), let libcurl manage the passive data connection. Do not send `PASV`/`EPSV` or `NLST` via `easy.ftp_commands` — QUOTE commands run on the control connection and libcurl will not open the data connection, resulting in 425 errors.
|
84
|
+
|
85
|
+
To get NLST-like output safely:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
|
89
|
+
list.username = 'user'
|
90
|
+
list.password = 'password'
|
91
|
+
list.proxy_url = 'http://proxy.example.com:80'
|
92
|
+
list.proxy_tunnel = true
|
93
|
+
|
94
|
+
# Ask libcurl to perform a listing (names only)
|
95
|
+
list.set(:dirlistonly, 1)
|
96
|
+
|
97
|
+
# If the proxy or server has trouble with EPSV/EPRT, you can adjust:
|
98
|
+
# list.set(:ftp_use_epsv, 0) # disable EPSV
|
99
|
+
# list.set(:ftp_use_eprt, 0) # disable EPRT (stick to IPv4 PASV)
|
100
|
+
# list.set(:ftp_skip_pasv_ip, 1) # ignore PASV host, reuse control host
|
101
|
+
|
102
|
+
list.perform
|
103
|
+
puts list.body
|
104
|
+
```
|
105
|
+
|
106
|
+
If you need a full `LIST` output instead of just names, omit `dirlistonly` and parse the server response accordingly. The key is to let libcurl initiate the data connection (PASV/EPSV) instead of trying to drive it via `ftp_commands`.
|
107
|
+
|
82
108
|
### Advanced FTP Usage with Various Options
|
83
109
|
```
|
84
110
|
puts "\n=== Advanced FTP Example ==="
|
@@ -327,6 +353,8 @@ end
|
|
327
353
|
|
328
354
|
### HTTP POST form:
|
329
355
|
|
356
|
+
Note: Instance methods like `easy.http_post(...)` do not accept a URL argument. Set the URL first (for example, `Curl::Easy.new(url)` or `easy.url = url`) and then call `easy.http_post(...)`. If you want to pass the URL directly to the call, use the class/module helpers such as `Curl::Easy.http_post(url, ...)` or `Curl.post(url, ...)`.
|
357
|
+
|
330
358
|
```ruby
|
331
359
|
c = Curl::Easy.http_post("http://my.rails.box/thing/create",
|
332
360
|
Curl::PostField.content('thing[name]', 'box'),
|
@@ -339,6 +367,19 @@ c = Curl::Easy.http_post("http://my.rails.box/thing/create",
|
|
339
367
|
c = Curl::Easy.new("http://my.rails.box/files/upload")
|
340
368
|
c.multipart_form_post = true
|
341
369
|
c.http_post(Curl::PostField.file('thing[file]', 'myfile.rb'))
|
370
|
+
|
371
|
+
### Custom request target
|
372
|
+
|
373
|
+
Some advanced scenarios need a request-target that differs from the URL host/path (for example, absolute-form targets or special values like `*`). If your libcurl supports `CURLOPT_REQUEST_TARGET` (libcurl ≥ 7.55), you can override it:
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
c = Curl::Easy.new("http://127.0.0.1:9129/methods")
|
377
|
+
c.request_target = "http://localhost:9129/methods" # absolute-form target
|
378
|
+
c.headers = { 'Host' => 'example.com' } # override Host header if needed
|
379
|
+
c.perform
|
380
|
+
```
|
381
|
+
|
382
|
+
For HTTPS, prefer `easy.resolve = ["host:443:IP"]` to keep Host/SNI/certificates aligned.
|
342
383
|
```
|
343
384
|
|
344
385
|
### Using HTTP/2
|
@@ -418,3 +459,31 @@ end
|
|
418
459
|
* `on_missing` is called when the response code is 4xx
|
419
460
|
* `on_failure` is called when the response code is 5xx
|
420
461
|
* `on_complete` is called in all cases.
|
462
|
+
|
463
|
+
### Cookies
|
464
|
+
|
465
|
+
- Manual cookies: Set the outgoing `Cookie` header via `easy.cookies = "name=value; other=val"`. This only affects the request header and does not modify libcurl's internal cookie engine.
|
466
|
+
- Cookie engine: Enable with `easy.enable_cookies = true`. Optionally set `easy.cookiefile` (to load) and/or `easy.cookiejar` (to persist). Cookies received via `Set-Cookie` go into this engine.
|
467
|
+
- Inspect engine cookies: `easy.cookielist` returns an array of strings (Netscape or Set-Cookie format).
|
468
|
+
- Modify engine cookies: use `easy.cookielist = ...` or `easy.set(:cookielist, ...)` with either a `Set-Cookie` style string, Netscape cookie lines, or special commands: `"ALL"` (clear), `"SESS"` (remove session cookies), `"FLUSH"` (write to jar), `"RELOAD"` (reload from file).
|
469
|
+
- Clearing manual cookies: assign an empty string (`easy.cookies = ''`). Assigning `nil` has no effect in current versions.
|
470
|
+
|
471
|
+
Examples:
|
472
|
+
|
473
|
+
```ruby
|
474
|
+
easy = Curl::Easy.new("https://example.com")
|
475
|
+
|
476
|
+
# Use the cookie engine and persist cookies
|
477
|
+
easy.enable_cookies = true
|
478
|
+
easy.cookiejar = "/tmp/cookies.txt"
|
479
|
+
easy.perform
|
480
|
+
|
481
|
+
# Later: inspect and tweak engine cookies
|
482
|
+
p easy.cookielist
|
483
|
+
easy.cookielist = 'ALL' # clear stored cookies
|
484
|
+
|
485
|
+
# Send custom Cookie header for a single request
|
486
|
+
easy.cookies = "flag=1; session_override=abc"
|
487
|
+
easy.perform
|
488
|
+
easy.cookies = '' # clear manual Cookie header
|
489
|
+
```
|
data/ext/curb.c
CHANGED
@@ -616,6 +616,11 @@ void Init_curb_core() {
|
|
616
616
|
#if HAVE_CURLOPT_PROXYHEADER
|
617
617
|
CURB_DEFINE(CURLOPT_PROXYHEADER);
|
618
618
|
#endif
|
619
|
+
#if HAVE_CURLOPT_REQUEST_TARGET
|
620
|
+
/* Allows overriding the Request-URI target used in the request line.
|
621
|
+
* Useful for absolute-form requests or special targets like "*". */
|
622
|
+
CURB_DEFINE(CURLOPT_REQUEST_TARGET);
|
623
|
+
#endif
|
619
624
|
#if HAVE_CURLOPT_HTTP200ALIASES
|
620
625
|
CURB_DEFINE(CURLOPT_HTTP200ALIASES);
|
621
626
|
#endif
|
data/ext/curb.h
CHANGED
@@ -28,11 +28,11 @@
|
|
28
28
|
#include "curb_macros.h"
|
29
29
|
|
30
30
|
// These should be managed from the Rake 'release' task.
|
31
|
-
#define CURB_VERSION "1.2.
|
32
|
-
#define CURB_VER_NUM
|
31
|
+
#define CURB_VERSION "1.2.1"
|
32
|
+
#define CURB_VER_NUM 1021
|
33
33
|
#define CURB_VER_MAJ 1
|
34
34
|
#define CURB_VER_MIN 2
|
35
|
-
#define CURB_VER_MIC
|
35
|
+
#define CURB_VER_MIC 1
|
36
36
|
#define CURB_VER_PATCH 0
|
37
37
|
|
38
38
|
|
data/ext/curb_easy.c
CHANGED
@@ -225,6 +225,24 @@ void curl_easy_mark(ruby_curl_easy *rbce) {
|
|
225
225
|
}
|
226
226
|
|
227
227
|
static void ruby_curl_easy_free(ruby_curl_easy *rbce) {
|
228
|
+
if (!rbce) {
|
229
|
+
return;
|
230
|
+
}
|
231
|
+
|
232
|
+
if (!NIL_P(rbce->multi)) {
|
233
|
+
VALUE multi_val = rbce->multi;
|
234
|
+
ruby_curl_multi *rbcm = NULL;
|
235
|
+
|
236
|
+
rbce->multi = Qnil;
|
237
|
+
|
238
|
+
if (!NIL_P(multi_val) && RB_TYPE_P(multi_val, T_DATA)) {
|
239
|
+
Data_Get_Struct(multi_val, ruby_curl_multi, rbcm);
|
240
|
+
if (rbcm) {
|
241
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
228
246
|
if (rbce->curl_headers) {
|
229
247
|
curl_slist_free_all(rbce->curl_headers);
|
230
248
|
}
|
@@ -310,6 +328,7 @@ static void ruby_curl_easy_zero(ruby_curl_easy *rbce) {
|
|
310
328
|
rbce->verbose = 0;
|
311
329
|
rbce->multipart_form_post = 0;
|
312
330
|
rbce->enable_cookies = 0;
|
331
|
+
rbce->cookielist_engine_enabled = 0;
|
313
332
|
rbce->ignore_content_length = 0;
|
314
333
|
rbce->callback_active = 0;
|
315
334
|
rbce->last_result = 0;
|
@@ -664,7 +683,16 @@ static VALUE ruby_curl_easy_proxypwd_get(VALUE self) {
|
|
664
683
|
* call-seq:
|
665
684
|
* easy.cookies => "name1=content1; name2=content2;"
|
666
685
|
*
|
667
|
-
* Obtain the
|
686
|
+
* Obtain the manually set Cookie header string for this Curl::Easy instance.
|
687
|
+
*
|
688
|
+
* Notes:
|
689
|
+
* - This corresponds to libcurl's CURLOPT_COOKIE and only affects the outgoing
|
690
|
+
* Cookie request header. It does NOT modify the internal libcurl cookie engine
|
691
|
+
* that stores cookies received via Set-Cookie.
|
692
|
+
* - To inspect or modify cookies stored in the cookie engine, use
|
693
|
+
* +easy.cookielist+ (getter) and +easy.cookielist=+ or +easy.set(:cookielist, ...)+ (setter).
|
694
|
+
* - To clear a previously set manual Cookie header, assign an empty string.
|
695
|
+
* Assigning +nil+ currently has no effect.
|
668
696
|
*/
|
669
697
|
static VALUE ruby_curl_easy_cookies_get(VALUE self) {
|
670
698
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookies);
|
@@ -674,7 +702,8 @@ static VALUE ruby_curl_easy_cookies_get(VALUE self) {
|
|
674
702
|
* call-seq:
|
675
703
|
* easy.cookiefile => string
|
676
704
|
*
|
677
|
-
* Obtain the cookiefile
|
705
|
+
* Obtain the cookiefile path for this Curl::Easy instance (used to load cookies when the
|
706
|
+
* cookie engine is enabled).
|
678
707
|
*/
|
679
708
|
static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
|
680
709
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookiefile);
|
@@ -684,7 +713,8 @@ static VALUE ruby_curl_easy_cookiefile_get(VALUE self) {
|
|
684
713
|
* call-seq:
|
685
714
|
* easy.cookiejar => string
|
686
715
|
*
|
687
|
-
* Obtain the cookiejar
|
716
|
+
* Obtain the cookiejar path for this Curl::Easy instance (used to persist cookies when the
|
717
|
+
* cookie engine is enabled).
|
688
718
|
*/
|
689
719
|
static VALUE ruby_curl_easy_cookiejar_get(VALUE self) {
|
690
720
|
CURB_OBJECT_HGETTER(ruby_curl_easy, cookiejar);
|
@@ -984,7 +1014,15 @@ static VALUE ruby_curl_easy_put_data_set(VALUE self, VALUE data) {
|
|
984
1014
|
* call-seq:
|
985
1015
|
* easy.ftp_commands = ["CWD /", "MKD directory"] => ["CWD /", ...]
|
986
1016
|
*
|
987
|
-
* Explicitly sets the list of commands to execute on the FTP server when calling perform
|
1017
|
+
* Explicitly sets the list of commands to execute on the FTP server when calling perform.
|
1018
|
+
*
|
1019
|
+
* NOTE:
|
1020
|
+
* - This maps to libcurl CURLOPT_QUOTE; it sends commands on the control connection.
|
1021
|
+
* - Do not include data-transfer commands like LIST/NLST/RETR/STOR here. libcurl does not
|
1022
|
+
* parse PASV/EPSV replies from QUOTE commands and will not establish the required data
|
1023
|
+
* connection. For directory listings, set CURLOPT_DIRLISTONLY (via `easy.set(:dirlistonly, true)`)
|
1024
|
+
* and request an FTP directory URL (e.g. "ftp://host/path/") so libcurl manages PASV/EPSV
|
1025
|
+
* and the data connection for you.
|
988
1026
|
*/
|
989
1027
|
static VALUE ruby_curl_easy_ftp_commands_set(VALUE self, VALUE ftp_commands) {
|
990
1028
|
CURB_OBJECT_HSETTER(ruby_curl_easy, ftp_commands);
|
@@ -1893,7 +1931,12 @@ static VALUE ruby_curl_easy_multipart_form_post_q(VALUE self) {
|
|
1893
1931
|
* easy.enable_cookies = boolean => boolean
|
1894
1932
|
*
|
1895
1933
|
* Configure whether the libcurl cookie engine is enabled for this Curl::Easy
|
1896
|
-
* instance.
|
1934
|
+
* instance. When enabled, cookies received via Set-Cookie are stored by libcurl
|
1935
|
+
* and automatically sent on subsequent matching requests. Use +easy.cookiefile+
|
1936
|
+
* to load cookies and +easy.cookiejar+ to persist them.
|
1937
|
+
*
|
1938
|
+
* This setting is independent from the manual Cookie header set via +easy.cookies+.
|
1939
|
+
* The manual header is additive and can be cleared by assigning an empty string.
|
1897
1940
|
*/
|
1898
1941
|
static VALUE ruby_curl_easy_enable_cookies_set(VALUE self, VALUE enable_cookies)
|
1899
1942
|
{
|
@@ -2486,9 +2529,8 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2486
2529
|
#endif
|
2487
2530
|
}
|
2488
2531
|
|
2489
|
-
/* Set up HTTP cookie handling if necessary
|
2490
|
-
|
2491
|
-
*/
|
2532
|
+
/* Set up HTTP cookie handling if necessary */
|
2533
|
+
/* Enable/attach cookie engine if requested, or implicitly via COOKIELIST usage */
|
2492
2534
|
if (rbce->enable_cookies) {
|
2493
2535
|
if (!rb_easy_nil("cookiejar")) {
|
2494
2536
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, rb_easy_get_str("cookiejar"));
|
@@ -2499,6 +2541,9 @@ VALUE ruby_curl_easy_setup(ruby_curl_easy *rbce) {
|
|
2499
2541
|
} else {
|
2500
2542
|
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* "" = magic to just enable */
|
2501
2543
|
}
|
2544
|
+
} else if (rbce->cookielist_engine_enabled) {
|
2545
|
+
/* Ensure cookie engine is enabled even if enable_cookies? is false. */
|
2546
|
+
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
|
2502
2547
|
}
|
2503
2548
|
|
2504
2549
|
if (!rb_easy_nil("cookies")) {
|
@@ -3595,6 +3640,12 @@ static VALUE ruby_curl_easy_num_connects_get(VALUE self) {
|
|
3595
3640
|
* Returned strings are in Netscape cookiejar format or in Set-Cookie format.
|
3596
3641
|
* Since 7.43.0 cookies in the Set-Cookie format without a domain name are not exported.
|
3597
3642
|
*
|
3643
|
+
* To modify the cookie engine (add/replace/remove), use +easy.cookielist= string+
|
3644
|
+
* or +easy.set(:cookielist, string)+ with one of the following accepted inputs:
|
3645
|
+
* - A Set-Cookie style header string: "Set-Cookie: name=value; Domain=example.com; Path=/; Expires=..."
|
3646
|
+
* - One or more lines in Netscape cookie file format (tab-separated fields)
|
3647
|
+
* - Special commands: "ALL" (clear all), "SESS" (remove session cookies), "FLUSH" (write to jar), "RELOAD" (reload from file)
|
3648
|
+
*
|
3598
3649
|
* @see https://curl.se/libcurl/c/CURLINFO_COOKIELIST.html option <code>CURLINFO_COOKIELIST</code> of
|
3599
3650
|
* <code>curl_easy_getopt(3)</code> to see how libcurl behaves.
|
3600
3651
|
* @note requires libcurl 7.14.1 or higher, otherwise +-1+ is always returned
|
@@ -3656,7 +3707,7 @@ static VALUE ruby_curl_easy_ftp_entry_path_get(VALUE self) {
|
|
3656
3707
|
return Qnil;
|
3657
3708
|
}
|
3658
3709
|
#else
|
3659
|
-
rb_warn("Installed libcurl is too old to support
|
3710
|
+
rb_warn("Installed libcurl is too old to support ftp_entry_path");
|
3660
3711
|
return Qnil;
|
3661
3712
|
#endif
|
3662
3713
|
}
|
@@ -3815,9 +3866,72 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3815
3866
|
VALUE cookiejar = val;
|
3816
3867
|
CURB_OBJECT_HSETTER(ruby_curl_easy, cookiejar);
|
3817
3868
|
} break;
|
3869
|
+
#if HAVE_CURLOPT_REQUEST_TARGET
|
3870
|
+
case CURLOPT_REQUEST_TARGET: {
|
3871
|
+
/* Forward request-target directly to libcurl as a string. */
|
3872
|
+
curl_easy_setopt(rbce->curl, CURLOPT_REQUEST_TARGET, NIL_P(val) ? NULL : StringValueCStr(val));
|
3873
|
+
} break;
|
3874
|
+
#endif
|
3818
3875
|
case CURLOPT_TCP_NODELAY: {
|
3819
3876
|
curl_easy_setopt(rbce->curl, CURLOPT_TCP_NODELAY, NUM2LONG(val));
|
3820
3877
|
} break;
|
3878
|
+
/* FTP-specific toggles */
|
3879
|
+
#if HAVE_CURLOPT_DIRLISTONLY
|
3880
|
+
case CURLOPT_DIRLISTONLY: {
|
3881
|
+
int type = rb_type(val);
|
3882
|
+
VALUE value;
|
3883
|
+
if (type == T_TRUE) {
|
3884
|
+
value = rb_int_new(1);
|
3885
|
+
} else if (type == T_FALSE) {
|
3886
|
+
value = rb_int_new(0);
|
3887
|
+
} else {
|
3888
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3889
|
+
}
|
3890
|
+
curl_easy_setopt(rbce->curl, CURLOPT_DIRLISTONLY, NUM2LONG(value));
|
3891
|
+
} break;
|
3892
|
+
#endif
|
3893
|
+
#if HAVE_CURLOPT_FTP_USE_EPSV
|
3894
|
+
case CURLOPT_FTP_USE_EPSV: {
|
3895
|
+
int type = rb_type(val);
|
3896
|
+
VALUE value;
|
3897
|
+
if (type == T_TRUE) {
|
3898
|
+
value = rb_int_new(1);
|
3899
|
+
} else if (type == T_FALSE) {
|
3900
|
+
value = rb_int_new(0);
|
3901
|
+
} else {
|
3902
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3903
|
+
}
|
3904
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPSV, NUM2LONG(value));
|
3905
|
+
} break;
|
3906
|
+
#endif
|
3907
|
+
#if HAVE_CURLOPT_FTP_USE_EPRT
|
3908
|
+
case CURLOPT_FTP_USE_EPRT: {
|
3909
|
+
int type = rb_type(val);
|
3910
|
+
VALUE value;
|
3911
|
+
if (type == T_TRUE) {
|
3912
|
+
value = rb_int_new(1);
|
3913
|
+
} else if (type == T_FALSE) {
|
3914
|
+
value = rb_int_new(0);
|
3915
|
+
} else {
|
3916
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3917
|
+
}
|
3918
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_USE_EPRT, NUM2LONG(value));
|
3919
|
+
} break;
|
3920
|
+
#endif
|
3921
|
+
#if HAVE_CURLOPT_FTP_SKIP_PASV_IP
|
3922
|
+
case CURLOPT_FTP_SKIP_PASV_IP: {
|
3923
|
+
int type = rb_type(val);
|
3924
|
+
VALUE value;
|
3925
|
+
if (type == T_TRUE) {
|
3926
|
+
value = rb_int_new(1);
|
3927
|
+
} else if (type == T_FALSE) {
|
3928
|
+
value = rb_int_new(0);
|
3929
|
+
} else {
|
3930
|
+
value = rb_funcall(val, rb_intern("to_i"), 0);
|
3931
|
+
}
|
3932
|
+
curl_easy_setopt(rbce->curl, CURLOPT_FTP_SKIP_PASV_IP, NUM2LONG(value));
|
3933
|
+
} break;
|
3934
|
+
#endif
|
3821
3935
|
case CURLOPT_RANGE: {
|
3822
3936
|
curl_easy_setopt(rbce->curl, CURLOPT_RANGE, StringValueCStr(val));
|
3823
3937
|
} break;
|
@@ -3895,8 +4009,23 @@ static VALUE ruby_curl_easy_set_opt(VALUE self, VALUE opt, VALUE val) {
|
|
3895
4009
|
#endif
|
3896
4010
|
#if HAVE_CURLOPT_COOKIELIST
|
3897
4011
|
case CURLOPT_COOKIELIST: {
|
4012
|
+
/* Forward to libcurl */
|
3898
4013
|
curl_easy_setopt(rbce->curl, CURLOPT_COOKIELIST, StringValueCStr(val));
|
3899
|
-
|
4014
|
+
/* Track whether the cookie engine should be enabled for requests.
|
4015
|
+
* According to libcurl docs, CURLOPT_COOKIELIST also enables the cookie engine
|
4016
|
+
* when provided with a non-command string. Some environments may still require
|
4017
|
+
* an explicit "enable" via CURLOPT_COOKIEFILE="" to send cookies on requests.
|
4018
|
+
* We do that in the perform setup when this flag is set.
|
4019
|
+
*/
|
4020
|
+
if (RB_TYPE_P(val, T_STRING)) {
|
4021
|
+
const char *s = StringValueCStr(val);
|
4022
|
+
if (!(strcmp(s, "ALL") == 0 || strcmp(s, "SESS") == 0 || strcmp(s, "FLUSH") == 0 || strcmp(s, "RELOAD") == 0)) {
|
4023
|
+
rbce->cookielist_engine_enabled = 1;
|
4024
|
+
}
|
4025
|
+
} else {
|
4026
|
+
/* Non-string values are unexpected; be conservative and do not enable. */
|
4027
|
+
}
|
4028
|
+
} break;
|
3900
4029
|
#endif
|
3901
4030
|
#if HAVE_CURLOPT_PROXY_SSL_VERIFYHOST
|
3902
4031
|
case CURLOPT_PROXY_SSL_VERIFYHOST:
|
data/ext/curb_easy.h
CHANGED
data/ext/curb_multi.c
CHANGED
@@ -76,6 +76,10 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
|
|
76
76
|
static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
|
77
77
|
static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
|
78
78
|
|
79
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg);
|
80
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm);
|
81
|
+
static void curl_multi_mark(void *ptr);
|
82
|
+
|
79
83
|
static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
80
84
|
// TODO: we could have an option to enable exception reporting
|
81
85
|
/* VALUE ret = rb_funcall(exception, rb_intern("message"), 0);
|
@@ -97,8 +101,67 @@ static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
|
97
101
|
return exception;
|
98
102
|
}
|
99
103
|
|
104
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg) {
|
105
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)arg;
|
106
|
+
VALUE easy = (VALUE)val;
|
107
|
+
ruby_curl_easy *rbce = NULL;
|
108
|
+
|
109
|
+
if (RB_TYPE_P(easy, T_DATA)) {
|
110
|
+
Data_Get_Struct(easy, ruby_curl_easy, rbce);
|
111
|
+
}
|
112
|
+
|
113
|
+
if (!rbce) {
|
114
|
+
return ST_CONTINUE;
|
115
|
+
}
|
116
|
+
|
117
|
+
if (rbcm && rbcm->handle && rbce->curl) {
|
118
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
119
|
+
}
|
120
|
+
|
121
|
+
rbce->multi = Qnil;
|
122
|
+
|
123
|
+
return ST_CONTINUE;
|
124
|
+
}
|
125
|
+
|
126
|
+
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
127
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
128
|
+
|
129
|
+
if (!rbcm || !rbce || !rbcm->attached) {
|
130
|
+
return;
|
131
|
+
}
|
132
|
+
|
133
|
+
st_data_t key = (st_data_t)rbce;
|
134
|
+
st_delete(rbcm->attached, &key, NULL);
|
135
|
+
}
|
136
|
+
|
137
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm) {
|
138
|
+
if (!rbcm || !rbcm->attached) {
|
139
|
+
return;
|
140
|
+
}
|
141
|
+
|
142
|
+
st_table *attached = rbcm->attached;
|
143
|
+
rbcm->attached = NULL;
|
144
|
+
|
145
|
+
st_foreach(attached, detach_easy_entry, (st_data_t)rbcm);
|
146
|
+
|
147
|
+
st_free_table(attached);
|
148
|
+
|
149
|
+
rbcm->active = 0;
|
150
|
+
rbcm->running = 0;
|
151
|
+
}
|
152
|
+
|
100
153
|
void curl_multi_free(ruby_curl_multi *rbcm) {
|
101
|
-
|
154
|
+
if (!rbcm) {
|
155
|
+
return;
|
156
|
+
}
|
157
|
+
|
158
|
+
rb_curl_multi_detach_all(rbcm);
|
159
|
+
|
160
|
+
if (rbcm->handle) {
|
161
|
+
curl_multi_cleanup(rbcm->handle);
|
162
|
+
rbcm->handle = NULL;
|
163
|
+
}
|
164
|
+
|
102
165
|
free(rbcm);
|
103
166
|
}
|
104
167
|
|
@@ -110,6 +173,18 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
110
173
|
|
111
174
|
rbcm->active = 0;
|
112
175
|
rbcm->running = 0;
|
176
|
+
|
177
|
+
if (rbcm->attached) {
|
178
|
+
st_free_table(rbcm->attached);
|
179
|
+
rbcm->attached = NULL;
|
180
|
+
}
|
181
|
+
|
182
|
+
rbcm->attached = st_init_numtable();
|
183
|
+
if (!rbcm->attached) {
|
184
|
+
curl_multi_cleanup(rbcm->handle);
|
185
|
+
rbcm->handle = NULL;
|
186
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
187
|
+
}
|
113
188
|
}
|
114
189
|
|
115
190
|
/*
|
@@ -124,6 +199,8 @@ VALUE ruby_curl_multi_new(VALUE klass) {
|
|
124
199
|
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Multi");
|
125
200
|
}
|
126
201
|
|
202
|
+
MEMZERO(rbcm, ruby_curl_multi, 1);
|
203
|
+
|
127
204
|
ruby_curl_multi_init(rbcm);
|
128
205
|
|
129
206
|
/*
|
@@ -131,8 +208,8 @@ VALUE ruby_curl_multi_new(VALUE klass) {
|
|
131
208
|
* If your structure references other Ruby objects, then your mark function needs to
|
132
209
|
* identify these objects using rb_gc_mark(value). If the structure doesn't reference
|
133
210
|
* other Ruby objects, you can simply pass 0 as a function pointer.
|
134
|
-
|
135
|
-
return Data_Wrap_Struct(klass,
|
211
|
+
*/
|
212
|
+
return Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
|
136
213
|
}
|
137
214
|
|
138
215
|
/*
|
@@ -292,6 +369,17 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
292
369
|
* If this number is not correct, the next call to curl_multi_perform will correct it. */
|
293
370
|
rbcm->running++;
|
294
371
|
|
372
|
+
if (!rbcm->attached) {
|
373
|
+
rbcm->attached = st_init_numtable();
|
374
|
+
if (!rbcm->attached) {
|
375
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
376
|
+
ruby_curl_easy_cleanup(easy, rbce);
|
377
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
378
|
+
}
|
379
|
+
}
|
380
|
+
|
381
|
+
st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy);
|
382
|
+
|
295
383
|
/* track a reference to associated multi handle */
|
296
384
|
rbce->multi = self;
|
297
385
|
|
@@ -332,9 +420,13 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
|
|
332
420
|
raise_curl_multi_error_exception(result);
|
333
421
|
}
|
334
422
|
|
335
|
-
rbcm->active
|
423
|
+
if (rbcm->active > 0) {
|
424
|
+
rbcm->active--;
|
425
|
+
}
|
336
426
|
|
337
427
|
ruby_curl_easy_cleanup( easy, rbce );
|
428
|
+
|
429
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
338
430
|
}
|
339
431
|
|
340
432
|
// on_success, on_failure, on_complete
|
@@ -1158,11 +1250,32 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
1158
1250
|
VALUE ruby_curl_multi_close(VALUE self) {
|
1159
1251
|
ruby_curl_multi *rbcm;
|
1160
1252
|
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
1161
|
-
|
1253
|
+
rb_curl_multi_detach_all(rbcm);
|
1254
|
+
|
1255
|
+
if (rbcm->handle) {
|
1256
|
+
curl_multi_cleanup(rbcm->handle);
|
1257
|
+
rbcm->handle = NULL;
|
1258
|
+
}
|
1259
|
+
|
1162
1260
|
ruby_curl_multi_init(rbcm);
|
1163
1261
|
return self;
|
1164
1262
|
}
|
1165
1263
|
|
1264
|
+
/* GC mark: keep attached easy VALUEs alive while associated. */
|
1265
|
+
static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) {
|
1266
|
+
VALUE easy = (VALUE)val;
|
1267
|
+
if (!NIL_P(easy)) rb_gc_mark(easy);
|
1268
|
+
return ST_CONTINUE;
|
1269
|
+
}
|
1270
|
+
|
1271
|
+
static void curl_multi_mark(void *ptr) {
|
1272
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
|
1273
|
+
if (!rbcm) return;
|
1274
|
+
if (rbcm->attached) {
|
1275
|
+
st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0);
|
1276
|
+
}
|
1277
|
+
}
|
1278
|
+
|
1166
1279
|
|
1167
1280
|
/* =================== INIT LIB =====================*/
|
1168
1281
|
void init_curb_multi() {
|
data/ext/curb_multi.h
CHANGED
@@ -8,18 +8,21 @@
|
|
8
8
|
#define __CURB_MULTI_H
|
9
9
|
|
10
10
|
#include "curb.h"
|
11
|
-
#include "curb_easy.h"
|
12
11
|
#include <curl/multi.h>
|
13
12
|
|
13
|
+
struct st_table;
|
14
|
+
|
14
15
|
typedef struct {
|
15
16
|
int active;
|
16
17
|
int running;
|
17
18
|
CURLM *handle;
|
19
|
+
struct st_table *attached;
|
18
20
|
} ruby_curl_multi;
|
19
21
|
|
20
22
|
extern VALUE cCurlMulti;
|
21
23
|
void init_curb_multi();
|
22
24
|
VALUE ruby_curl_multi_new(VALUE klass);
|
25
|
+
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
|
23
26
|
|
24
27
|
|
25
28
|
#endif
|
data/ext/extconf.rb
CHANGED
@@ -500,6 +500,7 @@ have_constant "curlusessl_try"
|
|
500
500
|
have_constant "curlusessl_control"
|
501
501
|
have_constant "curlusessl_all"
|
502
502
|
have_constant "curlopt_resolve"
|
503
|
+
have_constant "curlopt_request_target"
|
503
504
|
have_constant "curlopt_sslcert"
|
504
505
|
have_constant "curlopt_sslcerttype"
|
505
506
|
have_constant "curlopt_sslkey"
|
data/lib/curl/easy.rb
CHANGED
@@ -158,6 +158,22 @@ module Curl
|
|
158
158
|
set :proxy, url
|
159
159
|
end
|
160
160
|
|
161
|
+
#
|
162
|
+
# call-seq:
|
163
|
+
# easy.request_target = string => string
|
164
|
+
#
|
165
|
+
# Set the request-target used in the HTTP request line (libcurl CURLOPT_REQUEST_TARGET).
|
166
|
+
# Useful for absolute-form request targets (e.g., when speaking to proxies) or
|
167
|
+
# special targets like "*" (OPTIONS *). Requires libcurl with CURLOPT_REQUEST_TARGET support.
|
168
|
+
#
|
169
|
+
def request_target=(value)
|
170
|
+
if Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
|
171
|
+
set :request_target, value
|
172
|
+
else
|
173
|
+
raise NotImplementedError, "CURLOPT_REQUEST_TARGET is not supported by this libcurl"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
161
177
|
def ssl_verify_host=(value)
|
162
178
|
value = 1 if value.class == TrueClass
|
163
179
|
value = 0 if value.class == FalseClass
|
@@ -216,9 +232,18 @@ module Curl
|
|
216
232
|
# call-seq:
|
217
233
|
# easy.cookies = "name1=content1; name2=content2;" => string
|
218
234
|
#
|
219
|
-
# Set
|
220
|
-
# be NAME=CONTENTS, where NAME is the cookie name and
|
221
|
-
# Set multiple cookies in one string like this:
|
235
|
+
# Set the manual Cookie request header for this Curl::Easy instance.
|
236
|
+
# The format of the string should be NAME=CONTENTS, where NAME is the cookie name and
|
237
|
+
# CONTENTS is what the cookie should contain. Set multiple cookies in one string like this:
|
238
|
+
# "name1=content1; name2=content2;".
|
239
|
+
#
|
240
|
+
# Notes:
|
241
|
+
# - This only affects the outgoing Cookie header (libcurl CURLOPT_COOKIE) and does NOT
|
242
|
+
# alter the internal libcurl cookie engine (which stores cookies from Set-Cookie).
|
243
|
+
# - To change cookies stored in the engine, use {#cookielist} / {#cookielist=} or
|
244
|
+
# {#set} with :cookielist.
|
245
|
+
# - To clear a previously set manual Cookie header, assign an empty string ('').
|
246
|
+
# Assigning +nil+ has no effect in current curb versions.
|
222
247
|
#
|
223
248
|
def cookies=(value)
|
224
249
|
set :cookie, value
|
@@ -233,6 +258,8 @@ module Curl
|
|
233
258
|
# *Note* that you must set enable_cookies true to enable the cookie
|
234
259
|
# engine, or this option will be ignored.
|
235
260
|
#
|
261
|
+
# Note: assigning +nil+ has no effect; pass a path string to use a cookie file.
|
262
|
+
#
|
236
263
|
def cookiefile=(value)
|
237
264
|
set :cookiefile, value
|
238
265
|
end
|
@@ -241,16 +268,40 @@ module Curl
|
|
241
268
|
# call-seq:
|
242
269
|
# easy.cookiejar = string => string
|
243
270
|
#
|
244
|
-
# Set a cookiejar file to use for this Curl::Easy instance.
|
245
|
-
#
|
271
|
+
# Set a cookiejar file to use for this Curl::Easy instance. Cookies from the response
|
272
|
+
# will be written into this file.
|
246
273
|
#
|
247
274
|
# *Note* that you must set enable_cookies true to enable the cookie
|
248
275
|
# engine, or this option will be ignored.
|
249
276
|
#
|
277
|
+
# Note: assigning +nil+ has no effect; pass a path string to persist cookies to a file.
|
278
|
+
#
|
250
279
|
def cookiejar=(value)
|
251
280
|
set :cookiejar, value
|
252
281
|
end
|
253
282
|
|
283
|
+
#
|
284
|
+
# call-seq:
|
285
|
+
# easy.cookielist = string => string
|
286
|
+
#
|
287
|
+
# Modify cookies in libcurl's internal cookie engine (CURLOPT_COOKIELIST).
|
288
|
+
# Accepts a Set-Cookie style string, one or more lines in Netscape cookie file format,
|
289
|
+
# or one of the special commands: "ALL" (clear), "SESS" (remove session cookies),
|
290
|
+
# "FLUSH" (write to jar), "RELOAD" (reload from file).
|
291
|
+
#
|
292
|
+
# Examples:
|
293
|
+
# easy.cookielist = "Set-Cookie: session=42; Domain=example.com; Path=/;"
|
294
|
+
# easy.cookielist = [
|
295
|
+
# ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c1', 'v1'].join("\t"),
|
296
|
+
# ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c2', 'v2'].join("\t"),
|
297
|
+
# ''
|
298
|
+
# ].join("\n")
|
299
|
+
# easy.cookielist = 'ALL' # clear all cookies in the engine
|
300
|
+
#
|
301
|
+
def cookielist=(value)
|
302
|
+
set :cookielist, value
|
303
|
+
end
|
304
|
+
|
254
305
|
#
|
255
306
|
# call-seq:
|
256
307
|
# easy = Curl::Easy.new("url") do|c|
|
data/tests/tc_curl_multi.rb
CHANGED
@@ -573,6 +573,35 @@ class TestCurbCurlMulti < Test::Unit::TestCase
|
|
573
573
|
end
|
574
574
|
end
|
575
575
|
|
576
|
+
# Regression test for issue #267 (2015): ensure that when reusing
|
577
|
+
# easy handles with constrained concurrency, the callback receives
|
578
|
+
# the correct URL for each completed request rather than repeating
|
579
|
+
# the first URL.
|
580
|
+
def test_multi_easy_http_urls_unique_across_max_connects
|
581
|
+
urls = [
|
582
|
+
{ :url => TestServlet.url + '?q=1', :method => :get },
|
583
|
+
{ :url => TestServlet.url + '?q=2', :method => :get },
|
584
|
+
{ :url => TestServlet.url + '?q=3', :method => :get }
|
585
|
+
]
|
586
|
+
|
587
|
+
[1, 2, 3].each do |max|
|
588
|
+
results = []
|
589
|
+
Curl::Multi.http(urls.dup, {:pipeline => true, :max_connects => max}) do |easy, code, method|
|
590
|
+
assert_equal 200, code
|
591
|
+
assert_equal :get, method
|
592
|
+
results << easy.last_effective_url
|
593
|
+
end
|
594
|
+
|
595
|
+
# Ensure we saw one completion per input URL
|
596
|
+
assert_equal urls.size, results.size, "expected #{urls.size} results with max_connects=#{max}"
|
597
|
+
|
598
|
+
# And that each URL completed exactly once (no accidental reuse/mis-reporting)
|
599
|
+
expected_urls = urls.map { |u| u[:url] }
|
600
|
+
assert_equal expected_urls.to_set, results.to_set, "unexpected URLs for max_connects=#{max}: #{results.inspect}"
|
601
|
+
assert_equal expected_urls.size, results.uniq.size, "duplicate URLs seen for max_connects=#{max}: #{results.inspect}"
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
576
605
|
def test_multi_recieves_500
|
577
606
|
m = Curl::Multi.new
|
578
607
|
e = Curl::Easy.new("http://127.0.0.1:9129/methods")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: curb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ross Bamford
|
8
8
|
- Todd A. Fisher
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Curb (probably CUrl-RuBy or something) provides Ruby-language bindings
|
14
14
|
for the libcurl(3), a fully-featured client-side URL transfer library. cURL and
|
@@ -76,11 +76,6 @@ files:
|
|
76
76
|
- tests/tc_curl_postfield.rb
|
77
77
|
- tests/tc_curl_protocols.rb
|
78
78
|
- tests/tc_fiber_scheduler.rb
|
79
|
-
- tests/test_basic.rb
|
80
|
-
- tests/test_fiber_debug.rb
|
81
|
-
- tests/test_fiber_simple.rb
|
82
|
-
- tests/test_real_url.rb
|
83
|
-
- tests/test_simple_fiber.rb
|
84
79
|
- tests/timeout.rb
|
85
80
|
- tests/timeout_server.rb
|
86
81
|
- tests/unittests.rb
|
@@ -142,11 +137,6 @@ test_files:
|
|
142
137
|
- tests/tc_curl_postfield.rb
|
143
138
|
- tests/tc_curl_protocols.rb
|
144
139
|
- tests/tc_fiber_scheduler.rb
|
145
|
-
- tests/test_basic.rb
|
146
|
-
- tests/test_fiber_debug.rb
|
147
|
-
- tests/test_fiber_simple.rb
|
148
|
-
- tests/test_real_url.rb
|
149
|
-
- tests/test_simple_fiber.rb
|
150
140
|
- tests/timeout.rb
|
151
141
|
- tests/timeout_server.rb
|
152
142
|
- tests/unittests.rb
|
data/tests/test_basic.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
-
|
3
|
-
class TestBasic < Test::Unit::TestCase
|
4
|
-
include TestServerMethods
|
5
|
-
|
6
|
-
def setup
|
7
|
-
server_setup
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_basic_request
|
11
|
-
puts "\n=== Testing basic request ==="
|
12
|
-
easy = Curl::Easy.new(TestServlet.url)
|
13
|
-
easy.perform
|
14
|
-
puts "Response code: #{easy.response_code}"
|
15
|
-
puts "Body (first 100 chars): #{easy.body_str[0..100]}"
|
16
|
-
assert_equal 200, easy.response_code
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_slow_request
|
20
|
-
puts "\n=== Testing slow request ==="
|
21
|
-
url = TestServlet.url_to("/slow?seconds=0.1")
|
22
|
-
puts "URL: #{url}"
|
23
|
-
easy = Curl::Easy.new(url)
|
24
|
-
easy.perform
|
25
|
-
puts "Response code: #{easy.response_code}"
|
26
|
-
puts "Body: #{easy.body_str}"
|
27
|
-
assert_equal 200, easy.response_code
|
28
|
-
end
|
29
|
-
end
|
data/tests/test_fiber_debug.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
-
require 'async'
|
3
|
-
|
4
|
-
class TestFiberDebug < Test::Unit::TestCase
|
5
|
-
include TestServerMethods
|
6
|
-
|
7
|
-
def setup
|
8
|
-
server_setup
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_simple_fiber_request
|
12
|
-
puts "\n=== Starting simple fiber request test ==="
|
13
|
-
|
14
|
-
run_async do |task|
|
15
|
-
puts "Inside Async block"
|
16
|
-
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
17
|
-
|
18
|
-
multi = Curl::Multi.new
|
19
|
-
easy = Curl::Easy.new(TestServlet.url)
|
20
|
-
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
21
|
-
|
22
|
-
multi.add(easy)
|
23
|
-
puts "Added easy handle to multi"
|
24
|
-
|
25
|
-
# Perform without block first
|
26
|
-
puts "Calling perform..."
|
27
|
-
multi.perform
|
28
|
-
puts "Perform completed"
|
29
|
-
end
|
30
|
-
|
31
|
-
puts "Test completed"
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_fiber_with_block
|
35
|
-
puts "\n=== Starting fiber with block test ==="
|
36
|
-
|
37
|
-
run_async do |task|
|
38
|
-
puts "Inside Async block"
|
39
|
-
|
40
|
-
multi = Curl::Multi.new
|
41
|
-
easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.1"))
|
42
|
-
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
43
|
-
|
44
|
-
multi.add(easy)
|
45
|
-
|
46
|
-
block_calls = 0
|
47
|
-
multi.perform do
|
48
|
-
block_calls += 1
|
49
|
-
puts "Block called: #{block_calls}"
|
50
|
-
if block_calls < 5 # Limit iterations to prevent infinite loop
|
51
|
-
Async::Task.yield
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
puts "Perform completed, block called #{block_calls} times"
|
56
|
-
end
|
57
|
-
|
58
|
-
puts "Test completed"
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
def run_async(&block)
|
63
|
-
if defined?(Async) && Async.respond_to?(:run)
|
64
|
-
Async.run(&block)
|
65
|
-
else
|
66
|
-
Async(&block)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/tests/test_fiber_simple.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
-
require 'async'
|
3
|
-
|
4
|
-
class TestFiberSimple < Test::Unit::TestCase
|
5
|
-
include TestServerMethods
|
6
|
-
|
7
|
-
def setup
|
8
|
-
server_setup
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_simple_concurrent
|
12
|
-
puts "\n=== Testing simple concurrent requests ==="
|
13
|
-
|
14
|
-
results = []
|
15
|
-
|
16
|
-
if Async.respond_to?(:run)
|
17
|
-
Async.run do |task|
|
18
|
-
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
19
|
-
|
20
|
-
multi = Curl::Multi.new
|
21
|
-
|
22
|
-
# Add 3 requests
|
23
|
-
3.times do |i|
|
24
|
-
easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.2&id=#{i}"))
|
25
|
-
easy.on_complete { |curl|
|
26
|
-
results << { id: i, code: curl.response_code }
|
27
|
-
puts "Request #{i} completed"
|
28
|
-
}
|
29
|
-
multi.add(easy)
|
30
|
-
end
|
31
|
-
|
32
|
-
puts "Starting perform..."
|
33
|
-
start_time = Time.now
|
34
|
-
multi.perform # No block
|
35
|
-
elapsed = Time.now - start_time
|
36
|
-
puts "Perform completed in #{elapsed.round(2)}s"
|
37
|
-
end
|
38
|
-
else
|
39
|
-
Async do |task|
|
40
|
-
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
41
|
-
|
42
|
-
multi = Curl::Multi.new
|
43
|
-
|
44
|
-
# Add 3 requests
|
45
|
-
3.times do |i|
|
46
|
-
easy = Curl::Easy.new(TestServlet.url_to("/slow?seconds=0.2&id=#{i}"))
|
47
|
-
easy.on_complete { |curl|
|
48
|
-
results << { id: i, code: curl.response_code }
|
49
|
-
puts "Request #{i} completed"
|
50
|
-
}
|
51
|
-
multi.add(easy)
|
52
|
-
end
|
53
|
-
|
54
|
-
puts "Starting perform..."
|
55
|
-
start_time = Time.now
|
56
|
-
multi.perform # No block
|
57
|
-
elapsed = Time.now - start_time
|
58
|
-
puts "Perform completed in #{elapsed.round(2)}s"
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
assert_equal 3, results.size
|
63
|
-
results.each { |r| assert_equal 200, r[:code] }
|
64
|
-
end
|
65
|
-
end
|
data/tests/test_real_url.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/curb'))
|
2
|
-
require 'async'
|
3
|
-
|
4
|
-
puts "Testing fiber scheduler with real URLs..."
|
5
|
-
|
6
|
-
# Test without fiber scheduler
|
7
|
-
puts "\n1. Without fiber scheduler:"
|
8
|
-
start = Time.now
|
9
|
-
multi = Curl::Multi.new
|
10
|
-
|
11
|
-
easies = []
|
12
|
-
3.times do |i|
|
13
|
-
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
14
|
-
easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
|
15
|
-
multi.add(easy)
|
16
|
-
easies << easy
|
17
|
-
end
|
18
|
-
|
19
|
-
multi.perform
|
20
|
-
elapsed = Time.now - start
|
21
|
-
puts "Total time: #{elapsed.round(2)}s"
|
22
|
-
|
23
|
-
# Test with fiber scheduler
|
24
|
-
puts "\n2. With fiber scheduler:"
|
25
|
-
if Async.respond_to?(:run)
|
26
|
-
Async.run do
|
27
|
-
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
28
|
-
|
29
|
-
start = Time.now
|
30
|
-
multi = Curl::Multi.new
|
31
|
-
|
32
|
-
easies = []
|
33
|
-
3.times do |i|
|
34
|
-
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
35
|
-
easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
|
36
|
-
multi.add(easy)
|
37
|
-
easies << easy
|
38
|
-
end
|
39
|
-
|
40
|
-
multi.perform
|
41
|
-
elapsed = Time.now - start
|
42
|
-
puts "Total time: #{elapsed.round(2)}s"
|
43
|
-
end
|
44
|
-
else
|
45
|
-
Async do
|
46
|
-
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
47
|
-
|
48
|
-
start = Time.now
|
49
|
-
multi = Curl::Multi.new
|
50
|
-
|
51
|
-
easies = []
|
52
|
-
3.times do |i|
|
53
|
-
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
54
|
-
easy.on_complete { |curl| puts "Request #{i} completed: #{curl.response_code}" }
|
55
|
-
multi.add(easy)
|
56
|
-
easies << easy
|
57
|
-
end
|
58
|
-
|
59
|
-
multi.perform
|
60
|
-
elapsed = Time.now - start
|
61
|
-
puts "Total time: #{elapsed.round(2)}s"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
puts "\nDone!"
|
data/tests/test_simple_fiber.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/curb'))
|
2
|
-
require 'async'
|
3
|
-
|
4
|
-
puts "Testing simple fiber scheduler..."
|
5
|
-
|
6
|
-
if Async.respond_to?(:run)
|
7
|
-
Async.run do
|
8
|
-
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
9
|
-
|
10
|
-
multi = Curl::Multi.new
|
11
|
-
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
12
|
-
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
13
|
-
|
14
|
-
multi.add(easy)
|
15
|
-
puts "Starting perform..."
|
16
|
-
multi.perform
|
17
|
-
puts "Perform completed"
|
18
|
-
end
|
19
|
-
else
|
20
|
-
Async do
|
21
|
-
puts "Fiber scheduler available: #{Curl::Multi.fiber_scheduler_available?}"
|
22
|
-
|
23
|
-
multi = Curl::Multi.new
|
24
|
-
easy = Curl::Easy.new("https://httpbin.org/delay/1")
|
25
|
-
easy.on_complete { |curl| puts "Request completed: #{curl.response_code}" }
|
26
|
-
|
27
|
-
multi.add(easy)
|
28
|
-
puts "Starting perform..."
|
29
|
-
multi.perform
|
30
|
-
puts "Perform completed"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
puts "Done!"
|