Qiitaの中の人も楽しみにしているらしい本連載ですが、今回からnginxが持つ内部のAPIについて解説していきます。
このあたりの知識はnginxの内部実装を理解するだけでなく、拡張モジュールを開発するのにも役立つと思います。
前回
nginxソースコードリーディング その2〜イベント駆動エンジン〜
リビジョン
5428:fcecb9c6a057
Cにおけるデータ型とアルゴリズム
PythonやRubyといったLLとは違い、Cは文字列、リスト、ハッシュテーブル、キューといった非常によく使う基本的なデータ構造やアルゴリズムの実装を処理系に含んでいない。
かといってどこでも使える汎用的なライブラリというのもこれまた難しいので、Cで書かれているソフトウェアは各々独自の実装を持っていることが非常によくある。僕自身何度連結リストをCで実装したかわからない。
nginxが持つデータ型
nginxもこの例に漏れず、独自に実装された様々なデータ型やアルゴリズムの実装を持っている。ぱっと思いつくものだけでこれだけある。
- 文字列
- メモリプール
- リスト
- ハッシュテーブル
- 赤黒木
- キュー
全部書いてたらキリがないけど、少なくともハッシュテーブルまではやろうと思う。というわけで今回は文字列。
ngx_str_t
nginxの文字列型は以下のように定義されている。
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
lenが文字列の長さ、dataが文字列へのポインタになる。いわゆる長さ付き文字列である。文字列リテラルを使ってngx_str_tの変数を初期化するには以下のマクロを使うと便利。
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
#define ngx_null_string { 0, NULL }
#define ngx_str_set(str, text) \
(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
すべてのAPIがngx_str_tを使っているわけではなく、単に標準ライブラリ関数をラップしているだけのものもある。
#define ngx_strstr(s1, s2) strstr((const char *) s1, (const char *) s2)
#define ngx_strlen(s) strlen((const char *) s)
ngx_str_tのデータを扱う際の注意点
ngx_str_tが長さ付き文字列型であることは説明したが、それを踏まえた上でこのデータ型を扱う際の注意点について解説する。(過去に何度かハマった)
例えばnginxの以下のロケーションにアクセスするとしよう。
location /hello {
curlで以下のURLにアクセスする。
curl http://localhost:8000/hello
この場合URIは/helloになる。URIはngx_http_request_sのメンバでngx_str_t型で定義されている。
struct ngx_http_request_s {
(省略)
ngx_str_t uri;
(省略)
}
nginxの中でprintfなどでURIの中身を見てみる。
printf("uri:%s\n", r->uri.data);
するとこうなる。
/hello HTTP/1.1
User-Agent
何故か余計なものがくっついてくる。試しに長さも出力してみる。
printf("%s:%zd\n", r->uri.data, r->uri.len);
長さは合ってる。
/hello HTTP/1.1
User-Agent
, 6
つまり、ngx_str_t型のデータは文字列の長さ+1のところで0終端されているとは限らない。
だからngx_str_tのdataポインタに対してstrlenみたいな関数を呼ばないようにしよう。
また、この前提は他の文字列APIを使う上でも重要である。
例えばngx_pstrdupというメモリプールから必要な確保を確保して文字列データのコピーをつくる関数がある。
ngx_str_t ns = ngx_string("bokko");
u_char *ss = ngx_pstrdup(pool, &ns);
この関数の実装は以下のようになっている。
u_char *
ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src)
{
u_char *dst;
dst = ngx_pnalloc(pool, src->len);
if (dst == NULL) {
return NULL;
}
ngx_memcpy(dst, src->data, src->len);
return dst;
}
返す文字列は0終端していない。というか0終端用の領域(最後の1バイト)すら確保していない。
なのでこの関数の以下のように別のngx_str_tのメンバに代入する形でだけ使うようにしないと思わぬ事故に見舞われる。(実際のコードから抜粋)
conf->server_name.len = name.len;
conf->server_name.data = ngx_pstrdup(cf->pool, &name);