HTTP/3 Request with PHP Curl Extension
HTTP/3 is the third major version of HTTP, and is based on QUIC. Unlike HTTP/1.1 and HTTP/2 which relied on TCP, HTTP/3 is based on a multiplexed UDP protocol named QUIC. HTTP/3, along with TLS 1.3, can provide huge performance and latency improvements. Although HTTP/3 changes a lot of transport layer semantics (e.g. the shift from TCP to UDP), the HTTP semantics of request headers, request methods, responses, and status codes.
HTTP/3 is now supported in all major browsers, while HTTP clients and web servers such as Curl, Nginx, and Litespeed provide experimental support. Caddy Server even has HTTP/3 support enabled by default.
Making use of the experimental HTTP/3 support available in Curl, PHP's Curl extension can be built with HTTP/3 support. This article explains how to compile the PHP Curl extension and its dependencies with HTTP/3 support, and how to make HTTP/3 requests with PHP.
How to make HTTP/3 Request with PHP Curl Extension
Curl has an option named CURLOPT_HTTP_VERSION
, that can be used to set the HTTP versions that can be used in the HTTP request by the Curl handler. By default, current Curl builds default to HTTP/2 with HTTP/1.1 fallback. If the web server does not support HTTP/2, Curl will seamlessly use HTTP/1.1.
For HTTP/3, Curl behaves the same way. Curl has an approach called HTTPS Eyeballing, that attempts to establish a QUIC handshake but with a 200ms hard timeout. This ensures that HTTP/3 will be used if the connection is fast enough, but does not have any major impact on requests that do not utilize HTTP/3.
To make an HTTP/3 with Curl:
- Curl must be built with HTTP/3 support
- Curl version 7.66 or later
- PHP versions 8.2 or later
Making an HTTP/3 request with the PHP Curl extension is as easy as setting the CURLOPT_HTTP_VERSION
option:
$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
curl_exec($ch);
The
CURL_HTTP_VERSION_3
constant is only available in PHP 8.4 and later.
To ensure compatibility in case the CURL_HTTP_VERSION_3
constant is not declared, it is possible to declare it user-land, or simply pass the constant value to the curl_setopt
function.
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);
If HTTP/3 is supported in Curl, the snippet makes an HTTP/3 request using Curl:
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
...
The
CURL_HTTP_VERSION_3
option in Curl implies that Curl is allowed to use HTTP versions up to HTTP/3. If the remote server does not support HTTP/3, Curl will silently and seamlessly fall back to another HTTP version both supported by the server and Curl.Note that using
CURL_HTTP_VERSION_3
on a Curl extension not built with HTTP/3 support will cause the request to returnfalse
at bothcurl_setopt
andcurl_exec
calls.
The following snippet uses CURL_HTTP_VERSION_3ONLY
(= 31
), which tells Curl to use HTTP/3 with no fallback. If the remote server and Curl do not support HTTP/3, the request will fail.
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);
Before making HTTP/3 requests with Curl, make sure to check Curl for HTTP/3 support first.
CURL_HTTP_VERSION_3
orCURL_VERSION_HTTP3
?
CURL_VERSION_HTTP3
is a feature flag, with a value assigned to it that can be used in bit-wise operations to determine HTTP/3 support. Available since Curl 7.66 and PHP 8.2.CURL_HTTP_VERSION_3
andCURL_HTTP_VERSION_3ONLY
are constants that are options for the Curl optionCURLOPT_HTTP_VERSION
.CURL_HTTP_VERSION_3
was added in Curl 7.66, and the...3ONLY
in Curl 7.88.CURL_VERSION_HTTP3
andCURL_HTTP_VERSION_3
are not the same.
Detect HTTP/3 Support in PHP Curl Extension
The fact that PHP constants CURL_VERSION_HTTP3
, CURL_HTTP_VERSION_3
, and CURL_HTTP_VERSION_3ONLY
are declared does not imply that Curl is built with HTTP/3 support.
There are three ways to check if the Curl extension supports HTTP/3.
Check phpinfo
output
The phpinfo()
output and php -i
shows if the Curl extension is built with HTTP/3 support:
With curl_version
function
The features
value of the curl_version()
function return array is a bitmask of all features supported by Curl.
The following snippet shows detecting HTTP/3 support using curl_version()
function and the feature flag CURL_VERSION_HTTP3
:
if (defined('CURL_VERSION_HTTP3') && curl_version()['features'] & CURL_VERSION_HTTP3) {
// HTTP/3 supported
}
Return value of curl_setopt
calls
When setting the CURLOPT_HTTP_VERSION
option to CURL_HTTP_VERSION_3
, Curl returns false
if HTTP/3 is not built into the Curl extension:
$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.
Build PHP Curl Extension with HTTP/3 Support
The HTTP/3 standard itself is still in Proposed Standard status, which is one level behind becoming an Internet Standard. While most major browsers already support HTTP/3, there can be other hardware and software in between the web server (of which, not many support HTTP/3 in the first place) that do not support HTTP/3, or meet the underlying requirements for HTTP/3, such as allowing UDP traffic to the ports.
Support for HTTP/3 in Curl itself is marked as experimental too. Further, none of the prebuild packages for the PHP Curl extension in Debian/Ubuntu and Fedora/RHEL and derivatives are built with HTTP/3 support yet.
Enabling HTTP/3 support in PHP Curl extension requires compiling libcurl with the necessary libraries that Curl itself depends on, and then compiling the Curl extension with that libcurl.
This is not recommended for production systems.
Curl's HTTP/3 documentation provides up-to-date instructions on compiling Curl with HTTP/3. Curl can be different cryptographic and transport libraries, but as per multiple tests by PHP.Watch, building Curl with ngtcp2
, nghttp3
, and WolfSSL
yielded the best results. Some combinations that included a patched version of OpenSSL simply did not work and segfaulted when attempting to execute the request.
1. Build Dependencies
Build tools such as the C compiler, the make
tool, and other essentials must be installed on the system.
PHP.Watch has guides for Debian/Ubuntu and Fedora/RHEL systems.
2. Build Curl with ngtcp2, nghttp3, and WolfSSL
Most up-to-date instructions are available in Curl documentation.
# 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
Change the prefixes ~/build/wolfssl
, ~/build/nghttp3
, and ~/build/nghttp3
if necessary.
3. Build PHP Curl extension with new libcurl
Because of the make install
call on Curl above, Curl binary and libcurl are installed system-wide. When PHP is compiled with the Curl extension, it now picks the new libcurl build with HTTP/3 support.
Detailed guides on compiling PHP on guides for Debian/Ubuntu and Fedora/RHEL systems should list all of the steps.
Make sure to ./configure
PHP with --with-curl
. If Curl is not installed system-wide (i.e. not in /usr/local
), it is also possible to specify the directory here.
A quick guide on compiling PHP as follows:
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
Summary
PHP's Curl extension can be built with experimental support for HTTP/3. It unfortunately requires compiling the Curl extension, which makes it challenging for systems that depend on updates from operating system package repositories for security and bug fix updates.
The following snippet shows how to make an HTTP/3 request on a system with HTTP/3 support built in:
$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
curl_exec($ch);
- If the remote server does not support HTTP/3, it attempts to use HTTP/2 or HTTP/1.1, depending on the availability and support.
- If Curl has no HTTP/3 support, both
curl_setopt
andcurl_exec
calls fail.
HTTP/3 requests can be made more defensively as shown in the snippet below. It checks if Curl has HTTP/3 support built-in, checks if the CURL_HTTP_VERSION_3
constant is declared, and allows Curl to fall back to different HTTP versions if HTTP/3 is not available:
$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);