リバースプロキシでheaderを消したくてheaders-more-nginx-moduleについて調べてみた[備忘録]

OpenRestyというNginxの拡張モジュールを開発しているOSS団体が公開しているheaders-more-nginx-moduleのソースコードを読んだので、それの備忘録です。

# どういうモジュール？
https://github.com/openresty/headers-more-nginx-module/tree/master

`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`の中に以下のようにありました。
```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
};
```
では、詳細に見ていきます。一行目、
```c
static ngx_command_t  ngx_http_headers_more_filter_commands[]
```
ではngx_command_t構造体の配列を用いて、新しいディレクティブを定義しています。[公式のドキュメント](https://www.nginx.com/resources/wiki/extending/api/configuration/#ngx-command-t)にも同様の例があります。
```
   { ngx_string("more_clear_input_headers"),
```
少し飛ばしてここでは、`more_clear_input_headers` というNginxディレクティブを定義しています。

```c
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                        |NGX_CONF_1MORE
```
ここではディレクティブがどのコンテキスト(ディレクティブ)で使用できるかを定義しています。[公式のドキュメント](https://www.nginx.com/resources/wiki/extending/api/configuration/)に書いてありますが、一応抜き出しておきます。
| 引数 | 説明 |
|:-----------|:------------|
| 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を消す作業をしています。

```c
NGX_HTTP_LOC_CONF_OFFSET,
0
```
ドキュメントによると[Which location the directive’s value should be saved to](https://www.nginx.com/resources/wiki/extending/api/configuration/#c.ngx_command_t.conf)と、この0はそのoffsetだそうですが、あまり理解できませんでした....。

では続いて、このコールバック関数についてみていきます。

## ngx_http_headers_more_clear_input_headers関数について
この関数は`ngx_http_headers_more_headers_in.c`内で、以下のように定義されています。

```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`で定義されており、
```c
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;
```
と定義されています。この構造体にデータを入れているのは、その下の
```c
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数|

https://www.nginx.com/resources/wiki/extending/api/alloc/#ngx-array-create


その下に行くと、opcode(おそらくoperation codeのこと)を使用している箇所があります。
```c
     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`に関連しているのは以下のコードです。
```c
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の数なのかなぁと思いますが、正直あんまりわかりません。
```c
ngx_str_t                            value = ngx_null_string;
```
実際にヘッダーを消しているのは以下の行です。
```c
ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
```
この関数は第一引数(`&hv->value`)がデータを消す予定のメモリのポインタ、第二引数が`sizeof(ngx_http_complex_value_t)`データを消す(メモリを0にする)長さを取ります。
ここでポインターを保持している`hv`は、この関数の先頭で
```c
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を消すのって、結構大変なんすね....。

以上です。