HTTP2のライブラリ「nghttp2」を紹介するエントリです。
nghttp2のインストールは#1をご覧ください。
概要
今回はC++から呼び出すことができるHTTP2のハイレベルなAPI「libnghttp2_asio」を使います。
これを使うと簡単にC++でHTTP2サーバを立ててリクエストをさばくことができます。
boostをインストールする必要があります。
configureを見るとboost1.54以上があれば大丈夫なようなので、ソースではなくパッケージでインストールします。
Boostのインストール
sudo apt-get install libboost-all-dev libboost-dev
リビルド
nghttp2のディレクトリでオプションをつけてリビルドします。
vagrantだとメモリ不足でコンパイル中に落ちる可能性があるので、その場合はvagrant仮想マシンのメモリをvagrantfileでメモリを増やします。
./configure --enable-asio-lib
sudo make
sudo make install
正しくインストールされたことを確認する。
ll /usr/local/lib/
libnghttp2.a libnghttp2.so.5 libnghttp2_asio.la libnghttp2_asio.so.1.0.0 python3.4/
libnghttp2.la libnghttp2.so.5.7.2 libnghttp2_asio.so pkgconfig/
libnghttp2.so libnghttp2_asio.a libnghttp2_asio.so.1 python2.7/
ソースを準備する
nghttp2の公式から、下記のサンプルを用意します。
ホストとポートを書き換えています。
server.cpp
#include <nghttp2/asio_http2_server.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
int main(int argc, char *argv[]) {
boost::system::error_code ec;
http2 server;
server.handle("/", [](const request &req, const response &res) {
res.write_head(200);
res.end("hello, world\n");
});
if (server.listen_and_serve(ec, "nghttp2.test", "8080")) {
std::cerr << "error: " << ec.message() << std::endl;
}
}
コンパイル
g++ -o server server.cpp -lnghttp2_asio -lboost_system -std=c++11
gccのバージョンの問題なのかstd::cerrでエラーが出たので、ソースの頭に「#include <iostream>
」を追加しました。
g++ -o server server.cpp -lnghttp2_asio -lboost_system -std=c++11
SSLのメソッドがないと怒られたので、
g++ -o server server.cpp -lnghttp2_asio -lboost_system -std=c++11 -lssl -lcrypto
pthread系がないと怒られたので、
g++ -o server server.cpp -lnghttp2_asio -lboost_system -std=c++11 -lssl -lcrypto -lpthread
成功しました。
ライブラリパスを通しておきます。
vi ~/.bashrc
export LD_LIBRARY_PATH=/usr/local/lib/
source ~/.bashrc
実行します。
./server
クライアントから確認します。
nghttp -nv http://nghttp2.test:8080
[ 0.001] Connected
[ 0.001] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.003] send HEADERS frame <length=39, flags=0x05, stream_id=1>
; END_STREAM | END_HEADERS
(padlen=0)
; Open new stream
:method: GET
:path: /
:scheme: http
:authority: nghttp2.test:8080
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/0.7.10-DEV
[ 0.007] recv SETTINGS frame <length=6, flags=0x00, stream_id=0>
(niv=1)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[ 0.007] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.008] recv (stream_id=1) :status: 200
[ 0.008] recv (stream_id=1) date: Tue, 31 Mar 2015 03:55:18 GMT
[ 0.009] recv HEADERS frame <length=25, flags=0x04, stream_id=1>
; END_HEADERS
(padlen=0)
; First response header
[ 0.009] recv DATA frame <length=13, flags=0x01, stream_id=1>
; END_STREAM
[ 0.010] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.010] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
使い方はページリクエストをさばいて、リクエストコンテキストが参照できて、レスポンスオブジェクトに書き込む普通のWebフレームワークインターフェースのようです。
SSLに対応させる
server_ssl.cpp
#include <iostream>
#include <nghttp2/asio_http2_server.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
int main(int argc, char *argv[]) {
boost::system::error_code ec;
boost::asio::ssl::context tls(boost::asio::ssl::context::sslv23);
tls.use_private_key_file("server.key", boost::asio::ssl::context::pem);
tls.use_certificate_chain_file("server.crt");
configure_tls_context_easy(ec, tls);
http2 server;
server.handle("/index.html", [](const request &req, const response &res) {
res.write_head(200);
res.end(file_generator("index.html"));
});
if (server.listen_and_serve(ec, tls, "nghttp2.test", "8080")) {
std::cerr << "error: " << ec.message() << std::endl;
}
}
index.htmlを用意します。
<html>
<body>
Hello nghttp2 asio API!
</body>
</html>
ビルドして実行
g++ -o server server_ssl.cpp -lnghttp2_asio -lboost_system -std=c++11 -lssl -lcrypto -lpthread
./server
確認 (Chromeからでも確認できます)
nghttp -nv https://nghttp2.test:8080/index.html
サーバープッシュ
サーバープッシュにチャンレジしてみましょう。
公式にサンプルが載っていますので、ホストとポートだけ書き換えます。
また、server.num_threads(4);を追加してスレッド数を4に増加させています。
server_push.cpp
#include <iostream>
#include <nghttp2/asio_http2_server.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
int main(int argc, char *argv[]) {
boost::system::error_code ec;
boost::asio::ssl::context tls(boost::asio::ssl::context::sslv23);
tls.use_private_key_file("server.key", boost::asio::ssl::context::pem);
tls.use_certificate_chain_file("server.crt");
configure_tls_context_easy(ec, tls);
http2 server;
server.num_threads(4); // スレッド数4
std::string style_css = "h1 { color: green; }";
server.handle("/", [&style_css](const request &req, const response &res) {
boost::system::error_code ec;
auto push = res.push(ec, "GET", "/style.css");
push->write_head(200);
push->end(style_css);
res.write_head(200);
res.end(R"(
<!DOCTYPE html><html lang="en">
<title>HTTP/2 FTW</title><body>
<link href="/style.css" rel="stylesheet" type="text/css">
<h1>This should be green</h1>
</body></html>
)");
});
server.handle("/style.css",
[&style_css](const request &req, const response &res) {
res.write_head(200);
res.end(style_css);
});
if (server.listen_and_serve(ec, tls, "nghttp2.test", "8080")) {
std::cerr << "error: " << ec.message() << std::endl;
}
}
g++ -o server server_push.cpp -lnghttp2_asio -lboost_system -std=c++11 -lssl -lcrypto -lpthread
./server
フレームを確認します。
nghttp -nv https://nghttp2.test:8080/
[ 0.002] Connected
[ 0.008][NPN] server offers:
* h2
* h2-16
* h2-14
The negotiated protocol: h2
[ 0.013] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.014] send HEADERS frame <length=39, flags=0x05, stream_id=1>
; END_STREAM | END_HEADERS
(padlen=0)
; Open new stream
:method: GET
:path: /
:scheme: https
:authority: nghttp2.test:8080
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/0.7.10-DEV
[ 0.017] recv SETTINGS frame <length=6, flags=0x00, stream_id=0>
(niv=1)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[ 0.018] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.018] recv (stream_id=1) :method: GET
[ 0.018] recv (stream_id=1) :scheme: https
[ 0.018] recv (stream_id=1) :authority: nghttp2.test:8080
[ 0.018] recv (stream_id=1) :path: /style.css
[ 0.018] recv PUSH_PROMISE frame <length=29, flags=0x04, stream_id=1>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.018] recv (stream_id=1) :status: 200
[ 0.018] recv (stream_id=1) date: Tue, 31 Mar 2015 04:35:46 GMT
[ 0.018] recv HEADERS frame <length=25, flags=0x04, stream_id=1>
; END_HEADERS
(padlen=0)
; First response header
[ 0.019] recv DATA frame <length=168, flags=0x01, stream_id=1>
; END_STREAM
[ 0.019] recv (stream_id=2) :status: 200
[ 0.019] recv (stream_id=2) date: Tue, 31 Mar 2015 04:35:46 GMT
[ 0.019] recv HEADERS frame <length=2, flags=0x04, stream_id=2>
; END_HEADERS
(padlen=0)
; First push response header
[ 0.019] recv DATA frame <length=20, flags=0x01, stream_id=2>
; END_STREAM
[ 0.020] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.020] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
スタイルシートがストリームID2でPUSH_PROMISEで予約され、その後にGETしていないのにデータがプッシュされているのが分かります。
[ 0.018] recv (stream_id=1) :path: /style.css
[ 0.018] recv PUSH_PROMISE frame <length=29, flags=0x04, stream_id=1>
; END_HEADERS
(padlen=0, promised_stream_id=2)
[ 0.019] recv HEADERS frame <length=2, flags=0x04, stream_id=2>
[ 0.019] recv DATA frame <length=20, flags=0x01, stream_id=2>