開発ガイド
はじめに
コード レイアウト
-
auto
— ビルド スクリプト -
src
-
core
— 基本的なタイプと関数 — 文字列、配列、ログ、プールなど。 -
event
— イベントのコア-
modules
— イベント通知モジュール:epoll
,kqueue
,select
など。
-
-
http
— コア HTTP モジュールと共通コード-
modules
— 他のHTTPモジュール -
v2
— HTTP/2
-
-
mail
— メールモジュール -
os
— プラットフォーム固有のコード-
unix
-
win32
-
-
stream
— ストリームモジュール
-
インクルードファイル
以下の2つの #include
文は各nginxファイルの最初に現れるべきです:
#include <ngx_config.h> #include <ngx_core.h>
それに加えて、HTTPコードは以下を含まなければなりません
#include <ngx_http.h>
メールコードは以下を含まなければなりません
#include <ngx_mail.h>
ストリームコードは以下を含まなければなりません
#include <ngx_stream.h>
整数
一般的な目的のために、nginxのコードはintptr_t
とuintptr_t
のそれぞれについての型定義である二つ整数型 ngx_int_t
と ngx_uint_t
を使います。
共通リターンコード
nginxのほとんどの関数は以下のコードを返します:
-
NGX_OK
— 操作が成功 -
NGX_ERROR
— 操作が失敗 -
NGX_AGAIN
— 操作が未完了; 関数は再び呼ばれます。 -
NGX_DECLINED
— 例えば設定の中で無効にされているため、操作が却下された。これはエラーではありません。 -
NGX_BUSY
— リソースが利用できません。 -
NGX_DONE
— 操作が完了したか、他の場所で続いている。また、代替の成功コードとして使われます。 -
NGX_ABORT
— 関数が中断されました。また、代替のエラーコードとして使われます。
エラーハンドリング
ngx_errno
マクロは最後のシステムエラーコードを返します。POSIXプラットフォーム上のerrno
にマップされ、Windows内でGetLastError()
コールにマップされます。ngx_socket_errno
マクロは最後のソケットエラーの番号を返します。ngx_errno
マクロのように、POSIXプラットフォーム上のerrno
へマップされます。Windows上ではWSAGetLastError()
にマップされます。ngx_errno
あるいは ngx_socket_errno
の値への連続する1度以上のアクセスはパフォーマンスの問題を起こすかもしれません。エラーの値が複数回使われるかもしれない場合は、型ngx_err_t
のローカル変数に格納します。エラーを設定するには、ngx_set_errno(errno)
と ngx_set_socket_errno(errno)
マクロを使ってください。
ngx_errno
と ngx_socket_errno
の値はログ関数 ngx_log_error()
と ngx_log_debugX()
に渡されるかもしれません。その場合システムエラーのテキストはログメッセージに追加されます。
ngx_errno
を使う例:
ngx_int_t ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo) { ngx_err_t err; if (kill(pid, signo) == -1) { err = ngx_errno; ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo); if (err == NGX_ESRCH) { return 2; } return 1; } return 0; }
文字列
概要
Cの文字列については、nginxは符号無し文字型ポインタ u_char *
を使います。
nginxの文字列型 ngx_str_t
は以下のように定義されます:
typedef struct { size_t len; u_char *data; } ngx_str_t;
len
フィールドは文字列の長さを保持し、data
は文字列のデータを保持します。ngx_str_t
に保持されている文字列はlen
バイトの後にnull終端子があるかも知れません。ほとんどの場合はありません。しかし、コードのある部分(例えば、設定をパースする場合)、ngx_str_t
オブジェクトはnull終端だと知られています。これは文字列の比較を単純化し、文字列をシステムコールに渡すのを容易にします。
nginxでの文字列操作はsrc/core/ngx_string.h
内で定義されます。それらの幾つかは標準C関数のラッパーです:
-
ngx_strcmp()
-
ngx_strncmp()
-
ngx_strstr()
-
ngx_strlen()
-
ngx_strchr()
-
ngx_memcmp()
-
ngx_memset()
-
ngx_memcpy()
-
ngx_memmove()
他の文字列関数はnginx固有です
-
ngx_memzero()
— メモリをゼロで埋めます。 -
ngx_explicit_memzero()
—ngx_memzero()
と同じことをしますが、この呼び出しはコンパイラのdead store elimination optimizationで削除されることはありません。この関数は、パスワードやキーのようなセンシティブなデータを消去するために使えます。 -
ngx_cpymem()
—ngx_memcpy()
と同じ事をしますが、最後の宛先のアドレスを返します。これは複数の文字列を連続して追加するのに便利です。 -
ngx_movemem()
—ngx_memmove()
と同じ事をしますが、最後の宛先のアドレスを返します。 -
ngx_strlchr()
— 二つのポインタによって分割された文字列内の文字を検索します。
以下の関数は大文字小文字の変換と比較を行います:
-
ngx_tolower()
-
ngx_toupper()
-
ngx_strlow()
-
ngx_strcasecmp()
-
ngx_strncasecmp()
以下のマクロは文字列の初期化を単純化します:
-
ngx_string(text)
— C文字列リテラルtext
からのngx_str_t
型のための静的なイニシャライザ -
ngx_null_string
—ngx_str_t
型のための静的な空文字列イニシャライザ -
ngx_str_set(str, text)
— C文字列リテラルtext
を使ってngx_str_t *
型の文字列str
を初期化します -
ngx_str_null(str)
— 空の文字列を使ってngx_str_t *
型の文字列str
を初期化します
フォーマット
以下のフォーマット関数はnginx固有の型をサポートします:
-
ngx_sprintf(buf, fmt, ...)
-
ngx_snprintf(buf, max, fmt, ...)
-
ngx_slprintf(buf, last, fmt, ...)
-
ngx_vslprintf(buf, last, fmt, args)
-
ngx_vsnprintf(buf, max, fmt, args)
これらの関数によってサポートされるフォーマットオプションの完全なリストはsrc/core/ngx_string.c
の中にあります。それらのうちの幾つか:
-
%O
—off_t
-
%T
—time_t
-
%z
—ssize_t
-
%i
—ngx_int_t
-
%p
—void *
-
%V
—ngx_str_t *
-
%s
—u_char *
(null-terminated) -
%*s
—size_t + u_char *
それらを符号無しにするために、ほとんどの型でu
を前に付けることができます。出力を16進数に変換するためには、X
または x
を使ってください。
例えば:
u_char buf[NGX_INT_T_LEN]; size_t len; ngx_uint_t n; /* set n here */ len = ngx_sprintf(buf, "%ui", n) — buf;
数値変換
数値変換の幾つかの関数はnginx内で実装されています。最初の4つは指定された長さの文字列を指定された型の正数に変換します。エラー時にはそれらは NGX_ERROR
を返します:
-
ngx_atoi(line, n)
—ngx_int_t
-
ngx_atosz(line, n)
—ssize_t
-
ngx_atoof(line, n)
—off_t
-
ngx_atotm(line, n)
—time_t
さらに2つの数値の変換関数があります。最初の4つと似て、エラー時にはそれらは NGX_ERROR
を返します。
-
ngx_atofp(line, n, point)
— 指定された長さの固定小数点float数を正数値型ngx_int_t
に変換します。結果はpoint
10進位置だけ左シフトされます。数値を表す文字列はpoints
以下の少数を持たないことを期待されます。例えば、ngx_atofp("10.5", 4, 2)
は1050
を返します。 -
ngx_hextoi(line, n)
— 正数の16進数表現をngx_int_t
に変換します。
正規表現
nginxでの正規表現インタフェースはPCRE ライブラリのラッパーです。対応するヘッダファイルは src/core/ngx_regex.h
です。
文字列のマッチングのために正規表現を使うためには、まず、コンパイルされる必要があります。これは通常設定フェーズで行われます。PCREサポートは任意のため、インタフェースを使用している全てのコードは周りを取り巻くNGX_PCRE
マクロによって保護されなければなりません:
#if (NGX_PCRE) ngx_regex_t *re; ngx_regex_compile_t rc; u_char errstr[NGX_MAX_CONF_ERRSTR]; ngx_str_t value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'"); ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value; rc.pool = cf->pool; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; /* rc.options can be set to NGX_REGEX_CASELESS */ if (ngx_regex_compile(&rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err); return NGX_CONF_ERROR; } re = rc.regex; #endif
コンパイルが成功した後で、ngx_regex_compile_t
構造内のcaptures
とnamed_captures
フィールドは正規表現内でそれぞれ見つかった全てのcaptureとnamed captureをカウントします。
それからコンパイルされた正規表現は文字列に対する一致に使われます:
ngx_int_t n; int captures[(1 + rc.captures) * 3]; ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'."); n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3); if (n >= 0) { /* string matches expression */ } else if (n == NGX_REGEX_NO_MATCHED) { /* no match was found */ } else { /* some error */ ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n); }
ngx_regex_exec()
への引数はコンパイルされた正規表現re
、input
に一致する文字列、見つかったcaptures
を保持するための整数の任意の配列、そして配列のsize
です。captures
配列のサイズはPCRE APIで要求されるように、3の倍数でなければなりません。例では、サイズはcaptureの総数プラス1、一致した文字列自身、から計算されます。
一致した場合、captureは以下のようにしてアクセスすることができます:
u_char *p; size_t size; ngx_str_t name, value; /* all captures */ for (i = 0; i < n * 2; i += 2) { value.data = input.data + captures[i]; value.len = captures[i + 1] — captures[i]; } /* accessing named captures */ size = rc.name_size; p = rc.names; for (i = 0; i < rc.named_captures; i++, p += size) { /* capture name */ name.data = &p[2]; name.len = ngx_strlen(name.data); n = 2 * ((p[0] << 8) + p[1]); /* captured value */ value.data = &input.data[captures[n]]; value.len = captures[n + 1] — captures[n]; }
ngx_regex_exec_array()
関数はngx_regex_elt_t
要素 (関連名を使ってコンパイルされた単なる正規表現)の配列、一致した文字列、およびログを受け付けます。関数は合致が見つかるか、残りの表現がなくなるまで、表現を配列から文字列に適用します。一致がある場合は返り値は NGX_OK
、そうでなければ NGX_DECLINED
か、エラーの場合はNGX_ERROR
です。
時間
ngx_time_t
構造は秒、ミリ秒 およびGMTオフセットについての3つの個々の型を持つ時間を表現します:
typedef struct { time_t sec; ngx_uint_t msec; ngx_int_t gmtoff; } ngx_time_t;
ngx_tm_t
構造はUNIXプラットフォーム上のstruct tm
およびWindows上のSYSTEMTIME
のエイリアスです。
現在の時間を取得するには、望ましい形式のキャッシュされた時間の値を表す、利用可能なグローバル変数のうちの1つにアクセスするだけで通常は十分です。
利用可能な文字列表現は以下の通りです:
-
ngx_cached_err_log_time
— エラーログのエントリで使われます:"1970/09/28 12:00:00"
-
ngx_cached_http_log_time
— HTTPアクセスログのエントリで使われます:"28/Sep/1970:12:00:00 +0600"
-
ngx_cached_syslog_time
— syslogのエントリで使われます:"Sep 28 12:00:00"
-
ngx_cached_http_time
— HTTPヘッダ内で使われます:"Mon, 28 Sep 1970 06:00:00 GMT"
-
ngx_cached_http_log_iso8601
— ISO 8601 標準形式:"1970-09-28T12:00:00+06:00"
ngx_time()
と ngx_timeofday()
マクロは現在の時間の値を秒で返し、キャッシュされた時間の値をアクセスする好ましい方法です。
明示的に時間を取得するには、ngx_gettimeofday()
を使ってください。これは引数(struct timeval
へのポインタ)を更新します。nginxがシステムコールからイベントループへ帰る時に、時間は常に更新されます。時間をすぐに更新するにはngx_time_update()
を呼ぶか、シグナルハンドラのコンテキスト内で時間を更新する場合はngx_time_sigsafe_update()
を呼びます。
以下の関数はtime_t
を指示された砕けた時間表現に変換します。各ペア内の最初の関数はtime_t
を ngx_tm_t
に変換し、2つ目は(_libc_
挿入を使って) struct tm
に変換します:
-
ngx_gmtime(), ngx_libc_gmtime()
— UTCとして表現される時間 -
ngx_localtime(), ngx_libc_localtime()
— ローカルタイムゾーンへの相対で表現される時間
ngx_http_time(buf, time)
関数はHTTPヘッダ内で使うのに適した文字列表現を返します (例えば、"Mon, 28 Sep 1970 06:00:00 GMT"
)。ngx_http_cookie_time(buf, time)
はHTTPクッキーに適した文字列表現を返す文字列表現関数を返します ("Thu, 31-Dec-37 23:55:55 GMT"
)。
コンテナ
配列
nginx配列型ngx_array_t
は以下のように定義されます
typedef struct { void *elts; ngx_uint_t nelts; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_array_t;
配列の要素はelts
フィールド内で利用可能です。nelts
フィールドは要素の数を保持します。size
フィールドは1つの要素のサイズを保持し、配列が初期化される時に設定されます。
プール内で配列を作るにはngx_array_create(pool, n, size)
呼び出しを使い、既に割り当てられている配列オブジェクトを初期化するには ngx_array_init(array, pool, n, size)
呼び出しを使ってください。
ngx_array_t *a, b; /* create an array of strings with preallocated memory for 10 elements */ a = ngx_array_create(pool, 10, sizeof(ngx_str_t)); /* initialize string array for 10 elements */ ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));
要素を配列に追加するには以下の関数を使ってください:
-
ngx_array_push(a)
は後に1つの要素を追加し、それへのポインタを返します。 -
ngx_array_push_n(a, n)
は後にn
個の要素を追加し、最初の要素へのポインタを返します。
現在割り当てられているメモリの量が新しい要素を受け入れるのに十分な大きさでは無い場合、新しいメモリのブロックが割り当てられ、既存の要素はそれにコピーされます。新しいメモリブロックは通常は既存のものの2倍の大きさです。
s = ngx_array_push(a); ss = ngx_array_push_n(&b, 3);
リスト
Nginxではリストは配列が連続したもので、一時的に大量の項目を挿入するのに適しています。ngx_list_t
のリストの型は以下のように定義されます:
typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
実際の項目はリスト部分に格納されます。これは以下のように定義されます:
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; };
使う前にリストはngx_list_init(list, pool, n, size)
を呼び出すことで初期化されるか、ngx_list_create(pool, n, size)
を呼び出すことで生成されるべきです。両方の関数は引数として1つの項目のサイズとリスト部分あたりの項目の数を取ります。綱目をリストに追加するには、ngx_list_push(list)
関数を使ってください。項目をイテレートするには、例で示すように直接リストのフィールドをアクセスします:
ngx_str_t *v; ngx_uint_t i; ngx_list_t *list; ngx_list_part_t *part; list = ngx_list_create(pool, 100, sizeof(ngx_str_t)); if (list == NULL) { /* error */ } /* add items to the list */ v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "foo"); v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "bar"); /* iterate over the list */ part = &list->part; v = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; v = part->elts; i = 0; } ngx_do_smth(&v[i]); }
リストは主にHTTP入力と出力ヘッダのために使われます。
リストは項目の削除をサポートしません。しかし、必要な場合は、項目は実際にリストから削除すること無しに内部的に失われたものとしてマークすることができます。例えば、HTTP出力ヘッダ(ngx_table_elt_t
オブジェクトとして格納されます)を失われたものとしてマークするには、ngx_table_elt_t
内のhash
フィールドを0に設定します。このやり方でマークされた項目は、ヘッダがイテレートされる時に明示的にスキップされます。 iterated over.
キュー
nginxでは、キューはでしゃばりの二重にリンクされたリストで、各ノードは以下のように定義されます:
typedef struct ngx_queue_s ngx_queue_t; struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next; };
先頭のキューノードはどのようなデータともリンクしていません。使う前にリストヘッドを初期化するために、ngx_queue_init(q)
呼び出しを使ってください。キューは以下の操作をサポートします:
-
ngx_queue_insert_head(h, x)
,ngx_queue_insert_tail(h, x)
— 新しいノードを挿入する -
ngx_queue_remove(x)
— キューのノードを削除する -
ngx_queue_split(h, q, n)
— 分割されたキュー内のキューの後部を返しながら、ノードのキューを分割する -
ngx_queue_add(h, n)
— 2つ目のキューを最初のキューに追加する -
ngx_queue_head(h)
,ngx_queue_last(h)
— 最初あるいは最後のキューのノードを取得する -
ngx_queue_sentinel(h)
- Get a queue sentinel object to end iteration at -
ngx_queue_data(q, type, link)
— キューのフィールドのオフセットを考慮しながら、キューノードのデータ構造の開始への参照を取得する
例:
typedef struct { ngx_str_t value; ngx_queue_t queue; } ngx_foo_t; ngx_foo_t *f; ngx_queue_t values, *q; ngx_queue_init(&values); f = ngx_palloc(pool, sizeof(ngx_foo_t)); if (f == NULL) { /* error */ } ngx_str_set(&f->value, "foo"); ngx_queue_insert_tail(&values, &f->queue); /* insert more nodes here */ for (q = ngx_queue_head(&values); q != ngx_queue_sentinel(&values); q = ngx_queue_next(q)) { f = ngx_queue_data(q, ngx_foo_t, queue); ngx_do_smth(&f->value); }
赤黒木
src/core/ngx_rbtree.h
ヘッダファイルは赤黒木の効果的な実装への参照を提供します。
typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; /* custom per-tree data here */ } my_tree_t; typedef struct { ngx_rbtree_node_t rbnode; /* custom per-node data */ foo_t val; } my_node_t;
木を全体として扱うために、二つのノードが必要です: ルートと歩哨です。一般的に、葉がデータへのリンクあるいは埋め込みを含む木へデータを整理しながら、独自の構造へ追加されます。
木を初期化するには:
my_tree_t root; ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);
木を横断し新しい値を追加するには、"insert_value
" 関数を使ってください。例えば、ngx_str_rbtree_insert_value
関数は ngx_str_t
型を扱います。引数は、挿入のルート ノード、新しく生成されたノード、および木の歩哨へのポインタです。
void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
行き来はとても率直なもので、以下の捜索機能パターンを使って実演することができます:
my_node_t * my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash) { ngx_int_t rc; my_node_t *n; ngx_rbtree_node_t *node, *sentinel; node = rbtree->root; sentinel = rbtree->sentinel; while (node != sentinel) { n = (my_node_t *) node; if (hash != node->key) { node = (hash < node->key) ? node->left : node->right; continue; } rc = compare(val, node->val); if (rc < 0) { node = node->left; continue; } if (rc > 0) { node = node->right; continue; } return n; } return NULL; }
compare()
関数は、未満、等しい、あるいは大きい値を返す、古典的な比較関数です。検索を高速化し、大きくなるかもしれないユーザオブジェクトの比較を避けるために、整数ハッシュフィールドが使われます。
ノードを木に追加するために、新しいノードを割り当て、それを初期化し、そしてngx_rbtree_insert()
を呼びます:
my_node_t *my_node; ngx_rbtree_node_t *node; my_node = ngx_palloc(...); init_custom_data(&my_node->val); node = &my_node->rbnode; node->key = create_key(my_node->val); ngx_rbtree_insert(&root->rbtree, node);
ノードを削除するには、ngx_rbtree_delete()
関数を呼んでください:
ngx_rbtree_delete(&root->rbtree, node);
Hash
ハッシュテーブル関数は src/core/ngx_hash.h
で定義されています。確実およびワイルドカードの両方の一致がサポートされます。後者は特別なセットアップを必要とし、以下の別の章で説明されます。
ハッシュを初期化する前に、nginxがそれを最適化してビルドできるように、保持するだろう要素の数を知る必要があります。設定する必要がある2つのパラメータは、別のドキュメント内で説明されるように、max_size
と bucket_size
です。通常ユーザによって設定可能です。ハッシュの初期化設定はngx_hash_init_t
型を使って格納され、ハッシュ自身は ngx_hash_t
です:
ngx_hash_t foo_hash; ngx_hash_init_t hash; hash.hash = &foo_hash; hash.key = ngx_hash_key; hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "foo_hash"; hash.pool = cf->pool; hash.temp_pool = cf->temp_pool;
key
はハッシュの整数キーを文字列から生成する関数へのポインタです。2つの一般的なキー生成関数があります: ngx_hash_key(data, len)
と ngx_hash_key_lc(data, len)
。後者は文字列を全て小文字の文字に変換します。つまり渡される文字列は書き込み可能でなければなりません。それが当てはまらなければ、キー配列を初期化する時に、関数にNGX_HASH_READONLY_KEY
フラグを渡します (以下を見てください)。
ハッシュのキーはngx_hash_keys_arrays_t
に格納され、ngx_hash_keys_array_init(arr, type)
を使って初期化されます: 2つ目のパラメータ (type
) はハッシュのためにあらかじめ割り当てられたリソースの量を制御し、NGX_HASH_SMALL
または NGX_HASH_LARGE
のどちらかです。ハッシュに数千の要素を含むつもりの場合は、後者が適切です。
ngx_hash_keys_arrays_t foo_keys; foo_keys.pool = cf->pool; foo_keys.temp_pool = cf->temp_pool; ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);
キーをハッシュキーの配列に挿入するには、ngx_hash_add_key(keys_array, key, value, flags)
関数を使います:
ngx_str_t k1 = ngx_string("key1"); ngx_str_t k2 = ngx_string("key2"); ngx_hash_add_key(&foo_keys, &k1, &my_data_ptr_1, NGX_HASH_READONLY_KEY); ngx_hash_add_key(&foo_keys, &k2, &my_data_ptr_2, NGX_HASH_READONLY_KEY);
ハッシュテーブルを構築するには、ngx_hash_init(hinit, key_names, nelts)
関数を呼びます:
ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);
もしmax_size
あるいは bucket_size
パラメータが十分に大きくない場合は、関数は失敗します。
ハッシュが構築される時、要素を検索するためにngx_hash_find(hash, key, name, len)
関数を使います:
my_data_t *data; ngx_uint_t key; key = ngx_hash_key(k1.data, k1.len); data = ngx_hash_find(&foo_hash, key, k1.data, k1.len); if (data == NULL) { /* key not found */ }
ワイルドカード マッチング
ワイルドカードと連携するハッシュを作るには、ngx_hash_combined_t
型を使ってください。それは、上で説明されるハッシュ型を含み、二つの追加のキー配列を持ちます: dns_wc_head
と dns_wc_tail
。基本的なプロパティの初期化は通常のハッシュに似ています:
ngx_hash_init_t hash ngx_hash_combined_t foo_hash; hash.hash = &foo_hash.hash; hash.key = ...;
NGX_HASH_WILDCARD_KEY
フラグを使ってワイルドカードキーを追加することが可能です:
/* k1 = ".example.org"; */ /* k2 = "foo.*"; */ ngx_hash_add_key(&foo_keys, &k1, &data1, NGX_HASH_WILDCARD_KEY); ngx_hash_add_key(&foo_keys, &k2, &data2, NGX_HASH_WILDCARD_KEY);
関数はワイルドカードを理解しキーを対応する配列に追加します。ワイルドカードの構文と一致アルゴリズムの説明については、map モジュール ドキュメントを参照してください。
追加されるキーの内容に応じて、3つのキー配列まで初期化する必要があります: 1つは確実な一致(以下で説明されます)、2つ目移行は文字列の始めあるいは終わりから一致の開始を有効にするためのものです:
if (foo_keys.dns_wc_head.nelts) { ngx_qsort(foo_keys.dns_wc_head.elts, (size_t) foo_keys.dns_wc_head.nelts, sizeof(ngx_hash_key_t), cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, foo_keys.dns_wc_head.elts, foo_keys.dns_wc_head.nelts) != NGX_OK) { return NGX_ERROR; } foo_hash.wc_head = (ngx_hash_wildcard_t *) hash.hash; }
キー配列はソートされる必要があり、初期化の結果は結合されたハッシュに追加されなければなりません。dns_wc_tail
配列の初期化は同じように行われます。
結合されたハッシュ内での照合はngx_hash_find_combined(chash, key, name, len)
によって扱われます:
/* key = "bar.example.org"; — will match ".example.org" */ /* key = "foo.example.com"; — will match "foo.*" */ hkey = ngx_hash_key(key.data, key.len); res = ngx_hash_find_combined(&foo_hash, hkey, key.data, key.len);
メモリ管理
ヒープ
システム ヒープからメモリを割り当てるには、以下の関数を使ってください:
-
ngx_alloc(size, log)
— システム ヒープからメモリを割り当てる。これはログのサポートを持つmalloc()
のラッパーです。割り当てエラーとデバッグ情報はlog
に記録されます。 -
ngx_calloc(size, log)
—ngx_alloc()
のようにシステム ヒープからメモリを割り当てますが、割り当ての後でメモリは0で埋められます。 -
ngx_memalign(alignment, size, log)
— システム ヒープから揃えられたメモリが割り当てられます。これは関数を提供するこれらのプラットフォーム上のposix_memalign()
周りのラッパーです。そうでなければ実装は最大の割り当てを提供するngx_alloc()
で代用されます。 -
ngx_free(p)
— 割り当てられたメモリを開放します。これはfree()
のラッパーです
プール
ほとんどのnginxの割り当てはプール内で行われます。nginxのプールで割り当てられたメモリはプールが破壊された時に自動的に開放されます。これにより割り当てのパフォーマンスが良くなり、メモリの制御が簡単になります。
プールはメモリの連続するブロックの中でオブジェクトを内部的に割り当てます。ブロックが埋まると、新しいブロックが割り当てられ、プールメモリブロックリストへ追加されます。割り当てのリクエストがブロックに収まるにはあまりに大きい場合、リクエストはシステムのアロケータに転送され、返ってきたポインタは更なる割り当て解除のためにプール内に格納されます。
nginxのプールのための型は ngx_pool_t
です。以下の操作がサポートされます:
-
ngx_create_pool(size, log)
— 指定されたブロックサイズを持つプールを作成する。返されたプールオブジェクトは同じようにプール内に割り当てられます。size
は少なくともNGX_MIN_POOL_SIZE
でNGX_POOL_ALIGNMENT
の倍数でなければなりません。 -
ngx_destroy_pool(pool)
—プールオブジェクト自身を含む全てのプールメモリを解放します。 -
ngx_palloc(pool, size)
— 指定されたプールから揃えられたメモリが割り当てられます。 -
ngx_pcalloc(pool, size)
— 指定されたプールから揃えられたメモリが割り当てられ、0で埋められます。 -
ngx_pnalloc(pool, size)
— 指定されたプールから揃っていないメモリが割り当てられます。ほとんどの場合文字列を割り当てるために使われます。 -
ngx_pfree(pool, p)
— 指定されたプール内で以前に割り当てられたメモリを開放します。システムのアロケータに転送されたリクエストの結果の割り当てのみ開放することができます。
u_char *p; ngx_str_t *s; ngx_pool_t *pool; pool = ngx_create_pool(1024, log); if (pool == NULL) { /* error */ } s = ngx_palloc(pool, sizeof(ngx_str_t)); if (s == NULL) { /* error */ } ngx_str_set(s, "foo"); p = ngx_pnalloc(pool, 3); if (p == NULL) { /* error */ } ngx_memcpy(p, "foo", 3);
nginxではチェイン リンク (ngx_chain_t
) が積極的に使われるため、nginxのプールの実装はそれらを再利用する方法を提供します。ngx_pool_t
の chain
フィールドは再利用のために以前割り当てられたリンクのリストを準備し続けます。プール内での効率的なチェインリンクの割り当てのために、ngx_alloc_chain_link(pool)
関数を使ってください。この関数はプールリスト内のフリーなチェインリンクを探し、プールリストが空であれば新しいチェインリンクを割り当てます。リンクを解放するには、ngx_free_chain(pool, cl)
関数を呼びます。
クリーンナップ ハンドラはプール内に登録することができます。クリーンナップ ハンドラはプールが破壊される時に呼ばれる引数を持つコールバックです。プールは通常(HTTPリクエストのような)特定のnginxオブジェクトに紐付けられ、オブジェクトがその生存期間の最後に達した時に破壊されます。プールのクリーンナップの登録はリソースを解放し、ファイル ディスクリプタを閉じ、あるいはメインのオブジェクトに関連する共有データへの最後の調整を行う便利な方法です。
プールのクリーンナップを登録するには、ngx_pool_cleanup_add(pool, size)
を呼びます。これは呼び出し元によって情報が与えられるngx_pool_cleanup_t
ポインタを返します。クリーンナップ ハンドラのための内容を割り当てるためにsize
引数を使います。
ngx_pool_cleanup_t *cln; cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { /* error */ } cln->handler = ngx_my_cleanup; cln->data = "foo"; ... static void ngx_my_cleanup(void *data) { u_char *msg = data; ngx_do_smth(msg); }
共有メモリ
共有メモリはプロセス間での共通データを共有するためにnginxによって使われます。ngx_shared_memory_add(cf, name, size, tag)
関数はサイクルに新しい共有メモリ エントリ ngx_shm_zone_t
を追加します。関数は領域のname
とsize
を受け取ります。各共有ゾーンはユニークな名前を持たなければなりません。もし指定された名前
と タグ
を持つ共有領域のエントリが存在する場合、既存の領域のエントリは再利用されます。同じ名前を持つ既存のエントリが異なるタグを持つ場合は、関数はエラーで失敗します。通常、モジュール構造のアドレスがタグ
として渡され、1つのnginxモジュール内で名前によって共有領域を再利用することができます。
共有メモリ エントリ構造 ngx_shm_zone_t
は以下のフィールドを持ちます:
-
init
— 共有領域が実際のメモリにマップされた後で呼ばれる初期化コールバック -
data
— 任意のデータをinit
コールバックに渡すために使われるデータコンテキスト -
noreuse
— 古いサイクルからの共有領域の再利用を無効にするフラグ -
tag
— 共有領域のタグ -
shm
— 少なくとも以下のフィールドを持つ、プラットフォーム固有のngx_shm_t
型のオブジェクト:-
addr
— 最初はNULLのマップされた共有メモリアドレス -
size
— 共有メモリサイズ -
name
— 共有メモリ名 -
log
— 共有メモリログ -
exists
— 共有メモリがマスタープロセスから継承されたことを示すフラグ (Windows固有)
-
共有ゾーン エントリは設定がパースされた後でngx_init_cycle()
内の実際のメモリにマップされます。POSIXシステム上では、mmap()
syscall が共有匿名マッピングを生成するために使われます。Windows上では、CreateFileMapping()
/ MapViewOfFileEx()
ペアが使われます。
共有メモリ内で割り当てるために、nginxはスラブ プール ngx_slab_pool_t
を提供します。割り当てメモリのためのslabプールが各nginx共有領域内で自動的に作成されます。プールは共有ゾーンの最初に位置していて、表現(ngx_slab_pool_t *) shm_zone->shm.addr
によってアクセスすることができます。共有領域内でメモリを割り当てるためには、ngx_slab_alloc(pool, size)
または ngx_slab_calloc(pool, size)
のどちらかを呼び出します。メモリを解放するには、ngx_slab_free(pool, p)
を呼びます。
スラブ プールは全ての共有ゾーンをページに分割します。各ページは同じサイズのオブジェクトを割り当てるために使われます。指定されたサイズは2の乗数で、8バイトの最小サイズより大きくなければなりません。適合しない値は上に丸められます。各ページのためのビットマスクは、どのブロックが使用中でどれが割り当てのためにフリーであるかを追跡します。半分のページ(通常 2048バイト)より大きい場合、割り当ては一度にページ全体まで行われます。
同時アクセスから共有メモリ内のデータを守るためには、ngx_slab_pool_t
のmutex
フィールド内で利用可能なmutexを使ってください。mutexはメモリの割り当てと解放の間にslabプールによって最も一般的に使われますが、共有領域内で割り当てられる他のユーザデータの構造を保護するために使うことができます。mutexをロックあるいはアンロックするために、それぞれngx_shmtx_lock(&shpool->mutex)
あるいは ngx_shmtx_unlock(&shpool->mutex)
を使ってください。
ngx_str_t name; ngx_foo_ctx_t *ctx; ngx_shm_zone_t *shm_zone; ngx_str_set(&name, "foo"); /* allocate shared zone context */ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t)); if (ctx == NULL) { /* error */ } /* add an entry for 64k shared zone */ shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module); if (shm_zone == NULL) { /* error */ } /* register init callback and context */ shm_zone->init = ngx_foo_init_zone; shm_zone->data = ctx; ... static ngx_int_t ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data) { ngx_foo_ctx_t *octx = data; size_t len; ngx_foo_ctx_t *ctx; ngx_slab_pool_t *shpool; value = shm_zone->data; if (octx) { /* reusing a shared zone from old cycle */ ctx->value = octx->value; return NGX_OK; } shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; if (shm_zone->shm.exists) { /* initialize shared zone context in Windows nginx worker */ ctx->value = shpool->data; return NGX_OK; } /* initialize shared zone */ ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t)); if (ctx->value == NULL) { return NGX_ERROR; } shpool->data = ctx->value; return NGX_OK; }
ログ
ログのためにnginxはngx_log_t
オブジェクトを使います。nginxのロガーはいくつかの種類の出力をサポートします:
- stderr — 標準エラーへのログ (stderr)
- file — ファイルへのログ
- syslog — syslogへのログ
- memory — 開発目的のための内部的なメモリストレージへのログ; メモリは後でデバッガーを使ってアクセスすることができます
ロガーのインスタンスは、お互いにnext
フィールドを使ってリンクされたロガーのチェインに成り得ます。この場合、各メッセージはチェイン内の全てのロガーに書き込まれます。
各ロガーについて、重大度はどのメッセージがログに書かれるかを制御します (割り当てられたレベルあるいはそれより高いイベントだけが記録されます)。以下の重要度のレベルがサポートされます:
-
NGX_LOG_EMERG
-
NGX_LOG_ALERT
-
NGX_LOG_CRIT
-
NGX_LOG_ERR
-
NGX_LOG_WARN
-
NGX_LOG_NOTICE
-
NGX_LOG_INFO
-
NGX_LOG_DEBUG
デバッグのログのために、デバッグのマスクが同様にチェックされます。デバッグのマスクは以下の通りです:
-
NGX_LOG_DEBUG_CORE
-
NGX_LOG_DEBUG_ALLOC
-
NGX_LOG_DEBUG_MUTEX
-
NGX_LOG_DEBUG_EVENT
-
NGX_LOG_DEBUG_HTTP
-
NGX_LOG_DEBUG_MAIL
-
NGX_LOG_DEBUG_STREAM
通常、ロガーは既存のnginxコードによってerror_log
ディレクティブから生成され、サイクル、設定、クライアント接続および他のオブジェクトのほとんど全ての処理のステージで利用可能です。
Nginx は以下のログ マクロを提供します:
-
ngx_log_error(level, log, err, fmt, ...)
— エラーのログ -
ngx_log_debug0(level, log, err, fmt)
,ngx_log_debug1(level, log, err, fmt, arg1)
など — 8までのサポートされる形式の引数を持つデバッグログ
ログメッセージはスタック上のサイズNGX_MAX_ERROR_STR
(現在のところ 2048 バイト)のバッファ内で整形されます。メッセージは重要度、プロセスID(PID)、接続ID(log->connection
に格納されます) およびシステムエラーのテキストが前に付きます。非デバッグメッセージについては、もっと特定の情報がログメッセージに付くようにlog->handler
が呼ばれます。HTTP モジュールは、クライアントとサーバアドレス、現在のアクション(log->action
に格納されています)、クライアント リクエスト ライン、サーバ名などを記録するためにログ ハンドラとしてngx_http_log_error()
関数を設定します。
/* specify what is currently done */ log->action = "sending mp4 to client"; /* error and debug log */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
上野例は以下のようなログ エントリになります:
2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1" 2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000
サイクル
サイクルオブジェクトは特定の設定から生成されるnginxの実行時コンテキストを保持します。その型は ngx_cycle_t
です。現在のサイクルはngx_cycle
グローバル変数によって参照され、nginxのワーカーが開始した時にそれらによって継承されます。nginxの設定がリロードされる度に、新しいサイクルが新しいnginx設定から生成されます; 古いサイクルは通常新しいものがうまく生成された後で削除されます。
サイクルは ngx_init_cycle()
関数によって生成されます。これは以前のサイクルを引数として取ります。 argument. 関数は以前のサイクルの設定ファイルの場所を特定し、以前のサイクルからできる限り多くのリソースを継承します。nginxが開始する時に"init cycle" と呼ばれるplaceholderサイクルが生成され、その後設定によって構築された実際のサイクルによって置き換えられます。
サイクルのメンバーは以下を含みます:
-
pool
— サイクル プール。新しい各サイクルのために作られます。 -
log
— サイクル ログ。最初に古いサイクルから継承され、設定が読み込まれた後でnew_log
を指すように設定されます。 -
new_log
— 設定によって作られたサイクル ログ。ルート スコープのerror_log
ディレクティブによって影響を受けます。 -
connections
,connection_n
— 型ngx_connection_t
の接続の配列。各nginxのワーカーを初期化する間にイベントモジュールによって作成されます。nginxの設定内のworker_connections
ディレクティブは接続connection_n
の数を設定します。 -
free_connections
,free_connection_n
— リストと、現在利用可能な接続の数。利用可能な接続が無い場合、nginxのワーカーは新しいクライアントの接続を拒否するか、upstreamサーバに接続します。 -
files
,files_n
— ファイル ディスクリプタからnginxの接続へのマッピングのための配列。このマッピングはイベントモジュールによって使用され、NGX_USE_FD_EVENT
フラグ (現在のところ、poll
とdevpoll
) を持ちます。 -
conf_ctx
— コア モジュールの設定の配列。nginxの設定ファイルの読み込みの間に設定は生成され、調合されます。 -
modules
,modules_n
— 型ngx_module_t
のモジュールの配列で、静的および動的の両方であり、現在の設定によってロードされます。 -
listening
— 型ngx_listening_t
のlisteningオブジェクトの配列。listeningオブジェクトはngx_create_listening()
を呼ぶ異なるモジュールのlisten
ディレクティブによって通常追加されます。listenソケットはlisteningオブジェクトに基づいて生成されます。 -
paths
— 型ngx_path_t
のパスの配列パスは、特定のディレクティブ上で操作することができるモジュールからngx_add_path()
関数を呼び出すことで追加されます。これらのディレクティブが無い場合は、設定の読み込みの後でこれらのディレクティブが生成されます。さらに、二つのハンドラを各パスについて設定することができます:- path loader — nginxの開始あるいはリロード後の60秒以内に一度だけ実行されます。通常、ローダーはディレクトリを読み込み、nginx共有メモリにデータを格納します。ハンドラは専用のnginxプロセス “nginx cache loader” から呼ばれます。
- path manager — 定期的に実行されます。通常はマネージャーが古いファイルをディレクトリから削除し、変更を反映するためにnginxメモリを更新します。ハンドラは専用の “nginx cache manager” プロセスから呼ばれます。
-
open_files
— 型ngx_open_file_t
のオープン ファイル オブジェクトのリスト。これらは関数ngx_conf_open_file()
の呼び出しによって生成されます。現在のところ、nginxは記録のためにこの種類のオープン ファイルを使用します。設定の読み込みの後で、nginxはopen_files
リストから全てのファイルを開き、オブジェクトのfd
フィールド内の各ファイル ディスクリプ多を格納します。ファイルは追記モードで開かれ、無い場合は作成されます。再オープンシグナル(最も多くはUSR1
)を受け取った時に、リスト内のファイルはnginxワーカーによって再オープンされます。この場合、fd
フィールド内のディスクリプタは新しい値に変更されます。 -
shared_memory
— 共有メモリゾーンのリスト。それぞれはngx_shared_memory_add()
関数の呼び出しによって追加されます。共有ゾーンは全てのnginxプロセスの中で同じアドレス範囲にマップされ、例えばHTTPキャッシュ インメモリ ツリーのような共通データを共有するために使われます
バッファ
入力/出力オペレーションのために、nginxはバッファ型 ngx_buf_t
を提供します。通常、それは宛先に書き込まれるためのデータを保持するためか、ソースから読み込むために使われます。バッファはメモリ内あるいはファイル内のデータを参照することができ、技術的にはバッファが同時に両方を参照することができます。バッファのためのメモリは個々に割り当てられ、バッファ構造 ngx_buf_t
には関係しません。
ngx_buf_t
構造は以下のフィールドを持ちます:
-
start
,end
— メモリブロックの境界で、バッファのために割り当てられます。 -
pos
,last
— メモリバッファの境界; 通常はstart
..end
の部分領域です。 -
file_pos
,file_last
— ファイル バッファの境界。ファイルの先頭からのオフセットとして表現されます。 -
tag
— ユニークな値で、バッファを区別するために使われます; 通常はバッファの再利用のために異なるnginxモジュールによって生成されます -
file
— ファイル オブジェクト。 -
temporary
— バッファが書き込み可能なメモリを参照することを示すフラグ。 -
memory
— バッファが読み込みのみ可能なメモリを参照することを示すフラグ。 -
in_file
— バッファがファイル内のデータを参照することを示すフラグ。 -
flush
— バッファより前の全てのデータがフラッシュされるべきであることを示すフラグ。 -
recycled
— バッファが再利用可能でできる限り消費されなければならないことを示すフラグ。 -
sync
— バッファが何もデータを運ばないか、flush
またはlast_buf
のような特別なシグナルを運ぶことを示すフラグ。デフォルトでは、nginxはそのようなバッファをエラー条件として考慮しますが、このフラグはnginxにエラーのチェックをスキップすることを伝えます。 -
last_buf
— バッファが出力内の最後であることを示すフラグ。 -
last_in_chain
— リクエストあるいはサブリクエスト内にそれ以上のデータバッファが無いことを示すフラグ。 -
shadow
— 通常、バッファがshadowからのデータを使うという意味で、現在のバッファに関係する他の("shadow")バッファへのリファレンス。バッファが消費された時、通常shadowバッファも消費されたとしてマークされます。 -
last_shadow
— 特定のshadowバッファを参照する最後のバッファであることを示すフラグ。 -
temp_file
— バッファが一時ファイル内にあることを示すフラグ。
入力および出力操作のために、バッファはチェインでリンクされます。チェインは型ngx_chain_t
のチェイン リンクの順列で、以下のように定義されます:
typedef struct ngx_chain_s ngx_chain_t; struct ngx_chain_s { ngx_buf_t *buf; ngx_chain_t *next; };
各チェーンリンクはバッファへの参照と次のチェーンリンクへの参照を保持します。
An example of using buffers and chains:
ngx_chain_t * ngx_get_my_chain(ngx_pool_t *pool) { ngx_buf_t *b; ngx_chain_t *out, *cl, **ll; /* first buf */ cl = ngx_alloc_chain_link(pool); if (cl == NULL) { /* error */ } b = ngx_calloc_buf(pool); if (b == NULL) { /* error */ } b->start = (u_char *) "foo"; b->pos = b->start; b->end = b->start + 3; b->last = b->end; b->memory = 1; /* read-only memory */ cl->buf = b; out = cl; ll = &cl->next; /* second buf */ cl = ngx_alloc_chain_link(pool); if (cl == NULL) { /* error */ } b = ngx_create_temp_buf(pool, 3); if (b == NULL) { /* error */ } b->last = ngx_cpymem(b->last, "foo", 3); cl->buf = b; cl->next = NULL; *ll = cl; return out; }
ネットワーク
接続
接続型 ngx_connection_t
はソケットディスクリプタのラッパーです。以下のフィールドを含みます:
-
fd
— ソケット ディスクリプタ -
data
— 任意の接続コンテキスト通常それはHTTPリクエストあるいはストリームセッションのような接続の上に構築された高レベルオブジェクトへのポインタです。 -
read
,write
— 接続のための読み込みおよび書き込みイベント。 -
recv
,send
,recv_chain
,send_chain
— 接続のための I/Oオペレーション。 -
pool
— 接続プール。 -
log
— 接続ログ。 -
sockaddr
,socklen
,addr_text
— バイナリおよびテキスト形式のリモート ソケット アドレス。 -
local_sockaddr
,local_socklen
— バイナリ形式のローカル ソケット アドレス。最初は、これらのフィールドは空です。ローカル ソケット アドレスを取得するには、ngx_connection_local_sockaddr()
関数を使ってください。 -
proxy_protocol_addr
,proxy_protocol_port
- 接続のためにPROXYプロトコルが有効な場合、PROXYプロトコルのクライアント アドレスとポート。 -
ssl
— 接続のための SSL コンテキスト。 -
reusable
— 接続が再利用できる状態にあることを示すフラグ。 -
close
— 接続が再利用され、閉じる必要があることを示すフラグ。
nginxの接続は透過的にSSL層をカプセル化します。この場合、接続のssl
フィールドはngx_ssl_connection_t
構造へのポインタを保持し、SSL_CTX
と SSL
を含む接続のための全てのSSLに関係するデータを持ち続けます。recv
, send
, recv_chain
および send_chain
ハンドラはSSL有効な関数へも設定されます。
nginxの設定内のworker_connections
ディレクティブはnginxワーカー毎の接続の数を制限します。全ての接続の構造は、ワーカーが開始する時にあらかじめ作成され、サイクルオブジェクトのconnections
フィールドの中に格納されます。接続の構造を取り出すには、ngx_get_connection(s, log)
関数を使ってください。それはs
引数をソケットディスクリプタとして取ります。これはconnection構造の中でラップされる必要があります。
ワーカーあたりの接続の数が制限されるため、nginxは現在使われている接続を横取りする方法を提供します。接続の再利用を有効あるいは無効にするために、ngx_reusable_connection(c, reusable)
関数を呼んでください。ngx_reusable_connection(c, 1)
の呼び出しは接続構造のreuse
フラグを設定し、その接続をサイクルのreusable_connections_queue
に挿入します。ngx_get_connection()
はサイクルのfree_connections
リストの中に利用可能な接続が無いと分かるといつでも特定の数の再利用可能な接続lを解放するためにngx_drain_connections()
を呼びます。そのようなそれぞれの接続について、close
フラグが設定され、ngx_close_connection(c)
を呼びだすことで接続を解放することになっている読み込みハンドラが呼ばれ、再利用できるようにします。接続が再利用可能な時に状態を抜けるためにngx_reusable_connection(c, 0)
が呼ばれます。HTTPクライアント接続はnginxでの再利用可能な接続の例です; クライアントからの最初のリクエストのバイトが受信されるまでそれらは再利用可能としてマークされます。
イベント
イベント
nginxでのイベント オブジェクト ngx_event_t
は特定のイベントが起きたことを伝えるための仕組みを提供します。
ngx_event_t
内のフィールドは以下を含みます:
-
data
— イベントハンドラ内で使われる任意のイベント コンテキスト。通常はイベントに関係する接続へのポインタです。 -
handler
— イベントが起きた時に発動されるコールバック関数。 -
write
— 書き込みイベントを示すフラグフラグが無いことは読み込みイベントであることを示します。 -
active
— I/O通知を受け取るために登録されたイベントであることを示すフラグ。通常はepoll
,kqueue
,poll
のような通知の仕組みから。 -
ready
— イベントがI/O通知を受け取ったことを示すフラグ。 -
delayed
— I/Oがレートの制限により遅れたことを示すフラグ。 -
timer
— イベントを時間木に挿入するための赤黒木ノード。 -
timer_set
— イベント タイマーが設定され、まだ期限切れになっていないことを示すフラグ。 -
timedout
— イベント タイマーが期限切れになったことを示すフラグ。 -
eof
— データの読み込み中にEOFが発生したことを示すフラグ。 -
pending_eof
— フラグ。たとえそれ以前に何らかのデータが利用可能だったかも知れないが、ソケット上でEOFが起ころうとしていることを意味します。フラグはEPOLLRDHUP
epoll
イベント、あるいはEV_EOF
kqueue
フラグによって配送されます。 -
error
— (読み込みイベントの)読み込み中、あるいは(書き込みイベントの)書き込み中にエラーが発生したことを示すフラグ。 -
cancelable
— ワーカーをシャットダウン中にイベントが無視されなければならないことを示すフラグ。キャンセルできないタイマーイベントのスケジュールが無くなるまで、gracefulワーカー シャットダウンは遅延されます。 -
posted
— イベントがキューに投稿されたことを示すフラグ。 -
queue
— イベントをキューに投稿するためのキュー ノード。
I/O イベント
ngx_get_connection()
関数を呼び出すことで取得される各接続は2つの備え付けのイベントを持ちます。c->read
と c->write
。それらはソケットの読み込みあるいは書き込みの準備ができたという通知を受け取るために使われます。全てのそのようなイベントはEdge-Triggeredモードで操作されます。ソケットの状態の変更時にそれらは通知を引き起こすことを意味します。例えば、ソケット上の部分的な読み込みの実施は、ソケットにもっと多くのデータが到着するまでnginxに繰り返しの読み込み通知を配送しないでしょう。背後で動くI/O通知の仕組みが本質的に Level-Triggered(poll
, select
)であった場合でも、nginxは通知を Edge-Triggered に変換します。異なるプラットフォーム上の全ての通知システムに渡ってnginxのイベント通知に一貫性を持たせるには、ngx_handle_read_event(rev, flags)
と ngx_handle_write_event(wev, lowat)
がI/Oソケットの通知あるいはソケット上の全てのI/O呼び出し関数の後で呼ばれる必要があります。通常、関数は各読み込みあるいは書き込みイベントハンドラの最後に一度呼ばれます。
タイマー イベント
タイムアウトが期限切れになった時に通知を送信するためにイベントを設定することができます。例えば、ngx_current_msec
変数はEpochから費やされngx_msec_t
に切り取られたミリ秒数を保持します。その現在の値はngx_current_msec
変数から取得することができます。
関数 ngx_add_timer(ev, timer)
はイベントのタイムアウトを設定し、ngx_del_timer(ev)
は以前に設定されたタイムアウトを削除します。グローバル タイムアウト 赤黒木 ngx_event_timer_rbtree
は現在設定されている全てのタイムアウトを格納します。木の中のキーはngx_msec_t
の型で、イベントが発生した時の時間です。木の構造により高速な挿入と削除操作が可能で、最も近いタイムアウトにアクセスします。nginxはI/Oイベントおよびタイムアウトの期限切れイベントまでどれだけ待つかを見つけるために使います。
ポストされたイベント
イベントがポストされるかも知れません。ハンドルが現在のイベントループの繰り返しの中でどこかの時点の後で呼ばれるかも知れないという意味です。イベントのポストはコードを単純化しスタックオーバーフローを避けるための良い練習です。ポストされたイベントはポストキューの中に保持されます。ngx_post_event(ev, q)
マクロはイベント ev
をポスト キューq
に投稿します。ngx_delete_posted_event(ev)
マクロはイベントev
を現在投稿されているキューから削除します。通常、イベントはngx_posted_events
キューに投稿されます。これはイベントループの終わりで処理されます — 全てのI/Oとタイマーイベントが既に処理された後。関数 ngx_event_process_posted()
はイベントキューを処理するために呼ばれます。キューが空になるまでイベントハンドラを呼びます。このことは、ポストされたイベントハンドラは現在のイベントループの繰り返しの中で処理されるされるようにもっと多くのイベントをポストすることができることを意味します。
例:
void ngx_my_connection_read(ngx_connection_t *c) { ngx_event_t *rev; rev = c->read; ngx_add_timer(rev, 1000); rev->handler = ngx_my_read_handler; ngx_my_read(rev); } void ngx_my_read_handler(ngx_event_t *rev) { ssize_t n; ngx_connection_t *c; u_char buf[256]; if (rev->timedout) { /* timeout expired */ } c = rev->data; while (rev->ready) { n = c->recv(c, buf, sizeof(buf)); if (n == NGX_AGAIN) { break; } if (n == NGX_ERROR) { /* error */ } /* process buf */ } if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ } }
イベントループ
nginxのマスタープロセスを除いて、全てのnginxプロセスはI/Oを行い、そういうわけでイベントループを持ちます。(nginxのマスタプロセスは代わりにシグナルが到着するのを待つsigsuspend()
呼び出しの中でほとんどの時間を過ごします。) nginxのイベントループはngx_process_events_and_timers()
関数の中で実装されます。これはプロセスが終了するまで繰り返し呼ばれます。
イベントループは以下のステージを持ちます:
-
ngx_event_find_timer()
を呼び出すことで、期限切れになりそうなタイムアウトを見つけます。この関数はタイマー木の最も左のノードを見つけ、ノードが期限切れになるまでミリ秒の数値を返します。 -
nginxの設定によって選択された、イベント通知の仕組みに固有のハンドラを呼び出すことでI/Oイベントを処理します。このハンドラは次のタイムアウトが期限切れになるまで少なくとも1つのI/Oイベントが起きるのを待ちます。読み込みあるいは書き込みイベントが起きる時に、
ready
フラグが設定され、イベントのハンドラが呼ばれます。Linuxについては、通常ngx_epoll_process_events()
ハンドラが使われます。これはI/Oイベントを待つためにepoll_wait()
を呼びます。 -
ngx_event_expire_timers()
を呼び出すことでタイマーを期限切れにします。タイマーツリーは期限切れになっていないタイムアウトが見つかるまで最も左の要素から右まで繰り返します。各期限切れのノードについては、timedout
イベントフラグが設定され、timer_set
がリセットされ、イベントハンドラが呼ばれます。 -
ngx_event_process_posted()
を呼び出すことで投稿されたイベントを処理します。関数はポストされたイベントキューから最初の要素を繰り返し削除し、キューが空になるまで要素のハンドラを呼びます。
全てのnginxプロセスもシグナルを処理します。シグナルハンドラは、ngx_process_events_and_timers()
呼び出しの後でチェックされるグローバル変数にのみ設定します。
プロセス
nginxには処理の幾つかのタイプがあります:プロセスの型はngx_process
グローバル変数の中に保持され、以下のうちの1つです:
-
NGX_PROCESS_MASTER
— マスタープロセス。NGINX設定を読み、サイクルを作成し、子プロセスを開始および制御します。何もI/Oを行わず、シグナルにのみ応答します。そのサイクル関数はngx_master_process_cycle()
です。 -
NGX_PROCESS_WORKER
— ワーカープロセス。クライアントの接続を処理します。マスタープロセスによって開始され、シグナルと同様にチャネルコマンドに応答します。そのサイクル関数はngx_worker_process_cycle()
です。worker_processes
ディレクティブで設定されるように、複数のワーカープロセスがあります。 -
NGX_PROCESS_SINGLE
— 1つのプロセス。master_process off
モードでのみ存在し、そのモード内で実行中の唯一のプロセス。(マスタープロセスがするような)サイクルを作成し、(ワーカープロセスがするような)クライアント接続を処理します。そのサイクル関数はngx_single_process_cycle()
です。 -
NGX_PROCESS_HELPER
— ヘルパープロセス。現在のところ2つの種類があります: キャッシュマネージャーとキャッシュローダー。両方のためのサイクル関数はngx_cache_manager_process_cycle()
です。
nginxプロセスは以下のシグナルを処理します:
-
NGX_SHUTDOWN_SIGNAL
(ほとんどのシステム上でSIGQUIT
) — gracefully シャットダウン。このシグナルを受け取る時、マスタープロセスはシャットダウンシグナルを全ての子プロセスに送信します。子プロセスが残っていない場合、マスターはサイクルプールを破棄し、終了します。ワーカープロセスがこのシグナルを受け取る時、それは全てのlisteningソケットを閉じ、キャンセルできないイベントがスケジュールされなくなるまで待ち、サイクルプールを破棄して終了します。キャッシュマネージャーあるいはキャッシュローダー プロセスはこのシグナルを受け取り、すぐに終了します。プロセスがこのシグナルを受け取るとngx_quit
変数は1
に設定され、処理された後ですぐに再設定されます。ワーカープロセスがシャットダウン状態にある間はngx_exiting
変数は1
に設定されます。 -
NGX_TERMINATE_SIGNAL
(ほとんどのシステム上でSIGTERM
) — 終了します。このシグナルを受け取ると、マスタープロセスは全ての子プロセスに終了シグナルを送信します。子プロセスが1秒以内に終了しない場合は、マスタープロセスはkillするためにSIGKILL
シグナルを送信します。子プロセスが無くなると、マスタープロセスはサイクルプールを破壊し、終了します。ワーカープロセス、キャッシュマネージャー プロセス あるいは キャッシュローダー プロセスがこのシグナルを受け取ると、サイクルプールを破棄し終了します。このシグナルが受け取られると、変数ngx_terminate
は1
に設定されます。 -
NGX_NOACCEPT_SIGNAL
(ほとんどのシステムでSIGWINCH
) - 全てのワーカーとヘルパープロセスをシャットダウンします。このシグナルを受け取る時、マスタープロセスは子プロセスをシャットダウンします。以前に開始された新しいnginxバイナリが終了すると、古いマスターの子プロセスは再び開始されます。ワーカープロセスがこのシグナルを受け取ると、debug_points
ディレクティブで設定されたデバッグモードでシャットダウンします。 -
NGX_RECONFIGURE_SIGNAL
(ほとんどのシステムでSIGHUP
) - 再設定します。このシグナルを受け取ると、マスタープロセスは設定を再読み込みし、それに基づいて新しいサイクルを生成します。新しいサイクルの生成が成功すると、古いサイクルは削除され、新しい子プロセスが開始されます。一方で、古い子プロセスはNGX_SHUTDOWN_SIGNAL
シグナルを受け取ります。シングルプロセスモードでは、nginxは新しいサイクルを生成しますが、古いサイクルに紐付けられているアクティブな接続を持つクライアントがもう無くなるまで古いサイクルが維持されます。ワーカーとヘルパー プロセスはこのシグナルを無視します。 -
NGX_REOPEN_SIGNAL
(ほとんどのシステムでSIGUSR1
) — ファイルを開き直します。マスタープロセスはこのシグナルをワーカーに送信します。これはサイクルに関連する全てのopen_files
を開き直します。 -
NGX_CHANGEBIN_SIGNAL
(SIGUSR2
on most systems) — nginxのバイナリを変更します。マスタープロセスは新しいnginxバイナリを開始し、全てのlistenソケットのリストを渡します。“NGINX”
環境変数で渡されるテキスト形式のリストはセミコロンで区切られたディスクリプタの数字から成ります。新しいnginxバイナリは“NGINX”
変数を読み、ソケットをその初期サイクルに追加します。他のプロセスはこのシグナルを無視します。
全てのnginxのワーカープロセスはPOSIXシグナルを受け取り適切に処理することができますが、マスタープロセスはシグナルをワーカーとヘルパーに渡すために標準的なkill()
システムコールを使いません。代わりにnginxは全てのnginxプロセス間でメッセージを送信することができる内部プロセス ソケットのペアを使います。しかし、現在のところメッセージはマスターから子へのみ送信されます。メッセージは標準的なシグナルを運びます。
スレッド
nginxワーカープロセスをブロックしてしまうものを別のスレッドタスクに押し付けることができます。例えば、nginxはfile I/Oをするためにスレッドを使うように設定することができます。他の使い方は、非同期インタフェースを持たないため通常はnginxで使うことができないライブラリです。スレッド インタフェースはクライアント接続を処理する既存の非同期のやり方のためのヘルパーであることを思い出してください。決して置き換えをするつもりではありません。
同期を扱うためには、以下のpthreads
プリミティブのラッパーが利用可能です:
-
typedef pthread_mutex_t ngx_thread_mutex_t;
-
ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
-
typedef pthread_cond_t ngx_thread_cond_t;
-
ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
各タスクのために新しいスレッドを作成する代わりに、nginxはthread_pool 戦略を実装します。複数のスレッドプールは異なる目的(例えば、ディスクの異なるセット上でのI/Oの実施)のために設定されるかもしれません。各スレッドプールは起動時に生成され、タスクのキューを処理する限られた数のスレッドを含みます。タスクが完了した時に、事前定義された完了ハンドラが呼ばれます。
src/core/ngx_thread_pool.h
ヘッダファイルは関連する定義を含みます:
struct ngx_thread_task_s { ngx_thread_task_t *next; ngx_uint_t id; void *ctx; void (*handler)(void *data, ngx_log_t *log); ngx_event_t event; }; typedef struct ngx_thread_pool_s ngx_thread_pool_t; ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name); ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name); ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size); ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);
設定時に、スレッドを喜んで使うモジュールはngx_thread_pool_add(cf, name)
を呼ぶことでスレッドプールへのリファレンスを取得する必要があります。これは指定された名前
の新しいスレッドプールを作成するか、既に存在する場合はその名前を持つプールへのリファレンスを返します。
実行時に特定のスレッドプールtp
のキューへタスク
を追加するには、ngx_thread_task_post(tp, task)
関数を使ってください。スレッド内で関数を実行するには、ngx_thread_task_t
構造を使ってパラメータを渡し、ハンドラを完了ハンドラをセットアップします:
typedef struct { int foo; } my_thread_ctx_t; static void my_thread_func(void *data, ngx_log_t *log) { my_thread_ctx_t *ctx = data; /* this function is executed in a separate thread */ } static void my_thread_completion(ngx_event_t *ev) { my_thread_ctx_t *ctx = ev->data; /* executed in nginx event loop */ } ngx_int_t my_task_offload(my_conf_t *conf) { my_thread_ctx_t *ctx; ngx_thread_task_t *task; task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t)); if (task == NULL) { return NGX_ERROR; } ctx = task->ctx; ctx->foo = 42; task->handler = my_thread_func; task->event.handler = my_thread_completion; task->event.data = ctx; if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) { return NGX_ERROR; } return NGX_OK; }
モジュール
新しいモジュールの追加
各スタンドアローンのnginxモジュールは少なくとも2つのファイルを含む別個のディレクトリ内にあります: config
とモジュールソースコードを持つファイル。config
ファイルはnginxがモジュールを統合するために必要な全ての情報を含みます。例えば:
ngx_module_type=CORE ngx_module_name=ngx_foo_module ngx_module_srcs="$ngx_addon_dir/ngx_foo_module.c" . auto/module ngx_addon_name=$ngx_module_name
config
ファイルは以下の変数を設定およびアクセスすることができるPOSIXシェルスクリプトです:
-
ngx_module_type
— 構築するモジュールの型。可能な値はCORE
,HTTP
,HTTP_FILTER
,HTTP_INIT_FILTER
,HTTP_AUX_FILTER
,MAIL
,STREAM
あるいはMISC
です。 -
ngx_module_name
— モジュール名。ソースファイルのセットから多数のモジュールを構築するには、空白区切りの名前のリストを指定します。最初の名前は動的モジュールのための出力バイナリの名前を示します。リスト内の名前はソースコード内で使われる名前に一致する必要があります。 -
ngx_addon_name
— 設定スクリプトのコンソール出力内で現れるモジュールの名前。 -
ngx_module_srcs
— モジュールをコンパイルするために使われるソースファイルの空白区切りのリスト。$ngx_addon_dir
変数はモジュールディレクトリのパスを表すために使うことができます。 -
ngx_module_incs
— モジュールをビルドするために必要なインクルードパス。 -
ngx_module_deps
— モジュールの依存の空白区切りのリスト。通常はヘッダファイルのリストです。 -
ngx_module_libs
— モジュールとリンクするライブラリの空白区切りのリスト。例えば、libpthread
をリンクするにはngx_module_libs=-lpthread
を使ってください。以下のマクロはnginxとして同じライブラリをリンクするために使うことができます:LIBXSLT
,LIBGD
,GEOIP
,PCRE
,OPENSSL
,MD5
,SHA1
,ZLIB
およびPERL
。 -
ngx_module_link
— ビルドシステムによって、動的なモジュールについてはDYNAMIC
、あるいは静的なモジュールについてはADDON
が設定される変数。リンク型に依存して実施する異なるアクションを決定するために使われます。 -
ngx_module_order
— モジュールのロード順;HTTP_FILTER
とHTTP_AUX_FILTER
モジュール型に便利です。このオプションの形式はモジュールの空白区切りのリストです。現在のモジュール名に続くリスト内のすべてのモジュールは、最後にはモジュールの初期化を順序を設定するモジュールのグローバルリストになります。フィルターモジュールにとって、後での初期化は早い実行を意味します。以下のモジュールはリファレンスとして一般的に使われます。
ngx_http_copy_filter_module
は他のフィルタモジュールのためにデータを読み込み、最初に実行されるものの1つにするためにリストの一番下近くに配置されます。ngx_http_write_filter_module
はクライアント ソケットへデータを書き込み、最後に実行されるようにリストの一番上近くに配置されます。デフォルトでは、フィルタハンドラがコピーフィルタハンドラの後で実行されるように、フィルタモジュールはモジュールリストの中の
ngx_http_copy_filter
の前に配置されます。他のモジュールの型については、デフォルトは空の文字列です。
モジュールをnginxに静的にコンパイルするには、設定スクリプトへ--add-module=/path/to/module
引数を使ってください。後でnginxに動的にロードするには、--add-dynamic-module=/path/to/module
引数を使ってください。
コア モジュール
モジュールはnginxの構築ブロックで、機能のほとんどはモジュールとして実装されます。モジュールのソースファイルは型ngx_module_t
のグローバル変数を含まなければなりません。これは以下のように定義されます:
struct ngx_module_s { /* private part is omitted */ void *ctx; ngx_command_t *commands; ngx_uint_t type; ngx_int_t (*init_master)(ngx_log_t *log); ngx_int_t (*init_module)(ngx_cycle_t *cycle); ngx_int_t (*init_process)(ngx_cycle_t *cycle); ngx_int_t (*init_thread)(ngx_cycle_t *cycle); void (*exit_thread)(ngx_cycle_t *cycle); void (*exit_process)(ngx_cycle_t *cycle); void (*exit_master)(ngx_cycle_t *cycle); /* stubs for future extensions are omitted */ };
省略されたprivate部分はモジュールのバージョン、シグネチャーを含み、事前定義されたマクロNGX_MODULE_V1
を使って埋められます。
各モジュールはprivateデータをctx
フィールドに保持し、設定ディレクティブを理解し、commands
配列の中で指定され、nginxのライフサイクルの特定のステージで起動することができます。モジュールのライフサイクルは以下のイベントから成ります:
- 設定ディレクティブハンドラはマスタープロセスのコンテキスト中の設定ファイルの中で現れた時に呼ばれます。
-
設定のパースが成功した後で、
init_module
ハンドラがマスタープロセスのコンテキストの中で呼ばれます。init_module
ハンドラは設定がロードされる度にマスタープロセスの中で呼ばれます。 -
マスタープロセスは1つ以上のワーカープロセスを生成し、それぞれの中で
init_process
ハンドラが呼ばれます。 -
ワーカープロセスがマスターからシャットダウンあるいは中止コマンドを受け取ると、それは
exit_process
ハンドラを起動します。 -
マスタープロセスは終了前に
exit_master
ハンドラを呼びます。
スレッドはnginx内で独自のAPIを使って捕捉のI/O機能としてのみ使われるため、init_thread
と exit_thread
ハンドラは正しく呼ばれません。init_masterハンドラは不必要なオーバーヘッドがあるため、init_master
ハンドラもありません。
モジュールのtype
型はctx
フィールド内に何が保持されるかを正確に定義します。その値は以下の型の1つです:
NGX_CORE_MODULE
NGX_EVENT_MODULE
NGX_HTTP_MODULE
NGX_MAIL_MODULE
NGX_STREAM_MODULE
NGX_CORE_MODULE
は最も基本的で、従って最も一般的で最も低レベルのモジュールの型です。他のモジュール型はその上に実装され、イベントあるいはHTTPリクエストの処理のような対応する問題ドメインを扱うための便利な方法を提供します。
コアモジュールのセットは ngx_core_module
, ngx_errlog_module
, ngx_regex_module
, ngx_thread_pool_module
そしてngx_openssl_module
モジュールを含みます。HTTPモジュール、ストリーム モジュール、メール モジュール およびイベントモジュールもコア モジュールです。コアモジュールのコンテキストは以下のように定義されます:
typedef struct { ngx_str_t name; void *(*create_conf)(ngx_cycle_t *cycle); char *(*init_conf)(ngx_cycle_t *cycle, void *conf); } ngx_core_module_t;
ここで、name
はモジュール名の文字列で、create_conf
と init_conf
はそれぞれモジュールの設定を生成および初期化する関数へのポインタです。コアモジュールについては、nginxは新しい設定をパースする前にcreate_conf
を呼び、全ての設定のパースが成功した後で init_conf
を呼ぶでしょう。代表的な create_conf
関数は設定のためのメモリを割り当て、デフォルトの値を設定します。
例えば、ngx_foo_module
と呼ばれる極度に単純化されたモジュールはこのように見えるかもしれません:
/* * Copyright (C) Author. */ #include <ngx_config.h> #include <ngx_core.h> typedef struct { ngx_flag_t enable; } ngx_foo_conf_t; static void *ngx_foo_create_conf(ngx_cycle_t *cycle); static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf); static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_foo_enable_post = { ngx_foo_enable }; static ngx_command_t ngx_foo_commands[] = { { ngx_string("foo_enabled"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, 0, offsetof(ngx_foo_conf_t, enable), &ngx_foo_enable_post }, ngx_null_command }; static ngx_core_module_t ngx_foo_module_ctx = { ngx_string("foo"), ngx_foo_create_conf, ngx_foo_init_conf }; ngx_module_t ngx_foo_module = { NGX_MODULE_V1, &ngx_foo_module_ctx, /* module context */ ngx_foo_commands, /* module directives */ NGX_CORE_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_foo_create_conf(ngx_cycle_t *cycle) { ngx_foo_conf_t *fcf; fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t)); if (fcf == NULL) { return NULL; } fcf->enable = NGX_CONF_UNSET; return fcf; } static char * ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_foo_conf_t *fcf = conf; ngx_conf_init_value(fcf->enable, 0); return NGX_CONF_OK; } static char * ngx_foo_enable(ngx_conf_t *cf, void *post, void *data) { ngx_flag_t *fp = data; if (*fp == 0) { return NGX_CONF_OK; } ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled"); return NGX_CONF_OK; }
設定ディレクティブ
ngx_command_t
型は1つの設置ディレクティブを定義します。設定をサポートする各モジュールは、引数をどうやって処理するか、どのハンドラを呼ぶかを記述する、そのような構造の配列を提供します:
typedef struct ngx_command_s ngx_command_t; 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; };
特殊な値 ngx_null_command
を使って配列を終えます。name
は設定ファイルの中で現れるようなディレクティブの名前です。例えば "worker_processes" あるいは "listen" です。type
はディレクティブが取る引数の数、その型、およびそれが現れるコンテキストを指定するフラグのビット フィールドです。フラグは以下の通りです:
-
NGX_CONF_NOARGS
— ディレクティブは何も引数を取りません。 -
NGX_CONF_1MORE
— ディレクティブは1つあるいは2つの引数を取ります。 -
NGX_CONF_2MORE
— ディレクティブは2つ以上の引数を取ります。 -
NGX_CONF_TAKE1
..NGX_CONF_TAKE7
— ディレクティブは確実に示された数の引数を取ります。 -
NGX_CONF_TAKE12
,NGX_CONF_TAKE13
,NGX_CONF_TAKE23
,NGX_CONF_TAKE123
,NGX_CONF_TAKE1234
— ディレクティブは異なる数の引数を取るかもしれません。オプションは指定された数に制限されます。例えば、NGX_CONF_TAKE12
は1つあるいは2つの引数を取ります。
ディレクティブの型のためのフラグは以下の通りです:
-
NGX_CONF_BLOCK
— ディレクティブはブロックです。つまり、括弧内に他のディレクティブを含むか、内部のコンテンツを処理するために独自のパーサを実装しさえするかもしれません。 -
NGX_CONF_FLAG
— ディレクティブは真偽値、on
あるいはoff
のどちらかを取ります。
ディレクティブのコンテキストはそれが設定内のどこに現れるかを定義します:
-
NGX_MAIN_CONF
— トップレベルのコンテキスト内。 -
NGX_HTTP_MAIN_CONF
—http
ブロック内。 -
NGX_HTTP_SRV_CONF
—server
ブロック内のhttp
ブロック内。 -
NGX_HTTP_LOC_CONF
—http
ブロック内のlocation
ブロックの中。 -
NGX_HTTP_UPS_CONF
—http
ブロック内のupstream
ブロックの中。 -
NGX_HTTP_SIF_CONF
—http
ブロックの中のserver
ブロック内のif
ブロックの中。 -
NGX_HTTP_LIF_CONF
—http
ブロックの中のlocation
ブロック内のif
ブロックの中。 -
NGX_HTTP_LMT_CONF
—http
ブロック内のlimit_except
ブロックの中。 -
NGX_STREAM_MAIN_CONF
—stream
ブロックの中。 -
NGX_STREAM_SRV_CONF
—stream
ブロック内のserver
ブロックの中。 -
NGX_STREAM_UPS_CONF
—stream
ブロック内のupstream
ブロックの中。 -
NGX_MAIL_MAIN_CONF
—mail
ブロックの中。 -
NGX_MAIL_SRV_CONF
—mail
ブロック内のserver
ブロックの中。 -
NGX_EVENT_CONF
—event
ブロックの中。 -
NGX_DIRECT_CONF
— コンテキストの階層構造を作成しないモジュールによって使われ、1つのグローバル設定のみを持ちます。この設定はconf
引数としてハンドラに渡されます。
設定パーサは間違ったディレクティブの場合にエラーを投げるためにこのフラグを使用し、適切な設定ポインタによって提供されるディレクティブハンドラを呼び出します。そしやって、異なるlocation内の同じディレクティブが個々の場所でそれらの値を格納できるようにします。
set
フィールドはディレクティブを処理するハンドラを定義し、パースされた値を対応する設定に格納します。一般的な変換を行う多くの関数があります:
-
ngx_conf_set_flag_slot
— リテラル文字列on
とoff
をそれぞれ1または0の値を持つngx_flag_t
値に変換する。 -
ngx_conf_set_str_slot
— 文字列をngx_str_t
型の値として格納する。 -
ngx_conf_set_str_array_slot
— 値を文字列ngx_str_t
の配列ngx_array_t
に追加します。もし配列がまだ存在しない場合はそれを生成します。 -
ngx_conf_set_keyval_slot
— キー-値のペアをキー-値のペアngx_keyval_t
の配列ngx_array_t
に追加します。最初の文字列はキーになり、2つ目は値になります。もし配列がまだ存在しない場合はそれを生成します。 -
ngx_conf_set_num_slot
— ディレクティブの引数をngx_int_t
値に変換します。 -
ngx_conf_set_size_slot
— size を バイトで表現されるsize_t
値に変換します。 -
ngx_conf_set_off_slot
— offset を バイトで表現されるoff_t
値に変換します。 -
ngx_conf_set_msec_slot
— time をミリ秒で表現されるngx_msec_t
値に変換します。 -
ngx_conf_set_sec_slot
— time を秒で表現されるtime_t
値に変換します。 -
ngx_conf_set_bufs_slot
— 与えられた2つの引数を、バッファの数とsizeを保持するngx_bufs_t
オブジェクトに変換します。 -
ngx_conf_set_enum_slot
— 与えられた引数をngx_uint_t
値に変換します。post
で渡されるnullで終了するngx_conf_enum_t
の配列は受付可能な文字列と対応する整数値を定義します。 -
ngx_conf_set_bitmask_slot
— 与えられた引数をngx_uint_t
値に変換します。各引数についてのマスク値は結果を生成する時にORされます。post
で渡されるnullで終了するngx_conf_bitmask_t
の配列は受付可能な文字列と対応するマスク値を定義します。 -
set_path_slot
— 与えられた引数をngx_path_t
値に変換し、全ての必要とされる初期化を行います。詳細は proxy_temp_path ディレクティブのドキュメントを見てください。 -
set_access_slot
— 与えられた引数をファイルパーミッションのマスクに変換します。詳細は、 proxy_store_access ディレクティブのドキュメントを見てください。
conf
フィールドはどの設定構造がディレクトリ ハンドラに渡されるかを定義します。コア モジュールはグローバル設定のみを持ち、それにアクセスするためにNGX_DIRECT_CONF
フラグを設定します。HTTP、ストリームあるいはメールのようなモジュールは設定の階層構造を持ちます。例えば、モジュールの設定はserver
, location
と if
スコープのために生成されます。
-
NGX_HTTP_MAIN_CONF_OFFSET
—http
ブロックのための設定。 -
NGX_HTTP_SRV_CONF_OFFSET
—http
ブロック内のserver
ブロックのための設定。 -
NGX_HTTP_LOC_CONF_OFFSET
—http
内のlocation
ブロックのための設定。 -
NGX_STREAM_MAIN_CONF_OFFSET
—stream
ブロックのための設定。 -
NGX_STREAM_SRV_CONF_OFFSET
—stream
ブロック内のserver
ブロックのための設定。 -
NGX_MAIL_MAIN_CONF_OFFSET
—mail
ブロックのための設定。 -
NGX_MAIL_SRV_CONF_OFFSET
—mail
ブロック内のserver
ブロックのための設定。
offset
はこの特定のディレクティブのために保持するモジュール設定構造内のフィールドのオフセットを定義します。代表的な使い方はoffsetof()
マクロを使用することです。
post
フィールドは2つの目的を持ちます: メインのハンドラが完了した後で呼ばれるハンドラを定義する、あるいは追加のデータをメインのハンドラに渡すために使われるかもしれません。最初の場合は、ngx_conf_post_t
構造はハンドラへのポインタを使って初期化される必要があります。例えば:
static char *ngx_do_foo(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_foo_post = { ngx_do_foo };
post
引数はngx_conf_post_t
オブジェクト自身で、data
は値へのポインタで、適切な型を使ってメインのハンドラによって引数から変換されます。
HTTP
接続
各HTTPクライアント接続は以下のステージを通過します:
-
ngx_event_accept()
クライアントのTCP接続を受け付けます。このハンドラはlistenソケット上での通知の読み込みに応じて呼ばれます。新しいngx_connection_t
オブジェクトはこのステージで新しく受け付けられたクライアントのソケットをラップするために生成されます。各nginxのlistenerは新しい接続オブジェクトを渡すためにハンドラを提供します。HTTP接続については、それはngx_http_init_connection(c)
です。 -
ngx_http_init_connection()
はHTTP接続の早期初期化を行います。このステージで、ngx_http_connection_t
オブジェクトは接続のために生成され、その参照は接続のdata
フィールドに格納されます。後でそれはHTTPリクエストオブジェクトによって置き換えられるでしょう。PROXY プロトコル パーサとSSLハンドシェイクもこのステージで開始されます。 -
ngx_http_wait_request_handler()
は読み込みイベントハンドラで、クライアントのソケットでデータが利用可能になると呼ばれます。このステージで、HTTPリクエストオブジェクトngx_http_request_t
が生成され、接続のdata
フィールドが設定されます。 -
ngx_http_process_request_line()
読み込みイベントハンドラはクライアントのリクエスト行を読み込みます。ハンドラはngx_http_wait_request_handler()
によって設定されます。データは接続のバッファ
に読み込まれます。バッファのサイズは最初はディレクティブclient_header_buffer_sizeによって設定されます。クライアント ヘッダ全体はバッファ内に収まると考えられています。もし初期サイズが十分では無い場合は、large_client_header_buffers
ディレクティブで容量を設定することで、より大きなバッファが割り当てられます。 -
ngx_http_process_request_headers()
は読み込みイベントハンドラで、クライアントリクエストヘッダを読み込むためにngx_http_process_request_line()
の後に設定されます。 -
ngx_http_core_run_phases()
はリクエストヘッダが完全に読み込みおよびパースされた時に呼ばれます。この関数はNGX_HTTP_POST_READ_PHASE
からNGX_HTTP_CONTENT_PHASE
までリクエストのフェーズを実行します。最後のフェーズは応答を生成し、それをフィルタチェーンによって渡すことを目的にしています。このフェーズでは応答はクライアントに送信される必要はありません。それはバッファされたままで、完了ステージで送信されるでしょう。 -
ngx_http_finalize_request()
は通常リクエストが全ての出力を生成あるいはエラーを生成した時に呼ばれます。後者では、適切なエラーページが調べられ、応答として使われます。もし応答がこの時点までに完全にクライアントに送信されていない場合は、出力データの送信を完了するためにHTTP書き込みngx_http_writer()
が起動されます。 -
クライアントへ完全な応答が送信され、リクエストを破壊することができる場合は、
ngx_http_finalize_connection()
が呼ばれます。クライアント接続のkeepalive機能が有効な場合、ngx_http_set_keepalive()
が呼ばれます。それが現在のリクエストを破壊し、接続上の次のリクエストを待ちます。そうでなければ、ngx_http_close_request()
がリクエストと接続の両方を破壊します
リクエスト
各クライアントのHTTPリクエストに関して、ngx_http_request_t
オブジェクトが生成されます。このオブジェクトの幾つかのフィールド:
-
connection
—ngx_connection_t
クライアント接続オブジェクトへのポインタ幾つかのリクエストは同時に同じ接続オブジェクトを参照するかも知れません - 1つのメインのリクエストとそのサブリクエスト。リクエストが削除された後で、新しいリクエストが同じ接続上で生成されるかもしれません。HTTP接続については、
ngx_connection_t
のdata
フィールドが応答を指します。接続に紐付けられた他のリクエストと対称的に、そのようなリクエストはアクティブと呼ばれます。アクティブなリクエストはクライアント接続イベントを処理するために使われ、応答をクライアントに出力することができます。通常、各リクエストは出力を送信できるように同じ場所でアクティブになります。 -
ctx
— HTTPモジュールコンテキストの配列各モジュールの型NGX_HTTP_MODULE
はリクエスト内に任意の値(通常、構造へのポインタ)を保持することができます。値はモジュールのctx_index
の場所のctx
配列の中に格納されます。以下のマクロはリクエストコンテキストを取得および設定する便利な方法を提供します:-
ngx_http_get_module_ctx(r, module)
—module
のコンテキストを返す -
ngx_http_set_ctx(r, c, module)
—module
のコンテキストとしてc
を設定する
-
-
main_conf
,srv_conf
,loc_conf
— 現在のリクエスト設定の配列設定はモジュールのctx_index
の場所に格納されます -
read_event_handler
,write_event_handler
- リクエストのための読み込みおよび書き込みイベントハンドラ通常は、HTTP接続のための読み込みおよび書き込みの両方のイベントハンドラはngx_http_request_handler()
に設定されます。この関数は現在有効なリクエストのread_event_handler
とwrite_event_handler
のハンドラを呼びます -
cache
— upstream応答をキャッシュするためのリクエストキャッシュオブジェクト -
upstream
— プロキシのためのリクエストupstreamオブジェクト -
pool
— リクエストプール。リクエストオブジェクト自身はこのプール内に割り当てられます。これはリクエストが削除された時に破棄されます。クライアント接続のライフタイムを通して利用可能でなければならない割り当てのために、ngx_connection_t
のプールを代わりに使います。 -
header_in
— クライアントのHTTPリクエスト ヘッダが読み込まれるバッファ。 -
headers_in
,headers_out
— 入力および出力HTTPヘッダオブジェクト。両方のオブジェクトはヘッダの生リストを保持する型ngx_list_t
のheaders
フィールドを含みます。それに加えて、例えばcontent_length_n
,status
のような分離されたフィールドとして取得および設定のための特定のヘッダが利用可能です。 -
request_body
— クライアントのリクエストボディオブジェクト -
start_sec
,start_msec
— リクエストが生成された時の時間ポイント。リクエストの維持期間を追跡するために使われます。 -
method
,method_name
— クライアントHTTPリクエストメソッドの数値および文字表現。メソッドの数値はマクロNGX_HTTP_GET
,NGX_HTTP_HEAD
,NGX_HTTP_POST
などを使ってsrc/http/ngx_http_request.h
内で定義されます。 -
http_protocol
— 元のテキストフォーム内のクライアントHTTPプロトコルバージョン (“HTTP/1.0”, “HTTP/1.1” など)。 -
http_version
— 数値形式のクライアントHTTPプロトコルバージョン (NGX_HTTP_VERSION_10
,NGX_HTTP_VERSION_11
など)。 -
http_major
,http_minor
— メジャーとマイナー部分に分割された数値形式のクライアントHTTPプロトコルバージョン。 -
request_line
,unparsed_uri
— 元のクライアント リクエスト内のリクエスト行とURI。 -
uri
,args
,exten
— 現在のリクエストのためのURI、引数およびファイル拡張子。ここでのURIの値は正規化のためクライアントによって送信された元のURIとは異なるかも知れません。リクエストの処理の間、これらの値は内部リダイレクトが実施されるとして変更することができます。 -
main
— メインのリクエストオブジェクトへのポインタこのオブジェクトはメインのリクエスト内で特定のサブタスクを実施するために生成されるサブリクエストとは対照的に、クライアントのHTTPリクエストを処理するために生成されます。 -
parent
— サブリクエストの親リクエストへのポインタ -
postponed
— 出力バッファおよびサブリクエストの送信され生成された順のリスト。リストはリクエストの出力の一部がサブリクエストによって生成された時に、一貫したリクエストの出力を提供するために後段のフィルタで使われます。 -
post_subrequest
— サブリクエストが完了した時にコンテキストと一緒に呼ばれるハンドラへのポインタ。メインのリクエストに使われないもの。 -
posted_requests
— 開始あるいは再開されるリクエストのリスト。これはリクエストのwrite_event_handler
を呼び出すことで行われます。通常、このハンドラはリクエストのメイン関数を保持します。これは最初にリクエストフェーズを実行し、その後に出力を生成します。リクエストは通常
ngx_http_post_request(r, NULL)
の呼び出しによって投稿されます。それは常にメインのリクエストposted_requests
リストに投稿されます。関数ngx_http_run_posted_requests(c)
は通過した接続のアクティブなリクエストのメインのリクエストの中で投稿された全てのリクエストを実行します。全てのイベントハンドラはngx_http_run_posted_requests
を呼びます。これは新しいポストされたリクエストに繋がるかもしれません。通常は、それはリクエストの読み込みあるいは書き込みハンドラを起動した後で呼ばれます。 -
phase_handler
— 現在のリクエストフェーズのインデックス。 -
ncaptures
,captures
,captures_data</c2 — リクエストの最後のregexの合致によって生成されたregexのキャプチャ。リクエストの処理の間に多数の箇所でregexが一致するかもしれません: mapの調査、SNIあるいはHTTP Hostによるサーバ調査、rewrite、proxy_redirectなど。調査によって生成されたキャプチャは上で述べられたフィールドに格納されます。フィールド
ncaptures
はキャプチャの数を、captures
は境界を、captures_data
は文字列を保持します。それらはregexが合致し、キャプチャを抽出するために使われるべきものです。それぞれの新しいregexがリクエストに一致した後で、キャプチャは新しい値を保持するために再設定されます。 -
count
— リクエストの参照カウンタ。フィールドはメインのリクエストについてのみ意味を持ちます。カウンタの増加は単純にr->main->count++
で行われます。カウンタを減少するにはngx_http_finalize_request(r, rc)
が呼ばれるべきです。サブリクエストの生成およびリクエストボディの読み込み処理の実行は両方ともカウンタを増加します。 -
subrequests
— 現在のサブリクエストの入れ子のレベル。各サブリクエストは親の入れ子レベルから1つ減ったものを継承します。値が0になると、エラーが起こります。メインのリクエストの値はNGX_HTTP_MAX_SUBREQUESTS
定数によって定義されます。 -
uri_changes
— そのリクエストに残されているURI変更の残り数。リクエストがURIを変更することができる回数の総数はNGX_HTTP_MAX_URI_CHANGES
定数によって制限されます。各変更に従って値は0になるまで減らされます。0になるとエラーが生成されます。通常あるいは名前付きのlocationへのRewriteおよび内部リダイレクトはURIの変更と見なされます。 -
blocked
— リクエスト上で保持されるブロックのカウンタ。この値が非0の間、リクエストは中止することができません。現在のところ、この値はAIO操作(POSIX AIO とスレッド操作)の延期とアクティブキャッシュのロックによって増加されます。 -
buffered
— どのモジュールがリクエストによって生成された出力をバッファしているかを示すビットマスク。多くのフィルタは出力をバッファすることができます; 例えば、sub_filter は部分文字列一致のためにデータをバッファすることができ、copyフィルタは空いている出力バッファの不足のためにデータをバッファすることができます。この値が非0である限り、リクエストはフラッシュの中止を終了されません。 -
header_only
— 出力がボディを必要としないことを示すフラグ。例えば、このフラグはHTTP HEADリクエストによって使われます。 -
keepalive
— クライアント接続のkeepaliveがサポートされるかを示すフラグ。値はHTTPバージョンと “Connection” ヘッダの値から継承されます -
header_sent
— 出力ヘッダがリクエストによって既に送信されたことを示すフラグ。 -
internal
— 現在のリクエストが内部的なものであることを示すフラグ。内部の状態に入るために、リクエストはリクエストが内部リダイレクトされるかサブリクエストである必要があります。内部リクエストは内部のlocationに入ることができます。 -
allow_ranges
— HTTP Rangeヘッダによってリクエストされたため、部分的な応答がクライアントに送信されるかもしれないことを示すフラグ。 -
subrequest_ranges
— サブリクエストが処理されている間、部分的な応答が送信されるかもしれないことを示すフラグ。 -
single_range
— 出力データの1つの連続する範囲だけがクライアントに送信することができることを示すフラグ。このフラグは通常データのストリームが送信される時に設定されます。例えば、プロキシされるサーバから1つのバッファ内で全ての応答が利用可能でない場合。 -
main_filter_need_in_memory
,filter_need_in_memory
— 出力がメモリバッファ内でなくファイル内で生成されなければならないことを示すフラグ。これはsendfileが有効な場合でもファイルバッファからデータを読み込むためのcopyフィルタへのシグナルです。これら二つのフラグの違いは、それらを送信するフィルタモジュールの場所です。フィルタチェーン内のpostponeフィルタの前に呼ばれるフィルタは、現在のリクエストの出力だけがメモリバッファから来なければならないことを要求するfilter_need_in_memory
を設定します。フィルタチェーン内で後で呼ばれるフィルタは、出力の送信の間にメインのリクエストと全てのサブリクエストの両方がメモリ内のファイルを読み込むことを要求するmain_filter_need_in_memory
を設定します -
filter_need_temporary
— リクエストの出力がreadonlyメモリバッファあるいはfileバッファではなく、一時バッファ内で生成されなければならないことを示すフラグ。これは、送信される時に出力が直接バッファ内で変更されるかも知れないフィルタによって使われます。
設定
各HTTPモジュールは3つの種類の設定を持つかも知れません:
-
この設定は
http
ブロック全体に適用されます。モジュールのためのグローバル設定としての関数。 -
サーバ設定 — 1つの
server
ブロックに適用されます。モジュールのためのサーバ固有の設定としての関数。 -
location 設定 — 1つの
location
,if
あるいはlimit_except
ブロックへ適用されます。モジュールのためのlocation固有の設定としての関数。
設定構造は関数の呼び出しによってnginx設定ステージで生成されます。これらの構造を割り当て、初期化し、そして合併します。以下の例はモジュールのための簡単な location 設定を生成する方法を示します。設定はunsigned integer型の1つの設定foo
を持ちます。
typedef struct { ngx_uint_t foo; } ngx_http_foo_loc_conf_t; static ngx_http_module_t ngx_http_foo_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_foo_create_loc_conf, /* create location configuration */ ngx_http_foo_merge_loc_conf /* merge location configuration */ }; static void * ngx_http_foo_create_loc_conf(ngx_conf_t *cf) { ngx_http_foo_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_foo_loc_conf_t)); if (conf == NULL) { return NULL; } conf->foo = NGX_CONF_UNSET_UINT; return conf; } static char * ngx_http_foo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_foo_loc_conf_t *prev = parent; ngx_http_foo_loc_conf_t *conf = child; ngx_conf_merge_uint_value(conf->foo, prev->foo, 1); }
例にあるように、ngx_http_foo_create_loc_conf()
関数は新しい設定構造を生成し、ngx_http_foo_merge_loc_conf()
は設定を高レベルからの他の設定とマージします。実際、serverとlocation設定はserverとlocationレベルだけに存在するだけでなく、その上の全ての設定のためにも生成されます。特に、server設定はmainレベルに生成され、location設定はmain, server および location レベルのために生成されます。これらの設定によりnginx設定ファイルの任意のレベルでserverおよびlocation固有の設定を指定することができます。結果的に設定がマージされます。NGX_CONF_UNSET
および NGX_CONF_UNSET_UINT
のような多くのマクロが、失われた設定を示しマージの間無視するために提供されます。ngx_conf_merge_value()
およびngx_conf_merge_uint_value()
のような標準のnginxマージ マクロ は、明示的な値で設定がされない場合に設定をマージしデフォルト値を設定する簡単な方法を提供します。異なる型のための完全なマクロのリストについては、src/core/ngx_conf_file.h
を見てください。
以下のマクロが利用可能です。設定時のHTTPモジュールのための設定へアクセスするため。それら全て最初の引数としてngx_conf_t
の参照を受け取ります。
-
ngx_http_conf_get_module_main_conf(cf, module)
-
ngx_http_conf_get_module_srv_conf(cf, module)
-
ngx_http_conf_get_module_loc_conf(cf, module)
以下の例は標準nginxコアモジュール ngx_http_core_module のlocation設定へのポインタを取得し、構造の handler
フィールド内に保持されたlocationコンテントハンドラを置き換えます。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r); static ngx_command_t ngx_http_foo_commands[] = { { ngx_string("foo"), NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, ngx_http_foo, 0, 0, NULL }, ngx_null_command }; static char * ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_bar_handler; return NGX_CONF_OK; }
以下のマクロが実行時にHTTPモジュールのための設定にアクセスするために利用可能です。
-
ngx_http_get_module_main_conf(r, module)
-
ngx_http_get_module_srv_conf(r, module)
-
ngx_http_get_module_loc_conf(r, module)
これらのマクロはHTTPリクエスト ngx_http_request_t
へのリファレンスを受け取ります。リクエストのメインの設定は変更されません。server 設定は、リクエストのためのバーチャルサーバを選択した後で、デフォルトのものから変わるかもしれません。リクエストの処理のために選択されたlocationの設定は、rewrite操作あるいは内部のリダイレクトの結果として複数回変更されるかもしれません。以下の例は実行時にモジュールのHTTP設定へアクセスする方法を示します。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_http_foo_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module); ... }
フェーズ
各HTTPリクエストはHTTPフェーズのシーケンスを通過します。各フェーズの中で、別個の種類の処理がリクエスト上で行われます。ほとんどのフェーズ内でモジュール固有のハンドラが登録可能で、多くの標準的なnginxモジュールがそれらのフェーズハンドラをリクエスト処理の特定のステージで呼ばれる方法として登録します。一旦リクエストがフェーズに到着すると、フェーズの処理が成功し、フェーズハンドラが呼ばれます。以下はnginx HTTPフェーズのリストです。
-
NGX_HTTP_POST_READ_PHASE
— 最初のフェーズ。ngx_http_realip_module は他のモジュールが起動される前にクライアントのアドレスの置き換えを有効にするために、このフェーズでハンドラを登録します。 -
NGX_HTTP_SERVER_REWRITE_PHASE
—server
ブロック内 (しかしlocation
ブロックの外側) で定義されたrewriteディレクティブが処理されるフェーズ。ngx_http_rewrite_module はこのフェーズにハンドラを差し込みます。 -
NGX_HTTP_FIND_CONFIG_PHASE
— リクエストURIに基づいてlocationが選択される特別なふぇjーズ。このフェーズの前に、関連するバーチャルサーバのためのデフォルトのlocationがリクエストに割り当てられ、location設定をリクエストしている全てのモジュールはデフォルトのserver locationのための設定を受け取ります。このフェーズは新しいlocationをリクエストに割り当てます。このフェーズでは追加のハンドラは登録することができません。 -
NGX_HTTP_REWRITE_PHASE
—NGX_HTTP_SERVER_REWRITE_PHASE
と同じだが、前のフェーズで選択された、locationで定義されたrewrite ruleのためのもの。 -
NGX_HTTP_POST_REWRITE_PHASE
— URIがrewriteの間に変更された場合、リクエストが新しいlocationにリダイレクトされる特別なフェーズ。これはNGX_HTTP_FIND_CONFIG_PHASE
を再び通過するリクエストによって実装されます。このフェーズでは追加のハンドラは登録することができません。 -
NGX_HTTP_PREACCESS_PHASE
— アクセス制御に関係しない、ハンドラの異なる型のための共通のフェーズ。標準のnginxモジュールngx_http_limit_conn_module および ngx_http_limit_req_module はこのフェーズにハンドラを登録します -
NGX_HTTP_ACCESS_PHASE
— クライアントがリクエストを作成する許可があるかを検証するフェーズ。ngx_http_access_module および ngx_http_auth_basic_module のような標準のnginxモジュールはこのフェーズにハンドラを登録します。デフォルトでは、リクエストが次のフェーズを続けるためにクライアントはこのフェーズで登録された全てのハンドラの認証チェックを通過しなければなりません。satisfy ディレクテイブは他のフェーズハンドラがクライアントを認証する場合処理の継続を許可するために使うことができます。 -
NGX_HTTP_POST_ACCESS_PHASE
— satisfy any ディレクティブが処理される特別なフェーズ。もし幾つかのアクセス フェーズ ハンドラがアクセスを拒否し、明示的に許可しない場合は、リクエストは終了されます。このフェーズでは追加のハンドラは登録することができません。 -
NGX_HTTP_PRECONTENT_PHASE
— ハンドラがコンテントの生成の前に呼ばれるフェーズ。 ngx_http_try_files_module と ngx_http_mirror_module のような標準的なモジュールはこのフェーズでそれらのハンドラを登録します。 -
NGX_HTTP_CONTENT_PHASE
— 応答が通常生成されるフェーズ。ngx_http_index_module あるいはngx_http_static_module
を含む、複数のnginxの標準モジュールがこのフェーズにハンドラを登録します。それらの1つが出力を生成するまで連続して呼ばれます。locationごとにコンテントハンドラを設定することもできます。ngx_http_core_moduleの location 設定がhandler
セットを持つ場合、このハンドラはコンテント ハンドラとして呼ばれ、このフェーズで差し込まれたハンドラは無視されます。 -
NGX_HTTP_LOG_PHASE
— リクエストの記録が行われるフェーズ。現在のところ ngx_http_log_module だけがアクセスログのためにこのステージでハンドラを登録します。ログフェーズハンドラはリクエスト処理のかなり最後の方で、リクエストを解放する直前に呼ばれます。
以下はアクセス前のフェーズハンドラの例です。
static ngx_http_module_t ngx_http_foo_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_str_t *ua; ua = r->headers_in->user_agent; if (ua == NULL) { return NGX_DECLINED; } /* reject requests with "User-Agent: foo" */ if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) { return NGX_HTTP_FORBIDDEN; } return NGX_DECLINED; } static ngx_int_t ngx_http_foo_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_foo_handler; return NGX_OK; }
フェーズハンドラは特定のコードを返すことを期待されます:
-
NGX_OK
— 次のフェーズに進みます。 -
NGX_DECLINED
— 現在のフェーズの次のハンドラに進みます。現在のハンドラが現在のフェーズの最後の場合、次のフェーズに進みます。 -
NGX_AGAIN
,NGX_DONE
— 例えば、非同期 I/O操作あるいは単なる遅延に成り得る何らかの将来のイベントまで、フェーズの操作を延期します。ngx_http_core_run_phases()
の呼び出しによってフェーズの処理が後で再開されると見なされます。 - フェーズハンドラによって返される他のどのような値もリクエストの終了コード、特にHTTP応答コードとして扱われます。リクエストは与えられたコードで終了されます。
幾つかのフェーズについては、返り値は少し異なる方法で扱われます。コンテントフェーズでは、NGX_DECLINED
以外のどのようなコードも終了コードと見なされます。locaion コンテント ハンドラからの返り値は終了コードと見なされます。satisfy anyモードのアクセスフェーズでは、NGX_OK
, NGX_DECLINED
, NGX_AGAIN
, NGX_DONE
意外の全ての返り値は拒否と見なされます。後に続くアクセスハンドラのいずれもが異なるコードでアクセスを許可あるいは拒否しない場合は、拒否コードが終了コードになるでしょう。
変数
既存の変数へのアクセス
変数はインデックス (これが最も一般的な方法です)あるいは名前(以下を見てください)を使って参照することができます。インデックスは変数が設定に追加される時に設定ステージで生成されます。変数のインデックスを取得するには、ngx_http_get_variable_index()
を使ってください:
ngx_str_t name; /* ngx_string("foo") */ ngx_int_t index; index = ngx_http_get_variable_index(cf, &name);
ここで、cf
はnginxの設定へのポインタで、name
は変数名を持つ文字列へのポインタです。関数はエラー時にNGX_ERROR
を返すか、そうでなければ有効なインデックスを返します。これは一般的に後で使うためにモジュール内のどこかに格納されます。
全てのHTTP変数はHTTPリクエストのコンテキスト内で評価され、結果はHTTPリクエストに固有でその中にキャッシュされます。変数を評価する全ての関数はngx_http_variable_value_t
型を返し、変数の値を表します:
typedef ngx_variable_value_t ngx_http_variable_value_t; typedef struct { unsigned len:28; unsigned valid:1; unsigned no_cacheable:1; unsigned not_found:1; unsigned escape:1; u_char *data; } ngx_variable_value_t;
ここで:
-
len
— 値の長さ -
data
— 値そのもの -
valid
— 値が有効 -
not_found
— 変数が見つからず、従ってdata
とlen
フィールドは無関係です; 例えば、対応する引数がリクエスト内で渡されなかった場合の$arg_foo
のような変数で起こるかも知れません -
no_cacheable
— 結果をキャッシュしません -
escape
— 値が出力でエスケープされる必要があると値にマークをするためにロギングモジュールによって内部的に使われます
ngx_http_get_flushed_variable()
と ngx_http_get_indexed_variable()
関数は変数の値を取得するために使われます。それらは同じインタフェースを持ちます - HTTP リクエスト r
を変数を評価するためのコンテキストとして受け取り、それを識別するためのindex
を受け取ります。一般的な使い方の例:
ngx_http_variable_value_t *v; v = ngx_http_get_flushed_variable(r, index); if (v == NULL || v->not_found) { /* we failed to get value or there is no such variable, handle it */ return NGX_ERROR; } /* some meaningful value is found */
関数間の違いはngx_http_get_indexed_variable()
はキャッシュされた値を返し、ngx_http_get_flushed_variable()
はキャッシュできない変数のためにキャッシュをフラッシュすることです。
SSIおよびPerlのような幾つかのモジュールは、設定時に名前が知られていない変数を扱う必要があります。従ってインデックスはそれらにアクセスするために使うことができませんが、ngx_http_get_variable(r, name, key)
関数が利用可能です。それは指定された名前name
と名前に由来するハッシュキー
を使って変数を検索します。
変数の作成
変数を作成するには、ngx_http_add_variable()
関数を使ってください。それは引数として、関数の挙動を制御する設定 (変数が登録された場所)、変数名およびフラグを取ります。
NGX_HTTP_VAR_CHANGEABLE
— 変数の再定義を可能にします: 他のモジュールが同じ名前で変数を定義しない場合は衝突はありません。これにより set ディレクティブが変数を上書きすることができます。NGX_HTTP_VAR_NOCACHEABLE
— キャッシングを無効にします。$time_local
のような変数にとって便利ですNGX_HTTP_VAR_NOHASH
— この変数が名前ではなくインデックスによってのみアクセス可能であることを示します。これはSSIあるいはPerlのようなモジュール内で変数が必要とされないことが分かっている場合に、使われるかも知れない小さな最適化です。NGX_HTTP_VAR_PREFIX
— この変数の名前のプリフィックスです。この場合、ハンドラは特定の変数の値を取得するための追加のロジックを実装する必要があります。例えば、リクエストの引数を検索し特定の引数の値を返す同じハンドラによって、全ての“arg_
” 変数が処理されます。
関数は、エラー時にはNULL、そうでなければngx_http_variable_t
へのポインタを返します:
struct ngx_http_variable_s { ngx_str_t name; ngx_http_set_variable_pt set_handler; ngx_http_get_variable_pt get_handler; uintptr_t data; ngx_uint_t flags; ngx_uint_t index; };
get
および set
ハンドラは変数値を取得あるいは設定するために呼ばれ、data
は変数ハンドラに渡され、index
変数を参照するために使われる割り当てられた可変のインデックスを持つでしょう。
通常、ngx_http_variable_t
構造のnullで終わる静的な配列はモジュールによって生成され、変数を設定に追加するために事前設定のステージで処理されます。例えば:
static ngx_http_variable_t ngx_http_foo_vars[] = { { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 }, ngx_http_null_variable }; static ngx_int_t ngx_http_foo_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_foo_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; }
例でのこの関数はHTTPモジュールのコンテキストのpreconfiguration
フィールドを初期化するために使われ、パーサーがこれらの変数を参照できるようにHTTP設定をパースする前に呼ばれます。
get
ハンドラは特定のリクエストのコンテキストの中で変数を評価する責任があります。例えば:
static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%uA", r->connection->number) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; }
内部エラー(例えば、メモリ割り当ての失敗)の場合はNGX_ERROR
を返し、そうでなければNGX_OK
を返します。変数の評価の状態を学ぶには、ngx_http_variable_value_t
内のフラグを調べてください (上の説明を見てください)。
set
ハンドラによって変数によるプロパティの参照を設定することができます。例えば、$limit_rate
変数の設定ハンドラはリクエストの limit_rate
フィールドを修正します:
... { ngx_string("limit_rate"), ngx_http_variable_request_set_size, ngx_http_variable_request_get_size, offsetof(ngx_http_request_t, limit_rate), NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... static void ngx_http_variable_request_set_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ssize_t s, *sp; ngx_str_t val; val.len = v->len; val.data = v->data; s = ngx_parse_size(&val); if (s == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid size \"%V\"", &val); return; } sp = (ssize_t *) ((char *) r + data); *sp = s; return; }
comoplex値
複雑な値という名前にも関わらず、それはテキスト、変数およびそれらの組み合わせを含む表現の評価を簡単にする方法を提供します。
ngx_http_compile_complex_value
内のcomplex値の概要は設定ステージにおいて、表現の評価の結果を取得するために実行時に使われるngx_http_complex_value_t
へコンパイルされます。
ngx_str_t *value; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; /* directive arguments */ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; ccv.zero = 1; ccv.conf_prefix = 1; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; }
ここで、ccv
はcomplex値 cv
を初期化することを必要とする全てのパラメータを保持します:
-
cf
— 設定ポインタ -
value
— パースする文字列(入力) -
complex_value
— コンパイルされた値(出力) -
zero
— 0で終了する値を有効にするフラグ -
conf_prefix
— 結果の前に構成プリフィックスを付けます(nginxが現在のところ構成を探しているディレクトリ) -
root_prefix
— 結果の前にルート プリフィックスを付けます (通常のnginxのインストレーション プリフィックス)
ゼロで終了する文字列を必要とするライブラリに結果が渡される場合はzero
フラグが利用可能です。ファイル名を扱う場合にはプリフィックスが便利です。
コンパイルが成功すると、cv.lengths
は表現内の変数の存在についての情報を含みます。NULL値は表現が静的なテキストのみを含むことを意味し、複雑な値としてではなく単純な文字列の中に格納できます。
ngx_http_set_complex_value_slot()
はディレクティブ定義の中でcomplex値を完全に正しく初期化するために使われる便利な関数です。
実行時には、complex値はngx_http_complex_value()
関数を使って計算されるかも知れません:
ngx_str_t res; if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) { return NGX_ERROR; }
リクエストr
と以前にコンパイルされた値 cv
が与えられると、関数は表現を評価し結果をres
に書きます。
リクエストのリダイレクト
HTTPリクエストはngx_http_request_t
構造のloc_conf
フィールドを経由して常にlocationに接続します。どのモジュールのどのlocation設定の場所でも ngx_http_get_module_loc_conf(r, module)
を呼び出すことでリクエストから検索されることができることを意味します。リクエストのlocationはリクエストの生存期間の中で何度か変更することができます。最初に、デフォルトのサーバのデフォルトのserver locationはリクエストに割り当てられます。リクエストが異なるサーバ(HTTP “Host” ヘッダあるいはSSL SNI拡張によって選択されます)に切り替えた場合、リクエストはそのサーバのデフォルトのlocationにも切り替えます。次のlocationの変更はNGX_HTTP_FIND_CONFIG_PHASE
リクエスト フェーズで起こります。このフェーズでlocationはサーバのために設定された全ての名前無しのlocationの間でリクエストのURIによって選択されます。ngx_http_rewrite_module はrewrite ディレクティブの結果としてNGX_HTTP_REWRITE_PHASE
リクエスト フェーズでリクエストURIを変更することができ、新しいURIに基づいた新しいlocationの選択のためにリクエストを NGX_HTTP_FIND_CONFIG_PHASE
フェーズに送り返します。
どの時点でもngx_http_internal_redirect(r, uri, args)
あるいは ngx_http_named_location(r, name)
のうちの1つを呼び出すことでリクエストを新しいlocationにリダイレクトすることもできます。
ngx_http_internal_redirect(r, uri, args)
関数はリクエストURIを変更し、リクエストを NGX_HTTP_SERVER_REWRITE_PHASE
フェーズに返します。リクエストはサーバのデフォルトのlocationを使って進みます。NGX_HTTP_FIND_CONFIG_PHASE
の後で新しいリクエストURIに基づいて新しいlocationが選択されます。
以下の例は新しいリクエスト引数を使って内部的なリダイレクトを行います。
ngx_int_t ngx_http_foo_redirect(ngx_http_request_t *r) { ngx_str_t uri, args; ngx_str_set(&uri, "/foo"); ngx_str_set(&args, "bar=1"); return ngx_http_internal_redirect(r, &uri, &args); }
関数ngx_http_named_location(r, name)
はリクエストを名前付きのlocationにリダイレクトします。locationの名前は引数として渡されます。リクエストは NGX_HTTP_REWRITE_PHASE
フェーズに切り替えた後で、locationは現在のサーバの全ての名前付きのlocationの中を検索します。
以下の例は名前付きの location @foo へのリダイレクトを行います。
ngx_int_t ngx_http_foo_named_redirect(ngx_http_request_t *r) { ngx_str_t name; ngx_str_set(&name, "foo"); return ngx_http_named_location(r, &name); }
両方の関数 - ngx_http_internal_redirect(r, uri, args)
と ngx_http_named_location(r, name)
はnginxモジュールが既にいくつかのコンテキストをリクエストの ctx
フィールドに格納している時に呼ばれるかもしれません。これらのコンテキストが新しいlocationの設置と矛盾するようになるかもしれません。矛盾を避けるために、全てのリクエストのコンテキストが両方のリダイレクト関数によって削除されます。
ngx_http_internal_redirect(r, uri, args)
あるいは ngx_http_named_location(r, name)
の呼び出しはリクエストの count
を増やします。一貫性のあるリクエストの参照カウントのために、リクエストをリダイレクトした後でngx_http_finalize_request(r, NGX_DONE)
を呼んでください。これは現在のリクエストコードのパスを終了し、カウンタを減らします。
リダイレクトおよびrewriteされたリクエストは内部的になり、internal locationをアクセスすることができます。内部リクエストは internal
フラグセットを持ちます。
サブリクエスト
サブリクエストは主におそらく他のデータと混ぜるために1つのリクエストの他への出力を挿入するために使われます。サブリクエストは通常のリクエストのように見えますが、幾らかのデータを親と共有します。サブリクエストはクライアントから他に何も受け取らないため、特にクライアントの入力に関係するすべてのフィールドは共有されます。サブリクエストのためのリクエストフィールドparent
は親のリクエストへのリンクを含み、メインリクエストについてはNULLです。フィールド main
はリクエストのグループ内のメインリクエストへのリンクを含みます。
サブリクエストは NGX_HTTP_SERVER_REWRITE_PHASE
フェーズ内で開始します。それは通常のリクエストとして同じサブリクエストのフェーズを通過し、その独自のURIに基づいてlocationを割り当てられます。
サブリクエストの出力ヘッダは常に無視されます。ngx_http_postpone_filter
はサブリクエストの出力ボディを親リクエストによって生成された他のデータに関連して正しい位置に置きます。
サブリクエストはアクティブなリクエストの概念と関係があります。リクエスト r
は、もし c->data == r
、c
はクライアントの接続オブジェクト、の時にアクティブと見なされます。どの時点においても、リクエストグループ内のアクティブなリクエストはバッファをクライアントに出力することができます。アクティブでは無いリクエストは出力をまだフィルターチェインに送信しますが、それはngx_http_postpone_filter
を超えて渡すことはなく、リクエストがアクティブになるまでフィルタによってバッファされたままです。リクエストの有効化には幾つかのルールがあります:
- 初めのうちはメインリクエストはアクティブです。
- アクティブリクエストの最初のサブリクエストは生成のすぐ後でアクティブになります。
-
リクエストより前の全てのデータがいったん送信されると、
ngx_http_postpone_filter
はアクティブ リクエストのサブリクエストのリストの中で次のリクエストを活性化します。 - リクエストが終了すると、その親が活性化されます。
関数ngx_http_subrequest(r, uri, args, psr, ps, flags)
を呼ぶことでサブリクエストを生成します。ここでr
は親リクエスト、uri
と args
はサブリクエストのURIと引数、psr
は新しく生成されたサブリクエストの参照を受け取る出力パラメータ、ps
はサブリクエストが終了される時に親リクエストに通知するためのコールバックオブジェクト、そしてflags
はフラグのビットマスク。以下のフラグが利用可能です:
-
NGX_HTTP_SUBREQUEST_IN_MEMORY
- 出六はクライアントに送信されませんがメモリ内に格納されます。フラグはプロキシされるモジュールのうちの1つによって処理されるサブリクエストにのみ影響します。サブリクエストが終了された後で、その出力は型ngx_buf_t
のr->out
内で利用可能です。 -
NGX_HTTP_SUBREQUEST_WAITED
- サブリクエストのdone
フラグは、たとえサブリクエストが終了時にアクティブでは無くても、設定されます。このサブリクエストフラグはSSIフィルタによって使われます。 -
NGX_HTTP_SUBREQUEST_CLONE
- サブリクエストはその親のクローンとして生成されます。それは同じlocationで開始され、親リクエストとして同じフェーズから進みます。
以下の例は/foo
のURIを持つサブリクエストを作成します。
ngx_int_t rc; ngx_str_t uri; ngx_http_request_t *sr; ... ngx_str_set(&uri, "/foo"); rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0); if (rc == NGX_ERROR) { /* error */ }
この例は現在のリクエストをクローンし、サブリクエストのための終了コールバックを設定します。
ngx_int_t ngx_http_foo_clone(ngx_http_request_t *r) { ngx_http_request_t *sr; ngx_http_post_subrequest_t *ps; ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); if (ps == NULL) { return NGX_ERROR; } ps->handler = ngx_http_foo_subrequest_done; ps->data = "foo"; return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps, NGX_HTTP_SUBREQUEST_CLONE); } ngx_int_t ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) { char *msg = (char *) data; ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "done subrequest r:%p msg:%s rc:%i", r, msg, rc); return rc; }
サブリクエストは通常ボディフィルタ内で生成されます。その場合それらの出力は任意の明示的なリクエストからの出力のように取り扱われることができます。つまり、最終的にサブリクエストの出力はサブリクエストの作成前に渡される全ての明示的なバッファの後、および作成後に渡される全てのバッファの前に、クライアントに送信されます。この順番はサブリクエストの大規模な構造についてさえも保持されます。以下の例では、全てのリクエストデータバッファの後だが、last_buf
フラグを持つ最後のバッファの前に、サブリクエストからの出力を挿入します。
ngx_int_t ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_buf_t *b; ngx_uint_t last; ngx_chain_t *cl, out; ngx_http_request_t *sr; ngx_http_foo_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } last = 0; for (cl = in; cl; cl = cl->next) { if (cl->buf->last_buf) { cl->buf->last_buf = 0; cl->buf->last_in_chain = 1; cl->buf->sync = 1; last = 1; } } /* Output explicit output buffers */ rc = ngx_http_next_body_filter(r, in); if (rc == NGX_ERROR || !last) { return rc; } /* * Create the subrequest. The output of the subrequest * will automatically be sent after all preceding buffers, * but before the last_buf buffer passed later in this function. */ if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) { return NGX_ERROR; } ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module); /* Output the final buffer with the last_buf flag */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->last_buf = 1; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
サブリクエストはデータの出力以外の他の目的のために生成することもできます。例えば、 ngx_http_auth_request_module モジュールはNGX_HTTP_ACCESS_PHASE
フェーズでサブリクエストを生成します。この時点で出力を無効にするには、サブリクエスト上でheader_only
フラグを設定します。これはサブリクエストボディがクライアントに送信されることを防ぎます。サブリクエストのヘッダは決してクライアントに送信されないことに注意してください。サブリクエストの結果はコールバック ハンドラ内で解析することができます。
リクエストの終了
HTTPリクエストは関数ngx_http_finalize_request(r, rc)
を呼ぶことで終了されます。全ての出力バッファがフィルタチェインに送信された後で通常はコンテント ハンドラによって終了されます。出力の幾つかはファルタチェインと一緒にどこかにバッファされたまま、この時点で出力の全てはクライアントに送信されないかもしれません。そうであれば、ngx_http_finalize_request(r, rc)
関数は出力の送信を終了するために自動的に特別なハンドラngx_http_writer(r)
を導入します。エラーの場合、またはもし標準的なHTTP応答コードがクライアントへ返される必要がある場合、リクエストも終了されます。
関数ngx_http_finalize_request(r, rc)
は以下のrc
値を期待します:
-
NGX_DONE
- 高速な終了。リクエストのcount
を減少し、それが0になったらリクエストを破棄します。現在のリクエストが破棄された後で、クライアントの接続はもっと多くのリクエストのために使うことができます。 -
NGX_ERROR
,NGX_HTTP_REQUEST_TIME_OUT
(408
),NGX_HTTP_CLIENT_CLOSED_REQUEST
(499
) - エラーの終了。可能な限りリクエストを終了し、クライアントの接続を閉じます。 -
NGX_HTTP_CREATED
(201
),NGX_HTTP_NO_CONTENT
(204
),NGX_HTTP_SPECIAL_RESPONSE
(300
)以上のコード - 特別な応答の終了。これらの値については、nginxはクライアントにそのコードのためのデフォルトの応答ページを送信するか、そのコードのために設置された場合はerror_page location への内部的なリダイレクトを行うかをします。 -
他のコードは終了コードが成功したと見なされ、応答ボディの送信を終了するためにリクエスト書き込みを有効化するかもしれません。一旦ボディが完全に送信されると、リクエストの
count
は減らされます。それが0になると、リクエストは破棄されますが、クライアント接続は他のリクエストのためにまだ使われるかもしれません。もしcount
が正の場合は、リクエスト内に完了していない活動があります。これは後の時点で終了されるでしょう。
リクエスト ボディ
クライアント リクエストのボディを扱うために、nginxはngx_http_read_client_request_body(r, post_handler)
と ngx_http_discard_request_body(r)
関数を提供します。最初の関数はリクエストボディを読み込み、request_body
リクエスト フィールドを使って利用可能にします。2つ目の関数はnginxにリクエスト ボディを破棄(読み込みそして無視する)ように指示します。これらの関数のうちの1つは各リクエストごとに呼ばれる必要があります。通常は、コンテキストハンドラが呼びます。
サブリクエストからのクライアント リクエスト ボディの読み込みあるいは破棄は許可されません。それは常にメイン リクエストの中で行われる必要があります。サブリクエストが作成された時に、メイン リクエストが以前リクエストボディを読み込んだ場合はサブリクエストによって使うことができる親のrequest_body
オブジェクトを継承します。
関数 ngx_http_read_client_request_body(r, post_handler)
リクエストボディの読み込みの処理を開始します。ボディが完全に読み込まれた場合、リクエストの処理を続けるためにpost_handler
コールバックが呼ばれます。もしリクエストボディが失われたか既に読み込まれていた場合は、コールバックがすぐに呼ばれます。関数 ngx_http_read_client_request_body(r, post_handler)
は型ngx_http_request_body_t
のrequest_body
リクエストフィールドを割り当てます。このオブジェクトのフィールドbufs
はバッファチェインとして結果を保持します。もしclient_body_buffer_sizeディレクティブによって指定される容量がメモリ内にボディ全体を収めるのに十分では無い場合は、ボディはメモリバッファあるいはファイルバッファ内に保存することができます。
以下の例はクライアント リクエスト ボディを読み込み、そのサイズを返します。
ngx_int_t ngx_http_foo_content_handler(ngx_http_request_t *r) { ngx_int_t rc; rc = ngx_http_read_client_request_body(r, ngx_http_foo_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { /* error */ return rc; } return NGX_DONE; } void ngx_http_foo_init(ngx_http_request_t *r) { off_t len; ngx_buf_t *b; ngx_int_t rc; ngx_chain_t *in, out; if (r->request_body == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } len = 0; for (in = r->request_body->bufs; in; in = in->next) { len += ngx_buf_size(in->buf); } b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN); if (b == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } b->last = ngx_sprintf(b->pos, "%O", len); b->last_buf = (r == r->main) ? 1: 0; b->last_in_chain = 1; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = b->last - b->pos; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { ngx_http_finalize_request(r, rc); return; } out.buf = b; out.next = NULL; rc = ngx_http_output_filter(r, &out); ngx_http_finalize_request(r, rc); }
以下のリクエストのフィールドはリクエストボディがどのように読み込まれるかを決定します:
-
request_body_in_single_buf
- ボディを1つのメモリバッファに読み込みます。 -
request_body_in_file_only
- たとえメモリバッファ内に収まる場合でも、常にボディをファイルに読み込みます。 -
request_body_in_persistent_file
- 作成の後ですぐにファイルをunlinkしません。このフラグを持つファイルは他のディレクトリに移動することができます。 -
request_body_in_clean_file
- リクエストが終了されない場合、ファイルをunlinkします。ファイルが他のディレクトリに移動すると考えられていたが何かの理由で移動されなかった場合、これは便利かもしれません。 -
request_body_file_group_access
- デフォルトの 0600 アクセス マスクを 0660 に置き換えることでファイルへのグループアクセスを有効にします。 -
request_body_file_log_level
- ファイルエラーを記憶する重要度レベル。 -
request_body_no_buffering
- バッファリング無しにリクエストボディを読み込みます。
request_body_no_buffering
フラグはリクエストボディの読み込みのバッファ無しモードを有効にします。このモードでは、ngx_http_read_client_request_body()
の呼び出しの後で、bufs
チェインはボディの一部だけを保持するかもしれません。次の部分を読み込むには、ngx_http_read_unbuffered_request_body(r)
関数を呼びます。返り値 NGX_AGAIN
とリクエストフラグreading_body
はもっと多くのデータが利用可能であることを示します。この関数を呼んだあとで、もしbufs
がNULLであれば、今のところは何も読み込むものはありません。リクエスト コールバック read_event_handler
はリクエストボディの次の部分が利用可能な時に呼ばれるでしょう。
リクエストボディフィルタ
リクエストボディ部分が読まれた後で、ngx_http_top_request_body_filter
変数に格納されている最初のボディフィルタハンドラを呼び出すことで、リクエストボディフィルタに渡されます。最後のハンドラngx_http_request_body_save_filter(r, cl)
が呼ばれるまで、各ボディハンドラがチェーン内の次のハンドラを呼ぶと仮定します。このハンドラはr->request_body->bufs
内のバッファを収集し、必要に応じてファイルに書き込みます。最後のリクエストボディバッファにはゼロ以外のlast_buf
フラグがあります。
フィルタがデータバッファを遅延させることを計画している場合は、初めて呼び出される時にr->request_body->filter_need_buffering
を1
に設定する必要があります。
以下は、リクエストボディを1秒遅延させる簡単なリクエストボディフィルタの例です。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #define NGX_HTTP_DELAY_BODY 1000 typedef struct { ngx_event_t event; ngx_chain_t *out; } ngx_http_delay_body_ctx_t; static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static void ngx_http_delay_body_cleanup(void *data); static void ngx_http_delay_body_event_handler(ngx_event_t *ev); static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_delay_body_module_ctx = { NULL, /* preconfiguration */ ngx_http_delay_body_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_delay_body_filter_module = { NGX_MODULE_V1, &ngx_http_delay_body_module_ctx, /* module context */ NULL, /* 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 ngx_http_request_body_filter_pt ngx_http_next_request_body_filter; static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_chain_t *cl, *ln; ngx_http_cleanup_t *cln; ngx_http_delay_body_ctx_t *ctx; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "delay request body filter"); ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t)); if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module); r->request_body->filter_need_buffering = 1; } if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (!ctx->event.timedout) { if (!ctx->event.timer_set) { /* cleanup to remove the timer in case of abnormal termination */ cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } cln->handler = ngx_http_delay_body_cleanup; cln->data = ctx; /* add timer */ ctx->event.handler = ngx_http_delay_body_event_handler; ctx->event.data = r; ctx->event.log = r->connection->log; ngx_add_timer(&ctx->event, NGX_HTTP_DELAY_BODY); } return ngx_http_next_request_body_filter(r, NULL); } rc = ngx_http_next_request_body_filter(r, ctx->out); for (cl = ctx->out; cl; /* void */) { ln = cl; cl = cl->next; ngx_free_chain(r->pool, ln); } ctx->out = NULL; return rc; } static void ngx_http_delay_body_cleanup(void *data) { ngx_http_delay_body_ctx_t *ctx = data; if (ctx->event.timer_set) { ngx_del_timer(&ctx->event); } } static void ngx_http_delay_body_event_handler(ngx_event_t *ev) { ngx_connection_t *c; ngx_http_request_t *r; r = ev->data; c = r->connection; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "delay request body event"); ngx_post_event(c->read, &ngx_posted_events); } static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf) { ngx_http_next_request_body_filter = ngx_http_top_request_body_filter; ngx_http_top_request_body_filter = ngx_http_delay_body_filter; return NGX_OK; }
応答
nginxでは、HTTP応答は任意の応答ボディが続く応答ヘッダを送信することで生成されます。ヘッダとボディの両方はフィルタチェインを通過し、結果的にクライアントのソケットに書き込まれます。nginxモジュールはハンドラをヘッダあるいはボディフィルタに差し込み、以前のハンドラからの出力を処理することができます。
応答ヘッダ
ngx_http_send_header(r)
関数は出力ヘッダを送信します。r->headers_out
がHTTP応答ヘッダが生成するために必要な全てのデータを含むまで、この関数を呼ばないでください。r->headers_out
内のstatus
フィールドは常に設定されなければなりません。もし応答ステータスがヘッダに続く応答ボディを示す場合、content_length_n
も同様に設定することができます。このフィールドのデフォルトの値は-1
です。このことはボディサイズが未知であることを意味します。この場合、chunked transfer encoding が使われます。任意のヘッダを出力するには、headers
リストを追加します。
static ngx_int_t ngx_http_foo_content_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_table_elt_t *h; /* send header */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = 3; /* X-Foo: foo */ h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "X-Foo"); ngx_str_set(&h->value, "foo"); rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } /* send body */ ... }
ヘッダフィルタ
ngx_http_send_header(r)
関数はngx_http_top_header_filter
変数に格納されている最初のフィルタハンドラを呼び出すことでヘッダフィルタチェーンを起動します。最後のハンドラngx_http_header_filter(r)
が呼ばれるまで、各ヘッダハンドラがチェーン内の次のハンドラを呼ぶと仮定します。最後のヘッダハンドラはr->headers_out
に基づいたHTTP応答を構築し、それを出力のためにngx_http_writer_filter
に渡します。
ヘッダ フィルターチェインにハンドラを追加するために、設定時にそのアドレスをグローバル変数ngx_http_top_header_filter
に格納します。前のハンドラのアドレスは通常モジュール内の静的変数に格納され、終了する前に新しく追加されたハンドラによって呼ばれます。
ヘッダーフィルターモジュールの以下の例はHTTPヘッダ "X-Foo: foo
" をステータス 200
を持つ各応答に追加します。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_foo_header_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_header_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_foo_header_filter_module = { NGX_MODULE_V1, &ngx_http_foo_header_filter_module_ctx, /* module context */ NULL, /* 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 ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r) { ngx_table_elt_t *h; /* * The filter handler adds "X-Foo: foo" header * to every HTTP 200 response */ if (r->headers_out.status != NGX_HTTP_OK) { return ngx_http_next_header_filter(r); } h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; } h->hash = 1; ngx_str_set(&h->key, "X-Foo"); ngx_str_set(&h->value, "foo"); return ngx_http_next_header_filter(r); } static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_foo_header_filter; return NGX_OK; }
応答ボディ
応答ボディを送信するには、ngx_http_output_filter(r, cl)
関数を呼びます。関数は複数回呼び出すことができます。毎回、それは応答ボディの部分をバッファチェインの形式で送信します。最後のボディバッファ内のlast_buf
フラグを設定します。
以下の例はボディとして "foo" を持つ完全なHTTP応答を生成します。例がサブリクエストとメインリクエストとして動作するために、出力の最後のバッファ内でlast_in_chain
フラグが設定されます。サブリクエストのための最後のバッファは出力全体を終らせないため、last_buf
フラグはメインのリクエストのためだけに設定されます。
static ngx_int_t ngx_http_bar_content_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; /* send header */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = 3; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } /* send body */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->last_buf = (r == r->main) ? 1: 0; b->last_in_chain = 1; b->memory = 1; b->pos = (u_char *) "foo"; b->last = b->pos + 3; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
Response body filters
関数 ngx_http_output_filter(r, cl)
はngx_http_top_body_filter
に格納されている最初のボディ フィルタ ハンドラを呼び出すことでボディフィルタを起動します。最後のハンドラngx_http_write_filter(r, cl)
が呼ばれるまで、各ボディハンドラがチェーン内の次のハンドラを呼ぶと仮定します。
ボディフィルタハンドラはバッファのチェーンを受け取ります。ハンドラはバッファを処理し、できる限り新しいチェーンを次のハンドラに渡すとされています。着信チェーンのチェーンリンクngx_chain_t
は呼び出し元に所属し、再利用または変更してはならないことに注意してください。ハンドラが完了するとすぐに、呼び出し元はそれを送信したバッファの追跡を続けるために出力チェーンのリンクを使うことができます。バッファチェインを保存、あるいは次のフィルタへ渡す前にバッファを代用するために、ハンドラは独自のチェインリンクを割り当てる必要があります。
以下はボディのバイト数をカウントする簡単なボディフィルタの例です。結果はアクセスログ内で使うことができる $counter
変数として利用可能です。
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> typedef struct { off_t count; } ngx_http_counter_filter_ctx_t; static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_counter_filter_module_ctx = { ngx_http_counter_add_variables, /* preconfiguration */ ngx_http_counter_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_counter_filter_module = { NGX_MODULE_V1, &ngx_http_counter_filter_module_ctx, /* module context */ NULL, /* 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 ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_str_t ngx_http_counter_name = ngx_string("counter"); static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_chain_t *cl; ngx_http_counter_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module); } for (cl = in; cl; cl = cl->next) { ctx->count += ngx_buf_size(cl->buf); } return ngx_http_next_body_filter(r, in); } static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; ngx_http_counter_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module); if (ctx == NULL) { v->not_found = 1; return NGX_OK; } p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN); if (p == NULL) { return NGX_ERROR; } v->data = p; v->len = ngx_sprintf(p, "%O", ctx->count) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; return NGX_OK; } static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var; var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_counter_variable; return NGX_OK; } static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf) { ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_counter_body_filter; return NGX_OK; }
フィルタモジュールのビルド
ボディあるいはヘッダフィルタを書く時、フィルタの順番内のフィルタの位置に特に注意してください。nginxの標準モジュールによって登録された多くのヘッダとボディフィルタがあります。nginxの標準モジュールは多くのヘッドおよびボディフィルタを登録します。それらに関して正しい場所に新しいフィルタモジュールを登録することが重要です。通常は、モジュールは後設定ハンドラにフィルタを登録します。処理の間にフィルタが呼ばれる順番は明確にそれらが登録された逆の順番です。
サードパーティのフィルタモジュールについては、nginxは特別なスロット HTTP_AUX_FILTER_MODULES
を提供します。スロット内でフィルタモジュールを登録するには、モジュールの設定の中で ngx_module_type
変数を HTTP_AUX_FILTER
に設定します。
以下の例は、たった1つのソースファイル ngx_http_foo_filter_module.c
を持つモジュールを仮定した、ファイルたモジュールの設定ファイルを示します。
ngx_module_type=HTTP_AUX_FILTER ngx_module_name=ngx_http_foo_filter_module ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c" . auto/module
バッファの再利用
バッファのストリームを発行あるいは変更する時、割り当てられたバッファを再利用することがしばしば望ましいです。nginxのコードの中で標準的で広範囲に適用されるやり方は、この目的のために二つのバッファのチェーンを保持します: free
と busy
. free
チェインは全てのフリーなバッファを保持します。これは再利用することができます。busy
チェーンは幾つかの他のフィルタハンドラによってまだ使われている現在のモジュールによって送信される全てのバッファを保持します。もしサイズが0より大きければ、バッファは使用中だと見なされます。通常、バッファがフィルタによって消費された場合、そのpos
(あるいはファイルバッファについては file_pos
) は last
(ファイルバッファについてはfile_last
)に進みます。いったんバッファが完全に消費されると、再利用することができます。free
チェーンに新しくfreeにされたバッファを追加するには、busy
チェーンの一番上でサイズが0のバッファをfree
に移動するのを繰り返すことで十分です。この操作は一般的で、これを行う専用の関数ngx_chain_update_chains(free, busy, out, tag)
があります。関数は出力チェーンout
を busy
に追加し、freeなバッファをbusy
の一番上から free
に移動します。指定されたtag
を持つバッファだけが再利用されます。これはモジュールによって割り当てられたバッファだけがモジュールによって再利用されます。
以下の例は各入力のバッファの前に “foo” 文字列を挿入するボディフィルタの例です。モジュールによって割り当てられた新しいバッファは可能であれば再利用されます。この例を適切に動作させるには、header filter を設定しcontent_length_n
を-1
に再設定することも必要ですが、関係するコードがここでは提供されないことに注意してください。
typedef struct { ngx_chain_t *free; ngx_chain_t *busy; } ngx_http_foo_filter_ctx_t; ngx_int_t ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *cl, *tl, *out, **ll; ngx_http_foo_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module); } /* create a new chain "out" from "in" with all the changes */ ll = &out; for (cl = in; cl; cl = cl->next) { /* append "foo" in a reused buffer if possible */ tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module; b->memory = 1; b->pos = (u_char *) "foo"; b->last = b->pos + 3; *ll = tl; ll = &tl->next; /* append the next incoming buffer */ tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = cl->buf; *ll = tl; ll = &tl->next; } *ll = NULL; /* send the new chain */ rc = ngx_http_next_body_filter(r, out); /* update "busy" and "free" chains for reuse */ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_foo_filter_module); return rc; }
ロードバランシング
ngx_http_upstream_module はリクエストをリモートサーバに渡すために必要な基本的な機能を提供します。HTTPあるいはFastCGIのような特定のプロトコルを実装するモジュールはこの機能を使います。モジュールは独自のロードバランシングモジュールを作成するためのインタフェースも提供し、デフォルトのラウンドロビンバランシングメソッドを実装します。
least_conn と hash モジュールは二者択一のロードバランシング方法を実装しますが、実際にはupstreamラウンドロビン モジュールの拡張として実装されており、サーバグループの表現のような多くのコードをそれと共有します。keepalive モジュールはupstream機能を拡張する独立したモジュールです。
ngx_http_upstream_module は対応するupstream ブロックを設定ファイルに置くことで明示的に、あるいはどこかの時点でサーバのリストへ評価されるURLを受け付けるproxy_pass のようなディレクティブを使うことで暗黙的に設定することができます。明示的なupstream設定を使って、別のロードバランシング方法が利用可能です。upstreamモジュールの設定は独自のディレクティブコンテキスト NGX_HTTP_UPS_CONF
を持ちます。その構造は以下のように定義されます:
struct ngx_http_upstream_srv_conf_s { ngx_http_upstream_peer_t peer; void **srv_conf; ngx_array_t *servers; /* ngx_http_upstream_server_t */ ngx_uint_t flags; ngx_str_t host; u_char *file_name; ngx_uint_t line; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ #if (NGX_HTTP_UPSTREAM_ZONE) ngx_shm_zone_t *shm_zone; #endif };
-
srv_conf
— upstreamモジュールの設定コンテキスト。 -
servers
—upstream
ブロック内のserverディレクティブの配列をパースした結果である、ngx_http_upstream_server_t
の配列。 -
flags
— 主に、ロードバランシング方法によってどの機能がサポートされるかをマークするフラグ。機能は server ディレクティブのパラメータとして設定されます:-
NGX_HTTP_UPSTREAM_CREATE
— proxy_pass ディレクティブと “friends” (FastCGI, SCGI など)によって自動的に生成されたupstreamと、明示的に定義されたupstreamを区別する。 -
NGX_HTTP_UPSTREAM_WEIGHT
— “weight
” パラメータがサポートされます -
NGX_HTTP_UPSTREAM_MAX_FAILS
— “max_fails
” パラメータがサポートされます -
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
— “fail_timeout
” パラメータがサポートされます -
NGX_HTTP_UPSTREAM_DOWN
— “down
” パラメータがサポートされます -
NGX_HTTP_UPSTREAM_BACKUP
— “backup
” パラメータがサポートされます -
NGX_HTTP_UPSTREAM_MAX_CONNS
— “max_conns
” パラメータがサポートされます
-
-
host
— upstreamの名前。 -
file_name, line
— 設定ファイルの名前とupstream
ブロックが置かれている行。 -
port
とno_port
— 明示的に定義されたupstreamグループのためには使われません。 -
shm_zone
— もしあれば、このupstreamグループによって使われる共有メモリ領域。 -
peer
— upstream設定の初期化のための一般的なメソッドを保持するオブジェクト:
ロードバランシングアルゴリズムを実装するモジュールは、これらのメソッドを設定しprivatetypedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t;
data
を初期化しなければなりません。もしinit_upstream
が設定のパースの間に初期化されなかった場合は、ngx_http_upstream_module
はそれをデフォルトのngx_http_upstream_init_round_robin
アルゴリズムに設定します。-
init_upstream(cf, us)
— 成功時には、サーバのグループの初期化とinit()
メソッドの初期化に責任がある設定時のメソッド。代表的なロードバランシングモジュールは、独自の設定をdata
フィールドに保持し使用する幾つかの有効なデータ構造を作成するために、upstream
ブロック内のサーバのリストを利用します。 -
init(r, us)
— ロードバランシングのために使われるリクエスト毎のngx_http_upstream_peer_t.peer
構造を初期化します (上で説明されたupstream毎のngx_http_upstream_srv_conf_t.peer
と混同しないでください)。data
引数としてサーバ選択を扱う全てのコールバックに渡されます。
-
nginxがリクエストを処理のために他のホストに渡す必要がある場合は、接続先のアドレスを取得するために設定されたロードバランシングメソッドを使います。メソッドは型ngx_peer_connection_t
のngx_http_upstream_t.peer
オブジェクトから取得されます。
struct ngx_peer_connection_s { ... struct sockaddr *sockaddr; socklen_t socklen; ngx_str_t *name; ngx_uint_t tries; ngx_event_get_peer_pt get; ngx_event_free_peer_pt free; ngx_event_notify_peer_pt notify; void *data; #if (NGX_SSL || NGX_COMPAT) ngx_event_set_peer_session_pt set_session; ngx_event_save_peer_session_pt save_session; #endif ... };
構造は以下のフィールドを持ちます:
-
sockaddr
,socklen
,name
— 接続先のupstreamサーバのアドレス; これはロードバランシング メソッドの出力パラメータです。 -
data
— ロードバランシング メソッドのリクエスト毎のデータ; 選択アルゴリズムの状態を保持し、通常はupstream設定へのリンクを含みます。引数としてサーバ選択を扱う全てのメソッドへ渡されるでしょう( belowを見てください)。 -
tries
— upstreamサーバへ接続できる試行の数。 -
get
,free
,notify
,set_session
およびsave_session
- ロードバランシングモジュールのメソッド。以下で説明されます。
全てのメソッドは少なくとも二つの引数を受け付けます; ngx_http_upstream_srv_conf_t.peer.init()
によって生成された peer接続オブジェクト pc
と data
。ロードバランシング モジュールの“chaining”により、pc.data
とは異なるかも知れないことに注意してください。
-
get(pc, data)
— メソッドは、upstreamモジュールがリクエストをupstreamサーバに渡す準備ができ、そのアドレスを知る必要がある時に呼ばれます。メソッドはngx_peer_connection_t
構造のsockaddr
,socklen
およびname
フィールドを埋める必要があります。返り値は以下のうちの1つです:-
NGX_OK
— サーバが選択された -
NGX_ERROR
— 内部エラーが発生した -
NGX_BUSY
— 現時点で利用可能なサーバが無い。これは以下のような多くの理由で起こり得ます: 動的なサーバグループが空、グループ内の全てのサーバが失敗状態にある、グループ内の全てのサーバが接続の最大数を既に扱っている、など。 -
NGX_DONE
— 背後にある接続が再利用され、upstreamサーバへの新しい接続を作成する必要がありません。この値はkeepalive
モジュールによって設定されます。
-
-
free(pc, data, state)
— upstreamモジュールが特定のサーバを使った作業が完了したした時にメソッドが呼ばれます。state
引数はupstream接続の完了状態です。ビットマスクは以下の可能な値です:-
NGX_PEER_FAILED
— 試行がunsuccessfulだった -
NGX_PEER_NEXT
— upstreamサーバがコード403
あるいは404
を返す特別な場合。これは failureとは見なされません。 -
NGX_PEER_KEEPALIVE
— 現在のところ使われていない
tries
カウンタも減らします。 -
-
notify(pc, data, type)
— OSSバージョンでは、現在のところ使われていません。 -
set_session(pc, data)
とsave_session(pc, data)
— upstreamサーバへセッションをキャッシュすることができるSSL固有のメソッド。実装はラウンドロビンバランシングメソッドによって提供されます。
例
nginx-dev-examplesリポジトリはnginxモジュールの例を提供します。
コードスタイル
一般的なルール
- テキストの幅の最大は80文字です
- インデントは4つのスペース
- タブは無し、後に続く空白は無し
- 同じ行上の要素の並びは空白で分割されます
- 16進数文字列は小文字
-
ファイル名、関数と型名、およびグローバル変数は
ngx_
あるいはngx_http_
とngx_mail_
のようなもっと特定のプリフィックスを持ちます
size_t ngx_utf8_length(u_char *p, size_t n) { u_char c, *last; size_t len; last = p + n; for (len = 0; p < last; len++) { c = *p; if (c < 0x80) { p++; continue; } if (ngx_utf8_decode(&p, last - p) > 0x10ffff) { /* invalid UTF-8 */ return n; } } return len; }
ファイル
一般的なソースファイルは2つの空行で分割される以下の章を持ちます:
- コピーライト文
- includes
- プリプロセッサ定義
- 型定義
- 関数のプロトタイプ
- 変数の定義
- 関数の定義
コピーライト文はこのようなものです:
/* * Copyright (C) Author Name * Copyright (C) Organization, Inc. */
ファイルが大きく書き換えられた場合は、著者のリストが更新され、新しい著者が一番上に追加されます。
ngx_config.h
と ngx_core.h
ファイルは常に最初に含まれ、ngx_http.h
, ngx_stream.h
あるいは ngx_mail.h
の1つが続きます。そして、オプションの外部ヘッダファイルに従います:
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #include <libxml/parser.h> #include <libxml/tree.h> #include <libxslt/xslt.h> #if (NGX_HAVE_EXSLT) #include <libexslt/exslt.h> #endif
ヘッダファイルは "header protection" と呼ばれるものを含まなければなりません:
#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_ #define _NGX_PROCESS_CYCLE_H_INCLUDED_ ... #endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
コメント
-
“
//
” comments are not used - テキストは英語で書かれ、アメリカ語の綴りが望ましいです
-
複数行のコメントは以下のように整形されます:
/* * The red-black tree code is based on the algorithm described in * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest. */
/* find the server configuration for the address:port */
プリプロセッサ
マクロ名はngx_
あるいは NGX_
(あるいはもっと具体的な)プリフィックスから始まります。定数のためのマクロ名は大文字です。パラメータ化されたマクロとイニシャライザのためのマクロは小文字です。マクロ名と値は少なくとも2つの空白で分離されます:
#define NGX_CONF_BUFFER 4096 #define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap) #define ngx_buf_size(b) \ (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos): \ (b->file_last - b->file_pos)) #define ngx_null_string { 0, NULL }
条件は丸括弧の中で、否定は外です:
#if (NGX_HAVE_KQUEUE) ... #elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \ || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT))) ... #elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL)) ... #elif (NGX_HAVE_POLL) ... #else /* select */ ... #endif /* NGX_HAVE_KQUEUE */
タイプ
型名は “_t
” サフィックスで終わります。定義された型名は少なくとも2つの空白で分離されます:
typedef ngx_uint_t ngx_rbtree_key_t;
構造型は typedef
を使って定義されます。構造の中で、メンバー型と名前が並べられます:
typedef struct { size_t len; u_char *data; } ngx_str_t;
ファイル内の異なる構造の間で割り当てを同じにしてください。自信を示す構造は “_s
”で終わる名前を持ちます。隣接する構造の定義は2つの空尾行で分離されます:
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; }; typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
各構造のメンバはそれ自身の行の上で定義されます:
typedef struct { ngx_uint_t hash; ngx_str_t key; ngx_str_t value; u_char *lowcase_key; } ngx_table_elt_t;
構造内の関数ポインタは “_pt
” で終わる定義された型を持ちます:
typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size); typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit); typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size); typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit); typedef struct { ngx_recv_pt recv; ngx_recv_chain_pt recv_chain; ngx_recv_pt udp_recv; ngx_send_pt send; ngx_send_pt udp_send; ngx_send_chain_pt udp_send_chain; ngx_send_chain_pt send_chain; ngx_uint_t flags; } ngx_os_io_t;
Enumerationは “_e
” で終わる型を持ちます:
typedef enum { ngx_http_fastcgi_st_version = 0, ngx_http_fastcgi_st_type, ... ngx_http_fastcgi_st_padding } ngx_http_fastcgi_state_e;
変数
変数は基本型の長さ、そしてアルファベット順でソートされて定義されます。型名と変数名は並べられます。型と名前の “columns” は2つの空白で分離されます。大きな配列は宣言ブロックの最後に配置されます:
u_char | | *rv, *p; ngx_conf_t | | *cf; ngx_uint_t | | i, j, k; unsigned int | | len; struct sockaddr | | *sa; const unsigned char | | *data; ngx_peer_connection_t | | *pc; ngx_http_core_srv_conf_t | |**cscfp; ngx_http_upstream_srv_conf_t| | *us, *uscf; u_char | | text[NGX_SOCKADDR_STRLEN];
静的およびグローバルは変数は宣言上で初期化されるかもしれません:
static ngx_str_t ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t ngx_crc32_table16[] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, ... 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
一般的に使われる型/名前 の組み合わせの1群があります。
u_char *rv; ngx_int_t rc; ngx_conf_t *cf; ngx_connection_t *c; ngx_http_request_t *r; ngx_peer_connection_t *pc; ngx_http_upstream_srv_conf_t *us, *uscf;
機能
全ての関数 (静的なものであっても)はプロトタイプを持つ必要があります。プロトタイプは引数名を含みます。長いプロトタイプは1つの連続する行上の1つのインデントでラップされます:
static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf); static char *ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module, ngx_uint_t ctx_index);
定義内の関数名は新しい行で始まります。関数の本文の開始および終了の括弧は別の行にあります。関数の本文はインデントされます。関数の間には2つの空の行があります:
static ngx_int_t ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len) { ... } static ngx_int_t ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt) { ... }
関数名と開始括弧の間には空白はありません。長い関数の呼び出しは連続する行が最初の関数の引数の位置から始まるようにラップされます。可能であれば最初の連続する行を79の位置で終わるように整形します:
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header: \"%V: %V\"", &h->key, &h->value); hc->busy = ngx_palloc(r->connection->pool, cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
ngx_inline
マクロは inline
の代わりに使われなければなりません:
static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);
式
“.
” と “−>
” を期待する二項演算子はそれらのオペランドから1つの空白で分離されなければなりません。単項演算子と添え字はそれらオペランドから空白によって分離されません:
width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];
型のキャストは1つの空白によってキャストされた表現から分離されます。型キャスト内のアスタリスクは1つの空白によって型名から分離されます:
len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);
表現が1つの行に収まらない場合は、ラップされます。行を区切る望ましい箇所は、二項演算子です。連続する行は表現の開始を使って整列されます:
if (status == NGX_HTTP_MOVED_PERMANENTLY || status == NGX_HTTP_MOVED_TEMPORARILY || status == NGX_HTTP_SEE_OTHER || status == NGX_HTTP_TEMPORARY_REDIRECT || status == NGX_HTTP_PERMANENT_REDIRECT) { ... }
p->temp_file->warn = "an upstream response is buffered " "to a temporary file";
最後の手段として、連続する行が79の位置で終わるように表現をラップすることができます:
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *));
上の規則は副表現にも適用されます。各副表現は独自のインデントレベルを持ちます:
if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) || c->stale_updating) && !r->background && u->conf->cache_background_update) { ... }
時には、キャストの後で表現をラップすることが便利です。この場合、連続する行はインデントされます:
node = (ngx_rbtree_node_t *) ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
ポインタはNULL
(0
ではない)に対して明示的に比較されます:
if (ptr != NULL) { ... }
条件とループ
“if
” キーワードは1つの空白によって条件から分離されます。開始の括弧は同じ行、あるいは条件が幾つかの行を取る場合は専用の行に置かれます。終了の括弧は専用の行、任意で“else if
/ else
”が続く行に置かれます。通常は “else if
/ else
” 部分の前に空の行があります:
if (node->left == sentinel) { temp = node->right; subst = node; } else if (node->right == sentinel) { temp = node->left; subst = node; } else { subst = ngx_rbtree_min(node->right, sentinel); if (subst->left != sentinel) { temp = subst->left; } else { temp = subst->right; } }
同じような整形規則が “do
” と “while
” ループに適用されます:
while (p < last && *p == ' ') { p++; }
do { ctx->node = rn; ctx = ctx->next; } while (ctx);
“switch
” キーワードは1つの空白によって条件から分離されます。開始の括弧は同じ行に置かれます。終了の括弧は専用の行に置かれます。“case
” キーワードは “switch
” を使って整列されます:
switch (ch) { case '!': looked = 2; state = ssi_comment0_state; break; case '<': copy_end = p; break; default: copy_end = p; looked = 0; state = ssi_start_state; break; }
ほとんどの “for
” ループはこのように整形されます:
for (i = 0; i < ccf->env.nelts; i++) { ... }
for (q = ngx_queue_head(locations); q != ngx_queue_sentinel(locations); q = ngx_queue_next(q)) { ... }
“for
” の幾つかの部分が省略される場合、これは “/* void */
” コメントによって示されます:
for (i = 0; /* void */ ; i++) { ... }
空の本文を持つループも同じ行に置かれるかもしれない “/* void */
” コメントによって示されます:
for (cl = *busy; cl->next; cl = cl->next) { /* void */ }
終わりのないループはこのように見えます:
for ( ;; ) { ... }
ラベル
ラベルは空の行で囲まれ、前のレベルにインデントされます:
if (i == 0) { u->err = "host not found"; goto failed; } u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t)); if (u->addrs == NULL) { goto failed; } u->naddrs = i; ... return NGX_OK; failed: freeaddrinfo(res); return NGX_ERROR;
メモリ問題のデバッグ
バッファオーバーランや解放後の使用エラーなどのメモリの問題をデバッグするために、最新のコンパイラでサポートされている AddressSanitizer (ASan)を使えます。gcc
やclang
でASanを有効にするには、コンパイラやリンカの-fsanitize=address
オプションを使います。これは、nginxをビルドする時に、configure
スクリプトの--with-cc-opt
--with-ld-opt
パラメータにオプションを追加することで行えます。
nginxのほとんどの割り当てはnginx内部のpoolから行われるため、メモリの問題をデバッグにするにはASanを有効にするだけでは不十分な場合があります。内部プールは、システムからメモリの大きなチャンクを割り当て、そこから小さな割り当てを切り取ります。ただし、このメカニズムはNGX_DEBUG_PALLOC
マクロを1
に設定することで無効にできます。この場合、割り当てはシステムアロケータに直接渡され、バッファ境界を完全に制御できます。
以下の設定業は、上記の情報をまとめたものです。サードパーティモジュールを開発し、様々なプラットフォームでnginxをテストする際に推奨されます。
auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1' --with-ld-opt=-fsanitize=address
共通の落とし穴
Cモジュールの書き方
最も一般的な落とし穴は、回避できる場合に本格的なCモジュールを書こうとすることです。ほとんどの場合、そのタスクは適切な設定を作成することで達成できます。モジュールを書くことが避けられない場合は、できるかぎり小さくシンプルにしてください。例えば、モジュールは一部の変数のみをエクスポートできます。
モジュールを開始する前に、以下の質問を考慮してください:
- 既に利用可能なモジュールを使って目的の機能を実装できますか?
- Perlまたはnjsのような組み込みのスクリプト言語を使って問題を解決できますか?
C文字列
nginxで最もよく使われている文字列、ngx_str_t は、C形式のゼロで終わる文字列ではありません。strlen()
またはstrstr()
のような標準Cライブラリ関数にデータを渡せません。代わりに、ngx_str_t
を受け付けるnginxの対応物か、データへのポインタと長さを使う必要があります。ただし、ngx_str_t
が是ゼロで終了する文字列へのポインタを保持する場合があります: 設定ファイルのパースの結果として得られる文字列はゼロで終了します。
グローバル変数
モジュールでグローバル変数を使わないでください。ほとんどの場合、これはグローバル変数を持つエラーです。グローバルデータは、設定サイクルに関連付けて、対応するメモリプールから割り当てる必要があります。これによりnginxはグレースフル設定再読み込みを実行できます。グローバル変数を使おうとすると、この機能が壊れる可能性があります。これは、2つの設定を同時に取得してそれらを取り除くことができないためです。グローバル変数が必要な時があります。この場合、再設定を適切に管理するには、特別な注意が必要です。また、コードで使われているライブラリにリロード時に壊れる可能性がある暗黙的なグローバル状態があるかを確認します。
手動のメモリ管理
エラーが起きやすいmalloc/freeのやり方を扱う代わりに、nginxのプールの使い方を学んでください。プールは作成され、オブジェクトに関連付けられます - 設定、サイクル、接続、HTTPリクエスト。オブジェクトが破棄されると、関連するプールも破棄されます。オブジェクトを扱う場合、対応するプールから必要な量を割り当てることができ、エラーが発生した場合でもメモリを解放する必要はありません。
スレッド
nginxでスレッドを使わないことをお勧めします。これにより確実に問題が発生します: ほとんどのnginx関数はスレッドセーフではありません。スレッドは、システムコールとスレッドセーフなライブラリ関数のみを実行することが望まれます。クライアント要求処理に関連しないコードを実行する必要がある場合、適切な方法はinit_process
モジュールハンドラでタイマーをスケジュールし、タイマーハンドラで必要なアクションを実行することです。内部的に、nginxはIO関連の操作を強化するためにスレッドを利用しますが、これは多くの制限がある特殊なケースです。
ブロッキングライブラリ
よくある間違いは、内部でブロックするライブラリを使うことです。ほとんどのライブラリは本質的に同期型でブロックします。つまり、一度に1つの操作を実行し、他のピアからの応答を待機する時間を浪費します。その結果、そのようなライブラリでリクエストが処理される場合、nginxのワーカー全体がブロックされ、パフォーマンスが低下します。非同期インタフェースを提供し、プロセス全体をブロックしないライブラリのみを使ってください。
外部サービスへのHTTPリクエスト
多くの場合、モジュールは外部サービスへのHTTP呼び出しを実行する必要があります。よくある間違いは、HTTPリクエストを行うためにlibcurlのような外部ライブラリを使うことです。nginx自体で実行できるタスクに大量の外部(おそらくブロックする!)コードを用意する必要は全くありません。
外部リクエストが必要な場合、2つの基本的な使用シナリオがあります:
- クライアント要求の処理のコンテキスト(例えばコンテントハンドラ)
- ワーカープロセスのコンテキスト(例えば、タイマーハンドラ)
最初のケースでは、サブリクエストAPIを使うのが最適です。外部サービスに直接アクセスする代わりに、nginx設定でlocationを宣言し、サブリクエストをこのlocationに送信します。このlocationはプロキシリクエストに限定されていませんが、他のnginxディレクティブが含まれる可能性があります。そのようなやり方の例は、ngx_http_auth_request moduleで実装されるauth_requestディレクティブです。
2つ目のケースでは、nginxで利用可能な基本的なHTTPクライアント機能を使うことができます。例えば、OCSPモジュールは単純なHTTPクライアントを実装します。