2015年1月23日金曜日

C++: cppnetlib-0.11 でstatus()やbody()で応答が返らない場合のメモ

とあるプロジェクトのhttp周りを Poco::Net から cppnetlib に移行しようと思ったところで不可解な現象に遭遇してしまった。その解決までのメモ。
cppnetlib-0.11 は ubuntu-14.10 なら apt で libcppnetlib-dev パッケージから導入できる。
ミニマルな http-client のコードを試す。
#include <string>
#include <iostream>
#include <boost/network.hpp>

auto main()
  -> int
{
  using current_client = boost::network::http::client;

  current_client::request req("http://www.google.co.jp/");
  request
    << boost::network::header( "Connection", "close" )
    << boost::network::header( "UserAgent" , "cppnetlib-test-minimal" )
    ;
  current_client client;
  auto response = client.get(request);
  const std::string body = body(response);
  std::cout << body << std::endl;
}
clang++ -std=c++11 cppnetlib-test-minimal.cxx -lboost_system -lboost_thread -lpthread -lcppnetlib-client-connections -lcppnetlib-uri
これを試すと Google のトップページのHTMLがコンソールに表示されて、さて、それではプロジェクトの Poco::Net を cppnetlib へ置き換えてみましょうか、と、なるはずだった。
どうもレスポンスが帰ってこないで処理が止まる。
どこで止まっているか確認すると、 body() が処理を返してくれない。ちなみに、 status() なども処理を返してくれない事を確認した。
とりあえず、サーバーへどんなリクエストが飛んでいるのか確認したいと思い、先ずは簡易的に
php -S 0.0.0.0:12345
などしてサーバーを立ち上げた。ここで python -m SimpleHTTPServer 12345 とするとリクエストが一応飛んでいる事にすら気付けなかったらしい。
php -S でも cppnetlib-test-minimal を実行して、body で処理が返らない状態では何も表示されなかったが、そのプロセスをCTRL+Cして停止した瞬間に次のようなログが観測された。
[Fri Jan 23 04:46:13 2015] 127.0.0.1:38148 Invalid request (Unexpected EOF)
どうやら何かしらのリクエストはサーバーへ飛んでいるが、何かがおかしいようだ。
そこで、 sudo ngrep -W byline -q -vv -d lo port 12345 などしてみたが、どうも普段使い慣れていないせいか、使い方が良くないのか何も表示されない。
そこでそこで、 sudo tcpdump -s0 -A -vv -i lo port 12345 などしてパケットを観測してみると、
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
04:46:31.379350 IP (tos 0x0, ttl 64, id 52369, offset 0, flags [DF], proto TCP (6), length 60)
    localhost.38150 > localhost.12345: Flags [S], cksum 0xfe30 (incorrect -> 0xcc73), seq 15574236, win 43690, options [mss 65495,sackOK,TS val 90531917 ecr 0,nop,wscale 7], length 0
E..<..@.@.p(..........09.............0.........
.ehM........
04:46:31.379367 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    localhost.12345 > localhost.38150: Flags [S.], cksum 0xfe30 (incorrect -> 0x841d), seq 1712288899, ack 15574237, win 43690, options [mss 65495,sackOK,TS val 90531917 ecr 90531917,nop,wscale 7], length 0
E..<..@.@.<.........09..f.t..........0.........
.ehM.ehM....
04:46:31.379376 IP (tos 0x0, ttl 64, id 52370, offset 0, flags [DF], proto TCP (6), length 52)
    localhost.38150 > localhost.12345: Flags [.], cksum 0xfe28 (incorrect -> 0x5662), seq 1, ack 1, win 342, options [nop,nop,TS val 90531917 ecr 90531917], length 0
E..4..@.@.p/..........09....f.t....V.(.....
.ehM.ehM
なるほど、わからん。
さすがにRAWパケットはhexdumpするなりしてきちんと数値をみないとよくわからないものの、少なくともHTTP Request Header を送信する以前のパケットに何らかの異常があって、TCPの3-way handshakeを完了できていないような、気がする。あくまでも気がする。
正常にTCPコネクションを確立できていれば、このあとクライアント側から HTTP Request Header が送信されるのが見えているはず。
ここで少々思案して、ふとおもむろに「おまじない」を1つ思い出したので追加してみる。
#define BOOST_NETWORK_NO_LIB
cppnetlibそのものをリンカーレス、つまりヘッダーオンリーモードで翻訳する BOOST_NETWORK_NO_LIB を定義してみた。
すると、 status() も body() も即座にreturnしてくれるようになった。
どういう事かと言うと、私は手抜きをして、 sudo apt-get install libcppnetlib-dev を使おうとしたけれど、私の環境では手ビルド版の bootst ライブラリーをデフォールト状態で include, link するようにしてあるので、 apt 版の cppnetlib のビルド時点での boost ライブラリーとは内部的に何かおかしな不整合が生じてもおかしくない。
案の定、 apt 版を apt-get purge して、 github リポジトリーを回収して(公式サイトの安定版パッケージでも同じ)、手ビルド、手導入すると、BOOST_NETWORK_NO_LIBの有無に関わらず問題なく動作した。
git clone git@github.com:cpp-netlib/cpp-netlib.git
git checkout cpp-netlib-0.11.1-final
mkdir build.clang++
cd build.clang++
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=release -DCMAKE_CXX_COMPILER=clang++
ninja
sudo ninja install
ほか -DCMAKE_INSTALL_PREFIX=$HOME などお好みで。
うっかりすると原因特定に手間取る問題で、あやうくいわゆるはまり状態になりかねないところでした。あぶないあぶない。
ちなみに、 cppnetlib の master は一般人には現在扱えない状態にあれこれとごちゃごちゃされている状態なので、githubリポジトリーから使う際には git checkout で安定リリース版にするのをお忘れなく。

0 件のコメント:

コメントを投稿