serf(HTTPクライアントライブラリ)のAPI
以前 、 serf のAPIが変態すぎると書きました。まあ、変態とまでは言っていませんが。
serf はHTTPのクライアント側の機能を提供するライブラリです。ApacheとGoogleの両方で顔が効く人が作っているので、Apache方面で名前が時々でます。次期Apache(Webサーバ)にもコードが一部取り込まれるかもと言われています。
HTTPクライアントライブラリのコードがなぜWebサーバ(HTTPサーバ機能)に?と疑問に思うかもしれません。
ひとつはApacheのHTTPプロキシ機能への取り込みです。HTTPプロキシはHTTPのサーバであると同時にクライアント機能も持つので、ここにserfが直接影響を与える可能性があります。もうひとつがバッファ管理周りのコードです。ネットワーク処理を効率的に行うには受信データや送信データのバッファを効率的に管理する必要があります。特にApacheのようにモジュール間をデータが流れていく構成の場合、無駄なメモリコピーを避けることと、メモリリークの回避を両立するには職人芸的な技が必要です。メモリコピーの無駄を気にせずメモリ解放もGC任せのプログラミングとは住む世界が違います。ハードボイルドな世界です。
serf は、バッファ管理が優れていると主張しています。以下がそれを説明した図です。
...ハードボイルドです。
以下はserfを使ったサンプルコードです。serfに付属のテストコードを簡略化して日本語コメントをつけました。
長いです。これでも(ほぼ)最小限のサンプルコードです。HTTPのサーバコードがこれぐらいの長さならまだ許容できますが、HTTPのクライアントライブラリを利用するコードがこんなに長いのは信じれません。だって、これでやっていることはHTTPのGETリクエストを投げてレスポンスを受信しているだけです。もっと複雑なPOSTリクエストを送信したり、レスポンスデータのパースが必要であれば、更にコードを書き足す必要があります。これらの複雑な処理が無いにも関わらず、この長さです。
個人的にコールバック関数を使うAPIは嫌いではありませんが、これはやりすぎな気がします。
それからバッファ管理が(額面どおり)優れているとしても、こんなにライブラリ利用者に負担を押し付けるのはどうかと思います。
// serfのサンプルコード
#include <stdio.h>
#include <stdlib.h>
#include <apr.h>
#include <apr_uri.h>
#include <apr_strings.h>
#include <apr_atomic.h>
#include <apr_base64.h>
#include <apr_getopt.h>
#include <apr_version.h>
#include <serf.h>
// テスト用にグローバル変数にしていますが、コールバック関数に渡すオブジェクト(serfではbatonと呼んでいます。APIの実装上はvoidポインタ)で渡すべき
apr_pool_t *mp;
serf_bucket_alloc_t *bkt_alloc;
// HTTP接続を切った時に呼ばれるコールバック関数(なくてもよい)
static void closed_connection(serf_connection_t *conn,
void *closed_baton,
apr_status_t why,
apr_pool_t *pool)
{
if (why) {
exit(1);
}
}
// HTTP接続処理準備のコールバック関数(これの必要性がいまいち不明)
static serf_bucket_t* conn_setup(apr_socket_t *skt,
void *setup_baton,
apr_pool_t *pool)
{
serf_bucket_t *c = serf_bucket_socket_create(skt, bkt_alloc);
return c;
}
// HTTPレスポンスを受信した時のコールバック関数
static serf_bucket_t* accept_response(serf_request_t *request,
serf_bucket_t *stream,
void *acceptor_baton,
apr_pool_t *pool)
{
/* get the per-request bucket allocator */
serf_bucket_alloc_t* bkt_alloc = serf_request_get_alloc(request);
/* Create a barrier so the response doesn't eat us! */
serf_bucket_t* c = serf_bucket_barrier_create(stream, bkt_alloc);
return serf_bucket_response_create(c, bkt_alloc);
}
// HTTPレスポンスデータ(レスポンスボディ)の受信処理ののコールバック関数
// レスポンスヘッダ受信処理は完了済み
static apr_status_t handle_response(serf_request_t *request,
serf_bucket_t *response,
void *handler_baton,
apr_pool_t *pool)
{
const char *data;
apr_size_t len;
serf_status_line sl;
apr_status_t status;
status = serf_bucket_response_status(response, &sl);
if (status) {
if (APR_STATUS_IS_EAGAIN(status)) {
return status;
}
exit(1);
}
while (1) {
// レスポンスボディの受信
status = serf_bucket_read(response, 2048, &data, &len);
if (SERF_BUCKET_READ_ERROR(status))
return status;
// デバッグのために標準出力へ出力
fwrite(data, 1, len, stdout);
// 受信終了
if (APR_STATUS_IS_EOF(status)) {
if (1) {
// (おまけ)レスポンスヘッダの処理例
serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
const char *val = serf_bucket_headers_get(hdrs, "Content-Type");
}
// 接続終了をなんらかのフラグでメインループに返す処理を書く(通常はbatonで渡ってきたオブジェクトにフラグを持たせる)
return APR_EOF;
}
// ノンブロッキングI/Oなので受信データがなければメインループに戻る
if (APR_STATUS_IS_EAGAIN(status))
return status;
}
/* NOTREACHED */
}
// HTTPリクエスト処理のためのコールバック関数
static apr_status_t setup_request(serf_request_t *request,
void *setup_baton,
serf_bucket_t **req_bkt,
serf_response_acceptor_t *acceptor,
void **acceptor_baton,
serf_response_handler_t *handler,
void **handler_baton,
apr_pool_t *pool)
{
serf_bucket_t *hdrs_bkt;
// リクエストURLのメソッドとパスの指定
*req_bkt = serf_bucket_request_create("GET", "/", NULL,
serf_request_get_alloc(request));
// 必要ならリクエストヘッダ指定
hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
serf_bucket_headers_setn(hdrs_bkt, "Host", "127.0.0.1");
*acceptor = accept_response; // コールバック関数
*acceptor_baton = NULL;
*handler = handle_response; // コールバック関数
*handler_baton = NULL;
return APR_SUCCESS;
}
int main(int argc, const char** argv)
{
apr_sockaddr_t *address;
serf_context_t *context;
serf_connection_t *connection;
serf_request_t *request;
// 初期化処理
apr_initialize();
atexit(apr_terminate);
apr_pool_create(&mp, NULL);
bkt_alloc = serf_bucket_allocator_create(mp, NULL, NULL);
context = serf_context_create(mp);
// 接続先サーバは 127.0.0.1 のポート80番(URLのパス指定はsetup_request内)
apr_sockaddr_info_get(&address, "127.0.0.1", APR_UNSPEC, 80, 0, mp);
// コールバック関数の指定は、関数とbaton(関数に渡すオブジェクト)のペアを指定する
connection = serf_connection_create(context, address,
conn_setup, NULL, // コールバック関数(必須)
closed_connection, NULL, // コールバック関数(なくてもよい)
mp);
request = serf_connection_request_create(connection,
setup_request, NULL); // コールバック関数(必須)
while (1) {
// 以下から状態に応じて以下のようにコールバック関数が呼ばれます
// conn_setupコールバック
// setup_requestコールバック
// serf_context_runがリターン
// サーバへ接続
// accept_responseコールバック
// サーバからレスポンス受信
// handle_responseコールバック
// EOF(受信終了)もしくは受信エラーまでhandle_responseコールバックを繰り返す
// serf_context_runがリターン
apr_status_t status = serf_context_run(context, SERF_DURATION_FOREVER, mp);
if (APR_STATUS_IS_TIMEUP(status)) {
continue;
}
if (status) {
printf("error %d\n", status);
break;
}
// handle_responseの中でEOFの時に設定したフラグをチェックして受信終了であればbreakする処理を書く
/* Debugging purposes only! */
serf_debug__closed_conn(bkt_alloc);
}
serf_connection_close(connection);
apr_pool_destroy(mp);
return 0;
}
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/serf-api/tbping