Help us understand the problem. What is going on with this article?

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

More than 3 years have 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が対応しています。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away