Nginxモジュールの開発方法についてのメモ(C++)

# 概要
C++でNginx moduleを実装し、Nginxで登録したリクエストに対して「Hello World」と表示するような処理を追加したときのメモ。

# ソースコード
以下に置いておく。
https://github.com/takarin0711/nginx-hello

# 必要なファイル
nginxのモジュールを実装するには、最低限2つのファイルが必要。
* configファイル
* cppファイル

configファイルにはモジュールの概要を書き、cppファイルにはモジュール実態を書く。
その他、ヘッダファイルなどは必要に応じて用意する。

# 動作イメージ
configファイルのserverディレクティブの中に以下のように記載して、http://localhost:8080/test にアクセスするとHello WorldをHTTP Responseとして返す。

confの設定内容イメージ
```
location = /test {
   hello 'Hello World';
} 
```

curlしたときのイメージ
```
$ curl localhost:8080/test
Hello World%
```

# 実装
## configファイル

```
if test -n "$ngx_module_link"; then
    ngx_module_type=HTTP
    ngx_module_name=ngx_http_hello_module
    ngx_module_srcs="$ngx_addon_dir/ngx_http_hello_module.cpp"
    . auto/module
else
    HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.cpp"
fi
```
ngx_module_typeにはビルドするモジュールの種類を記述。
ngx_module_nameにはモジュールの名前を記述。
HTTP_MODULESには依存するモジュールの名前を記述。
ngx_module_srcs, NGIX_ADDON_SRCSにはモジュールのパスを記述。
. auto/moduleは新しいモジュールビルドシステムを呼ぶために必要。

Nginxのバージョンによってconfigファイルの書き方が異なるらしい。
上記は新しいバージョンと古いバージョンに対応している(はず)。

(新しい書き方)

https://www.nginx.com/resources/wiki/extending/new_config/

(古い書き方)

https://clavier.hatenablog.com/?page=1445436979

## cppファイル

### configファイルの定義
NginxのConfigファイルのどのディレクティブ(main, server, location)で、このモジュールの設定が定義出来るか決める。

ngx_http_<モジュール名>_(main|srv|loc)_conf_t という構造体で、ディレクティブの設定値を保存する構造体を定義する。

```
# 今回はヘッダファイルの方に定義しています
typedef struct {
    ngx_str_t name;
} ngx_http_hello_loc_conf_t;
```

ディレクティブに具体的にどんな内容を書くかは以下のように定義する。
```
static ngx_conf_post_handler_pt ngx_http_hello_p = ngx_http_hello;

static ngx_command_t ngx_http_hello_commands[] = {
    {
        ngx_string("hello"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_hello_loc_conf_t, name),
        &ngx_http_hello_p
    },
    ngx_null_command
};

static char * ngx_http_hello(ngx_conf_t *cf, void *post, void *data)
{
    ngx_http_core_loc_conf_t *clcf;

    clcf = (ngx_http_core_loc_conf_t*)ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_hello_handler;
    ngx_str_t *name = (ngx_str_t*)data; // i.e., first field of ngx_http_hello_loc_conf_t;

    if (ngx_strcmp(name->data, "") == 0) {
        return (char*)NGX_CONF_ERROR;
    }

    hello_string.data = name->data;
    hello_string.len = ngx_strlen(hello_string.data);
    return NGX_CONF_OK;
}
```
ngx_http_hello_commands[]について
* 1番目の「ngx_string("hello")」の箇所にはディレクティブ名を書く。
* 2番目の「NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1」の箇所にはフラグを指定し、ディレクティブのかかる場所をOR指定する(今回の場合はHTTPのLocationディレクティブで利用し、"hello"が引数を1つ取る)。
* 3番目の「ngx_conf_set_str_slot」はディレクティブが設定されたときに呼び出される関数へのポインタ。この関数はディレクティブの値を受け取り、モジュール内の設定データ構造を更新する役割をする。「ngx_conf_set_str_slot」は文字列をngx_str_t型の値として格納する。
* 4番目の「NGX_HTTP_LOC_CONF_OFFSET」はディレクティブが設定されたときに呼び出される関数へのポインタ。この関数は設定ファイル内のディレクティブを解析し、設定データ構造を初期化および更新するために使用される。Locationディレクティブの時にはこれを設定する。
* 5番目の「offsetof(ngx_http_hello_loc_conf_t, name)」は設定データ構造内のデータメンバーへのオフセット。これにより、ディレクティブの値が設定データ構造内の特定のメンバーに格納される。つまり、helloディレクティブの第一引数に指定した文字列が、ngx_http_hello_loc_conf_tの構造体のnameに格納される。
* 6番目の「&ngx_http_hello_p」はディレクティブが処理された後に呼び出される関数へのポインタ。この関数はディレクティブの処理後に追加の処理を行うために使用される。
* コマンドの最後は、「ngx_null_command」を指定して終了する。

ngx_http_helloについて
* ngx_conf_tはNginxの設定ファイルを解析する際に使用されるコンテキスト情報を保持する構造体で、Nginxモジュールの設定データ構造体を初期化し、設定データを取得するために必要な情報を提供する。
* ngx_http_core_loc_conf_tはNginx HTTPモジュール内で使用される設定データ構造体の1つで、ロケーションブロック内の設定情報を格納するために使用される。

### module contextの設定
configに対する処理を定義する。
ngx_http_hello_module_ctx(モジュールのコンテキスト構造体)には、モジュール固有の設定データや情報を格納する必要がある。

```
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_hello_loc_conf_t  *conf;
    conf = (ngx_http_hello_loc_conf_t*)ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }
    return conf;
}

static ngx_http_module_t ngx_http_hello_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,                          /* postconfiguration */
    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */
    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */
    ngx_http_hello_create_loc_conf, /* create location configuration */
    NULL                           /* merge location configuration */
};
```

ngx_http_hello_create_loc_confについて
* ngx_pcallocはNginxのメモリ割り当て関数の1つで、設定データ構造体やリクエストデータ構造体など、メモリを割り当てて初期化する必要があるデータ構造を作成するために使用される。これによって、メモリリークを防ぎ、データの整合性を保つ。この関数は次の2つの引数を受け取る
  * 第1引数: メモリを割り当てるために使用するメモリプールへのポインタを指定する。
  * 第2引数: 割り当てるメモリのサイズ(バイト単位)。 

ngx_http_hello_module_ctxについて
* 1番目: この関数はNginxが設定ファイルを読み込む前に実行される。モジュールの初期化と設定ファイルのパース前の処理に使用される。
* 2番目: この関数は設定ファイルのパース後、設定データ構造を初期化した後に実行される。モジュールの後処理や設定の確定に使用される。
* 3番目: この関数は、メイン設定データ構造を初期化するために使用される。メイン設定データは、すべての設定ファイルブロックで共有されるデータを格納する。
* 4番目: メイン設定データ構造の初期化と結びついており、設定ファイルブロックごとのデータの初期化を行う。
* 5番目: サーバーレベルの設定データ構造を初期化するために使用される。これは、サーバーブロックごとの設定情報を格納する。
* 6番目: サーバーレベルの設定データ構造のマージに使用され、サーバーブロック間での設定の統合を行う。
* 7番目: ロケーションレベルの設定データ構造を初期化するために使用される。これは、ロケーションブロックごとの設定情報を格納する。
* 8番目: ロケーションレベルの設定データ構造のマージに使用され、ロケーションブロック間での設定の統合を行う。

### moduleの定義

```
ngx_module_t ngx_http_hello_module = {
    NGX_MODULE_V1,
    &ngx_http_hello_module_ctx,    /* module context */
    ngx_http_hello_commands,       /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};
```

ngx_http_hello_moduleについて
* 1番目: NGX_MODULE_V1はNginxのモジュールをバージョン1で初期化するためのマクロ。
* 2番目: Nginxモジュールの設定データ構造へのポインタを指定する。
* 3番目: Nginxカスタムモジュール内で定義されたカスタムディレクティブ(設定ブロック内で使用される設定情報)を指定する。
* 4番目: Nginxモジュールの種類を指定する。NGX_HTTP_MODULEはHTTPリクエストの処理に関連するモジュールを示す。指定できるものについては (https://mogile.web.fc2.com/nginx_wiki/extending/api/main/#ngx-module-t) を参照
* 5~11番目: モジュールが初期化時に呼び出される関数へのポインタを指定する。
* 12番目: NGX_MODULE_V1_PADDINGはNginxモジュールを初期化する際に使用されるポインタの配列。

### Handlerモジュール
Handlerでは、リクエストを受けてレスポンスを返すという実質的な処理をする。

```
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r)
{
    ngx_int_t    rc;
    ngx_buf_t   *b;
    ngx_chain_t  out;
    /* we response to 'GET' and 'HEAD' requests only */
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    /* discard request body, since we don't need it here */
    rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK) {
        return rc;
    }

    /* set the 'Content-type' header */
    r->headers_out.content_type_len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";
    /* send the header only, if the request type is http 'HEAD' */
    if (r->method == NGX_HTTP_HEAD) {
        r->headers_out.status = NGX_HTTP_OK;
        r->headers_out.content_length_n = hello_string.len;

        return ngx_http_send_header(r);
    }
    /* allocate a buffer for your response body */
    b = (ngx_buf_t*)ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    /* attach this buffer to the buffer chain */
    out.buf = b;
    out.next = NULL;

    /* adjust the pointers of the buffer */
    b->pos = hello_string.data;
    b->last = hello_string.data + hello_string.len;
    b->memory = 1;    /* this buffer is in memory */
    b->last_buf = 1;  /* this is the last buffer in the buffer chain */
    /* set the status line */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = hello_string.len;
    /* send the headers of your response */
    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send the buffer chain of your response */
    return ngx_http_output_filter(r, &out);
}
```

## ビルド以降の作業
以下に記載。

https://qiita.com/takarin0711/items/6f8a984acbf6843bfb25

# おわりに
まだ詳細わかってない部分も結構あるので、随時更新していきたい。

# 参考
http://nginx.org/en/docs/dev/development_guide.html

https://clavier.hatenablog.com/?page=1445436979

https://yone098.hatenablog.com/entry/20090930/1254275423