NginxでのModuleの作り方

Apacheモジュール作成は以前のエントリの通り手軽に出来ます。

Apacheモジュールの作成とgdbとloggerでのデバッグ方法 - よねのはてな

今回は、Nginxでモジュール作成してみたいという人向けです。

Nginxにおける処理の流れと押さえておきたい構造体、モジュール作成方法をのせておきます。

Nginx

http://nginx.net/
そもそもNginxってなんだ?という人は軽量超高速なHTTPサーバという理解でOKです。
実際にはReverse Proxy、Mail Proxyとしても使用可能で、ライセンスはNSD系。

Nginxについては以下を参照下さい。

Nginxの処理の流れ

おおまかなNginxの処理の流れは以下の通り。

1.HTTP要求をクライアントから受け取る
2.locationの設定にあったHandlerを選択(設定ファイル : nginx.conf)
3.設定があれば、load-balancerはバックエンドのサーバを選択
4.Handlerは、自信の処理を行い最初のフィルタに出力バッファを渡す
5.次のフィルタがあれば次のフィルタに出力バッファを渡す
6.更に次のフィルタがあれば次のフィルタに出力バッファを渡す(繰り返し)
7.クライアントにレスポンスを返す

押さえておきたい構造体1

とりあえず押さえておくべき構造体を3つ。

ngx_command_s(core/ngx_conf_file.h)
struct ngx_command_s {
    ngx_str_t     name;
    ngx_uint_t    type;
    char          *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t    conf;
    ngx_uint_t    offset;
    void          *post;
};
各要素

name要素はディレクティブ指定(スペースは無し)。型はngx_str_tでNginxでは文字列は基本的にこの構造体とngx_stringを使用。

  • 例:ngx_string("yone098")

type要素にはフラグを指定し、ディレクティブのかかる場所をOR指定します

  • 例:NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS
  • NGX_HTTP_MAIN_CONF:ディレクティブは、メインの設定で有効
  • NGX_HTTP_SRV_CONF:ディレクティブは、サーバの設定で有効
  • NGX_HTTP_LOC_CONF:ディレクティブのlocation設定で有効
  • NGX_HTTP_UPS_CONF:ディレクティブ上流設定で有効
  • NGX_CONF_NOARGS:ディレクティブは引数無し
  • NGX_CONF_TAKE1:ディレクティブは引数1個
  • NGX_CONF_TAKE2:ディレクティブは引数2個

set要素は、モジュール構成を設定するための関数ポインタ。このセットアップ関数は、ディレクティブが検出されるとコールされ、以下の3つの引数を取ります。

  • ngx_conf_t構造体ポインタ
  • 現在の、ngx_command_t構造体ポインタ
  • モジュールのカスタム設定構造体ポインタ

conf、offset要素は、メインの設定、サーバ設定、ロケーション設定を保存。
(NGX_HTTP_MAIN_CONF_OFFSET, NGX_HTTP_SRV_CONF_OFFSET、NGX_HTTP_LOC_CONF_OFFSET)

post要素は、ほとんどの場合NULL指定。


コマンドの最後は、ngx_null_commandを指定して終了。

押さえておきたい構造体2

ngx_http_module_t(http/ngx_http_config.h)
typedef struct {
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);

    void       *(*create_main_conf)(ngx_conf_t *cf);
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    void       *(*create_srv_conf)(ngx_conf_t *cf);
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);

    void       *(*create_loc_conf)(ngx_conf_t *cf);
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

このモジュールコンテキストは、以下の関数リファレンス群構造体でメイン、サーバ、ロケーションの設定を作成します。

  • preconfiguration:事前設定
  • postconfiguration:設定後
  • create_main_conf:メインの設定の作成(mallocとdefault設定)
  • init_main_conf:メインの設定初期処理(nginx.confのデフォルト値の上書き)
  • create_srv_conf:サーバ設定の作成
  • merge_srv_conf:サーバ設定のマージ
  • create_loc_conf:ロケーション設定の作成
  • merge_loc_conf:ロケーション設定のマージ

押さえておきたい構造体3

ngx_module_t(core/hgx_conf_file.h)
ngx_module_t  ngx_http_<module name>_module = {
    NGX_MODULE_V1,
    &ngx_http_<module name>_module_ctx, /* module context */
    ngx_http_<module name>_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__moduleとします。
ここには、先ほど説明した構造体モジュールコンテキストや、コマンドを指定します。
上記は、シンプルなモジュール定義です。

モジュールの作成方法

独自モジュールを作成するには、2つのファイルを作成するだけです。
今回は、Hello Worldを出力するモジュールを作ります(同一フォルダに2ファイルを作成)

  • config
  • ngx_http__module.c
configファイル

単純なconfigファイル

ngx_addon_name=ngx_http_yone098_module
HTTP_MODULES="$HTTP_MODULES ngx_http_yone098_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_yone098_module.c"


ライブラリを使用するモジュールの場合、例えばMagicWandを使う場合、includeやライブラリ指定を行う。

ngx_feature="MagickWand"
ngx_feature_name=
ngx_feature_run=no
ngx_feature_incs="#include <wand/magick-wand.h>"
ngx_feature_path=
ngx_feature_libs=-lWand
ngx_feature_test="MagickWandGenesis()"
. auto/feature

if [ $ngx_found = yes ]; then
    ngx_addon_name=ngx_http_circle_gif_module
    HTTP_MODULES="$HTTP_MODULES ngx_http_circle_gif_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_circle_gif_module.c"
    CORE_LIBS="$CORE_LIBS -lWand"
else
    cat << END
$0: error: the Circle GIF addon requires the ImageMagick library.
END
    exit 1
fi

実際のモジュールのコード

hgx_http_yone098_module.c
/**
 * @author yone098
 */
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

typedef struct {
    ngx_flag_t           enable;
} ngx_http_yone098_loc_conf_t;


static char* ngx_http_yone098(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static void* ngx_http_yone098_create_loc_conf(ngx_conf_t *cf);

static ngx_command_t  ngx_http_yone098_commands[] = {
    { ngx_string("yone098"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_yone098,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

      ngx_null_command
};

static ngx_http_module_t  ngx_http_yone098_module_ctx = {
    NULL,  /* preconfiguration */
    NULL,  /* postconfiguration */

    NULL,  /* create main configuration */
    NULL,  /* init main configuration */

    NULL,  /* create server configuration */
    NULL,  /* merge server configuration */

    ngx_http_yone098_create_loc_conf,  /* create location configuration */
    NULL
};

ngx_module_t  ngx_http_yone098_module = {
    NGX_MODULE_V1,
    &ngx_http_yone098_module_ctx, /* module context */
    ngx_http_yone098_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
};

static void *
ngx_http_yone098_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_yone098_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_yone098_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    return conf;
}

static ngx_int_t
ngx_http_yone098_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;
    unsigned char *buff;
    char yone[124] = "Hello World!";
    int l = strlen(yone);

    ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "***Call Handler***");
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    rc = ngx_http_discard_request_body(r);
    if (rc != NGX_OK && rc != NGX_AGAIN) {
        return rc;
    }
    if (r->headers_in.if_modified_since) {
        return NGX_HTTP_NOT_MODIFIED;
    }

    // header設定
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = l;
    if (r->method == NGX_HTTP_HEAD) {
        rc = ngx_http_send_header(r);
        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
            return rc;
        }
    }

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    out.buf = b;
    out.next = NULL;
    buff = ngx_palloc(r->pool, l);
    if (buff == NULL) {
      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate memory.");
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    ngx_memcpy(buff, yone, l);

    b->pos = buff;
    b->last = buff + l;
    b->memory = 1;
    b->last_buf = 1;

    rc = ngx_http_send_header(r);
    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }
    ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Success! yone098");

    return ngx_http_output_filter(r, &out);
}

static char *
ngx_http_yone098(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    // location設定を取得しHandler設定
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_yone098_handler;

    return NGX_CONF_OK;
}

command_tのset要素に、ngx_http_yone098を設定しngx_http_yone098の処理においてlocation設定を取得しハンドラーを設定します。
ハンドラーの中では、リクエストを解析したり(ngx_http_request_t)、レスポンスを設定します。

configure

configファイルとモジュールがあるフォルダを指定して add-module指定します。

./configure --add-module=/home/yone/ngx_module_sample
make
sudo make install
nginx.conf

/yone098のリクエストに対してのyone098モジュール設定をします。

  location /yone098 {
    yone098;
  }

Nginxを起動すると、http://server/yone098 にアクセスするとHello Worldが表示されます。

開発時にデバッグするには

ngx_log_error関数を使って、デバッグ出力します。
第一引数に、指定

ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "test");

第一引数には、core/ngx_log.hにあるとおり以下が指定可能です。

#define NGX_LOG_STDERR            0
#define NGX_LOG_EMERG             1
#define NGX_LOG_ALERT             2
#define NGX_LOG_CRIT              3
#define NGX_LOG_ERR               4
#define NGX_LOG_WARN              5
#define NGX_LOG_NOTICE            6
#define NGX_LOG_INFO              7
#define NGX_LOG_DEBUG             8


NgixでもApache同様、手軽にモジュール作成が出来るのが分かったところで皆さんモジュールやフィルターを作成してみてください。