libcurl 的 Ruby 绑定
Curb (probably CUrl-RuBy or something) provides Ruby-language bindings for the libcurl(3), a fully-featured client-side URL transfer library. cURL and libcurl live at https://curl.se/libcurl/ .
Curb is a work-in-progress, and currently only supports libcurl's easy and multi modes.
A big advantage to Curb over all other known ruby http libraries is it's ability to handle timeouts without the use of threads.
Curb is copyright (c) 2006 Ross Bamford, and released under the terms of the Ruby license. See the LICENSE file for the gory details.
res = Curl.get("https://www.google.com/") {|http|
http.timeout = 10 # raise exception if request/response not handled within 10 seconds
}
puts res.code
puts res.head
puts res.body
res = Curl.post("https://your-server.com/endpoint", {post: "this"}.to_json) {|http|
http.headers["Content-Type"] = "application/json"
}
puts res.code
puts res.head
puts res.body
require 'curb'
puts "=== FTP Download Example ==="
ftp = Curl::Easy.new('ftp://ftp.example.com/remote/file.txt')
ftp.username = 'user'
ftp.password = 'password'
ftp.perform
puts ftp.body
puts "\n=== FTP Upload Example ==="
upload = Curl::Easy.new('ftp://ftp.example.com/remote/upload.txt')
upload.username = 'user'
upload.password = 'password'
upload.upload = true
upload.put_data = File.read('local_file.txt')
upload.perform
puts "\n=== FTP Directory Listing Example ==="
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
list.username = 'user'
list.password = 'password'
list.set(:dirlistonly, 1)
list.perform
puts list.body
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.
To get NLST-like output safely:
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
list.username = 'user'
list.password = 'password'
list.proxy_url = 'http://proxy.example.com:80'
list.proxy_tunnel = true
# Ask libcurl to perform a listing (names only)
list.set(:dirlistonly, 1)
# If the proxy or server has trouble with EPSV/EPRT, you can adjust:
# list.set(:ftp_use_epsv, 0) # disable EPSV
# list.set(:ftp_use_eprt, 0) # disable EPRT (stick to IPv4 PASV)
# list.set(:ftp_skip_pasv_ip, 1) # ignore PASV host, reuse control host
list.perform
puts list.body
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.
To retrieve the full LIST output (permissions, owner, size, timestamp, name), simply do not set dirlistonly:
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
list.username = 'user'
list.password = 'password'
# Explicitly ensure names+metadata (LIST) rather than NLST
# list.set(:dirlistonly, 0) # optional; default is LIST for directory URLs
list.perform
puts list.body # multi-line LIST output
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:
list = Curl::Easy.new('ftp://ftp.example.com/remote/directory/')
list.username = 'user'
list.password = 'password'
list.proxy_url = 'http://proxy.example.com:80'
list.proxy_tunnel = true
# Optional tweaks if the proxy/server combination struggles
# list.set(:ftp_use_epsv, 0)
# list.set(:ftp_use_eprt, 0)
# list.set(:ftp_skip_pasv_ip, 1)
list.perform
puts list.body
…
puts "\n=== Parallel FTP Downloads Example ==="
urls = [
'ftp://ftp.example.com/file1.txt',
'ftp://ftp.example.com/file2.txt',
'ftp://ftp.example.com/file3.txt'
]
options = {
:username => 'user',
:password => 'password',
:timeout => 30,
:on_success => proc { |easy| puts "Successfully downloaded: #{easy.url}" },
:on_failure => proc { |easy, code| puts "Failed to download: #{easy.url} (#{code})" }
}
Curl::Multi.download(urls, options) do |curl, file_path|
puts "Completed downloading to: #{file_path}"
end
curb is a libcurl binding and intentionally supports protocols beyond HTTP.
Do not pass untrusted URLs to Curl.get, Curl::Easy.new, or related raw
helpers without application-level validation. For user-supplied URLs, enable the
safety policy for the current Ractor before making requests:
Curl.safe! do |config|
config.network_policy = :public # block local/private destination IPs
config.max_body_bytes = 1_000_000 # cap buffered/callback response bytes
end
curl = Curl.get(user_url) # allows only http/https, including redirects
To allow a different protocol set, configure it explicitly. Redirects default to the same protocol list:
Curl.safe! do |config|
config.protocols = [:http, :ftp]
config.max_body_bytes = 1_000_000
end
For local per-handle policy instead of process-wide policy, use
easy.safe_http! and easy.max_body_bytes = ... before perform.
With network_policy = :public, curb checks peer addresses when libcurl opens
the socket and blocks local/private destinations. Proxies, resolve,
connect_to, DoH URL overrides, and Unix socket paths are disabled by default
under this policy unless explicitly allowed in the safety config. Custom DNS
server overrides are rejected. To use a trusted explicit proxy without
re-enabling environment proxies, set allowed_proxy_hosts and configure
easy.proxy_url on the request.
For stricter egress, combine the public network policy with host and CIDR allowlists. Host allowlists gate the configured request URL and, when supported by libcurl, each followed redirect before the request is sent. CIDR allowlists are checked against the resolved peer address at socket-open time:
Curl.safe! do |config|
config.network_policy = :public
config.allowed_hosts = ["api.example.com"]
config.allowed_proxy_hosts = ["proxy.example.com"]
config.allowed_cidrs = ["93.184.216.0/24", "2606:2800:220::/48"]
end
By default, responses are buffered into body when no on_body callback is
configured. For untrusted or large responses, use on_body, download, and/or
max_body_bytes so a remote endpoint cannot force unbounded memory growth.
max_body_bytes is enforced for downloads as well as buffered responses and
custom body callbacks.
On Ruby 3.0+, curb can perform requests from multiple Ractors when it is built
against a libcurl that reports the CURL_VERSION_THREADSAFE capability
(libcurl 7.84.0 or newer). Check Curl::RACTOR_SAFE at runtime. Builds that do
not meet those requirements continue to work in the main Ractor but do not
advertise native Ractor safety.
Create Curl::Easy and Curl::Multi handles inside the Ractor that uses them;
do not operate on the same native handle concurrently from different Ractors.
Safety configuration, Curl::Multi.default_timeout, Curl::Multi.autoclose,
and deferred cleanup queues are isolated per Ractor. Call Curl.safe! in each
Ractor that needs the safety policy:
raise "this curb build is not Ractor-safe" unless Curl::RACTOR_SAFE
workers = 4.times.map do
Ractor.new do
Curl.safe! { |config| config.protocols = [:http, :https] }
easy = Curl::Easy.new("https://example.com/")
easy.perform
easy.response_code
end
end
# Ruby 4.0 uses Ractor#value; earlier Ractor releases use Ractor#take.
statuses = workers.map do |worker|
worker.respond_to?(:value) ? worker.value : worker.take
end
Curb enables CURLOPT_NOSIGNAL on newly initialized and reset Easy handles by
default so separate handles can be used safely from parallel Ruby execution.
Applications can still override the option explicitly when required.
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...)curb version)A non-exhaustive set of compatibility versions of the libcurl library with this gem are as follows. (Note that these are only the ones that have been tested and reported to work across a variety of platforms / rubies)
The upper bounds for recent releases reflect the compatibility work documented in the changelog, including the libcurl 8.16.0 regression workaround in 1.2.2 and the libcurl 8.20.0 scheduler fixes in 1.3.5. The 1.3.7 upper bound also includes expected compatibility with libcurl 8.21.0; this version is not yet an explicit CI target.
| Gem Version | Release Date | libcurl versions |
|---|---|---|
| 1.3.7 | Jul 10, 2026 | 7.58 – 8.21.0 |
| 1.3.6 | Jun 17, 2026 | 7.58 – 8.20.0 |
| 1.3.5 | May 14, 2026 | 7.58 – 8.20.0 |
| 1.3.4 | May 12, 2026 | 7.58 – 8.16.0 |
| 1.3.3 | May 11, 2026 | 7.58 – 8.16.0 |
| 1.3.2 | Apr 23, 2026 | 7.58 – 8.16.0 |
| 1.3.1 | Apr 05, 2026 | 7.58 – 8.16.0 |
| 1.3.0 | Apr 02, 2026 | 7.58 – 8.16.0 |
| 1.2.2 | Sep 18, 2025 | 7.58 – 8.16.0 |
| 1.2.1 | Sep 17, 2025 | 7.58 – 8.12.1 |
| 1.2.0 | Aug 31, 2025 | 7.58 – 8.12.1 |
| 1.1.0 | Jul 24, 2025 | 7.58 – 8.12.1 |
| 1.0.9 | Feb 15, 2025 | 7.58 – 8.12.1 |
| 1.0.8 | Feb 10, 2025 | 7.58 – 8.12.1 |
| 1.0.7 | Feb 09, 2025 | 7.58 – 8.12.1 |
| 1.0.6 | Aug 23, 2024 | 7.58 – 8.12.1 |
| 1.0.5 | Jan 2023 | 7.58 – 8.12.1 |
| 1.0.4 | Jan 2023 | 7.58 – 8.12.1 |
| 1.0.3* | Dec 2022 | 7.58 – 8.12.1 |
| 1.0.2* | Dec 2022 | 7.58 – 8.12.1 |
| 1.0.1 | Apr 2022 | 7.58 – 8.12.1 |
| 1.0.0 | Jan 2022 | 7.58 – 8.12.1 |
| 0.9.8 | Jan 2019 | 7.58 – 7.81 |
| 0.9.7 | Nov 2018 | 7.56 – 7.60 |
| 0.9.6 | May 2018 | 7.51 – 7.59 |
| 0.9.5 | May 2018 | 7.51 – 7.59 |
| 0.9.4 | Aug 2017 | 7.41 – 7.58 |
| 0.9.3 | Apr 2016 | 7.26 – 7.58 |
Avoid using these versions; they are known to have issues with segmentation faults.
... will usually be as simple as:
$ gem install curb
On Windows, make sure you're using the DevKit and the development version of libcurl. Unzip, then run this in your command line (alter paths to your curl location, but remember to use forward slashes):
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
Note that with Windows moving from one method of compiling to another as of Ruby 2.4 (DevKit -> MYSYS2),
the usage of Ruby 2.4+ with this gem on windows is unlikely to work. It is a
暂无开放 Issues,或尚未同步最近议题。