開発ガイド

はじめに
     コードレイアウト
     インクルードファイル
     整数
     一般的なリターンコード
     エラー処理
文字列
     概要
     書式設定
     数値変換
     正規表現
時間
コンテナ
     配列
     リスト
     キュー
     赤黒木
     ハッシュ
メモリ管理
     ヒープ
     プール
     共有メモリ
ロギング
サイクル
バッファ
ネットワーク
     接続
イベント
     イベント
     I/Oイベント
     タイマーイベント
     ポストされたイベント
     イベントループ
プロセス
スレッド
モジュール
     新しいモジュールの追加
     コアモジュール
     設定ディレクティブ
HTTP
     接続
     リクエスト
     設定
     フェーズ
     変数
     複合値
     リクエストリダイレクト
     サブリクエスト
     リクエストのファイナライズ
     リクエストボディ
     リクエストボディフィルタ
     レスポンス
     レスポンスボディ
     レスポンスボディフィルタ
     フィルタモジュールの構築
     バッファの再利用
     ロードバランシング

コーディングスタイル
     一般的なルール
     ファイル
     コメント
     プリプロセッサ
     
     変数
     関数
     
     条件文とループ
     ラベル
メモリ問題のデバッグ
よくある落とし穴
     Cモジュールの作成
     C文字列
     グローバル変数
     手動メモリ管理
     スレッド
     ブロッキングライブラリ
     外部サービスへのHTTPリクエスト

はじめに

コードレイアウト

インクルードファイル

次の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_tngx_uint_tを使用します。これらはそれぞれintptr_tuintptr_tのtypedefです。

一般的なリターンコード

nginxのほとんどの関数は、次のコードを返します。

エラー処理

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関数のラッパーです

その他の文字列関数はnginx固有のものです

次の関数は、大文字と小文字の変換と比較を実行します

次のマクロは、文字列の初期化を簡略化します

書式設定

次の書式設定関数は、nginx固有の型をサポートしています

これらの関数でサポートされている書式設定オプションの完全なリストは、src/core/ngx_string.cにあります。その一部は次のとおりです

ほとんどの型に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を返します。

追加の数値変換関数が2つあります。最初の4つと同様に、エラーの場合はNGX_ERRORを返します。

正規表現

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_time()およびngx_timeofday()マクロは、現在の時間値を秒単位で返し、キャッシュされた時間値にアクセスするための推奨される方法です。

時間を明示的に取得するには、ngx_gettimeofday()を使用します。これは、引数(struct timevalへのポインタ)を更新します。時間は、nginxがシステムコールからイベントループに戻るときに常に更新されます。時間をすぐに更新するには、ngx_time_update()を呼び出すか、シグナルハンドラーコンテキストで時間を更新する場合はngx_time_sigsafe_update()を呼び出します。

以下の関数は、time_t を指定された分解された時間表現に変換します。各ペアの最初の関数は time_tngx_tm_t に変換し、2番目の関数 (_libc_ の接尾辞付き) は struct tm に変換します。

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

配列に要素を追加するには、次の関数を使用します。

現在割り当てられているメモリの量が新しい要素を格納するのに十分でない場合、新しいメモリブロックが割り当てられ、既存の要素がコピーされます。新しいメモリブロックは通常、既存のものの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_thashフィールドをゼロに設定します。このようにマークされたアイテムは、ヘッダーの反復処理時に明示的にスキップされます。

キュー

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) 呼び出しを使用します。キューは次の操作をサポートします。

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_sizebucket_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_headdns_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);

メモリ管理

ヒープ

システムヒープからメモリを割り当てるには、次の関数を使用します。

プール

ほとんどの nginx の割り当てはプールで行われます。nginx プールで割り当てられたメモリは、プールが破棄されると自動的に解放されます。これにより、割り当てパフォーマンスが向上し、メモリ制御が容易になります。

プールは内部的に連続したメモリブロックにオブジェクトを割り当てます。ブロックがいっぱいになると、新しいブロックが割り当てられてプールメモリブロックリストに追加されます。要求された割り当てがブロックに収まるには大きすぎる場合、要求はシステムアロケーターに転送され、返されたポインターは後で解放するためにプールに格納されます。

nginx プールの型は ngx_pool_t です。次の操作がサポートされています。

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_tchain フィールドは、再利用の準備ができている以前に割り当てられたリンクのリストを保持します。プール内のチェーンリンクを効率的に割り当てるには、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 をサイクルに追加します。この関数は、ゾーンの namesize を受け取ります。各共有ゾーンには一意の名前が必要です。指定された nametag を持つ共有ゾーンエントリが既に存在する場合、既存のゾーンエントリが再利用されます。同じ名前で別のタグを持つ既存のエントリがある場合、関数はエラーで失敗します。通常、モジュール構造のアドレスが tag として渡され、1つの nginx モジュール内で名前で共有ゾーンを再利用できるようになります。

共有メモリ エントリ構造 ngx_shm_zone_t には、次のフィールドがあります。

共有ゾーンのエントリは、設定解析後に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_tmutexフィールドで利用可能なミューテックスを使用します。ミューテックスは、メモリの割り当てと解放中にスラブプールで最も一般的に使用されますが、共有ゾーンで割り当てられた他のユーザーデータ構造を保護するために使用することもできます。ミューテックスをロックまたはアンロックするには、それぞれ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ロガーは、いくつかのタイプの出力をサポートしています。

ロガーインスタンスは、nextフィールドで互いにリンクされたロガーのチェーンにすることができます。この場合、各メッセージはチェーン内のすべてのロガーに書き込まれます。

各ロガーに対して、重大度レベルは、ログに書き込まれるメッセージを制御します(そのレベル以上のイベントのみがログに記録されます)。次の重大度レベルがサポートされています。

デバッグロギングの場合、デバッグマスクもチェックされます。デバッグマスクは次のとおりです。

通常、ロガーはerror_logディレクティブから既存のnginxコードによって作成され、サイクル、設定、クライアント接続、その他のオブジェクトの処理のほぼすべての段階で利用可能です。

nginxは、次のロギングマクロを提供します。

ログメッセージは、スタック上のサイズ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の起動時に「初期化サイクル」と呼ばれるプレースホルダーサイクルが作成され、その後、構成から構築された実際のサイクルに置き換えられます。

サイクルのメンバーには以下が含まれます。

バッファ

入出力操作のために、nginxはバッファー型ngx_buf_tを提供します。通常、これは宛先に書き込まれるデータ、またはソースから読み取られるデータを保持するために使用されます。バッファーはメモリまたはファイル内のデータを参照でき、技術的にはバッファーが両方を同時に参照することも可能です。バッファーのメモリは個別に割り当てられ、バッファ構造ngx_buf_tとは関係ありません。

ngx_buf_t構造には、次のフィールドがあります。

入出力操作の場合、バッファーはチェーンでリンクされています。チェーンは、次のように定義された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は、ソケット記述子のラッパーです。これには、次のフィールドが含まれます。

nginx のコネクションは、SSL レイヤーを透過的にカプセル化できます。この場合、コネクションの ssl フィールドは、ngx_ssl_connection_t 構造体へのポインタを保持し、SSL_CTX および SSL を含む、コネクションに関連するすべての SSL データを保持します。recvsendrecv_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 のフィールドには以下が含まれます。

I/Oイベント

ngx_get_connection() 関数を呼び出して取得した各コネクションには、ソケットが読み取りまたは書き込みの準備ができたという通知を受信するために使用される 2 つの添付イベント (c->readc->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() 関数で実装されており、プロセスが終了するまで繰り返し呼び出されます。

イベントループには次のステージがあります。

すべての nginx プロセスは、シグナルも処理します。シグナルハンドラーは、ngx_process_events_and_timers() 呼び出し後にチェックされるグローバル変数を設定するだけです。

プロセス

nginx には、いくつかのタイプのプロセスがあります。プロセスのタイプは、グローバル変数 ngx_process に保持され、次のいずれかになります。

nginx プロセスは、次のシグナルを処理します。

すべてのnginxワーカープロセスはPOSIXシグナルを受信して適切に処理できますが、マスタープロセスは標準のkill()システムコールを使用してワーカーやヘルパーにシグナルを渡しません。代わりに、nginxはすべてのnginxプロセス間でメッセージを送信できるプロセス間ソケットペアを使用します。ただし、現在、メッセージはマスターから子にのみ送信されます。メッセージは標準のシグナルを運びます。

スレッド

nginxワーカープロセスをブロックする可能性のあるタスクを別のスレッドにオフロードすることが可能です。たとえば、nginxは、ファイルI/Oを実行するためにスレッドを使用するように構成できます。別のユースケースとして、非同期インターフェースを持たないため、通常はnginxで使用できないライブラリがあります。スレッドインターフェースは、クライアント接続を処理するための既存の非同期アプローチのヘルパーであり、決して置き換えを目的としたものではないことに注意してください。

同期を処理するために、pthreadsプリミティブに対する次のラッパーが利用可能です。

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シェルスクリプトです。

モジュールを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ライフサイクルの特定の段階で呼び出すことができます。モジュールライフサイクルは、次のイベントで構成されます。

スレッドはnginxで独自のAPIを持つ補足的なI/O機能としてのみ使用されるため、init_threadおよびexit_threadハンドラーは現在呼び出されません。また、不要なオーバーヘッドになるため、init_masterハンドラーもありません。

モジュールのtypeは、ctxフィールドに格納されるものを正確に定義します。その値は、次のいずれかのタイプです。

NGX_CORE_MODULEは最も基本的で、したがって最も一般的で最も低レベルのタイプのモジュールです。他のモジュールタイプは、その上に実装されており、イベントやHTTPリクエストの処理など、対応するドメインをより便利に処理する方法を提供します。

コアモジュールのセットには、ngx_core_modulengx_errlog_modulengx_regex_modulengx_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_confinit_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は、ディレクティブが取る引数の数、そのタイプ、およびそれが表示されるコンテキストを指定するフラグのビットフィールドです。フラグは次のとおりです。

ディレクティブタイプのフラグは以下の通りです。

ディレクティブのコンテキストは、設定ファイル内のどこに記述できるかを定義します。

設定パーサーは、これらのフラグを使用して、誤った場所にディレクティブが記述された場合にエラーをスローし、適切な設定ポインターとともにディレクティブハンドラーを呼び出します。これにより、異なる場所にある同じディレクティブが、異なる場所に値を格納できるようになります。

set フィールドは、ディレクティブを処理し、解析された値を対応する設定に格納するハンドラーを定義します。一般的な変換を実行する関数がいくつかあります。

conf フィールドは、どの設定構造がディレクティブハンドラーに渡されるかを定義します。コアモジュールはグローバル設定のみを持ち、それにアクセスするために NGX_DIRECT_CONF フラグを設定します。HTTP、Stream、Mailなどのモジュールは、設定の階層構造を作成します。例えば、モジュールの設定は、serverlocation、および if スコープに対して作成されます。

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クライアント接続は、以下のステージを経ます。

リクエスト

クライアントHTTPリクエストごとに、ngx_http_request_t オブジェクトが作成されます。このオブジェクトの一部のフィールドは以下のとおりです。

設定

各HTTPモジュールには、次の3種類の構成があります。

構成構造は、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_UNSETNGX_CONF_UNSET_UINTなどの多数のマクロは、設定がないことを示し、マージ中にそれを無視するために提供されます。標準のnginxマージマクロ(ngx_conf_merge_value()ngx_conf_merge_uint_value()など)は、設定をマージし、構成が明示的な値を提供しなかった場合にデフォルト値を設定する便利な方法を提供します。さまざまなタイプのマクロの完全なリストについては、src/core/ngx_conf_file.hを参照してください。

構成時にHTTPモジュールの構成にアクセスするために、次のマクロが利用可能です。これらはすべて、最初の引数としてngx_conf_t参照を取ります。

次の例では、標準の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モジュールの構成にアクセスするために、次のマクロが利用可能です。

これらのマクロは、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フェーズのリストです。

以下は、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_DECLINED 以外の戻り値コードはすべて終了コードとみなされます。ロケーションのコンテンツハンドラーからの戻り値コードはすべて終了コードとみなされます。アクセスフェーズでは、satisfy any モードでは、NGX_OKNGX_DECLINEDNGX_AGAINNGX_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;

ここで

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() 関数を使用します。この関数は、構成(変数が登録される場所)、変数名、および関数の動作を制御するフラグを引数として受け取ります。

この関数は、エラーの場合は 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 を初期化するために必要なすべてのパラメーターを保持します。

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_subrequest(r, uri, args, psr, ps, flags)を呼び出すことでサブリクエストを作成します。ここで、rは親リクエスト、uriargsはサブリクエストのURIと引数、psrは新しく作成されたサブリクエストの参照を受け取る出力パラメータ、psはサブリクエストが完了したことを親リクエストに通知するためのコールバックオブジェクト、flagsはフラグのビットマスクです。次のフラグを使用できます。

次の例では、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値を想定しています。

リクエストボディ

クライアントリクエストのボディを処理するために、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_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_buffering1に設定する必要があります。

以下は、リクエストボディを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_outstatusフィールドは常に設定する必要があります。レスポンスステータスがヘッダーの後にレスポンスボディが続くことを示す場合は、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 — 構成ファイルの名前と
  • portno_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

    ...
};

構造には次のフィールドがあります。

  • sockaddrsocklen
  • data — ロードバランシング方式の
  • tries — アップストリーム
  • getfreenotify

すべてのメソッドは、少なくとも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)および

コーディングスタイル

一般的なルール

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.hngx_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_ */

コメント

プリプロセッサ

マクロ名は、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));

ポインタは、明示的にNULL0ではない)と比較されます。

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モジュールを作成しようとすることです。ほとんどの場合、タスクは適切な設定を作成することで実現できます。モジュールを作成することが避けられない場合は、できるだけ小さくシンプルにしてください。たとえば、モジュールはいくつかの変数をエクスポートするだけですむ場合があります。

モジュールを開始する前に、次の質問を検討してください。

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クライアントを実装しています。