Edited at

curl エクステンションで HTTP/2 リクエストを送信する

More than 1 year has passed since last update.


curl のビルドオプション

システムにインストールされている curl のバージョンが 7.3.3 とそれ以降であり、0.6.0 とそれ以降の nghttp2 がシステムにインストールされている状態でビルドオプションに --with-nghttp2=PATH が指定されていれば、PHP の curl エクステンションで HTTP/2 が利用できます。

./configure --prefix=`pwd`/local --with-nghttp2

curl version: 7.42.0-DEV
Host setup: x86_64-apple-darwin14.1.0
Install prefix: /Users/masakielastic/test/curl/local
Compiler: gcc
SSL support: enabled (OpenSSL)
SSH support: no (--with-libssh2)
zlib support: enabled
GSS-API support: no (--with-gssapi)
TLS-SRP support: no (--enable-tls-srp)
resolver: default (--enable-ares / --enable-threaded-resolver)
IPv6 support: enabled
Unix sockets support: enabled
IDN support: enabled
Build libcurl: Shared=yes, Static=yes
Built-in manual: enabled
--libcurl option: enabled (--disable-libcurl-option)
Verbose errors: enabled (--disable-verbose)
SSPI support: no (--enable-sspi)
ca cert bundle: no
ca cert path: no
LDAP support: enabled (OpenLDAP)
LDAPS support: enabled
RTSP support: enabled
RTMP support: no (--with-librtmp)
metalink support: no (--with-libmetalink)
HTTP2 support: enabled (nghttp2)
Protocols: DICT FILE FTP FTPS GOPHER HTTP HTTPS IMAP IMAPS LDAP LDAPS POP3 POP3S RTSP SMB SMBS SMTP SMTPS TELNET TFTP

curl -V を実行すれば、nghttp2 のビルドオプションが有効であるかを調べることができます。

curl 7.41.0 (x86_64-apple-darwin14.1.0) libcurl/7.41.0 OpenSSL/1.0.2a zlib/1.2.5 nghttp2/0.7.7

Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets


curl コマンドによるリクエスト

次のコマンドを実行してレスポンスを目で確認してみましょう。

curl --http2 -v https://http2bin.org/get

ヘッダーだけ出力することもできます。

curl -I --http2 https://http2bin.org/get

結果は次のとおりです。「HTTP/2.0 200」が含まれていることを確認します。

*   Trying 104.131.161.90...

* Connected to http2bin.org (104.131.161.90) port 443 (#0)
* ALPN, offering h2-14, http/1.1
* successfully set certificate verify locations:
* CAfile: /usr/local/etc/openssl/cert.pem
CApath: none
* NPN, negotiated HTTP2 (h2-14)
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: OU=Domain Control Validated; OU=EssentialSSL; CN=www.http2bin.org
* start date: 2015-02-21 00:00:00 GMT
* expire date: 2016-02-21 23:59:59 GMT
* issuer: C=GB; ST=Greater Manchester; L=Salford; O=COMODO CA Limited; CN=COMODO RSA Domain Validation Secure Server CA
* SSL certificate verify ok.
* Using HTTP2
* http2_send len=54
* before_frame_send() was called
* on_frame_send() was called
* before_frame_send() was called
* on_frame_send() was called
> GET /get HTTP/1.1
Host: http2bin.org
Accept: */*

* http2_recv: 16384 bytes buffer
* nread=27
* on_frame_recv() was called with header 4
* nghttp2_session_mem_recv() returns 27
* before_frame_send() was called
* on_frame_send() was called
* http2_recv: 16384 bytes buffer
* nread=9
* on_frame_recv() was called with header 4
* nghttp2_session_mem_recv() returns 9
* http2_recv: 16384 bytes buffer
* nread=345
* on_begin_headers() was called
* got http2 header: server: h2o/1.1.1
* got http2 header: date: Mon, 16 Mar 2015 21:47:58 GMT
* got http2 header: content-type: application/json
* got http2 header: access-control-allow-origin: *
* got http2 header: access-control-allow-credentials: true
* got http2 header: x-clacks-overhead: GNU Terry Pratchett
* on_frame_recv() was called with header 1
* on_data_chunk_recv() len = 218, stream = 1
* 218 data written
* on_frame_recv() was called with header 0
* on_stream_close() was called, error_code = 0
* nghttp2_session_mem_recv() returns 345
< HTTP/2.0 200
< server:h2o/1.1.1
< date:Mon, 16 Mar 2015 21:47:58 GMT
< content-type:application/json
< access-control-allow-origin:*
< access-control-allow-credentials:true
< x-clacks-overhead:GNU Terry Pratchett
<
{
"args": {},
"headers": {
"Accept": "*/*",
"Connection": "keep-alive",
"Host": "http2bin.org",
"Via": "2 http2bin.org"
},
"origin": "106.184.75.63",
"url": "https://http2bin.org/get"
}
* Connection #0 to host http2bin.org left intact


インストール


homebrew

あらかじめ最新の OpenSSL が使えるようにしておきます。brew link コマンドを実行しておかないと、Mac OS X にインストールされてあるデフォルトの OpenSSL が使われます。

brew update

brew install openssl
brew link openssl --force
openssl version

curl をインストールする際のビルドオプションに --with-nghttp2 を指定します。

brew install curl --with-nghttp2 --with-openssl

brew link curl --force

以前の公式 fomula ではビルドオプションが利用できなかったのと最新バージョンを試したかったので、自分の formula を用意していました。次のコマンドでインストールできます。

brew update

brew install masakielastic/http2/curl --with-openssl
brew link curl --force


linuxbrew

主要な Linux ディストリビューションで個人のディレクトリの領域に curl をインストールして試すのであれば、パッケージ管理ツールの linuxbrew が便利です。linuxbrew は Mac OSX の homebrew の Linux 対応バージョンです。Ubunutu に導入した際に次のパッケージを追加で導入する必要がありました。

sudo apt-get install pkg-conf python-setuptools

次のコマンドで curl がインストールされます。

brew install curl --with-nghttp2


Ubuntu 16.04 LTS

再コンパイルすることで HTTP/2 を利用できるようになります。launchpad に 手順 が記されています。

最初に nghttp2 とビルドに必要なツールをインストールします。

sudo apt install libnghttp2-dev

sudo apt build-dep curl

次にソースコードをダウンロードします。

apt source curl

cd curl-7.47.0

debian/control を編集して Build-Depends の項目で libnghttp2-dev を追加します。

Build-Depends: debhelper (>= 9),

autoconf,
automake,
ca-certificates,
groff-base,
libgnutls-dev,
libidn11-dev,
libkrb5-dev,
libldap2-dev,
libnss3-dev,
librtmp-dev (>= 2.4+20131018.git79459a2-3~),
libssl-dev,
libnghttp2-dev,
...

ビルドしてインストールします。テストに5分から10分ほどかかります。

sudo debian/rules binary

sudo dpkg -i ../*deb


CURL_HTTP_VERSION_2_0 の定義

HTTP/2 リクエストを送信するために必要なことは CURLOPT_HTTP_VERSION に対応する値として CURL_HTTP_VERSION_2_0 を指定することだけです。PHP 5.6.8、5.5.24 系から使えるようになります。それ以前のバージョンでは CURL_HTTP_VERSION_2_0 を自分で定義する必要があります。

if (!defined('CURL_HTTP_VERSION_2_0')) {

define('CURL_HTTP_VERSION_2_0', CURL_HTTP_VERSION_1_1 + 1);
}

なお、curl/curl.h では CURL_HTTP_VERSION_2_0 は次のように enum で定義されています。

enum {

CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd
like the library to choose the best possible
for us! */

CURL_HTTP_VERSION_1_0, /* please use HTTP 1.0 in the request */
CURL_HTTP_VERSION_1_1, /* please use HTTP 1.1 in the request */
CURL_HTTP_VERSION_2_0, /* please use HTTP 2.0 in the request */

CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */
};


curl エクステンション

if (!defined('CURL_HTTP_VERSION_2_0')) {

define('CURL_HTTP_VERSION_2_0', CURL_HTTP_VERSION_1_1 + 1);
}

$url = 'https://http2bin.org/get';

$opts = [
CURLOPT_VERBOSE => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSL_VERIFYPEER => false
];

$ch = curl_init($url);
curl_setopt_array($ch, $opts);
curl_exec($ch);
curl_close($ch);


Guzzle

バージョン 5 を対象とします。

use GuzzleHttp\Client;

if (!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', CURL_HTTP_VERSION_1_1 + 1);
}

$client = new Client();
$client
->get(
'https://http2bin.org/get', [
'future' => true,
'config' => [
'curl' => [
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0
]
],
'debug' => true
]
)
->then(function($response) {
var_dump(
$response->getStatusCode(),
$response->getHeader('Content-Type'),
(string) $response->getBody()
);
});


RingPHP

use GuzzleHttp\Ring\Client\CurlHandler;

if (!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', CURL_HTTP_VERSION_1_1 + 1);
}

$handler = new CurlHandler();
$response = $handler([
'http_method' => 'GET',
'scheme' => 'https',
'uri' => '/get',
'headers' => [
'host' => ['http2bin.org']
],
'client' => [
'curl' => [
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0
],
'debug' => true
]
]);

$response->then(function (array $response) {
var_dump($response);
});


HTTP/2 が利用できるかの判定方法

HTTP/2 が利用できるかどうかで curl のオプションの構成を変えたい場合、curl_version を使います。

if (!defined('CURL_VERSION_HTTP2')) {

define('CURL_VERSION_HTTP2', 1<<16);
}

if (curl_version()['features'] & CURL_VERSION_HTTP2) {
echo "HTTP/2 は利用できます。\n";
} else {
echo "HTTP/2 は利用できません。\n";
}

CURL_VERSION_HTTP2curl/curl.h で定義されています。

#define CURL_VERSION_HTTP2        (1<<16) /* HTTP2 support built-in */

#define CURL_VERSION_GSSAPI (1<<17)
/* Built against a GSS-API library */
#define CURL_VERSION_KERBEROS5 (1<<18)
/* Kerberos V5 auth is supported */
#define CURL_VERSION_UNIX_SOCKETS (1<<19)
/* Unix domain sockets support */


HTTP/2 対応のサーバーを利用する


h2o

h2o バージョン 1.3 から php-fpm に対応しています。Mac OS X であれば、homebrew を使ってインストールおよび起動できます。設定ファイルのパスは /usr/local/etc/h2o/h2o.conf で、ポート番号は 8080 です。

brew install h2o

ln -sfv /usr/local/opt/h2o/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.h2o.plist


h2o.conf

file.custom-handler:

extension: .php
fastcgi.connect:
host: 127.0.0.1
port: 9000
type: tcp
listen:
port: 8080
ssl:
certificate-file: /path/to/server.crt
key-file: /path/to/server.key
hosts:
"127.0.0.1.xip.io:8080":
paths:
/:
file.dir: /usr/local/var/h2o/

Ubuntu の php-fpm を利用する場合、Unix ソケット形式で書く必要がありました。

file.custom-handler:

extension: .php
fastcgi.connect:
port: /var/run/php5-fpm.sock
type: unix

PHP の開発バージョンを試すためにシステムにあらかじめ導入されている php-fpm 以外の Fast CGI のプロセスが必要であれば、h2o に生成と管理を任せることができます。次の設定は Ubuntu で確認しました。

file.custom-handler:

extension: .php
fastcgi.spawn:
command: "PHP_FCGI_CHILDREN=10 exec /usr/bin/php-cgi"
user: nobody


nginx

nginx 1.9.5 から利用できるようになりました。ソースコードからビルドする場合、--with-http_v2_module オプションを指定します。Debian/Ubuntu の場合、nginx.org のリポジトリを利用することができます。/etc/apt/sources.list に次のようなコードを追加します。

deb http://nginx.org/packages/ubuntu/ trusty nginx

deb-src http://nginx.org/packages/ubuntu/ trusty nginx

listen ディレクティブに指定します。

server {

listen 443 ssl http2 default_server;

ssl_certificate server.crt;
ssl_certificate_key server.key;

#...
}


Apache

Apache 2.4.17 で mod_http2 モジュールが利用できるようになりました。ビルドオプションに --enable-http2 を指定します。Ubuntu の場合、PPA が利用できます。

sudo add-apt-repository -y ppa:ondrej/apache2

設定ファイルに Protocols ディレクティブを追加します。

# /etc/apache2/sites-available/000-default.conf

Protocols h2c http/1.1

# /etc/apache2/sites-available/default-ssl.conf
Protocols h2 http/1.1


リバースプロキシーによる HTTP/2 対応

ポート番号 80 で HTTP1 のサーバーが起動しており、ポート番号 8080 から HTTP2 クライアントでアクセスできるようにしてみましょう。


nghttpx

nghttpx であれば、次のコマンドを実行します。SSL/TLS の証明書はあらかじめ用意してあることが前提です。

nghttpx -f0.0.0.0,8080 -b127.0.0.1,80 server.key server.crt


h2o

h2o の場合、次のような設定ファイルを用意します。


proxy.conf

listen:

port: 8080
ssl:
certificate-file: examples/h2o/server.crt
key-file: examples/h2o/server.key
hosts:
default:
paths:
/:
proxy.reverse.url: http://127.0.0.1:80/
access-log: /dev/stdout

h2o を起動させてみましょう。

h2o -c proxy.conf


CDN サービス

CloudFlare は HTTP/2 に対応しており、独自の SSL/TLS を設定できない共有ホスティングサービスで利用できます。私はウェブクロウの無料プランで HTTP/2 通信を確認しました (参考記事)。

2016年4月にサーバープッシュに対応されました。PHP のサンプルコードをブログの記事で紹介されています。


共有ホスティングサービス

2016年5月の時点で日本国内の業者は対応していません。ロリポップが今後の対応を宣言しています。海外では A2 HostingSiteGroundが対応しています。