PHP curlでHTTP/2リクエストを実行するための設定 on CentOS 7

先日、とあるWebサービスの開発担当者から
「APNs Provider APIを利用してiOSにプッシュ通知するため、LinuxサーバーのPHP curlでHTTP/2リクエストができるようにしてほしい」
というご依頼がありました。

その設定手順をここにまとめます。

※Web HTTPサーバーのHTTP/2対応ではなく、HTTPクライアントのHTTP/2対応のお話です。

今回試したサーバー環境は、AWS EC2のCentOS 7.3で、RemiリポジトリのPHP 7.1がインストールされているものとします。
試していませんが、PHP 5.5, 5.6, 7.0でも、同じ手順でいけると思います。

isd必要なソフトウェア

僕が調べた限り、PHP curlでHTTP/2リクエストを実行するために必要なパッケージは以下です。

  • openssl 1.0.2e以上
  • nghttp2 1.0.0以上
  • curl 7.43以上
  • PHP 5.5.24以上またはPHP 5.6.8以上またはPHP7.0.7以上またはPHP7.1以上

情報ソースは以下。

・HTTP/2 with curl
https://curl.haxx.se/docs/http2.html

・PHP 定義済み定数
http://php.net/manual/ja/curl.constants.php

opensslについては、1.0.1でもよいのかもしれませんが、TLS接続でHTTP/2を利用する場合、使用するOpenSSLライブラリがALPNをサポートしている必要があるそうです。
ALPNをサポートするopensslが1.0.2以降のため、「openssl 1.0.2e以上」という条件が出てきたのだと思われます。

(参考)
・HTTP/2を実際に使用するためのサーバー設定
http://knowledge.sakura.ad.jp/knowledge/7735/

isd現在のソフトウェアバージョンを確認

必要なソフトウェアをアップデート、インストールする前に、それぞれの現在のバージョンを確認します。

CentOS

 # cat /etc/system-release

CentOS Linux release 7.3.1611 (Core)

 

openssl

 # openssl version

OpenSSL 1.0.1e-fips 11 Feb 2013

 

PHP

 # php --version

PHP 7.1.10 (cli) (built: Sep 27 2017 08:27:18) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologiess

 

curl

 # curl --version

curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.21 Basic ECC zlib/1.2.7 libidn/1.28 libssh2/1.4.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp
Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz unix-sockets

 

PHP curlモジュール

 # php -r 'phpinfo();' | less

...
curl

cURL support => enabled
cURL Information => 7.29.0
Age => 3
Features
AsynchDNS => Yes
CharConv => No
Debug => No
GSS-Negotiate => Yes
IDN => Yes
IPv6 => Yes
krb4 => No
Largefile => Yes
libz => Yes
NTLM => Yes
NTLMWB => Yes
SPNEGO => No
SSL => Yes
SSPI => No
TLS-SRP => No
Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtsp, scp, sftp, smtp, smtps, telnet, tftp
Host => x86_64-redhat-linux-gnu
SSL Version => NSS/3.21 Basic ECC  ★
ZLib Version => 1.2.7
libSSH Version => libssh2/1.4.3

 

↑★の箇所から、curlのSSLは、OpenSSLではなく、NSSを使用していることがわかります。

isdアップデート、インストール

  • opensslのアップデート
  • nghttp2ライブラリのインストール
  • HTTP/2対応curlのインストール

を順に行います。

opensslのアップデート

CentOS 7系では、現在、openssl 1.0.2kがリリースされているので、最新バージョンにアップデートします。

 # yum update openssl

...
更新:
  openssl.x86_64 1:1.0.2k-8.el7

依存性を更新しました:
  openssl-libs.x86_64 1:1.0.2k-8.el7

 

また、curlのビルド時に、pkgconfig向けファイルやヘッダが必要となるので、openssl-develをインストールします。

 # yum install openssl-devel

...
インストール:
  openssl-devel.x86_64 1:1.0.2k-8.el7

依存性関連をインストールしました:
  keyutils-libs-devel.x86_64 0:1.5.8-3.el7   krb5-devel.x86_64 0:1.15.1-8.el7
  libcom_err-devel.x86_64 0:1.42.9-10.el7    libkadm5.x86_64 0:1.15.1-8.el7
  libselinux-devel.x86_64 0:2.5-11.el7       libsepol-devel.x86_64 0:2.5-6.el7
  libverto-devel.x86_64 0:0.2.5-4.el7        pcre-devel.x86_64 0:8.32-17.el7
  zlib-devel.x86_64 0:1.2.7-17.el7

依存性を更新しました:
  e2fsprogs.x86_64 0:1.42.9-10.el7       e2fsprogs-libs.x86_64 0:1.42.9-10.el7
  krb5-libs.x86_64 0:1.15.1-8.el7        libcom_err.x86_64 0:1.42.9-10.el7
  libselinux.x86_64 0:2.5-11.el7         libselinux-python.x86_64 0:2.5-11.el7
  libselinux-utils.x86_64 0:2.5-11.el7   libss.x86_64 0:1.42.9-10.el7

 

opensslのバージョンを確認します。

 # openssl version

OpenSSL 1.0.2k-fips  26 Jan 2017

 

nghttp2ライブラリのインストール

HTTP/2対応のcurlをビルドする際に、nghttp2が必要となるのでインストールします。
nghttp2のソースをダウンロードしてビルドしてもよいのですが、EPELリポジトリにパッケージがあるので、yumでインストールしたほうが簡単ですし、管理しやすいです。

nghttp2コマンドではなく、ライブラリと、pkgconfig向けファイルが必要なので、libnghttp2とlibnghttp2-develをインストールします。

 # yum --enablerepo=epel install libnghttp2 libnghttp2-devel

...
インストール:
  libnghttp2.x86_64 0:1.21.1-1.el7    libnghttp2-devel.x86_64 0:1.21.1-1.el7

 # rpm -ql libnghttp2

/usr/lib64/libnghttp2.so.14
/usr/lib64/libnghttp2.so.14.13.1
...

 # rpm -ql libnghttp2-devel

/usr/include/nghttp2
/usr/include/nghttp2/nghttp2.h
/usr/include/nghttp2/nghttp2ver.h
/usr/lib64/libnghttp2.so
/usr/lib64/pkgconfig/libnghttp2.pc
...

 

HTTP/2対応curlのインストール

CentOS 7に含まれるcurlは、バージョンが7.29.0と古いため、最新バージョンのソースからビルドしてインストールします。

curlオフィシャルサイトのダウンロードページ
https://curl.haxx.se/download.html
より、最新バージョンのcurlのソースをダウンロード、展開します。

 # cd /usr/local/src/
 # wget https://curl.haxx.se/download/curl-7.56.0.tar.gz
 # tar zxvf curl-7.56.0.tar.gz
 # cd curl-7.56.0/

 # ./configure --help

configureの実行時は、以下のオプションをつけます。

  • –with-ssl(SSLサポートを有効化)
  • –with-nghttp2(HTTP/2サポートを有効化)
  • –enable-libcurl-option(libcurlを生成する?デフォルトで有効のようですが念のため)

※opensslやnghttp2をソースからインストールしたときは、ライブラリファイルのディレクトリパスをldconfigコマンドで追加したうえで、–with-ssl=/usr/local/openssl, –with-nghttp2=/usr/local のようにパスを指定します。

※gccなど、ビルドに必要なツールがインストールされていない場合は、
# yum groupinstall ‘Development Tools’
で、開発ツールをグループインストールしておくとよいでしょう。

 # ./configure --with-ssl --with-nghttp2 \
    --enable-libcurl-option

...
  curl version:     7.55.1
  Host setup:       x86_64-pc-linux-gnu
  Install prefix:   /usr/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:         POSIX threaded
  IPv6 support:     no      (--enable-ipv6)
  Unix sockets support: enabled
  IDN support:      no      (--with-{libidn2,winidn})
  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:   /etc/pki/tls/certs/ca-bundle.crt
  ca cert path:     no
  ca fallback:      no
  LDAP support:     no      (--enable-ldap / --with-ldap-lib / --with-lber-lib)
  LDAPS support:    no      (--enable-ldaps)
  RTSP support:     enabled
  RTMP support:     no      (--with-librtmp)
  metalink support: no      (--with-libmetalink)
  PSL support:      no      (libpsl not found)
  HTTP2 support:    enabled (nghttp2)  ★
  Protocols:        DICT FILE FTP FTPS GOPHER HTTP HTTPS IMAP IMAPS POP3 POP3S RTSP SMB SMBS SMTP SMTPS TELNET TFTP

 

↑の★を付けた箇所について、

  • SSL supportが enable で、OpenSSLを使用していること。
  • –libcurl option が enable であること。
  • HTTP2 support が enable で、nghttp2 を使用していること。

を確認します。

問題なければ、ビルド、インストールします。

 # make
 # make install

...
Libraries have been installed in:
   /usr/local/lib

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the '-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the 'LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the 'LD_RUN_PATH' environment variable
     during linking
   - use the '-Wl,-rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to '/etc/ld.so.conf'

...

  /bin/sh ../libtool   --mode=install /usr/bin/install -c curl '/usr/local/bin'
libtool: install: /usr/bin/install -c .libs/curl /usr/local/bin/curl
...

 

configureでprefixを指定しなかったので、デフォルトで、ライブラリは /usr/local/lib に、コマンドは /usr/local/bin にインストールされました。

メッセージ出力のとおり、ライブラリのディレクトリをライブラリパスに追加します。

 # echo '/usr/local/lib' > /etc/ld.so.conf.d/custom-libs.conf
 # ldconfig

 

/usr/local/lib 配下のcurlライブラリが追加されたことを確認します。

 # ldconfig -p | grep curl

     libcurl.so.4 (libc6,x86-64) => /usr/local/lib/libcurl.so.4
        libcurl.so.4 (libc6,x86-64) => /lib64/libcurl.so.4
        libcurl.so (libc6,x86-64) => /usr/local/lib/libcurl.so

 

isd動作確認

curlコマンドの確認

 # /usr/local/bin/curl --version

curl 7.56.0 (x86_64-pc-linux-gnu) libcurl/7.56.0 OpenSSL/1.0.2k zlib/1.2.7 nghttp2/1.21.1
Release-Date: 2017-10-04
Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy

 

↑curlが、libcurl/7.56.0, OpenSSL/1.0.2k, nghttp2/1.21.1 を含んでいることがわかります。
また、Features に、HTTP2 があります。

curlコマンドで、HTTP/2リクエストで https://www.google.co.jp/ にアクセスしてみます。

 # /usr/local/bin/curl -vso /dev/null --http2 https://www.google.co.jp/

*   Trying 172.217.25.99...
* TCP_NODELAY set
* Connected to www.google.co.jp (172.217.25.99) port 443 (#0)
* ALPN, offering h2  ★
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
} [5 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [96 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [3915 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [148 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [70 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20): ★
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES128-GCM-SHA256 ★
* ALPN, server accepted to use h2 ★
* Server certificate:
*  subject: C=US; ST=California; L=Mountain View; O=Google Inc; CN=*.google.com
*  start date: Oct  3 17:45:20 2017 GMT
*  expire date: Dec 26 17:44:00 2017 GMT
*  subjectAltName: host "www.google.co.jp" matched cert's "*.google.co.jp"
*  issuer: C=US; O=Google Inc; CN=Google Internet Authority G2
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use  ★
* Connection state changed (HTTP/2 confirmed)  ★
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
} [5 bytes data]  ★
* Using Stream ID: 1 (easy handle 0x1c13340)
} [5 bytes data]
> GET / HTTP/2  ★
> Host: www.google.co.jp
> User-Agent: curl/7.56.0
> Accept: */*
>
{ [5 bytes data]
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
} [5 bytes data]
< HTTP/2 200
< date: Wed, 11 Oct 2017 06:47:55 GMT
< expires: -1
< cache-control: private, max-age=0
< content-type: text/html; charset=Shift_JIS
< p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
< server: gws
< x-xss-protection: 1; mode=block
< x-frame-options: SAMEORIGIN
< set-cookie: 1P_JAR=2017-10-11-06; expires=Wed, 18-Oct-2017 06:47:55 GMT; path=/; domain=.google.co.jp
< set-cookie: NID=114=ufyp9MiA_y9Hv5xeZ0S3xVKTddyCZ4NzhwQclE7ziuIIlgnIk57ior1TBbDJQNxbtBQyhuy7ittOkW9HzPry1RDkr4yLYzIO6NkI6wXG4QXEg3PUxn_UB3XS5l4GTYLF; expires=Thu, 12-Apr-2018 06:47:55 GMT; path=/; domain=.google.co.jp; HttpOnly
< alt-svc: quic=":443"; ma=2592000; v="39,38,37,35"
< accept-ranges: none
< vary: Accept-Encoding
<
{ [5 bytes data]
* Connection #0 to host www.google.co.jp left intact

 

↑★の箇所から、ALPN, TLS 1.2でネゴシエーションして、HTTP/2でアクセスしていることがわかります。

PHP curlの確認

PHP curlモジュールを確認します。

 # php -r 'phpinfo();' | less

...
curl

cURL support => enabled
cURL Information => 7.56.0  ★
Age => 3
Features
AsynchDNS => Yes
CharConv => No
Debug => No
GSS-Negotiate => No
IDN => No
IPv6 => Yes
krb4 => No
Largefile => Yes
libz => Yes
NTLM => Yes
NTLMWB => Yes
SPNEGO => No
SSL => Yes
SSPI => No
TLS-SRP => No
Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, pop3, pop3s, rtsp, smb, smbs, smtp, smtps, telnet, tftp
Host => x86_64-pc-linux-gnu
SSL Version => OpenSSL/1.0.2k  ★
ZLib Version => 1.2.7

 

↑★の箇所で、curlのバージョンが、7.56.0となり、SSLはNSSではなく、OpenSSL 1.0.2kを使用していることがわかります。

PHPテストスクリプトを作成して、HTTP/2リクエストで https://www.google.co.jp/ にアクセスしてみます。

(参考)
・curl エクステンションで HTTP/2 リクエストを送信する
https://qiita.com/masakielastic/items/f563437c44b0d4c04f87

 # vi curl_test.php

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

$url = 'https://www.google.co.jp/';

$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);

?>

 

スクリプトを実行します。

 # php curl_test.php


*   Trying 216.58.197.195...
* TCP_NODELAY set
* Connected to www.google.co.jp (216.58.197.195) port 443 (#0)
* ALPN, offering h2  ★
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES128-GCM-SHA256 ★
* ALPN, server accepted to use h2 ★
* Server certificate:
*  subject: C=US; ST=California; L=Mountain View; O=Google Inc; CN=*.google.com
*  start date: Sep 26 11:09:35 2017 GMT
*  expire date: Dec 19 10:59:00 2017 GMT
*  issuer: C=US; O=Google Inc; CN=Google Internet Authority G2
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use ★
* Connection state changed (HTTP/2 confirmed) ★
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 ★
* Using Stream ID: 1 (easy handle 0x7fa5fa7f6120)
> GET / HTTP/2 ★
Host: www.google.co.jp
Accept: */*

* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 200 ★
< date: Wed, 11 Oct 2017 07:01:57 GMT
< expires: -1
< cache-control: private, max-age=0
< content-type: text/html; charset=Shift_JIS
< p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
< server: gws
< x-xss-protection: 1; mode=block
< x-frame-options: SAMEORIGIN
< set-cookie: 1P_JAR=2017-10-11-07; expires=Wed, 18-Oct-2017 07:01:57 GMT; path=/; domain=.google.co.jp
< set-cookie: NID=114=mHV9cy97pI1Rn2Xea54XHnRyPJK8lhOKt5b4DL1zVI61H7NASpL4GlKBtt6alTycRDBhQnBQj2C1_P43XZBTYwf0QG7ot665JDs9Udn_cnPOUbF3LvLFw-J45DFFchYP; expires=Thu, 12-Apr-2018 07:01:57 GMT; path=/; domain=.google.co.jp; HttpOnly
< alt-svc: quic=":443"; ma=2592000; v="39,38,37,35"
< accept-ranges: none
< vary: Accept-Encoding
<
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head><meta content="???E???y?????????????c?[?????????                         ????B???????????@?\??p????A???T???y???????????                                  ???B" name="description"><meta content="noodp" name="robots"><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" itemprop="image"><title>Google</title>
...

 

↑★の箇所から、ALPN, TLS 1.2でネゴシエーションして、HTTP/2でアクセスしていることがわかります。

以上で、PHP curlでHTTP/2リクエストを実行する設定は完了です。

Webアプリケーション経由でPHP curlを使用する場合は、差し替えたPHP curlを使用するよう、ApacheやFastCGIなどのPHPモジュールを含むプロセスを再起動しましょう。

ここまであげた参考記事のほか、以下の記事も参考にさせていただきました。
ありがとうございます。

(参考)
・curl エクステンションで HTTP/2 リクエストを送信する
https://qiita.com/masakielastic/items/f563437c44b0d4c04f87

・nghttp2でhttp2を試してみる
https://qiita.com/sao_rio/items/e95e3989e08e0482be67

・[PHP]CurlでのSSL接続をOpenSSL方式に変更する
http://kayakuguri.github.io/blog/2016/07/07/curl-openssl-tls/

 

(関連記事)
・PHP curlでHTTP/2リクエストを実行するための設定 on CentOS 6
https://inaba-serverdesign.jp/blog/20171017/php_curl_http2_centos6.html
 

Follow me!