OpenRestyというNginxの拡張モジュールを開発しているOSS団体が公開しているheaders-more-nginx-moduleのソースコードを読んだので、それの備忘録です。
どういうモジュール?
ngx_headers_more - Set and clear input and output headers...more than "add"!という、nginxがhandleするhttp requestのheaderをアレコレできるnginx用moduleです。
ライセンスはBSDです。
なぜ調べた?
ひょんなことから、Nignx製のリバースプロキシで特定のheaderを削除したいな~ということがあり、どのように実現しているのかを知りたかったからです。
で、見てみると結構コンパクトで、勉強に良さそうだったので見てみるか~となりました。
本編
ソースコードの構成
src/
├ ddebug.hh
├ ngx_http_headers_more_filter_module.c
├ ngx_http_headers_more_filter_module.h
├ ngx_http_headers_more_headers_in.c
├ ngx_http_headers_more_headers_in.h
├ ngx_http_headers_more_headers_out.c
├ ngx_http_headers_more_headers_out.h
├ ngx_http_headers_more_utils.c
└ ngx_http_headers_more_utils.h
ngx_http_headers_more_headers_inはNginxに入ってくるhttp関連、ngx_http_headers_more_headers_outは出ていくhttp関連です。moduleとして取りまとめているのがngx_http_headers_more_filter_moduleです。
more_clear_input_headers の挙動を調べる
ディレクティブの追加
今回自分が知りたいのは、http requestからheaderを削除する挙動です。
なので、そこに絞ってコードを読んでいきたいと思います。
まず、more_clear_input_headersがどこにあるのかを調べると、ngx_http_headers_more_filter_module.cの中に以下のようにありました。
static ngx_command_t ngx_http_headers_more_filter_commands[] = {
~~中略~~
{ ngx_string("more_clear_input_headers"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|NGX_CONF_1MORE,
ngx_http_headers_more_clear_input_headers,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL},
ngx_null_command
};
では、詳細に見ていきます。一行目、
static ngx_command_t ngx_http_headers_more_filter_commands[]
ではngx_command_t構造体の配列を用いて、新しいディレクティブを定義しています。公式のドキュメントにも同様の例があります。
{ ngx_string("more_clear_input_headers"),
少し飛ばしてここでは、more_clear_input_headers というNginxディレクティブを定義しています。
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
|NGX_CONF_1MORE
ここではディレクティブがどのコンテキスト(ディレクティブ)で使用できるかを定義しています。公式のドキュメントに書いてありますが、一応抜き出しておきます。
| 引数 | 説明 |
|---|---|
| NGX_HTTP_MAIN_CONF | HTTP directive で利用可能。 |
| NGX_HTTP_SRV_CONF | server directiveで利用可能 |
| NGX_HTTP_LOC_CONF | location derectiveで利用可能。 |
| NGX_HTTP_LIF_CONF | location block if statements で利用可能。 |
| NGX_CONF_1MORE | このディレクティブは一つ以上の引数を取る |
ngx_http_headers_more_clear_input_headers,
これがこのディレクティブが呼ばれた時に実行されるcallback関数です。
つまり、この関数がheaderを消す作業をしています。
NGX_HTTP_LOC_CONF_OFFSET,
0
ドキュメントによるとWhich location the directive’s value should be saved toと、この0はそのoffsetだそうですが、あまり理解できませんでした....。
では続いて、このコールバック関数についてみていきます。
ngx_http_headers_more_clear_input_headers関数について
この関数はngx_http_headers_more_headers_in.c内で、以下のように定義されています。
char *
ngx_http_headers_more_clear_input_headers(ngx_conf_t *cf,
ngx_command_t *cmd, void *conf)
{
return ngx_http_headers_more_parse_directive(cf, cmd, conf,
ngx_http_headers_more_opcode_clear);
}
この関数の引数のngx_conf?tはNginx configの構造体で、ngx_command_tは先ほども登場したディレクティブの構造体です。
これを見ると、ngx_http_headers_more_parse_directive関数を実行しており、その引数にNginxのconfig、directive、ngx_http_headers_more_opcode_clearという意味ありげな引数を渡しています。
これを調べてみます。
ngx_http_headers_more_parse_directive関数について
この関数は同じファイルの下の方で定義されています。長いのでここに張り付ける事はしませんが、要素だけかいつ間みたいと思います。
ここで大事になってくるのが以下の構造体です。
ngx_http_headers_more_cmd_t *cmd;
これまでのコードの中でcmdとはNginxの提供するngx_command_t構造体でしたが、これ以降はこのモジュール特有の構造体に代わり、これまでのcmdはngx_cmdになります。この構造体はngx_http_headers_more_filter_module.hで定義されており、
typedef struct {
ngx_array_t *types; /* of ngx_str_t */
ngx_array_t *statuses; /* of ngx_uint_t */
ngx_array_t *headers; /* of ngx_http_header_val_t */
ngx_flag_t is_input;
} ngx_http_headers_more_cmd_t;
と定義されています。この構造体にデータを入れているのは、その下の
cmd->headers = ngx_array_create(cf->pool, 1, sizeof(ngx_http_headers_more_header_val_t));
です。ngx_array_create関数は、Nginxのメモリプール内でメモリのアロケーションを行います。
| 引数 | 説明 |
|---|---|
| cf->pool | 使用するメモリプール |
| 1 | 配列の要素数 |
| sizeof(ngx_http_headers_more_header_val_t) | 要素のbytes数 |
その下に行くと、opcode(おそらくoperation codeのこと)を使用している箇所があります。
if (arg[i].data[0] != '-') {
rc = ngx_http_headers_more_parse_header(cf, cmd_name,
&arg[i], cmd->headers,
opcode, ngx_http_headers_more_set_handlers);
ここで、また別のngx_http_headers_more_parse_headerという関数を呼び、opcodeやheaderなど関係ありそうな引数を渡しています。
ngx_http_headers_more_parse_header関数について
こちらも長いので全てを記載することはしませんが、その中でngx_http_headers_more_opcode_clearに関連しているのは以下のコードです。
if (opcode == ngx_http_headers_more_opcode_clear) {
value.len = 0;
}
if (value.len == 0) {
ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
return NGX_OK;
}
気になるのはこのvalueという変数ですが、これは関数の先頭で以下のように定義されて、この関数を通して利用されています。おそらくheaderの数なのかなぁと思いますが、正直あんまりわかりません。
ngx_str_t value = ngx_null_string;
実際にヘッダーを消しているのは以下の行です。
ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
この関数は第一引数(&hv->value)がデータを消す予定のメモリのポインタ、第二引数がsizeof(ngx_http_complex_value_t)データを消す(メモリを0にする)長さを取ります。
ここでポインターを保持しているhvは、この関数の先頭で
ngx_http_headers_more_parse_header(ngx_conf_t *cf, ngx_str_t *cmd_name,
ngx_str_t *raw_header, ngx_array_t *headers,
ngx_http_headers_more_opcode_t opcode,
ngx_http_headers_more_set_header_t *handlers)
{
ngx_http_headers_more_header_val_t *hv;
ngx_uint_t i;
ngx_str_t key = ngx_null_string;
ngx_str_t value = ngx_null_string;
ngx_flag_t seen_end_of_key;
ngx_http_compile_complex_value_t ccv;
u_char *p;
hv = ngx_array_push(headers);
if (hv == NULL) {
return NGX_ERROR;
}
として定義されており、ngx_http_headers_more_cmd_tからヘッダーの情報だけを抜き出しているように見えます。
このようにしてheaderの情報を消すようです。見ている限り、すべてのheaderを強制的に消してそうです。特定のheaderだけを消すことはできないような気がしますが、どうなんでしょう....。
http requestからheaderを消すのって、結構大変なんすね....。
以上です。