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でも、同じ手順でいけると思います。
必要なソフトウェア
僕が調べた限り、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を実際に使用するためのサーバー設定
https://knowledge.sakura.ad.jp/7735/
現在のソフトウェアバージョンを確認
必要なソフトウェアをアップデート、インストールする前に、それぞれの現在のバージョンを確認します。
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を使用していることがわかります。
アップデート、インストール
- 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
動作確認
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