cURLpp

cURLというプログラムがある.コマンド名は小文字のcurlでRIA用言語のcurlと紛らわしいのだが,こちらはwgetのようなプログラム.要するにコマンドラインからwebコンテンツにアクセスするツールなのだが,拡張可能なようでさまざまなプロトコルに対応している.このcURLはlibcurlというライブラリを使って書かれており,libcurlのAPIも公開されていて様々な言語バインディングが作成されている.

cURLppはこの一つでc++バインディングを提供している.C++からは,当然cURLのC APIを直接使うこともできるのだけど,例外処理などが整理されていて使いやすそうなのでちょっといじってみた.

ファイルのダウンロード

下記は第一引数にURLを指定してダウンロードするサンプル.基本的に

  • Easy というクラスを作る
  • Easy にオプションをどんどん追加していく
  • perform()を呼び出す

という構造になっているのが分かる.

try直後のCleanupは,cURL全体の初期化と後始末をするオブジェクト.コンストラクタで初期化を,デストラクタで後始末をしてくれるので,これをmainで定義しておけば自動的に初期化と後始末が行われる,というわけ.

#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Options.hpp>

int main(int argc, const char ** argv)
{
  try {
    cURLpp::Cleanup myCleanup;

    cURLpp::Easy myRequest;
    using namespace cURLpp::Options;
    // 出力先の指定
    myRequest.setOpt(new WriteStream(& std::cout));
    myRequest.setOpt(new Url(argv[1]));
    myRequest.perform();

  } catch( cURLpp::RuntimeError &e ) {
    std::cout << e.what() << std::endl;
  } catch( cURLpp::LogicError &e ) {
    std::cout << e.what() << std::endl;
  }
  return 0;
}
出力先の切り替え

上のコードでは出力先にcoutを指定している.実はデフォルトでcoutに出力されるのでこのコードは不要.出力先を切り替えるには,WriteStreamの代わりにWriteFunctionを使う.読み出したデータの処理をある関数にさせるには下記のようにする.まずコールバック関数を定義しておく.このサンプルでは結局ファイルディスクリプタ1に書き出しているので結果は同じ.実際に書き出したサイズを返しているのに注意.

size_t callback(char * ptr, size_t size, size_t nmemb) {
    write(1, ptr, size * nmemb);
    return size * nmemb;
}

これをWriteFunctionに登録する.

myRequest.setOpt(
  new WriteFunction(
    cURLpp::Types::WriteFunctionFunctor(callback)));
オブジェクトメドッドで出力を処理

関数ではなく,オブジェクトのメソッドで出力を処理することもできる.至れり尽くせりだ.まず,コールバック用メソッドを持つクラスを定義.

class FObj {
    int fd;
public:
    FObj(int fd):fd(fd){}
    size_t callback(char * ptr, size_t size, size_t nmemb) {
        write(fd, ptr, size * nmemb);
        return size * nmemb;
    }
};

で,これをutilspp::make_functor という関数を介して登録する.

FObj o(1);
myRequest.setOpt(
  new WriteFunction(
    cURLpp::Types::WriteFunctionFunctor(
      utilspp::make_functor(&o, &FObj::callback))));

ファイルのアップロード

POSTによるファイルのアップロードも簡単.コア部分のコードを示す.

void uploadFile(const char * url, const char * filename) {
  cURLpp::Easy request;

  // ファイルサイズの取得
  struct stat st;
  if (stat(filename, &st) < 0){
     perror("fstat");
     exit(1);
  }

  // ヘッダの作成.ヘッダはstd::list<std::string> として作る.
  char buf [100];
  std::list< std::string > headers;
  headers.push_back("Content-Type: text/*");
  sprintf(buf, "Content-Length: %d", st.st_size);
  headers.push_back(buf);

  using namespace cURLpp::Options;
  // file の中身をifstreamとしてcURLに渡す
  request.setOpt(new ReadStream(new std::ifstream(filename)));
  request.setOpt(new InfileSize(st.st_size));
  request.setOpt(new Upload(true));
  request.setOpt(new HttpHeader(headers));
  // defaultは1.1.1.0にするとExpect: 100-continueが出なくなる
  request.setOpt(new HttpVersion(1.0));
  request.setOpt(new Url(url));

  request.perform();
}

所感

すごくよくできている.本当はC++の標準ライブラリにこのレベルのライブラリが含まれているといいとおもうのだけど.

使い勝手の点では,ちょっと面倒なことが出てきそう.データの読み書きはcURLppが主体になってコールバックを読み出すスタイルなので,プログラム本体のほうが主体的にread/writeをするスタイルだったばあいに,両者を橋渡しするモジュールが必要になりそうだ.ちょっと面倒くさそうだ.