h2o Advent Calendar の2日目です。
h2o はスタンドアロンの HTTP サーバーだけでなく、 libh2o というライブラリとしてビルドすることもできます。今回は libh2o をビルドして、 hello world サーバーを作るまでやってみます。
環境は Ubuntu と Mac OS X を想定しています。
cmake
h2o はビルドシステムに cmake を利用しています。 brew なり apt-get なりで cmake を用意しておきましょう。
他に、後述する libuv のために automake, libtool が必要です。 C++ も入ってなかったら用意しておきましょう。
libuv
h2o のスタンドアロン版では libuv 1.0.x への依存が optional なのですが、 libh2o では必須になっているようです(少なくとも僕は libuv 無しでのビルドには失敗しました)。まずは libuv をセットアップしましょう。
libuv の 1.0 系は最近出たばかりなので、ソースからビルドしていきます。 libuv のサイト はとてもシンプルで、すぐにダウンロードリンクが見つかるはずです。
以下の手順では、 preifx=$HOME/local/stow/libuv-1.0.x
にインストールしつつ、 stow で $HOME/local
配下に symlink を貼ります。
このへんは好みで直接共通の prefix 配下にインストールするなり、 paco で uninstall 可能にするなり、個別の prefix にインストールして各種PATHを1つ1つ設定するなりしてください。
$ wget http://libuv.org/dist/v1.0.1/libuv-v1.0.1.tar.gz
$ tar xf libuv-v1.0.1.tar.gz
$ cd libuv-v1.0.1/
$ ./autogen.sh
$ ./configure --prefix=$HOME/local/stow/libuv-1.0.1
$ make -j4
$ make install
$ cd ~/local/stow
$ stow libuv-1.0.1/
$ ls ../lib
libuv.1.dylib libuv.a libuv.dylib libuv.la pkgconfig
libh2o のビルド
libuv を cmake に見つけさせるためと、 libuv の動的ライブラリをリンクするための環境変数を設定しておきます。
$ export CMAKE_INCLUDE_PATH=$HOME/local/include
$ export CMAKE_LIBRARY_PATH=$HOME/local/lib
$ export LD_LIBRARY_PATH=$HOME/local/lib
libh2o をチェックアウトします。
$ git clone git@github.com:h2o/h2o.git
$ cd h2o
$ git submodule init
Submodule 'deps/klib' (https://github.com/attractivechaos/klib.git) registered for path 'deps/klib'
Submodule 'deps/picohttpparser' (https://github.com/h2o/picohttpparser.git) registered for path 'deps/picohttpparser'
Submodule 'deps/picotest' (https://github.com/h2o/picotest.git) registered for path 'deps/picotest'
Submodule 'deps/yoml' (https://github.com/h2o/yoml.git) registered for path 'deps/yoml'
$ git submodule update
ビルド用のディレクトリを作ってそこでビルドします。ディレクトリを作るのは必須ではないですが、 CLion (JetBrains の新しい C/C++ 用IDE) を使うときに無いと警告されますし、実際 cmake が1発で通らなかった時にビルドディレクトリを消すだけでリセットできるのは便利です。
$ mkdir build
$ cd build
$ cmake ..
$ make libh2o
$ ls
CMakeCache.txt CMakeFiles Makefile cmake_install.cmake libh2o.a
$ install libh2o.a ~/local/lib
hello world
さて、 libh2o を使ってみましょう。 examples/libh2o ディレクトリの中に simple.c というファイルがあるので、 cp simple.c hello.c して、 hello world を書きます。
改造したソースコードは gist に置きました。
hello world のハンドラになっている関数だけ張っておきます。
static int hello(h2o_handler_t *self, h2o_req_t *req)
{
static h2o_generator_t generator = { NULL, NULL };
static h2o_iovec_t body = {H2O_STRLIT("Hello, World!\n")};
req->res.status = 200;
req->res.reason = "OK";
req->res.content_length = body.len;
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, H2O_STRLIT("text/plain; charset=utf-8"));
h2o_start_response(req, &generator);
h2o_send(req, &body, 1, 1);
return 0;
}
h2o_start_response はまだ何をしているのか把握してなくて、コピペのままです。
h2o_send の第二引数は h2o_iovec_t の配列ですが、これは char*
と size_t
の構造体になっていて、文字列リテラルから作る場合は H2O_STRLIT
マクロが便利です。
第三引数は第二引数の配列の長さ、第四引数は最後の h2o_send かどうか、です。
最初に試した時は transfer-encoding: chunked だったので、 req->res
に当たりをつけて Clion で . をタイプしたらすぐに content_length が補完候補に見つかったので、 body.len を突っ込んでシンプルなレスポンスにします。
これをビルドしてみます。いいビルド方法が見つからなかったのでCコンパイラを直接叩いています。 Mac でビルドすると勝手にシステムの openssl を見つけられたので -lssl -lcrypto
をつけていますが、 Ubuntu などで openssl 無しでビルドする場合は外してください。
$ cc -O2 -I ../../include -I ../../deps/picohttpparser -I ../../deps/yoml -I $HOME/local/include -L $HOME/local/lib hello.c -lh2o -luv -lcrypto -lssl -o hello
起動してみましょう。
$ ./hello
別セッションから叩いてみます。
$ curl -vv http://localhost:7890/
* Adding handle: conn: 0x7fc0b9004000
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7fc0b9004000) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 7890 (#0)
* Trying ::1...
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 7890 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:7890
> Accept: */*
>
< HTTP/1.1 200 OK
< date: Mon, 01 Dec 2014 15:49:34 GMT
* Server h2o/0.1 is not blacklisted
< server: h2o/0.1
< connection: keep-alive
< content-length: 14
< content-type: text/plain; charset=utf-8
<
Hello, World!
* Connection #0 to host localhost left intact
$ wrk http://localhost:7890/
Running 10s test @ http://localhost:7890/
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 206.67us 206.17us 6.16ms 95.23%
Req/Sec 24.35k 5.45k 30.78k 78.49%
461496 requests in 10.00s, 75.70MB read
Requests/sec: 46153.12
Transfer/sec: 7.57MB
どうやらちゃんと動いているようです。