読者です 読者をやめる 読者になる 読者になる

Nginxのモジュールの作り方 C言語でHandlers編

概要

モジュールの種類

Nginxで開発できるmoduleの種類は、ざっくりとわけると以下の3種類があります。

  • Handlers・・・locationを指定して処理が可能
  • Load balancers・・・upstream moduleやload balancer moduleと連携して使う。load balancer moduleにはRound RobinとIP Hashがbuild-inされているけど、それ以外の分散方法として使うことが出来る
  • Filters・・・Handlerのレスポンスに対して処理を行う。例えばgzip filterはresponseをgzip圧縮する。

今回対象とするのはHandlersをC言語で実装することにします。

必要となるファイル

特定パスにアクセスしたら『hello world』と出力するようなモジュールを作ってみます。
stub_status moduleのイケてない版のようのモジュールです。
nginxのモジュールを実装するには、最低限2つのファイルが必要になります。

  • config
  • ngx_http_hello_module.c

configファイルにはモジュールの概要を書き、モジュール実態はngx_http_hello_module.cになります。

動作イメージ

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

location /hello/ {
   hello 'Hello World';
} 

実装

config

configファイルにはモジュールの概要や説明などを書きます。

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

ngx_addon_nameにはモジュールの名前を書きます。
HTTP_MODULESには依存するモジュールの名前を書きます。
NGIX_ADDON_SRCSにはモジュールのパスを書きます。

HTTP_MODULESの他にどんなModuleがあるかは、auto/modulesのスクリプトを見るとわかります。
1.9.2では以下のように書かれています。

459     modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
460              $HTTP_HEADERS_FILTER_MODULE \
461              $HTTP_AUX_FILTER_MODULES \
462              $HTTP_COPY_FILTER_MODULE \
463              $HTTP_RANGE_BODY_FILTER_MODULE \
464              $HTTP_NOT_MODIFIED_FILTER_MODULE"

module.c

コンフィグファイルの定義

NginxのConfigファイルのどのディテクティブ(main, server, location)で、このモジュールの設定が定義出来るか決めます。
ngx_http<モジュール名>(main|srv|loc)_conf_t という構造体で、ディレクティブの設定値を保存する構造体を定義します。

その他のngx_http_core_loc_conf_tなどの定義は、src/core/ngx_config.h にある。
ngx_str_t のような型は、src/core/nginx_config.h にある。

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
};

このモジュールを利用するには、コンフィグファイル内にhelloというKeyを定義するのが一番最初の『ngx_string("hello")』です。
次の『NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1』は、HTTPのLocationディテクティブで利用し、"hello"が引数を1つ取るということです。
引数の個数についてはsrc/core/ngx_conf_file.h に、コンフィグファイルのどのディテクティブに定義を表するかは src/http/ngx_http_config.h に定義されています。
3番目の『ngx_conf_set_str_slot』引数を取る場合の型を定義するお作法として、関数ポインタを記載します。ここで記載出来る関数名はsrc/core/ngx_conf_file.h の328行目から340行目までに定義されています。
4番目の 『NGX_HTTP_LOC_CONF_OFFSET』はLocationディテクティブの時にはこれを設定します。src/http/ngx_http_config.hの50〜52にsrv、mainの時に設定する関数が定義されています。
5番目では、ngx_http_hello_loc_conf_t->nameに設定
ngx_null_commandは、配列の最後を表します。モジュールとしてhello以外Keyが存在する場合は、ngx_null_commandの前に同様の定義をします。

module contextの設定

ngx_httpmodule_ctxという名前で、下記のコメントのようなコンフィグに対する処理を定義します。
ngx_http_hello_create_loc_conf関数で、コンフィグの初期化をしていますが、複数のlocationに対応するような場合にはngx_httpmerge_loc_confというような名前にする必要があります。

static ngx_http_module_t ngx_http_hello_module_ctx = {
  NULL,                          /* pre configuration */
  NULL,                          /* post configuration */
  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 */
};

/* The function which initializes memory for the module configuration structure
*/
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
  ngx_http_hello_loc_conf_t  *conf;
  conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
  if (conf == NULL) {
   return NULL;
  }
  return conf;
}

moduleの定義

NGX_MODULE_V1とNGX_MODULE_V1_PADDINGで挟んだ内容がモジュールの定義(ngx_module_t)になります。
それぞれの処理に合わせてコールバック処理登録を登録します。

/*
 * The module which binds the context and commands
 *
 */
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(CORE, MAIL, EVENT, ...) */
     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
};

Handler

Handlerでは、リクエストを受けてレスポンスか返すという実質的な処理を実装します。

/*
 * Main handler function of the module.
 */
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_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);
}

ビルドしてみる

モジュールのソースコードを配備します。

~# mkdir /usr/local/src/ngx_http_hello_module
~# ls /usr/local/src/ngx_http_hello_module
config  ngx_http_hello_module.c

nginx-buildの0.4.0以降を使ってビルドします。

~# useradd --shell /sbin/nologin nginx
~# apt-get update
~# apt-get install gcc make
~# wget https://github.com/cubicdaiya/nginx-build/releases/download/v0.4.1/nginx-build-linux-amd64-0.4.1.tar.gz
~# tar xvfz nginx-build-linux-amd64-0.4.1.tar.gz
~# chmod +x nginx-build 
~# ./nginx-build -d work -pcre -zlib -openssl -verbose --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx.lock --http-log-path=/var/log/nginx/access.log --with-http_stub_status_module --with-debug --add-module=/usr/local/src/ngx_http_hello_module
~# cd work/1.9.2/nginx-1.9.2/
~/work/1.9.2/nginx-1.9.2# make install
~#  /usr/sbin/nginx -c /etc/nginx/nginx.conf

ソースコードhideji/ngx_http_hello_module · GitHubにあります。

起動スクリプトを利用る場合はJasonGiedymin/nginx-init-ubuntu · GitHubを参照してください。

参考資料

簡単なHandlerの作り方は以下が参考になります。