LoginSignup
7
6

More than 5 years have passed since last update.

nghttp2を使用する #3

Last updated at Posted at 2015-03-31

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>

sample.png

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6