curb 0.8.5 → 1.3.6

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +579 -0
  3. data/Rakefile +85 -27
  4. data/doc.rb +48 -8
  5. data/ext/banned.h +32 -0
  6. data/ext/curb.c +563 -233
  7. data/ext/curb.h +19 -10
  8. data/ext/curb_easy.c +3229 -368
  9. data/ext/curb_easy.h +42 -0
  10. data/ext/curb_errors.c +117 -18
  11. data/ext/curb_errors.h +9 -5
  12. data/ext/curb_macros.h +33 -21
  13. data/ext/curb_multi.c +1904 -272
  14. data/ext/curb_multi.h +11 -3
  15. data/ext/curb_postfield.c +149 -77
  16. data/ext/curb_postfield.h +1 -0
  17. data/ext/curb_upload.c +38 -11
  18. data/ext/curb_upload.h +2 -0
  19. data/ext/extconf.rb +355 -35
  20. data/lib/curb.rb +1 -0
  21. data/lib/curl/download.rb +160 -0
  22. data/lib/curl/easy.rb +422 -88
  23. data/lib/curl/multi.rb +295 -56
  24. data/lib/curl.rb +676 -11
  25. data/tests/bug_crash_on_debug.rb +14 -28
  26. data/tests/bug_crash_on_progress.rb +32 -16
  27. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  28. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  29. data/tests/bug_follow_redirect_288.rb +83 -0
  30. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  31. data/tests/bug_issue102.rb +17 -0
  32. data/tests/bug_issue_noproxy.rb +56 -0
  33. data/tests/bug_issue_post_redirect.rb +93 -0
  34. data/tests/bug_issue_spnego.rb +41 -0
  35. data/tests/bug_multi_segfault.rb +1 -0
  36. data/tests/bug_poison.rb +29 -0
  37. data/tests/bug_raise_on_callback.rb +30 -0
  38. data/tests/helper.rb +400 -44
  39. data/tests/leak_trace.rb +237 -0
  40. data/tests/mem_check.rb +3 -0
  41. data/tests/tc_curl.rb +31 -1
  42. data/tests/tc_curl_download.rb +98 -7
  43. data/tests/tc_curl_easy.rb +985 -66
  44. data/tests/tc_curl_easy_cookielist.rb +277 -0
  45. data/tests/tc_curl_easy_request_target.rb +41 -0
  46. data/tests/tc_curl_easy_resolve.rb +48 -0
  47. data/tests/tc_curl_maxfilesize.rb +212 -0
  48. data/tests/tc_curl_multi.rb +1111 -51
  49. data/tests/tc_curl_native_coverage.rb +145 -0
  50. data/tests/tc_curl_network_policy.rb +1475 -0
  51. data/tests/tc_curl_postfield.rb +207 -30
  52. data/tests/tc_curl_protocols.rb +388 -0
  53. data/tests/tc_fiber_scheduler.rb +584 -0
  54. data/tests/tc_ftp_options.rb +39 -0
  55. data/tests/tc_gc_compact.rb +223 -0
  56. data/tests/tc_test_server_methods.rb +110 -0
  57. data/tests/timeout.rb +30 -6
  58. metadata +66 -31
  59. data/README +0 -194
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 11348a158e7da3d3406acd021be1a15f881ce2c607ff04e60b522a3bd92cd828
4
+ data.tar.gz: 0c524ce914df77b109a2aad85289386b2bf54028733fcf4ec5657a84abf511b8
5
+ SHA512:
6
+ metadata.gz: b4ebd6b04943bfc099a6593ede1c70298555ed1a6613958e8628cde43c30b8c7f8b86990d91c7a350481589495fe7d5dc4ad1ac3c313823bb7fb21e483e8910c
7
+ data.tar.gz: 0a78bc41496259eae9989d25e4a47c57a9a9aed6f79b00e77efae691b487a730e71e6cdf8081ae35d33893a84b033219ae24c40cf47effd8e3d1b0ca3591f222
data/README.md ADDED
@@ -0,0 +1,579 @@
1
+ # Curb - Libcurl bindings for Ruby
2
+
3
+ [![CI](https://github.com/taf2/curb/actions/workflows/ci.yml/badge.svg)](https://github.com/taf2/curb/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/taf2/curb/branch/master/graph/badge.svg)](https://codecov.io/gh/taf2/curb)
5
+ [![Gem Version](https://badge.fury.io/rb/curb.svg)](https://badge.fury.io/rb/curb)
6
+
7
+ * [CI Build Status](https://github.com/taf2/curb/actions/workflows/ci.yml)
8
+ * [rubydoc rdoc](http://www.rubydoc.info/github/taf2/curb/)
9
+ * [github project](http://github.com/taf2/curb/tree/master)
10
+
11
+ Curb (probably CUrl-RuBy or something) provides Ruby-language bindings for the
12
+ libcurl(3), a fully-featured client-side URL transfer library.
13
+ cURL and libcurl live at [https://curl.se/libcurl/](https://curl.se/libcurl/) .
14
+
15
+ Curb is a work-in-progress, and currently only supports libcurl's `easy` and `multi` modes.
16
+
17
+ A big advantage to Curb over all other known ruby http libraries is it's ability to handle timeouts without the use of threads.
18
+
19
+ ## License
20
+
21
+ Curb is copyright (c) 2006 Ross Bamford, and released under the terms of the
22
+ Ruby license. See the LICENSE file for the gory details.
23
+
24
+ ## Easy mode
25
+
26
+ GET request
27
+ ```
28
+ res = Curl.get("https://www.google.com/") {|http|
29
+ http.timeout = 10 # raise exception if request/response not handled within 10 seconds
30
+ }
31
+ puts res.code
32
+ puts res.head
33
+ puts res.body
34
+ ```
35
+
36
+ POST request
37
+ ```
38
+ res = Curl.post("https://your-server.com/endpoint", {post: "this"}.to_json) {|http|
39
+ http.headers["Content-Type"] = "application/json"
40
+ }
41
+ puts res.code
42
+ puts res.head
43
+ puts res.body
44
+ ```
45
+
46
+ ## FTP Support
47
+
48
+ require 'curb'
49
+
50
+ ### Basic FTP Download
51
+ ```ruby
52
+ puts "=== FTP Download Example ==="
53
+ ftp = Curl::Easy.new('ftp://ftp.example.com/remote/file.txt')
54
+ ftp.username = 'user'
55
+ ftp.password = 'password'
56
+ ftp.perform
57
+ puts ftp.body
58
+ ```
59
+
60
+ ### FTP Upload
61
+ ```ruby
62
+ puts "\n=== FTP Upload Example ==="
63
+ upload = Curl::Easy.new('ftp://ftp.example.com/remote/upload.txt')
64
+ upload.username = 'user'
65
+ upload.password = 'password'
66
+ upload.upload = true
67
+ upload.put_data = File.read('local_file.txt')
68
+ upload.perform
69
+ ```
70
+
71
+ ### List Directory Contents
72
+ ```ruby
73
+ puts "\n=== FTP Directory Listing Example ==="
74
+ list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
75
+ list.username = 'user'
76
+ list.password = 'password'
77
+ list.set(:dirlistonly, 1)
78
+ list.perform
79
+ puts list.body
80
+ ```
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
+
108
+ #### Full LIST directory listing
109
+ To retrieve the full `LIST` output (permissions, owner, size, timestamp, name), simply do not set `dirlistonly`:
110
+
111
+ ```ruby
112
+ list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
113
+ list.username = 'user'
114
+ list.password = 'password'
115
+
116
+ # Explicitly ensure names+metadata (LIST) rather than NLST
117
+ # list.set(:dirlistonly, 0) # optional; default is LIST for directory URLs
118
+
119
+ list.perform
120
+ puts list.body # multi-line LIST output
121
+ ```
122
+
123
+ Through an HTTP proxy tunnel, the same considerations apply as the NLST example above — just omit `dirlistonly` and keep the optional EPSV/EPRT/PASV tweaks if needed:
124
+
125
+ ```ruby
126
+ list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
127
+ list.username = 'user'
128
+ list.password = 'password'
129
+ list.proxy_url = 'http://proxy.example.com:80'
130
+ list.proxy_tunnel = true
131
+
132
+ # Optional tweaks if the proxy/server combination struggles
133
+ # list.set(:ftp_use_epsv, 0)
134
+ # list.set(:ftp_use_eprt, 0)
135
+ # list.set(:ftp_skip_pasv_ip, 1)
136
+
137
+ list.perform
138
+ puts list.body
139
+ ```
140
+
141
+ ### Advanced FTP Usage with Various Options
142
+ ```
143
+ puts "\n=== Advanced FTP Example ==="
144
+ advanced = Curl::Easy.new do |curl|
145
+ curl.url = 'ftp://ftp.example.com/remote/file.txt'
146
+ curl.username = 'user'
147
+ curl.password = 'password'
148
+
149
+ # FTP Options
150
+ curl.ftp_response_timeout = 30
151
+ curl.ftp_create_missing_dirs = true # Create directories if they don't exist
152
+ curl.ftp_filemethod = Curl::CURL_MULTICWD # Use multicwd method for traversing paths
153
+
154
+ # SSL/TLS Options for FTPS
155
+ curl.use_ssl = Curl::CURLUSESSL_ALL # Use SSL/TLS for control and data
156
+ curl.ssl_verify_peer = true
157
+ curl.ssl_verify_host = true
158
+ curl.cacert = "/path/to/cacert.pem"
159
+
160
+ # Progress callback
161
+ curl.on_progress do |dl_total, dl_now, ul_total, ul_now|
162
+ puts "Download: #{dl_now}/#{dl_total} Upload: #{ul_now}/#{ul_total}"
163
+ true # must return true to continue
164
+ end
165
+
166
+ # Debug output
167
+ curl.verbose = true
168
+ curl.on_debug do |type, data|
169
+ puts "#{type}: #{data}"
170
+ true
171
+ end
172
+ end
173
+
174
+ advanced.perform
175
+ ```
176
+
177
+ ### Parallel FTP Downloads
178
+ ```
179
+ puts "\n=== Parallel FTP Downloads Example ==="
180
+ urls = [
181
+ 'ftp://ftp.example.com/file1.txt',
182
+ 'ftp://ftp.example.com/file2.txt',
183
+ 'ftp://ftp.example.com/file3.txt'
184
+ ]
185
+ ```
186
+
187
+ ### Common options for all connections
188
+ ```
189
+ options = {
190
+ :username => 'user',
191
+ :password => 'password',
192
+ :timeout => 30,
193
+ :on_success => proc { |easy| puts "Successfully downloaded: #{easy.url}" },
194
+ :on_failure => proc { |easy, code| puts "Failed to download: #{easy.url} (#{code})" }
195
+ }
196
+
197
+ Curl::Multi.download(urls, options) do |curl, file_path|
198
+ puts "Completed downloading to: #{file_path}"
199
+ end
200
+ ```
201
+
202
+ ## Security considerations
203
+
204
+ `curb` is a libcurl binding and intentionally supports protocols beyond HTTP.
205
+ Do not pass untrusted URLs to `Curl.get`, `Curl::Easy.new`, or related raw
206
+ helpers without application-level validation. For user-supplied URLs, enable the
207
+ process-wide safety policy before making requests:
208
+
209
+ ```ruby
210
+ Curl.safe! do |config|
211
+ config.network_policy = :public # block local/private destination IPs
212
+ config.max_body_bytes = 1_000_000 # cap buffered/callback response bytes
213
+ end
214
+
215
+ curl = Curl.get(user_url) # allows only http/https, including redirects
216
+ ```
217
+
218
+ To allow a different protocol set, configure it explicitly. Redirects default to
219
+ the same protocol list:
220
+
221
+ ```ruby
222
+ Curl.safe! do |config|
223
+ config.protocols = [:http, :ftp]
224
+ config.max_body_bytes = 1_000_000
225
+ end
226
+ ```
227
+
228
+ For local per-handle policy instead of process-wide policy, use
229
+ `easy.safe_http!` and `easy.max_body_bytes = ...` before `perform`.
230
+
231
+ With `network_policy = :public`, curb checks peer addresses when libcurl opens
232
+ the socket and blocks local/private destinations. Proxies, `resolve`,
233
+ `connect_to`, DoH URL overrides, and Unix socket paths are disabled by default
234
+ under this policy unless explicitly allowed in the safety config. Custom DNS
235
+ server overrides are rejected. To use a trusted explicit proxy without
236
+ re-enabling environment proxies, set `allowed_proxy_hosts` and configure
237
+ `easy.proxy_url` on the request.
238
+
239
+ For stricter egress, combine the public network policy with host and CIDR
240
+ allowlists. Host allowlists gate the configured request URL and, when supported
241
+ by libcurl, each followed redirect before the request is sent. CIDR allowlists
242
+ are checked against the resolved peer address at socket-open time:
243
+
244
+ ```ruby
245
+ Curl.safe! do |config|
246
+ config.network_policy = :public
247
+ config.allowed_hosts = ["api.example.com"]
248
+ config.allowed_proxy_hosts = ["proxy.example.com"]
249
+ config.allowed_cidrs = ["93.184.216.0/24", "2606:2800:220::/48"]
250
+ end
251
+ ```
252
+
253
+ By default, responses are buffered into `body` when no `on_body` callback is
254
+ configured. For untrusted or large responses, use `on_body`, `download`, and/or
255
+ `max_body_bytes` so a remote endpoint cannot force unbounded memory growth.
256
+ `max_body_bytes` is enforced for downloads as well as buffered responses and
257
+ custom body callbacks.
258
+
259
+ ## You will need
260
+
261
+ * A working Ruby installation (`2.0.0+` will work but `2.1+` preferred) (it's possible it still works with 1.8.7 but you'd have to tell me if not...)
262
+ * A working libcurl development installation
263
+ (Ideally one of the versions listed in the compatibility chart below that maps to your `curb` version)
264
+ * A sane build environment (e.g. gcc, make)
265
+
266
+ ## Version Compatibility chart
267
+
268
+ A **non-exhaustive** set of compatibility versions of the libcurl library
269
+ with this gem are as follows. (Note that these are only the ones that have been
270
+ tested and reported to work across a variety of platforms / rubies)
271
+
272
+ | Gem Version | Release Date | libcurl versions |
273
+ | ----------- | -------------- | ----------------- |
274
+ | 1.0.8 | Feb 10, 2025 | 7.58 – 8.12.1 |
275
+ | 1.0.7 | Feb 09, 2025 | 7.58 – 8.12.1 |
276
+ | 1.0.6 | Aug 23, 2024 | 7.58 – 8.12.1 |
277
+ | 1.0.5 | Jan 2023 | 7.58 – 8.12.1 |
278
+ | 1.0.4 | Jan 2023 | 7.58 – 8.12.1 |
279
+ | 1.0.3* | Dec 2022 | 7.58 – 8.12.1 |
280
+ | 1.0.2* | Dec 2022 | 7.58 – 8.12.1 |
281
+ | 1.0.1 | Apr 2022 | 7.58 – 8.12.1 |
282
+ | 1.0.0 | Jan 2022 | 7.58 – 8.12.1 |
283
+ | 0.9.8 | Jan 2019 | 7.58 – 7.81 |
284
+ | 0.9.7 | Nov 2018 | 7.56 – 7.60 |
285
+ | 0.9.6 | May 2018 | 7.51 – 7.59 |
286
+ | 0.9.5 | May 2018 | 7.51 – 7.59 |
287
+ | 0.9.4 | Aug 2017 | 7.41 – 7.58 |
288
+ | 0.9.3 | Apr 2016 | 7.26 – 7.58 |
289
+
290
+ ```*avoid using these version are known to have issues with segmentation faults```
291
+
292
+ ## Installation...
293
+
294
+ ... will usually be as simple as:
295
+
296
+ $ gem install curb
297
+
298
+ On Windows, make sure you're using the [DevKit](http://rubyinstaller.org/downloads/) and
299
+ the [development version of libcurl](http://curl.se/gknw.net/7.39.0/dist-w32/curl-7.39.0-devel-mingw32.zip). Unzip, then run this in your command
300
+ line (alter paths to your curl location, but remember to use forward slashes):
301
+
302
+ gem install curb --platform=ruby -- --with-curl-lib=C:/curl-7.39.0-devel-mingw32/lib --with-curl-include=C:/curl-7.39.0-devel-mingw32/include
303
+
304
+ Note that with Windows moving from one method of compiling to another as of Ruby `2.4` (DevKit -> MYSYS2),
305
+ the usage of Ruby `2.4+` with this gem on windows is unlikely to work. It is advised to use the
306
+ latest version of Ruby 2.3 available [HERE](https://dl.bintray.com/oneclick/rubyinstaller/rubyinstaller-2.3.3.exe)
307
+
308
+ Or, if you downloaded the archive:
309
+
310
+ $ rake compile && rake install
311
+
312
+ If you have a weird setup, you might need extconf options. In this case, pass
313
+ them like so:
314
+
315
+ $ rake compile EXTCONF_OPTS='--with-curl-dir=/path/to/libcurl --prefix=/what/ever' && rake install
316
+
317
+ Curb is tested only on GNU/Linux x86 and Mac OSX - YMMV on other platforms.
318
+ If you do use another platform and experience problems, or if you can
319
+ expand on the above instructions, please report the issue at http://github.com/taf2/curb/issues
320
+
321
+ On Ubuntu, the dependencies can be satisfied by installing the following packages:
322
+
323
+ 18.04 and onwards
324
+
325
+ $ sudo apt-get install libcurl4 libcurl3-gnutls libcurl4-openssl-dev
326
+
327
+ < 18.04
328
+
329
+ $ sudo apt-get install libcurl3 libcurl3-gnutls libcurl4-openssl-dev
330
+
331
+ On RedHat:
332
+
333
+ $ sudo yum install ruby-devel libcurl-devel openssl-devel
334
+
335
+ Curb has fairly extensive RDoc comments in the source. You can build the
336
+ documentation with:
337
+
338
+ $ rake doc
339
+
340
+ ## Usage & examples
341
+
342
+ Curb provides two classes:
343
+
344
+ * `Curl::Easy` - simple API, for day-to-day tasks.
345
+ * `Curl::Multi` - more advanced API, for operating on multiple URLs simultaneously.
346
+
347
+ To use either, you will need to require the curb gem:
348
+
349
+ ```ruby
350
+ require 'curb'
351
+ ```
352
+
353
+ ### Super simple API (less typing)
354
+
355
+ ```ruby
356
+ http = Curl.get("http://www.google.com/")
357
+ puts http.body
358
+
359
+ http = Curl.post("http://www.google.com/", {:foo => "bar"})
360
+ puts http.body
361
+
362
+ http = Curl.get("http://www.google.com/") do |http|
363
+ http.headers['Cookie'] = 'foo=1;bar=2'
364
+ end
365
+ puts http.body
366
+ ```
367
+
368
+ ### Simple fetch via HTTP:
369
+
370
+ ```ruby
371
+ c = Curl::Easy.perform("http://www.google.co.uk")
372
+ puts c.body
373
+ ```
374
+
375
+ Same thing, more manual:
376
+
377
+ ```ruby
378
+ c = Curl::Easy.new("http://www.google.co.uk")
379
+ c.perform
380
+ puts c.body
381
+ ```
382
+
383
+ ### Additional config:
384
+
385
+ ```ruby
386
+ http = Curl::Easy.perform("http://www.google.co.uk") do |curl|
387
+ curl.headers["User-Agent"] = "myapp-0.0"
388
+ curl.verbose = true
389
+ end
390
+ ```
391
+
392
+ Same thing, more manual:
393
+
394
+ ```ruby
395
+ c = Curl::Easy.new("http://www.google.co.uk") do |curl|
396
+ curl.headers["User-Agent"] = "myapp-0.0"
397
+ curl.verbose = true
398
+ end
399
+
400
+ c.perform
401
+ ```
402
+
403
+ ### HTTP basic authentication:
404
+
405
+ ```ruby
406
+ c = Curl::Easy.new("http://github.com/")
407
+ c.http_auth_types = :basic
408
+ c.username = 'foo'
409
+ c.password = 'bar'
410
+ c.perform
411
+ ```
412
+
413
+ ### HTTP "insecure" SSL connections (like curl -k, --insecure) to avoid Curl::Err::SSLCACertificateError:
414
+
415
+ ```ruby
416
+ c = Curl::Easy.new("https://github.com/")
417
+ c.ssl_verify_peer = false
418
+ c.perform
419
+ ```
420
+
421
+ ### Supplying custom handlers:
422
+
423
+ ```ruby
424
+ c = Curl::Easy.new("http://www.google.co.uk")
425
+
426
+ c.on_body { |data| print(data) }
427
+ c.on_header { |data| print(data) }
428
+
429
+ c.perform
430
+ ```
431
+
432
+ ### Reusing Curls:
433
+
434
+ ```ruby
435
+ c = Curl::Easy.new
436
+
437
+ ["http://www.google.co.uk", "http://www.ruby-lang.org/"].map do |url|
438
+ c.url = url
439
+ c.perform
440
+ c.body
441
+ end
442
+ ```
443
+
444
+ ### HTTP POST form:
445
+
446
+ 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, ...)`.
447
+
448
+ ```ruby
449
+ c = Curl::Easy.http_post("http://my.rails.box/thing/create",
450
+ Curl::PostField.content('thing[name]', 'box'),
451
+ Curl::PostField.content('thing[type]', 'storage'))
452
+ ```
453
+
454
+ ### HTTP POST file upload:
455
+
456
+ ```ruby
457
+ c = Curl::Easy.new("http://my.rails.box/files/upload")
458
+ c.multipart_form_post = true
459
+ c.http_post(Curl::PostField.file('thing[file]', 'myfile.rb'))
460
+
461
+ ### Custom request target
462
+
463
+ 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:
464
+
465
+ ```ruby
466
+ c = Curl::Easy.new("http://127.0.0.1:9129/methods")
467
+ c.request_target = "http://localhost:9129/methods" # absolute-form target
468
+ c.headers = { 'Host' => 'example.com' } # override Host header if needed
469
+ c.perform
470
+ ```
471
+
472
+ For HTTPS, prefer `easy.resolve = ["host:443:IP"]` to keep Host/SNI/certificates aligned.
473
+ ```
474
+
475
+ ### Using HTTP/2
476
+
477
+ ```ruby
478
+ c = Curl::Easy.new("https://http2.akamai.com")
479
+ c.set(:HTTP_VERSION, Curl::HTTP_2_0)
480
+
481
+ c.perform
482
+ puts (c.body.include? "You are using HTTP/2 right now!") ? "HTTP/2" : "HTTP/1.x"
483
+ ```
484
+
485
+ ### Multi Interface (Basic HTTP GET):
486
+
487
+ ```ruby
488
+ # make multiple GET requests
489
+ easy_options = {:follow_location => true}
490
+ # Use Curl::CURLPIPE_MULTIPLEX for HTTP/2 multiplexing
491
+ multi_options = {:pipeline => Curl::CURLPIPE_HTTP1}
492
+
493
+ Curl::Multi.get(['url1','url2','url3','url4','url5'], easy_options, multi_options) do|easy|
494
+ # do something interesting with the easy response
495
+ puts easy.last_effective_url
496
+ end
497
+ ```
498
+
499
+ ### Multi Interface (Basic HTTP POST):
500
+
501
+ ```ruby
502
+ # make multiple POST requests
503
+ easy_options = {:follow_location => true, :multipart_form_post => true}
504
+ multi_options = {:pipeline => Curl::CURLPIPE_HTTP1}
505
+
506
+
507
+ url_fields = [
508
+ { :url => 'url1', :post_fields => {'f1' => 'v1'} },
509
+ { :url => 'url2', :post_fields => {'f1' => 'v1'} },
510
+ { :url => 'url3', :post_fields => {'f1' => 'v1'} }
511
+ ]
512
+
513
+ Curl::Multi.post(url_fields, easy_options, multi_options) do|easy|
514
+ # do something interesting with the easy response
515
+ puts easy.last_effective_url
516
+ end
517
+ ```
518
+
519
+ ### Multi Interface (Advanced):
520
+
521
+ ```ruby
522
+ responses = {}
523
+ requests = ["http://www.google.co.uk/", "http://www.ruby-lang.org/"]
524
+ m = Curl::Multi.new
525
+ # add a few easy handles
526
+ requests.each do |url|
527
+ responses[url] = ""
528
+ c = Curl::Easy.new(url) do|curl|
529
+ curl.follow_location = true
530
+ curl.on_body{|data| responses[url] << data; data.size }
531
+ curl.on_success {|easy| puts "success, add more easy handles" }
532
+ end
533
+ m.add(c)
534
+ end
535
+
536
+ m.perform do
537
+ puts "idling... can do some work here"
538
+ end
539
+
540
+ requests.each do|url|
541
+ puts responses[url]
542
+ end
543
+ ```
544
+
545
+ ### Easy Callbacks
546
+
547
+ * `on_success` is called when the response code is 2xx
548
+ * `on_redirect` is called when the response code is 3xx
549
+ * `on_missing` is called when the response code is 4xx
550
+ * `on_failure` is called when the response code is 5xx
551
+ * `on_complete` is called in all cases.
552
+
553
+ ### Cookies
554
+
555
+ - 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.
556
+ - 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.
557
+ - Inspect engine cookies: `easy.cookielist` returns an array of strings (Netscape or Set-Cookie format).
558
+ - 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).
559
+ - Clearing manual cookies: assign an empty string (`easy.cookies = ''`). Assigning `nil` has no effect in current versions.
560
+
561
+ Examples:
562
+
563
+ ```ruby
564
+ easy = Curl::Easy.new("https://example.com")
565
+
566
+ # Use the cookie engine and persist cookies
567
+ easy.enable_cookies = true
568
+ easy.cookiejar = "/tmp/cookies.txt"
569
+ easy.perform
570
+
571
+ # Later: inspect and tweak engine cookies
572
+ p easy.cookielist
573
+ easy.cookielist = 'ALL' # clear stored cookies
574
+
575
+ # Send custom Cookie header for a single request
576
+ easy.cookies = "flag=1; session_override=abc"
577
+ easy.perform
578
+ easy.cookies = '' # clear manual Cookie header
579
+ ```