使用 PHP Curl 扩展发送 HTTP/3 请求
HTTP/3 是 HTTP 的第三个大版本,它基于 QUIC。不像基于 TCP 的 HTTP/1.1 和 HTTP/2,HTTP 是基于一个多路复用的 UDP 协议,QUIC。HTTP/3 以及 TLS 1.3,在性能和延迟上进行了巨大改进。虽然 HTTP/3 修改了许多传输层语义(比如,从 TCP 到 UDP 的转变),但保留了 HTTP 语法请求头、请求方法、响应和状态方法。
现在大部分浏览器都支持 HTTP/3,而 HTTP 客户端和网络服务器比如 Curl、Nginx 和 Litespeed 提供了实验性支持。Caddy 服务器甚至默认启用 HTTP/3 支持。
利用 Curl 中提供的实验性 HTTP/3 支持,PHP 的 Curl 扩展可以使用 HTTP/3 支持构建。本文解释了如何使用 HTTP/3 支持编译 PHP Curl 扩展及其依赖项,以及如何使用 PHP 发出 HTTP/3 请求。
如何使用 PHP Curl 扩展发起 HTTP/3 请求
Curl 有一个名为 CURLOPT_HTTP_VERSION
的选项,它可用于设置 Curl handler 在 HTTP 请求中使用的 HTTP 版本。默认情况下,当前 Curl 构建默认为带有 HTTP 1.1 回退的 HTTP/2。如果 web 服务器不支持 HTTP/2,Curl 将无缝地使用 HTTP/1.1。
对于 HTTP/3,Curl 也是一样的行为。Curl 有称为 HTTPS Eyeballing 的方式,它尝试建立一个 QUIC 握手,不过有一个 200ms 的硬超时。这样可以确保在连接足够快的情况下使用 HTTP/3,但不会对不使用 HTTP/3 的请求产生任何重大影响。
它确保了如果连接够快,HTTP/3 将被使用,不过
使用 Curl 发起 HTTP/3 请求:
- Curl 必需使用 HTTP/3 支持进行编译
- Curl 版本大于 7.66
- PHP 版本大于 8.2
使用 PHP Curl 扩展发起 HTTP/3 请求很简单,只需设置 CURLOPT_HTTP_VERSION 选项:
$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
curl_exec($ch);
CURL_HTTP_VERSION_3 常量只在 PHP 8.4 及之后的版本中可用。
要确保 CURL_HTTP_VERSION_3
常量未被声明的情况下的兼容性,可以在用户空间 PHP 中对其进行声明,或者直接传入常量值到 curl_setopt
函数中。
if (!defined('CURL_HTTP_VERSION_3')) {
define('CURL_HTTP_VERSION_3', 30);
}
$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
// or
curl_setopt($ch, CURLOPT_HTTP_VERSION, 30);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_exec($ch);
如果 Curl 支持 HTTP/3,则该段代码会使用 Curl 发出 HTTP/3 请求:
HTTP/3 200
strict-transport-security: max-age=31536000;includeSubDomains;preload
content-type: text/html; charset=UTF-8
date: Sat, 28 Oct 2023 18:38:46 GMT
...
CURL_HTTP_VERSION_3
选项说明 Curl 可以使用的 HTTP 版本最高可为 HTTP/3。如果远程服务器不支持 HTTP/3,Curl 会静默并无缝回退到另一个服务器和 Curl 都支持的 HTTP 版本。请注意,在不支持 HTTP/3 的 CURL 扩展上使用
CURL_HTTP_VERSION_3
将导致在curl_setopt 和 curl_exec 调用时请求返回false
。
下面代码段使用 CURL_HTTP_VERSION_3ONLY
(= 31
),它告诉 Curl 使用 HTTP/3 而不回退版本。如果远程服务器和 Curl 不支持 HTTP/3,请求会失败。
if (!defined('CURL_HTTP_VERSION_3ONLY')) {
define('CURL_HTTP_VERSION_3ONLY', 31);
}
$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY);
// or
curl_setopt($ch, CURLOPT_HTTP_VERSION, 31);
curl_exec($ch);
在使用 Curl 发起 HTTP/3 请求前,请确保检测 Curl 是否有 HTTP/3 支持。
CURL_HTTP_VERSION_3
还是CURL_VERSION_HTTP3
?
CURL_VERSION_HTTP3
是一个 feature flag,它带有一个可用于位运算的值,用以确定是否支持 HTTP/3。在 Curl 7.66 及 PHP 8.2 起中可用CURL_HTTP_VERSION_3
和CURL_HTTP_VERSION_3ONLY
是 Curl 选项CURLOPT_HTTP_VERSION
中的常量。CURL_HTTP_VERSION_3
z在 Curl 7.66 中添加,CURL_HTTP_VERSION_3ONLY
在 Curl 7.88 中添加。CURL_VERSION_HTTP3
和CURL_HTTP_VERSION_3
是不同的的。
检测 PHP Curl 扩展中的 HTTP/3 支持
PHP 常量 CURL_VERSION_HTTP3
、CURL_HTTP_VERSION_3
和 CURL_HTTP_VERSION_3ONLY
被声明,并不能说明 Curl 是使用 HTTP/3 支持进行构建的。
有三种方法可以检测 Curl 扩展是否支持 HTTP/3。
检测 phpinfo
输出
phpinfo()
输出及php -i
显示 Curl 是否是使用 HTTP/3 支持进行构建:
phpinfo 输出显示 HTTP/3 支持
使用 curl_version
函数
curl_version()
函数的 features
值返回 Curl 支持的所有特性的位掩码。
下面代码显示使用 curl_version()
函数检测 HTTP/3 支持,特性标志为 CURL_VERSION_HTTP3
:
if (defined('CURL_VERSION_HTTP3') && curl_version()['features'] & CURL_VERSION_HTTP3) {
// HTTP/3 supported
}
curl_setopt
调用返回值
当 CURLOPT_HTTP_VERSION
选项设置为 CURL_HTTP_VERSION_3
时,如果 Curl 扩展没有使用 HTTP/3 构建,Curl 将返回 false
:
$ch = curl_init("https://php.watch/");
$h3_status = curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
if ($h3_status === false) {
curl_setopt($ch, CURLOPT_HTTP_VERSION, null)
}
curl_exec($ch);
Setting
CURLOPT_HTTP_VERSION = null
resets the failed attempt to select HTTP/3, and leaves Curl to pick the best HTTP version.
使用 HTTP/3 支持编译 PHP Curl 扩展
HTTP/3 标准本身仍处于“建议标准”状态,这比成为互联网标准晚了一个级别。虽然大多数主流浏览器已经支持 HTTP/3,但网络服务器之间可能还有其他硬件和软件(其中,最初支持 HTTP/3 的并不多)不支持 HTTP/3 或满足 HTTP/3 的基本要求,例如允许 UDP 流量到端口。
Curl 本身对 HTTP/3 的支持也被标记为实验性的。此外,Debian/Uubuntu 和 Fedora/REL 及其衍生产品中的 PHP Curl 扩展的预编译包都没有使用 HTTP/3 支持编译。
在 PHP Curl 扩展中启用 HTTP/3 支持需要使用 Curl 本身所依赖的必要库编译 libcurl,然后使用该 libcurl 编译 Curl 扩展。
不建议在生产环境中使用。
Curl 的 HTTP/3 文档提供了有关使用 HTTP/3 编译 Curl 的最新说明。Curl 可以是不同的加密和传输库,但根据 PHP 的多次测试,使用ngtcp2
、nghttp3
和 WolfSSL
编译 Curl 产生了最好的结果。一些包含修补版本的 OpenSSL 的组合根本不起作用,并且在尝试执行请求时出错。
1. 编译依赖
编译工具比如 C 编译器,make
工具,和其他必要工具必需在系统中安装。
2. 使用 ngtcp2、nghttp3 和 WolfSSL 构建 Curl
Curl 文档中提供了最新的说明。
# Prepare working space
cd ~
mkdir curl
mkdir build
cd curl
# Build WolfSSL
git clone https://github.com/wolfSSL/wolfssl.git
cd wolfssl
autoreconf -fi
./configure --prefix=~/build/~/build/wolfssl --enable-quic --enable-session-ticket --enable-earlydata --enable-psk --enable-harden --enable-altcertchains
make
make install
# Build nghttp3
cd ..
git clone -b v1.0.0 https://github.com/ngtcp2/nghttp3
cd nghttp3
autoreconf -fi
./configure --prefix=~/build/nghttp3 --enable-lib-only
make
make install
# Build ngtcp2
cd ..
git clone -b v1.0.1 https://github.com/ngtcp2/ngtcp2
cd ngtcp2
autoreconf -fi
./configure PKG_CONFIG_PATH=~/build/wolfssl/lib/pkgconfig:~/build/nghttp3/lib/pkgconfig LDFLAGS="-Wl,-rpath,~/build/wolfssl/lib" --prefix=~/build/ngtcp2 --enable-lib-only --with-wolfssl
make
make install
# Build curl
cd ..
git clone https://github.com/curl/curl
cd curl
autoreconf -fi
./configure --with-wolfssl=~/build/wolfssl --with-nghttp3=~/build/nghttp3 --with-ngtcp2=~/build/ngtcp2
make
make install
根据需要修改前缀 ~/build/wolfssl
,~/build/nghttp3
和 ~/build/nghttp3
。
3. 使用新的 libcurl 编译 PHP Curl 扩展
因为上述在 Curl 中调用 make install
,Curl 二进制及 libcurl 在系统上进行了安装。当 PHP 使用 Curl 扩展进行编译时,现在请使用 HTTP/3 支持的新 libcurl 编译。
请确保使用 --with-curl
配置 ./configure
PHP。如果 Curl 不是在系统范围上进行安装(比如,不在 /urs/local
中),也可以在此自定目录。
以下编译 PHP 的快速指南:
sudo apt install build-essential autoconf libtool bison re2c
git clone https://github.com/php/php-src.git --branch=master
cd php-src
./buildconf
./configure --with-curl
make -j $(nproc)
sudo make install
总结
PHP 的 Curl 扩展可以使用 HTTP/3 的实验性支持进行编译。不幸的是,这需要编译 Curl 扩展,这对于依赖从操作系统包仓库中进行操作系统更新,以进行安全维护或漏洞修复有一定的挑战。
下面代码显示了如何在支持 HTTP/3 的系统上发起 HTTP/3 请求:
$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
curl_exec($ch);
- 如果远程服务器不支持 HTTP/3,它会尝试使用 HTTP/3 或者 HTTP/1.1。
- 如果 Curl 没有 HTTP/3 支持,
curl_setopt
和curl_exec
调用都会失败。
下面代码中发起的的 HTTP/3 请求更有防御性。它检测 Curl 是否内置了 HTTP/3 支持,检测是否声明了 CURL_HTTP_VERSION_3
常量并允许 Curl 在 HTTP/3 不可用时,回退到不同的 HTTP 版本:
$ch = curl_init("https://php.watch/");
if (defined('CURL_VERSION_HTTP3') && defined('CURL_HTTP_VERSION_3') && curl_version()['features'] & CURL_VERSION_HTTP3) {
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
}
curl_exec($ch);