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
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;
このモジュールコンテキストは、以下の関数リファレンス群構造体でメイン、サーバ、ロケーションの設定を作成します。
押さえておきたい構造体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_
ここには、先ほど説明した構造体モジュールコンテキストや、コマンドを指定します。
上記は、シンプルなモジュール定義です。
モジュールの作成方法
独自モジュールを作成するには、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同様、手軽にモジュール作成が出来るのが分かったところで皆さんモジュールやフィルターを作成してみてください。