C++をプログラミング入門に使うならHTTPSぐらい喋れないと話にならんと言われたのでそういうライブラリを作った
C++17の新機能を余すところなく解説した「江添亮の詳説C++17」は3月9日に発売される。今はC++によるプログラミングの入門書を書こうとしているが、同僚から、果たしてC++をプログラミング入門に用いるのは適切なのだろうかという疑問を提示された。私はC++はハードウェアに直接マッピング可能な低級機能からそれを隠匿する高級機能まで揃っている上に、継ぎ接ぎだらけの型システムは
「21世紀の入門コード片という意味では https download くらい標準で喋って欲しさ」
と言われた。なるほど、そういうのはsystem("wget -q https://example.com")すればいいのではないだろうか。
「なるほど、しかしそれではファイル経由で扱うことになる。初心者ならstd::stringとかに入った状態で扱いたいはずだ」
一理ある。そういうライブラリを実装すればいいのではないか。せっかくだからpopenを使って標準出力をパイプで受け取り、それをistreamにしてみよう。幸い、libstdc++にはstdio_filebufがあるのでFILE *からfilebufが作れる。
というわけで以下のようなライブラリを作った。
int main()
{
popen_istream ps("curl -s https://example.com") ;
std::string line ;
while (ps)
{
std::getline( ps, line ) ;
std::cout << line << '\n' ;
}
}
とりあえず動く。
「しかし、HTTPSをお話したい理由というのは、サーバーの提供するWeb APIを叩きたいためだ。そのためにはサーバーにPOSTでデータを送りつけられるとなおよい」
wgetは標準入力からPOSTで送りつけるデータの入力に対応していないが、curlは対応している。なのでパイプで標準入力にデータを流し込み、標準出力をパイプで繋げばよいのではないか。
popenは入出力双方向のパイプに対応していない。この理由は、デッドロックを起こしやすいためだ。結局、入出力のバッファリングがユーザースペースで行われている場合、実際にパイプに書き込まれるタイミングが想定と異なる場合があり、また多くの伝統的なコマンドも、標準入力をすべて読み込まないうちは出力を始めないような設計のものも多いためだ。
なので、入出力を双方向にパイプするには古典的なpipeを使ってやる必要がある。iostreamはistreamとostreamで別々のfilebufを持てるためこのような入力先と出力先が違う場合にも対応できる。istreamとostreamが別々なので、ostreamにすべて書き込んだ時点でパイプを破棄することもできる。
とここまで考えて思ったが、やはり標準に高級なネットワークライブラリがほしい。HTTPSをお話できたりブラウザー操作ができたりするようなライブラリだ。
class popen_filebuf
{
FILE * fp ;
__gnu_cxx::stdio_filebuf<char> fb ;
static FILE * init_popen( std::string_view command, )
{
std::string cmd( command ) ; // for null-terminated c_str()
FILE * result = popen( cmd.c_str(), "r" ) ;
if ( result == nullptr )
throw std::runtime_error("popen failed.") ;
return result ;
}
protected :
auto get_filebuf_ptr() noexcept
{
return &fb ;
}
public :
popen_filebuf( std::string_view command )
: fp( init_popen( command ) ),
fb( fp, std::ios_base::in )
{ }
~popen_filebuf()
{
std::fclose( fp ) ;
}
} ;
class popen_istream :
protected popen_filebuf,
public std::istream
{
public :
popen_istream( std::string_view command )
: popen_filebuf( command ),
std::istream( get_filebuf_ptr() )
{ }
} ;
std::string popen_reader( std::string_view command )
{
popen_istream ps( command ) ;
std::istreambuf_iterator<char> iter( ps.rdbuf() ), end ;
std::string buf( iter, end ) ;
return buf ;
}