開発ガイド
はじめに
コードレイアウト
-
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コードは2つの整数型、ngx_int_t
とngx_uint_t
を使用します。これらはそれぞれintptr_t
とuintptr_t
のtypedefです。
一般的なリターンコード
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
の値に連続して複数回アクセスすると、パフォーマンスの問題が発生する可能性があります。エラー値が複数回使用される可能性がある場合は、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()
と同じことを行いますが、この呼び出しはコンパイラのデッドストア除去最適化によって削除されることはありません。この関数は、パスワードやキーなどの機密データをクリアするために使用できます。 -
ngx_cpymem()
—ngx_memcpy()
と同じことを行いますが、最終的な宛先アドレスを返します。これは、複数の文字列を連続して追加するのに便利です。 -
ngx_movemem()
—ngx_memmove()
と同じことを行いますが、最終的な宛先アドレスを返します。 -
ngx_strlchr()
— 2つのポインタで区切られた文字列内の文字を検索します。
次の関数は、大文字と小文字の変換と比較を実行します
-
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)
—ngx_str_t *
型の文字列str
をC文字列リテラルtext
で初期化します -
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終端) -
%*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)
— 指定された長さの固定小数点浮動小数点数を、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
フィールドには、正規表現で見つかったすべてのキャプチャと名前付きキャプチャの数がそれぞれ含まれます。
コンパイルされた正規表現は、文字列とのマッチングに使用できます
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の倍数である必要があります。この例では、サイズは、マッチした文字列自体のための1つを加えた、キャプチャの総数から計算されます。
一致するものがある場合、キャプチャには次のようにアクセスできます
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
フィールドには単一の要素のサイズが保持され、配列が初期化されるときに設定されます。
プールに配列を作成するには 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)
を呼び出すことによって作成する必要があります。どちらの関数も、単一のアイテムのサイズと、リストパーツごとのアイテム数を引数として受け取ります。リストにアイテムを追加するには、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
フィールドをゼロに設定します。このようにマークされたアイテムは、ヘッダーの反復処理時に明示的にスキップされます。
キュー
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)
- 反復処理を終了するためのキューの番兵オブジェクトを取得します。 -
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;
木全体を扱うには、ルートノードと番兵ノードの2つのノードが必要です。通常、これらはカスタム構造に追加され、リーフにデータへのリンクまたはデータを埋め込む木にデータを整理できます。
木を初期化するには
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);
ハッシュ
ハッシュテーブル関数は 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
型を使用します。これには上記で説明したハッシュ型が含まれており、さらに2つのキー配列 (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()
と同様にシステムヒープからメモリを割り当てますが、割り当て後にメモリをゼロで埋めます。 -
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)
— 指定されたプールからアラインされたメモリを割り当て、それをゼロで埋めます。 -
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);
チェーンリンク(ngx_chain_t
)はnginxで頻繁に使用されるため、nginxのプール実装はそれらを再利用する方法を提供します。ngx_pool_t
の chain
フィールドは、再利用の準備ができている以前に割り当てられたリンクのリストを保持します。プール内のチェーンリンクを効率的に割り当てるには、ngx_alloc_chain_link(pool)
関数を使用します。この関数は、プールリスト内の空きチェーンリンクを検索し、プールリストが空の場合は新しいチェーンリンクを割り当てます。リンクを解放するには、ngx_free_chain(pool, cl)
関数を呼び出します。
クリーンアップハンドラーはプールに登録できます。クリーンアップハンドラーは、プールが破棄されるときに呼び出される引数付きのコールバックです。プールは通常、特定の nginx オブジェクト (HTTP リクエストなど) に関連付けられており、オブジェクトの有効期間が終了すると破棄されます。プールクリーンアップを登録することは、リソースを解放したり、ファイル記述子を閉じたり、メインオブジェクトに関連付けられた共有データを最終調整したりするための便利な方法です。
プールクリーンアップを登録するには、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
を受け取ります。各共有ゾーンには一意の名前が必要です。指定された name
と tag
を持つ共有ゾーンエントリが既に存在する場合、既存のゾーンエントリが再利用されます。同じ名前で別のタグを持つ既存のエントリがある場合、関数はエラーで失敗します。通常、モジュール構造のアドレスが tag
として渡され、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()
システムコールが使用されます。Windowsでは、CreateFileMapping()
/ MapViewOfFileEx()
のペアが使用されます。
共有メモリ内で割り当てるために、nginxはスラブプールngx_slab_pool_t
型を提供します。メモリを割り当てるためのスラブプールは、各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バイト)を超えるサイズの場合、割り当ては一度に1ページ全体で行われます。
共有メモリ内のデータを同時アクセスから保護するには、ngx_slab_pool_t
の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
通常、ロガーはerror_log
ディレクティブから既存のnginxコードによって作成され、サイクル、設定、クライアント接続、その他のオブジェクトの処理のほぼすべての段階で利用可能です。
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()
関数によって作成されます。この関数は、前のサイクルの構成ファイルを特定し、可能な限り多くのリソースを前のサイクルから継承します。nginxの起動時に「初期化サイクル」と呼ばれるプレースホルダーサイクルが作成され、その後、構成から構築された実際のサイクルに置き換えられます。
サイクルのメンバーには以下が含まれます。
-
pool
— サイクルプール。新しいサイクルごとに作成されます。 -
log
— サイクルログ。最初は古いサイクルから継承され、構成が読み込まれた後、new_log
を指すように設定されます。 -
new_log
— 構成によって作成されたサイクルログ。これは、ルートスコープのerror_log
ディレクティブの影響を受けます。 -
connections
、connection_n
— 各nginxワーカーの初期化中にイベントモジュールによって作成された、ngx_connection_t
型の接続の配列。nginx構成のworker_connections
ディレクティブは、接続の数connection_n
を設定します。 -
free_connections
、free_connection_n
— 現在利用可能な接続のリストと数。利用可能な接続がない場合、nginxワーカーは新しいクライアントの受け入れ、またはアップストリームサーバーへの接続を拒否します。 -
files
、files_n
— ファイル記述子をnginx接続にマッピングするための配列。このマッピングは、NGX_USE_FD_EVENT
フラグ(現在はpoll
とdevpoll
)を持つイベントモジュールによって使用されます。 -
conf_ctx
— コアモジュール構成の配列。構成は、nginx構成ファイルの読み取り中に作成および入力されます。 -
modules
、modules_n
— 現在の構成によってロードされる、静的および動的なngx_module_t
型のモジュールの配列。 -
listening
—ngx_listening_t
型のリスニングオブジェクトの配列。リスニングオブジェクトは通常、ngx_create_listening()
関数を呼び出すさまざまなモジュールのlisten
ディレクティブによって追加されます。リスニングソケットは、リスニングオブジェクトに基づいて作成されます。 -
paths
—ngx_path_t
型のパスの配列。パスは、特定のディレクトリで操作を行うモジュールからngx_add_path()
関数を呼び出すことによって追加されます。これらのディレクトリは、構成の読み取り後に、存在しない場合はnginxによって作成されます。さらに、各パスに2つのハンドラーを追加できます。- パスローダー — nginxの起動またはリロード後、60秒に1回のみ実行されます。通常、ローダーはディレクトリを読み取り、データをnginx共有メモリに格納します。ハンドラーは、専用のnginxプロセス「nginxキャッシュローダー」から呼び出されます。
- パスマネージャー — 定期的に実行されます。通常、マネージャーは古いファイルをディレクトリから削除し、nginxメモリを更新して変更を反映します。ハンドラーは、専用の「nginxキャッシュマネージャー」プロセスから呼び出されます。
-
open_files
—ngx_conf_open_file()
関数を呼び出すことによって作成される、ngx_open_file_t
型のオープンファイルオブジェクトのリスト。現在、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
— 現在のバッファーに関連付けられた別の(「シャドウ」)バッファーへの参照。通常、バッファーがシャドウからのデータを使用しているという意味です。バッファーが消費されると、シャドウバッファーも通常は消費済みとしてマークされます。 -
last_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; };
各チェーンリンクは、そのバッファーへの参照と次のチェーンリンクへの参照を保持します。
バッファーとチェーンの使用例
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
引数には、コネクション構造体でラップする必要があるソケット記述子を指定します。
ワーカーごとのコネクション数が制限されているため、nginx は現在使用中のコネクションを取得する方法を提供しています。コネクションの再利用を有効または無効にするには、ngx_reusable_connection(c, reusable)
関数を呼び出します。ngx_reusable_connection(c, 1)
を呼び出すと、コネクション構造体で reuse
フラグが設定され、コネクションがサイクルの reusable_connections_queue
に挿入されます。ngx_get_connection()
が、サイクルの free_connections
リストに使用可能なコネクションがないことを検出した場合、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
— ワーカーのシャットダウン中にイベントを無視する必要があることを示すタイマーイベントフラグ。正常なワーカーシャットダウンは、キャンセルできないタイマーイベントがスケジュールされていない状態になるまで遅延します。 -
posted
— イベントがキューにポストされていることを示すフラグ。 -
queue
— イベントをキューにポストするためのキューノード。
I/Oイベント
ngx_get_connection()
関数を呼び出して取得した各コネクションには、ソケットが読み取りまたは書き込みの準備ができたという通知を受信するために使用される 2 つの添付イベント (c->read
と c->write
) があります。このようなイベントはすべて、エッジトリガーモードで動作します。これは、ソケットの状態が変化した場合にのみ通知をトリガーすることを意味します。たとえば、ソケットで部分的な読み取りを行っても、ソケットにさらにデータが到着するまで、nginx が読み取り通知を繰り返すことはありません。基盤となる I/O 通知メカニズムが本質的にレベルトリガー (poll
, select
など) である場合でも、nginx は通知をエッジトリガーに変換します。nginx のイベント通知を、異なるプラットフォーム上のすべての通知システムで一貫性のあるものにするには、I/O ソケット通知を処理した後、またはそのソケットで I/O 関数を呼び出した後、必ず ngx_handle_read_event(rev, flags)
関数および ngx_handle_write_event(wev, lowat)
関数を呼び出す必要があります。通常、これらの関数は、読み取りまたは書き込みイベントハンドラーの最後に 1 回呼び出されます。
タイマーイベント
イベントは、タイムアウトが期限切れになったときに通知を送信するように設定できます。イベントで使用されるタイマーは、過去の不明な時点からの経過時間をミリ秒単位でカウントし、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()
ハンドラーが使用され、epoll_wait()
を呼び出して I/O イベントを待機します。 ngx_event_expire_timers()
を呼び出して、タイマーを期限切れにします。タイマーツリーは、期限切れになっていないタイムアウトが見つかるまで、最も左側の要素から右に反復処理されます。期限切れになった各ノードについて、timedout
イベントフラグが設定され、timer_set
フラグがリセットされ、イベントハンドラーが呼び出されます。ngx_event_process_posted()
を呼び出して、ポストされたイベントを処理します。この関数は、ポストされたイベントキューから最初の要素を繰り返し削除し、キューが空になるまで要素のハンドラーを呼び出します。
すべての nginx プロセスは、シグナルも処理します。シグナルハンドラーは、ngx_process_events_and_timers()
呼び出し後にチェックされるグローバル変数を設定するだけです。
プロセス
nginx には、いくつかのタイプのプロセスがあります。プロセスのタイプは、グローバル変数 ngx_process
に保持され、次のいずれかになります。
-
NGX_PROCESS_MASTER
— NGINX の設定を読み取り、サイクルを作成し、子プロセスを開始および制御するマスタープロセス。I/O は実行せず、シグナルにのみ応答します。そのサイクル関数はngx_master_process_cycle()
です。 -
NGX_PROCESS_WORKER
— クライアントコネクションを処理するワーカープロセス。マスタープロセスによって起動され、マスタープロセスのシグナルとチャネルコマンドにも応答します。そのサイクル関数はngx_worker_process_cycle()
です。worker_processes
ディレクティブで設定されているように、複数のワーカープロセスが存在する可能性があります。 -
NGX_PROCESS_SINGLE
—master_process off
モードでのみ存在し、そのモードで実行される唯一のシングルプロセス。サイクルを作成し(マスタープロセスのように)、クライアントコネクションを処理します(ワーカープロセスのように)。そのサイクル関数はngx_single_process_cycle()
です。 -
NGX_PROCESS_HELPER
— ヘルパープロセス。現在、キャッシュマネージャーとキャッシュローダーの 2 つのタイプがあります。両方のサイクル関数はngx_cache_manager_process_cycle()
です。
nginx プロセスは、次のシグナルを処理します。
-
NGX_SHUTDOWN_SIGNAL
(ほとんどのシステムではSIGQUIT
)— 正常なシャットダウン。このシグナルを受信すると、マスタープロセスはすべての子プロセスにシャットダウンシグナルを送信します。子プロセスが残っていない場合、マスターはサイクルプールを破棄して終了します。ワーカープロセスがこのシグナルを受信すると、すべてのリスニングソケットを閉じ、キャンセルできないイベントのスケジュールがなくなるまで待機してから、サイクルプールを破棄して終了します。キャッシュマネージャーまたはキャッシュローダープロセスがこのシグナルを受信すると、すぐに終了します。プロセスがこのシグナルを受信すると、ngx_quit
変数が1
に設定され、処理後にすぐにリセットされます。ワーカープロセスがシャットダウン状態の間、ngx_exiting
変数は1
に設定されます。 -
NGX_TERMINATE_SIGNAL
(ほとんどのシステムではSIGTERM
)— 強制終了。このシグナルを受信すると、マスタープロセスはすべての子プロセスに強制終了シグナルを送信します。子プロセスが1秒以内に終了しない場合、マスタープロセスは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
)— nginxバイナリの変更。マスタープロセスは新しいnginxバイナリを開始し、すべてのリスニングソケットのリストを渡します。“NGINX”
環境変数で渡されるテキスト形式のリストは、セミコロンで区切られた記述子番号で構成されます。新しいnginxバイナリは“NGINX”
変数を読み取り、その初期化サイクルにソケットを追加します。他のプロセスはこのシグナルを無視します。
すべてのnginxワーカープロセスはPOSIXシグナルを受信して適切に処理できますが、マスタープロセスは標準のkill()
システムコールを使用してワーカーやヘルパーにシグナルを渡しません。代わりに、nginxはすべてのnginxプロセス間でメッセージを送信できるプロセス間ソケットペアを使用します。ただし、現在、メッセージはマスターから子にのみ送信されます。メッセージは標準のシグナルを運びます。
スレッド
nginxワーカープロセスをブロックする可能性のあるタスクを別のスレッドにオフロードすることが可能です。たとえば、nginxは、ファイル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は、タスクごとに新しいスレッドを作成するのではなく、スレッドプール戦略を実装します。複数のスレッドプールをさまざまな目的(たとえば、異なるディスクセットで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)
を呼び出すことによってスレッドプールへの参照を取得する必要があります。これは、指定されたname
で新しいスレッドプールを作成するか、既に存在する場合はその名前のスレッドプールへの参照を返します。
指定されたスレッドプールtp
のキューにtask
を追加するには、実行時に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
— configureスクリプトからのコンソール出力に表示されるモジュールの名前。 -
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に静的にコンパイルするには、configureスクリプトに--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 */ };
省略された非公開部分には、モジュールのバージョンと署名が含まれており、事前定義されたマクロNGX_MODULE_V1
を使用して入力されます。
各モジュールは、ctx
フィールドにプライベートデータを保持し、commands
配列で指定された構成ディレクティブを認識し、nginxライフサイクルの特定の段階で呼び出すことができます。モジュールライフサイクルは、次のイベントで構成されます。
- 構成ディレクティブハンドラーは、マスタープロセスのコンテキストで構成ファイルに表示されるときに呼び出されます。
- 構成が正常に解析された後、
init_module
ハンドラーがマスタープロセスのコンテキストで呼び出されます。init_module
ハンドラーは、構成がロードされるたびにマスタープロセスで呼び出されます。 - マスタープロセスは1つ以上のワーカープロセスを作成し、
init_process
ハンドラーがそれぞれのプロセスで呼び出されます。 - ワーカープロセスがマスターからシャットダウンまたは強制終了コマンドを受信すると、
exit_process
ハンドラーが呼び出されます。 - マスタープロセスは終了する前に
exit_master
ハンドラーを呼び出します。
スレッドはnginxで独自のAPIを持つ補足的なI/O機能としてのみ使用されるため、init_thread
およびexit_thread
ハンドラーは現在呼び出されません。また、不要なオーバーヘッドになるため、init_master
ハンドラーもありません。
モジュールのtype
は、ctx
フィールドに格納されるものを正確に定義します。その値は、次のいずれかのタイプです。
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
型は、単一の構成ディレクティブを定義します。構成をサポートする各モジュールは、引数を処理する方法と呼び出すハンドラーを記述するこのような構造体の配列を提供します。
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つ以上の引数を取る。 -
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
—http
ブロック内のserver
ブロック内で使用できます。 -
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
引数として渡されます。
設定パーサーは、これらのフラグを使用して、誤った場所にディレクティブが記述された場合にエラーをスローし、適切な設定ポインターとともにディレクティブハンドラーを呼び出します。これにより、異なる場所にある同じディレクティブが、異なる場所に値を格納できるようになります。
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_t
値に変換します。 -
ngx_conf_set_off_slot
— オフセットをバイト単位で表されるoff_t
値に変換します。 -
ngx_conf_set_msec_slot
— 時間をミリ秒単位で表されるngx_msec_t
値に変換します。 -
ngx_conf_set_sec_slot
— 時間を秒単位で表されるtime_t
値に変換します。 -
ngx_conf_set_bufs_slot
— 与えられた2つの引数を、バッファーの数とサイズを保持する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、Stream、Mailなどのモジュールは、設定の階層構造を作成します。例えば、モジュールの設定は、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接続を受け入れます。このハンドラーは、リッスンソケットでの読み取り通知に応答して呼び出されます。新しいクライアントソケットをラップするために、このステージで新しいngx_connection_t
オブジェクトが作成されます。各nginxリスナーは、新しい接続オブジェクトを渡すハンドラーを提供します。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()
によって設定されます。データは接続のbuffer
に読み込まれます。バッファーのサイズは、最初に 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)
—c
をmodule
のコンテキストとして設定します。
-
-
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
— プロキシ処理のためのリクエストアップストリームオブジェクト。 -
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
— リクエストの最後の正規表現マッチによって生成された正規表現キャプチャ。正規表現マッチは、リクエスト処理中にさまざまな場所で発生する可能性があります。マップルックアップ、SNIまたはHTTP Hostによるサーバーのルックアップ、リライト、proxy_redirectなど。ルックアップによって生成されたキャプチャは、上記のフィールドに保存されます。フィールドncaptures
はキャプチャ数を保持し、captures
はキャプチャ境界を保持し、captures_data
は正規表現がマッチされた文字列を保持し、キャプチャを抽出するために使用されます。新しい正規表現マッチごとに、リクエストキャプチャはリセットされ、新しい値を保持します。 -
count
— リクエスト参照カウンタ。このフィールドは、メインリクエストに対してのみ意味を持ちます。カウンタのインクリメントは、単純なr->main->count++
によって行われます。カウンタをデクリメントするには、ngx_http_finalize_request(r, rc)
を呼び出します。サブリクエストの作成とリクエストボディ読み取りプロセスの実行の両方で、カウンタがインクリメントされます。 -
subrequests
— 現在のサブリクエストのネストレベル。各サブリクエストは、親のネストレベルを継承し、1つ減らされます。値がゼロに達すると、エラーが生成されます。メインリクエストの値は、NGX_HTTP_MAX_SUBREQUESTS
定数によって定義されます。 -
uri_changes
— リクエストに残っているURI変更の数。リクエストがURIを変更できる合計回数は、NGX_HTTP_MAX_URI_CHANGES
定数によって制限されます。変更ごとに値はデクリメントされ、ゼロに達するとエラーが生成されます。リライトと、通常の場所または名前付きの場所への内部リダイレクトは、URI変更と見なされます。 -
blocked
— リクエストに保持されているブロックのカウンタ。この値がゼロ以外の間、リクエストを終了することはできません。現在、この値は保留中のAIO操作(POSIX AIOおよびスレッド操作)とアクティブなキャッシュロックによって増加します。 -
buffered
— リクエストによって生成された出力をバッファリングしたモジュールを示すビットマスク。多くのフィルタが出力をバッファリングできます。たとえば、sub_filterは部分文字列の一致のためにデータをバッファリングでき、コピーフィルタは空き出力バッファがないためにデータをバッファリングできます。この値がゼロ以外である限り、リクエストはフラッシュを保留して完了しません。 -
header_only
— 出力にボディが不要であることを示すフラグ。たとえば、このフラグはHTTP HEADリクエストで使用されます。 -
keepalive
— クライアント接続のキープアライブがサポートされているかどうかを示すフラグ。値は、HTTPバージョンと「Connection」ヘッダーの値から推測されます。 -
header_sent
— 出力ヘッダーがリクエストによって既に送信されていることを示すフラグ。 -
internal
— 現在のリクエストが内部であることを示すフラグ。内部状態に入るには、リクエストは内部リダイレクトを通過するか、サブリクエストである必要があります。内部リクエストは、内部の場所にアクセスできます。 -
allow_ranges
— HTTP Rangeヘッダーでリクエストされたとおりに、部分的なレスポンスをクライアントに送信できることを示すフラグ。 -
subrequest_ranges
— サブリクエストが処理されている間、部分的なレスポンスを送信できることを示すフラグ。 -
single_range
— 単一の連続した出力データ範囲のみをクライアントに送信できることを示すフラグ。このフラグは通常、プロキシされたサーバーからのストリームなどのデータを送信し、レスポンス全体が1つのバッファで利用できない場合に設定されます。 -
main_filter_need_in_memory
,filter_need_in_memory
— ファイルではなくメモリバッファで生成された出力を要求するフラグ。これは、sendfileが有効になっている場合でも、コピーフィルタがファイルバッファからデータを読み取るためのシグナルです。2つのフラグの違いは、それらを設定するフィルタモジュールの場所です。フィルタチェーンの延期フィルタの前に呼び出されるフィルタはfilter_need_in_memory
を設定し、現在のリクエスト出力のみがメモリバッファで来ることを要求します。フィルタチェーンの後半で呼び出されるフィルタはmain_filter_need_in_memory
を設定し、メインリクエストとすべてのサブリクエストの両方が出力を送信しながらメモリ内のファイルを読み取ることを要求します。 -
filter_need_temporary
— リクエスト出力が一時バッファで生成されることを要求するフラグ。ただし、読み取り専用のメモリバッファやファイルバッファでは生成されません。これは、送信先のバッファで直接出力を変更する可能性のあるフィルタによって使用されます。
設定
各HTTPモジュールには、次の3種類の構成があります。
- メイン構成 —
http
ブロック全体に適用されます。モジュールのグローバル設定として機能します。 - サーバー構成 — 単一の
server
ブロックに適用されます。モジュールのサーバー固有の設定として機能します。 - ロケーション構成 — 単一の
location
、if
、またはlimit_except
ブロックに適用されます。モジュールのロケーション固有の設定として機能します。
構成構造は、nginx構成段階で、構造を割り当て、初期化し、マージする関数を呼び出すことによって作成されます。次の例は、モジュールの単純なロケーション構成を作成する方法を示しています。構成には、符号なし整数のタイプである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()
はより高いレベルの構成と構成をマージします。実際、サーバーおよびロケーションの構成はサーバーおよびロケーションレベルでのみ存在するのではなく、それより上のすべてのレベルでも作成されます。具体的には、サーバー構成はメインレベルでも作成され、ロケーション構成はメイン、サーバー、およびロケーションレベルで作成されます。これらの構成により、nginx構成ファイルの任意のレベルでサーバー固有およびロケーション固有の設定を指定できます。最終的に構成はマージダウンされます。NGX_CONF_UNSET
やNGX_CONF_UNSET_UINT
などの多数のマクロは、設定がないことを示し、マージ中にそれを無視するために提供されます。標準のnginxマージマクロ(ngx_conf_merge_value()
やngx_conf_merge_uint_value()
など)は、設定をマージし、構成が明示的な値を提供しなかった場合にデフォルト値を設定する便利な方法を提供します。さまざまなタイプのマクロの完全なリストについては、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のロケーション構成へのポインタを取得し、構造体のhandler
フィールドに保持されているロケーションコンテンツハンドラを置き換えます。
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
への参照を受け取ります。リクエストのメイン構成は決して変更されません。サーバー構成は、リクエストの仮想サーバーが選択された後、デフォルトから変更される可能性があります。リクエストの処理用に選択されたロケーション構成は、リライト操作または内部リダイレクトの結果として複数回変更される可能性があります。次の例は、実行時にモジュールの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リクエストは、一連のフェーズを通過します。各フェーズでは、リクエストに対して異なるタイプの処理が実行されます。モジュール固有のハンドラは、ほとんどのフェーズで登録でき、多くの標準的なnginxモジュールは、リクエスト処理の特定の段階で呼び出される方法として、フェーズハンドラを登録します。フェーズは連続して処理され、リクエストがフェーズに到達すると、フェーズハンドラが呼び出されます。以下は、nginx HTTPフェーズのリストです。
-
NGX_HTTP_POST_READ_PHASE
— 最初のフェーズ。ngx_http_realip_moduleは、他のモジュールが呼び出される前にクライアントアドレスの置換を有効にするために、このフェーズでハンドラを登録します。 -
NGX_HTTP_SERVER_REWRITE_PHASE
—server
ブロック(ただし、location
ブロックの外側)で定義されたリライトディレクティブが処理されるフェーズ。ngx_http_rewrite_moduleはこのフェーズでハンドラをインストールします。 -
NGX_HTTP_FIND_CONFIG_PHASE
— リクエストURIに基づいてロケーションが選択される特別なフェーズ。このフェーズの前には、関連する仮想サーバーのデフォルトロケーションがリクエストに割り当てられ、ロケーション構成を要求するモジュールは、デフォルトのサーバーロケーションの構成を受け取ります。このフェーズでは、リクエストに新しいロケーションが割り当てられます。追加のハンドラはこのフェーズで登録できません。 -
NGX_HTTP_REWRITE_PHASE
—NGX_HTTP_SERVER_REWRITE_PHASE
と同じですが、前のフェーズで選択されたロケーションで定義されたリライトルールの場合です。 -
NGX_HTTP_POST_REWRITE_PHASE
— リライト中にURIが変更された場合に、リクエストが新しいロケーションにリダイレクトされる特別なフェーズ。これは、リクエストが再度NGX_HTTP_FIND_CONFIG_PHASE
を通過することによって実装されます。追加のハンドラはこのフェーズで登録できません。 -
NGX_HTTP_PREACCESS_PHASE
— アクセス制御に関連付けられていない、さまざまなタイプのハンドラの一般的なフェーズ。標準のnginxモジュールngx_http_limit_conn_module とngx_http_limit_req_moduleは、このフェーズでハンドラを登録します。 -
NGX_HTTP_ACCESS_PHASE
— クライアントがリクエストを行う権限があることが検証されるフェーズ。標準のnginxモジュール(ngx_http_access_moduleやngx_http_auth_basic_moduleなど)は、このフェーズでハンドラを登録します。デフォルトでは、クライアントはリクエストが次のフェーズに進むために、このフェーズに登録されたすべてのハンドラの認証チェックに合格する必要があります。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
— レスポンスが通常生成されるフェーズです。複数の nginx 標準モジュールが、ngx_http_index_module やngx_http_static_module
などを含め、このフェーズでハンドラーを登録します。これらのハンドラーは、いずれかが出力を行うまで順番に呼び出されます。また、ロケーションごとにコンテンツハンドラーを設定することも可能です。ngx_http_core_module のロケーション設定にhandler
が設定されている場合、それがコンテンツハンドラーとして呼び出され、このフェーズでインストールされたハンドラーは無視されます。 -
NGX_HTTP_LOG_PHASE
— リクエストのログ記録が実行されるフェーズです。現在、アクセスログ記録のために、ngx_http_log_module のみがこの段階でハンドラーを登録しています。ログフェーズハンドラーは、リクエスト処理の最後に、リクエストを解放する直前に呼び出されます。
以下は、preaccess フェーズハンドラーの例です。
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_table_elt_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
以外の戻り値コードはすべて終了コードとみなされます。ロケーションのコンテンツハンドラーからの戻り値コードはすべて終了コードとみなされます。アクセスフェーズでは、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
と、名前から導出されたハッシュ key
を持つ変数を検索します。
変数の作成
変数を作成するには、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 終端静的配列がモジュールによって作成され、構成に変数を作成するために preconfiguration 段階で処理されます。たとえば
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
変数の set ハンドラーは、リクエストの 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; }
複合値
複雑な値は、その名前にもかかわらず、テキスト、変数、およびそれらの組み合わせを含むことができる式を評価する簡単な方法を提供します。
ngx_http_compile_complex_value
内の複雑な値の記述は、構成段階で 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
は、複雑な値 cv
を初期化するために必要なすべてのパラメーターを保持します。
-
cf
— 構成ポインター -
value
— 解析される文字列(入力) -
complex_value
— コンパイルされた値(出力) -
zero
— ゼロ終端値を有効にするフラグ -
conf_prefix
— 結果に構成プレフィックス(nginx が現在構成を検索しているディレクトリ)を付加します。 -
root_prefix
— 結果にルートプレフィックス(通常の nginx インストールプレフィックス)を付加します。
zero
フラグは、結果をゼロ終端文字列を必要とするライブラリに渡す場合に役立ち、プレフィックスはファイル名を処理する場合に便利です。
コンパイルが成功すると、cv.lengths
には、式に変数があるかどうかの情報が含まれます。NULL 値は、式に静的なテキストのみが含まれており、複雑な値としてではなく、単純な文字列に格納できることを意味します。
ngx_http_set_complex_value_slot()
は、ディレクティブ宣言自体で複雑な値を完全に初期化するために使用される便利な関数です。
実行時に、複雑な値は 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
フィールドを介して常にロケーションに接続されます。つまり、モジュールのロケーション構成は、ngx_http_get_module_loc_conf(r, module)
を呼び出すことでリクエストからいつでも取得できます。リクエストロケーションは、リクエストのライフサイクル中に数回変更される可能性があります。最初は、デフォルトサーバーのデフォルトサーバーロケーションがリクエストに割り当てられます。リクエストが別のサーバーに切り替わる場合(HTTP 「Host」ヘッダーまたは SSL SNI 拡張機能によって選択される)、リクエストはそのサーバーのデフォルトロケーションにも切り替わります。ロケーションの次の変更は、NGX_HTTP_FIND_CONFIG_PHASE
リクエストフェーズで発生します。このフェーズでは、サーバーに構成された名前のないすべてのロケーションの中から、リクエスト URI によってロケーションが選択されます。ngx_http_rewrite_module は、rewrite ディレクティブの結果として、NGX_HTTP_REWRITE_PHASE
リクエストフェーズでリクエスト URI を変更し、新しい URI に基づいて新しいロケーションを選択するために、リクエストを NGX_HTTP_FIND_CONFIG_PHASE
フェーズに戻すことができます。
ngx_http_internal_redirect(r, uri, args)
または ngx_http_named_location(r, name)
のいずれかを呼び出すことで、いつでもリクエストを新しいロケーションにリダイレクトすることもできます。
ngx_http_internal_redirect(r, uri, args)
関数は、リクエスト URI を変更し、リクエストを NGX_HTTP_SERVER_REWRITE_PHASE
フェーズに戻します。リクエストは、サーバーのデフォルトロケーションで続行されます。その後、NGX_HTTP_FIND_CONFIG_PHASE
で、新しいリクエスト URI に基づいて新しいロケーションが選択されます。
次の例は、新しいリクエスト引数を使用して内部リダイレクトを実行します。
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)
は、リクエストを名前付きロケーションにリダイレクトします。ロケーションの名前は引数として渡されます。ロケーションは、現在のサーバーのすべての名前付きロケーションの中から検索され、その後、リクエストは NGX_HTTP_REWRITE_PHASE
フェーズに切り替わります。
次の例は、名前付きロケーション @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
フィールドにコンテキストを格納しているときに呼び出すことができます。これらのコンテキストが新しいロケーション構成と矛盾する可能性があります。矛盾を防ぐために、すべてのリクエストコンテキストは両方のリダイレクト関数によって消去されます。
ngx_http_internal_redirect(r, uri, args)
または ngx_http_named_location(r, name)
を呼び出すと、リクエストの count
が増加します。一貫したリクエスト参照カウントのために、リクエストをリダイレクトした後、ngx_http_finalize_request(r, NGX_DONE)
を呼び出してください。これにより、現在のリクエストコードパスが終了し、カウンターが減少します。
リダイレクトおよび書き換えられたリクエストは内部になり、internal ロケーションにアクセスできます。内部リクエストには、internal
フラグが設定されています。
サブリクエスト
サブリクエストは、主に、1つのリクエストの出力を別のリクエストの出力に挿入するために使用されます。これは他のデータと混合される可能性があります。サブリクエストは通常のリクエストのように見えますが、親リクエストと一部のデータを共有します。特に、サブリクエストはクライアントから他の入力を受け取らないため、クライアント入力に関連するすべてのフィールドが共有されます。サブリクエストのリクエストフィールド parent
には、親リクエストへのリンクが含まれており、メインリクエストの場合は NULL です。フィールド main
には、リクエストグループ内のメインリクエストへのリンクが含まれています。
サブリクエストは、NGX_HTTP_SERVER_REWRITE_PHASE
フェーズで開始します。これは、通常のリクエストと同じ後続のフェーズを通過し、独自の URI に基づいてロケーションが割り当てられます。
サブリクエストの出力ヘッダーは常に無視されます。ngx_http_postpone_filter
は、サブリクエストの出力ボディを、親リクエストによって生成された他のデータに対する適切な位置に配置します。
サブリクエストは、アクティブなリクエストの概念に関連しています。リクエストr
は、クライアント接続オブジェクトc
においてc->data == r
である場合にアクティブとみなされます。特定のある時点では、リクエストグループ内でアクティブなリクエストのみが、そのバッファをクライアントに出力できます。非アクティブなリクエストは、その出力をフィルターチェーンに送信できますが、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
- 出力はクライアントに送信されず、メモリに格納されます。このフラグは、プロキシモジュールの一つによって処理されるサブリクエストにのみ影響します。サブリクエストが完了すると、その出力はngx_buf_t
型のr->out
で利用可能になります。 -
NGX_HTTP_SUBREQUEST_WAITED
- サブリクエストが完了したときにアクティブでなくても、サブリクエストのdone
フラグが設定されます。このサブリクエストフラグは、SSIフィルターによって使用されます。 -
NGX_HTTP_SUBREQUEST_CLONE
- サブリクエストはその親のクローンとして作成されます。親リクエストと同じ場所で開始され、同じフェーズから進行します。
次の例では、URIが/foo
のサブリクエストを作成します。
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
をデクリメントし、ゼロになった場合はリクエストを破棄します。クライアント接続は、現在のリクエストが破棄された後、より多くのリクエストに使用できます。 -
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ロケーションが構成されている場合は、そのロケーションへの内部リダイレクトを実行します。 - 他のコードは成功した完了コードと見なされ、レスポンスボディの送信を完了するためにリクエストライターをアクティブにする可能性があります。ボディが完全に送信されると、リクエストの
count
がデクリメントされます。ゼロになると、リクエストは破棄されますが、クライアント接続は他のリクエストにも使用できます。count
が正の場合、リクエスト内に未完了のアクティビティがあり、後で完了します。
リクエストボディ
クライアントリクエストのボディを処理するために、nginxはngx_http_read_client_request_body(r, post_handler)
関数とngx_http_discard_request_body(r)
関数を提供します。最初の関数はリクエストボディを読み取り、request_body
リクエストフィールドを介して利用できるようにします。2番目の関数は、リクエストボディを破棄(読み取りと無視)するようにnginxに指示します。これらの関数のいずれかは、すべてのリクエストに対して呼び出す必要があります。通常、コンテンツハンドラーが呼び出しを行います。
サブリクエストからクライアントリクエストボディを読み取りまたは破棄することは許可されていません。常にメインリクエストで行う必要があります。サブリクエストが作成されると、親の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
- ボディを単一のメモリバッファに読み取ります。 -
request_body_in_file_only
- メモリバッファに収まる場合でも、常にボディをファイルに読み取ります。 -
request_body_in_persistent_file
- 作成直後にファイルをアンリンクしません。このフラグを持つファイルは、別のディレクトリに移動できます。 -
request_body_in_clean_file
- リクエストが完了したときにファイルをアンリンクします。これは、ファイルが別のディレクトリに移動されることになっていたが、何らかの理由で移動されなかった場合に役立ちます。 -
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)
関数は、出力ヘッダーを送信します。HTTPレスポンスヘッダーを生成するために必要なすべてのデータがr->headers_out
に含まれるまで、この関数を呼び出さないでください。r->headers_out
のstatus
フィールドは常に設定する必要があります。レスポンスステータスがヘッダーの後にレスポンスボディが続くことを示す場合は、content_length_n
も設定できます。このフィールドのデフォルト値は-1
であり、ボディサイズが不明であることを意味します。この場合、チャンク転送エンコーディングが使用されます。任意のヘッダーを出力するには、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
にそのアドレスを格納します。前のハンドラーのアドレスは通常、モジュールの静的変数に格納され、終了前に新しく追加されたハンドラーによって呼び出されます。
次の例は、ステータスが200
のすべてのレスポンスにHTTPヘッダー"X-Foo: foo
"を追加するヘッダーフィルターモジュールの例です。
#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); }
レスポンスボディフィルタ
関数ngx_http_output_filter(r, cl)
は、ngx_http_top_body_filter
変数に格納されている最初のボディフィルターハンドラーを呼び出すことによって、ボディフィルターチェーンを起動します。すべてのボディハンドラーは、最終ハンドラーngx_http_write_filter(r, cl)
が呼び出されるまで、チェーン内の次のハンドラーを呼び出すと想定されています。
ボディフィルタハンドラは、バッファのチェーンを
以下は、ボディ内のバイト数をカウントする
#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; }
フィルタモジュールの構築
ボディフィルタまたはヘッダフィルタを記述する
サードパーティのフィルタモジュールの場合、
次の例は、ソースファイルが1つだけの
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
バッファの再利用
バッファのストリームを発行または変更する
次の例は、受信バッファごとに文字列
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; }
ロードバランシング
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
— アップストリーム -
servers
— -
flags
— 主にロードバランシング-
NGX_HTTP_UPSTREAM_CREATE
— -
NGX_HTTP_UPSTREAM_WEIGHT
— -
NGX_HTTP_UPSTREAM_MAX_FAILS
— -
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
— -
NGX_HTTP_UPSTREAM_DOWN
— -
NGX_HTTP_UPSTREAM_BACKUP
— -
NGX_HTTP_UPSTREAM_MAX_CONNS
—
-
-
host
— アップストリームの名前。 -
file_name, line
— 構成ファイルの名前と -
port
とno_port
— -
shm_zone
— このアップストリーム -
peer
— アップストリーム構成を
ロードバランシングアルゴリズムをtypedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t;
-
init_upstream(cf, us)
— -
init(r, us)
— ロードバランシングに
-
nginxが処理のためにリクエストを別の
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
、 -
data
— ロードバランシング方式の -
tries
— アップストリーム -
get
、free
、notify
、
すべてのメソッドは、少なくとも2つの
-
get(pc, data)
— アップストリーム-
NGX_OK
— サーバが選択されました。 -
NGX_ERROR
— 内部エラーが発生 -
NGX_BUSY
— 現在利用可能なサーバが -
NGX_DONE
— 基盤となる接続が再利用
-
-
free(pc, data, state)
— アップストリーム-
NGX_PEER_FAILED
— 試行が -
NGX_PEER_NEXT
— アップストリーム -
NGX_PEER_KEEPALIVE
— 現在は
tries
カウンタも -
-
notify(pc, data, type)
— OSS -
set_session(pc, data)
および
例
コーディングスタイル
一般的なルール
- 最大テキスト幅は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つの空行で区切られた以下のセクションを含む場合があります。
- 著作権表示
- インクルード
- プリプロセッサ定義
- 型定義
- 関数プロトタイプ
- 変数定義
- 関数定義
著作権表示は以下のようになります。
/* * Copyright (C) Author Name * Copyright (C) Organization, Inc. */
ファイルが大幅に変更された場合は、著者のリストを更新し、新しい著者を先頭に追加する必要があります。
ngx_config.h
ファイルとngx_core.h
ファイルは常に最初にインクルードされ、その後にngx_http.h
、ngx_stream.h
、またはngx_mail.h
のいずれかが続きます。その後、オプションの外部ヘッダーファイルが続きます。
#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
ヘッダーファイルには、いわゆる「ヘッダー保護」を含める必要があります。
#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_ #define _NGX_PROCESS_CYCLE_H_INCLUDED_ ... #endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
コメント
- “
//
”コメントは使用しません。 - テキストは英語で記述し、アメリカ英語のスペルが推奨されます。
- 複数行のコメントは、次のようにフォーマットします。
/* * 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;
列挙型は“_e
”で終わる型を持ちます。
typedef enum { ngx_http_fastcgi_st_version = 0, ngx_http_fastcgi_st_type, ... ngx_http_fastcgi_st_padding } ngx_http_fastcgi_state_e;
変数
変数は、基本型の長さでソートされ、次にアルファベット順で宣言されます。型名と変数名は揃えます。型と名前の「列」は、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 };
一般的によく使用される型/名前の組み合わせがいくつかあります。
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;
関数
すべての関数(静的関数でさえも)は、プロトタイプを持つ必要があります。プロトタイプには、引数名を含めます。長いプロトタイプは、継続行で単一のインデントで折り返します。
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 *));
inline
の代わりにngx_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つのスペースで区切ります。型キャスト内のアスタリスクは、型名からスペースで区切ります。
len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 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;
メモリ問題のデバッグ
バッファオーバーランやuse-after-freeエラーなどのメモリ問題をデバッグするには、一部の最新コンパイラでサポートされているAddressSanitizer(ASan)を使用できます。gcc
およびclang
でASanを有効にするには、-fsanitize=address
コンパイラおよびリンカオプションを使用します。nginxをビルドするときは、configure
スクリプトの--with-cc-opt
および--with-ld-opt
パラメータにオプションを追加することで、これを行うことができます。
nginxでのほとんどの割り当ては、nginx内部のプールから行われるため、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呼び出しを実行する必要があります。一般的な間違いは、libcurlなどの外部ライブラリを使用してHTTPリクエストを実行することです。nginx自体で実行できるタスクに対して、大量の外部(おそらくブロッキング!)コードを持ち込むことは絶対に不要です。
外部リクエストが必要な場合は、2つの基本的な使用シナリオがあります。
- クライアントリクエストの処理のコンテキスト(たとえば、コンテンツハンドラ内)
- ワーカープロセスのコンテキスト(たとえば、タイマーハンドラ)
最初の場合、最適なのはサブリクエストAPIを使用することです。外部サービスに直接アクセスする代わりに、nginx構成で場所を宣言し、その場所にサブリクエストを送信します。この場所は、リクエストのプロキシに限定されず、他のnginxディレクティブを含めることができます。このようなアプローチの例は、auth_requestディレクティブであり、ngx_http_auth_requestモジュールに実装されています。
2番目のケースでは、nginxで利用可能な基本的なHTTPクライアント機能を使用できます。たとえば、OCSPモジュールは、シンプルなHTTPクライアントを実装しています。