開発ガイド

はじめに
     コード レイアウト
     インクルード ファイル
     数字
     共通 リターン コード
     エラーハンドリング
文字列
     概要
     フォーマット
     数値変換
     正規表現
時間
コンテナ
     配列
     リスト
     キュー
     赤黒木
     ハッシュ
メモリ管理
     ヒープ
     プール
     共有メモリ
ログ
サイクル
バッファ
ネットワーク
     接続
イベント
     イベント
     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のコードはintptr_tuintptr_tのそれぞれについての型定義である二つ整数型 ngx_int_tngx_uint_tを使います。

共通リターンコード

nginxのほとんどの関数は以下のコードを返します:

エラーハンドリング

ngx_errno マクロは最後のシステムエラーコードを返します。POSIXプラットフォーム上のerrnoにマップされ、Windows内でGetLastError() コールにマップされます。ngx_socket_errno マクロは最後のソケットエラーの番号を返します。ngx_errno マクロのように、POSIXプラットフォーム上のerrnoへマップされます。Windows上ではWSAGetLastError() にマップされます。ngx_errno あるいは ngx_socket_errno の値への連続する1度以上のアクセスはパフォーマンスの問題を起こすかもしれません。エラーの値が複数回使われるかもしれない場合は、型ngx_err_tのローカル変数に格納します。エラーを設定するには、ngx_set_errno(errno)ngx_set_socket_errno(errno)マクロを使ってください。

ngx_errnongx_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構造内のcapturesnamed_capturesフィールドは正規表現内でそれぞれ見つかった全てのcaptureとnamed captureをカウントします。

それからコンパイルされた正規表現は文字列に対する一致に使われます:

ngx_int_t  n;
int        captures[(1 + rc.captures) * 3];

ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'.");

n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3);
if (n >= 0) {
    /* string matches expression */

} else if (n == NGX_REGEX_NO_MATCHED) {
    /* no match was found */

} else {
    /* some error */
    ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n);
}

ngx_regex_exec()への引数はコンパイルされた正規表現reinputに一致する文字列、見つかったcapturesを保持するための整数の任意の配列、そして配列のsizeです。captures 配列のサイズはPCRE APIで要求されるように、3の倍数でなければなりません。例では、サイズはcaptureの総数プラス1、一致した文字列自身、から計算されます。

一致した場合、captureは以下のようにしてアクセスすることができます:

u_char     *p;
size_t      size;
ngx_str_t   name, value;

/* all captures */
for (i = 0; i < n * 2; i += 2) {
    value.data = input.data + captures[i];
    value.len = captures[i + 1] — captures[i];
}

/* accessing named captures */

size = rc.name_size;
p = rc.names;

for (i = 0; i < rc.named_captures; i++, p += size) {

    /* capture name */
    name.data = &p[2];
    name.len = ngx_strlen(name.data);

    n = 2 * ((p[0] << 8) + p[1]);

    /* captured value */
    value.data = &input.data[captures[n]];
    value.len = captures[n + 1] — captures[n];
}

ngx_regex_exec_array() 関数はngx_regex_elt_t要素 (関連名を使ってコンパイルされた単なる正規表現)の配列、一致した文字列、およびログを受け付けます。関数は合致が見つかるか、残りの表現がなくなるまで、表現を配列から文字列に適用します。一致がある場合は返り値は NGX_OK、そうでなければ NGX_DECLINEDか、エラーの場合はNGX_ERROR です。

時間

ngx_time_t 構造は秒、ミリ秒 およびGMTオフセットについての3つの個々の型を持つ時間を表現します:

typedef struct {
    time_t      sec;
    ngx_uint_t  msec;
    ngx_int_t   gmtoff;
} ngx_time_t;

ngx_tm_t 構造はUNIXプラットフォーム上のstruct tm およびWindows上のSYSTEMTIMEのエイリアスです。

現在の時間を取得するには、望ましい形式のキャッシュされた時間の値を表す、利用可能なグローバル変数のうちの1つにアクセスするだけで通常は十分です。

利用可能な文字列表現は以下の通りです:

ngx_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 フィールドは1つの要素のサイズを保持し、配列が初期化される時に設定されます。

プール内で配列を作るにはngx_array_create(pool, n, size) 呼び出しを使い、既に割り当てられている配列オブジェクトを初期化するには ngx_array_init(array, pool, n, size)呼び出しを使ってください。

ngx_array_t  *a, b;

/* create an array of strings with preallocated memory for 10 elements */
a = ngx_array_create(pool, 10, sizeof(ngx_str_t));

/* initialize string array for 10 elements */
ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));

要素を配列に追加するには以下の関数を使ってください:

現在割り当てられているメモリの量が新しい要素を受け入れるのに十分な大きさでは無い場合、新しいメモリのブロックが割り当てられ、既存の要素はそれにコピーされます。新しいメモリブロックは通常は既存のものの2倍の大きさです。

s = ngx_array_push(a);
ss = ngx_array_push_n(&b, 3);

リスト

Nginxではリストは配列が連続したもので、一時的に大量の項目を挿入するのに適しています。ngx_list_tのリストの型は以下のように定義されます:

typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

実際の項目はリスト部分に格納されます。これは以下のように定義されます:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};

使う前にリストはngx_list_init(list, pool, n, size)を呼び出すことで初期化されるか、ngx_list_create(pool, n, size)を呼び出すことで生成されるべきです。両方の関数は引数として1つの項目のサイズとリスト部分あたりの項目の数を取ります。綱目をリストに追加するには、ngx_list_push(list) 関数を使ってください。項目をイテレートするには、例で示すように直接リストのフィールドをアクセスします:

ngx_str_t        *v;
ngx_uint_t        i;
ngx_list_t       *list;
ngx_list_part_t  *part;

list = ngx_list_create(pool, 100, sizeof(ngx_str_t));
if (list == NULL) { /* error */ }

/* add items to the list */

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "foo");

v = ngx_list_push(list);
if (v == NULL) { /* error */ }
ngx_str_set(v, "bar");

/* iterate over the list */

part = &list->part;
v = part->elts;

for (i = 0; /* void */; i++) {

    if (i >= part->nelts) {
        if (part->next == NULL) {
            break;
        }

        part = part->next;
        v = part->elts;
        i = 0;
    }

    ngx_do_smth(&v[i]);
}

リストは主にHTTP入力と出力ヘッダのために使われます。

リストは項目の削除をサポートしません。しかし、必要な場合は、項目は実際にリストから削除すること無しに内部的に失われたものとしてマークすることができます。例えば、HTTP出力ヘッダ(ngx_table_elt_t オブジェクトとして格納されます)を失われたものとしてマークするには、ngx_table_elt_t内のhashフィールドを0に設定します。このやり方でマークされた項目は、ヘッダがイテレートされる時に明示的にスキップされます。 iterated over.

キュー

nginxでは、キューはでしゃばりの二重にリンクされたリストで、各ノードは以下のように定義されます:

typedef struct ngx_queue_s  ngx_queue_t;

struct ngx_queue_s {
    ngx_queue_t  *prev;
    ngx_queue_t  *next;
};

先頭のキューノードはどのようなデータともリンクしていません。使う前にリストヘッドを初期化するために、ngx_queue_init(q)呼び出しを使ってください。キューは以下の操作をサポートします:

例:

typedef struct {
    ngx_str_t    value;
    ngx_queue_t  queue;
} ngx_foo_t;

ngx_foo_t    *f;
ngx_queue_t   values, *q;

ngx_queue_init(&values);

f = ngx_palloc(pool, sizeof(ngx_foo_t));
if (f == NULL) { /* error */ }
ngx_str_set(&f->value, "foo");

ngx_queue_insert_tail(&values, &f->queue);

/* insert more nodes here */

for (q = ngx_queue_head(&values);
     q != ngx_queue_sentinel(&values);
     q = ngx_queue_next(q))
{
    f = ngx_queue_data(q, ngx_foo_t, queue);

    ngx_do_smth(&f->value);
}

赤黒木

src/core/ngx_rbtree.hヘッダファイルは赤黒木の効果的な実装への参照を提供します。

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

    /* custom per-tree data here */
} my_tree_t;

typedef struct {
    ngx_rbtree_node_t  rbnode;

    /* custom per-node data */
    foo_t              val;
} my_node_t;

木を全体として扱うために、二つのノードが必要です: ルートと歩哨です。一般的に、葉がデータへのリンクあるいは埋め込みを含む木へデータを整理しながら、独自の構造へ追加されます。

木を初期化するには:

my_tree_t  root;

ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);

木を横断し新しい値を追加するには、"insert_value" 関数を使ってください。例えば、ngx_str_rbtree_insert_value 関数は ngx_str_t 型を扱います。引数は、挿入のルート ノード、新しく生成されたノード、および木の歩哨へのポインタです。

void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
                                 ngx_rbtree_node_t *node,
                                 ngx_rbtree_node_t *sentinel)

行き来はとても率直なもので、以下の捜索機能パターンを使って実演することができます:

my_node_t *
my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash)
{
    ngx_int_t           rc;
    my_node_t          *n;
    ngx_rbtree_node_t  *node, *sentinel;

    node = rbtree->root;
    sentinel = rbtree->sentinel;

    while (node != sentinel) {

        n = (my_node_t *) node;

        if (hash != node->key) {
            node = (hash < node->key) ? node->left : node->right;
            continue;
        }

        rc = compare(val, node->val);

        if (rc < 0) {
            node = node->left;
            continue;
        }

        if (rc > 0) {
            node = node->right;
            continue;
        }

        return n;
    }

    return NULL;
}

compare() 関数は、未満、等しい、あるいは大きい値を返す、古典的な比較関数です。検索を高速化し、大きくなるかもしれないユーザオブジェクトの比較を避けるために、整数ハッシュフィールドが使われます。

ノードを木に追加するために、新しいノードを割り当て、それを初期化し、そしてngx_rbtree_insert()を呼びます:

    my_node_t          *my_node;
    ngx_rbtree_node_t  *node;

    my_node = ngx_palloc(...);
    init_custom_data(&my_node->val);

    node = &my_node->rbnode;
    node->key = create_key(my_node->val);

    ngx_rbtree_insert(&root->rbtree, node);

ノードを削除するには、ngx_rbtree_delete()関数を呼んでください:

ngx_rbtree_delete(&root->rbtree, node);

Hash

ハッシュテーブル関数は src/core/ngx_hash.h で定義されています。確実およびワイルドカードの両方の一致がサポートされます。後者は特別なセットアップを必要とし、以下の別の章で説明されます。

ハッシュを初期化する前に、nginxがそれを最適化してビルドできるように、保持するだろう要素の数を知る必要があります。設定する必要がある2つのパラメータは、別のドキュメント内で説明されるように、max_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 型を使ってください。それは、上で説明されるハッシュ型を含み、二つの追加のキー配列を持ちます: 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);

nginxではチェイン リンク (ngx_chain_t) が積極的に使われるため、nginxのプールの実装はそれらを再利用する方法を提供します。ngx_pool_tchain フィールドは再利用のために以前割り当てられたリンクのリストを準備し続けます。プール内での効率的なチェインリンクの割り当てのために、ngx_alloc_chain_link(pool) 関数を使ってください。この関数はプールリスト内のフリーなチェインリンクを探し、プールリストが空であれば新しいチェインリンクを割り当てます。リンクを解放するには、ngx_free_chain(pool, cl) 関数を呼びます。

クリーンナップ ハンドラはプール内に登録することができます。クリーンナップ ハンドラはプールが破壊される時に呼ばれる引数を持つコールバックです。プールは通常(HTTPリクエストのような)特定のnginxオブジェクトに紐付けられ、オブジェクトがその生存期間の最後に達した時に破壊されます。プールのクリーンナップの登録はリソースを解放し、ファイル ディスクリプタを閉じ、あるいはメインのオブジェクトに関連する共有データへの最後の調整を行う便利な方法です。

プールのクリーンナップを登録するには、ngx_pool_cleanup_add(pool, size)を呼びます。これは呼び出し元によって情報が与えられるngx_pool_cleanup_t ポインタを返します。クリーンナップ ハンドラのための内容を割り当てるためにsize引数を使います。

ngx_pool_cleanup_t  *cln;

cln = ngx_pool_cleanup_add(pool, 0);
if (cln == NULL) { /* error */ }

cln->handler = ngx_my_cleanup;
cln->data = "foo";

...

static void
ngx_my_cleanup(void *data)
{
    u_char  *msg = data;

    ngx_do_smth(msg);
}

共有メモリ

共有メモリはプロセス間での共通データを共有するためにnginxによって使われます。ngx_shared_memory_add(cf, name, size, tag) 関数はサイクルに新しい共有メモリ エントリ ngx_shm_zone_t を追加します。関数は領域のnamesize を受け取ります。各共有ゾーンはユニークな名前を持たなければなりません。もし指定された名前タグを持つ共有領域のエントリが存在する場合、既存の領域のエントリは再利用されます。同じ名前を持つ既存のエントリが異なるタグを持つ場合は、関数はエラーで失敗します。通常、モジュール構造のアドレスがタグとして渡され、1つのnginxモジュール内で名前によって共有領域を再利用することができます。

共有メモリ エントリ構造 ngx_shm_zone_t は以下のフィールドを持ちます:

共有ゾーン エントリは設定がパースされた後でngx_init_cycle()内の実際のメモリにマップされます。POSIXシステム上では、mmap() syscall が共有匿名マッピングを生成するために使われます。Windows上では、CreateFileMapping()/ MapViewOfFileEx() ペアが使われます。

共有メモリ内で割り当てるために、nginxはスラブ プール ngx_slab_pool_t を提供します。割り当てメモリのためのslabプールが各nginx共有領域内で自動的に作成されます。プールは共有ゾーンの最初に位置していて、表現(ngx_slab_pool_t *) shm_zone->shm.addr によってアクセスすることができます。共有領域内でメモリを割り当てるためには、ngx_slab_alloc(pool, size) または ngx_slab_calloc(pool, size)のどちらかを呼び出します。メモリを解放するには、ngx_slab_free(pool, p)を呼びます。

スラブ プールは全ての共有ゾーンをページに分割します。各ページは同じサイズのオブジェクトを割り当てるために使われます。指定されたサイズは2の乗数で、8バイトの最小サイズより大きくなければなりません。適合しない値は上に丸められます。各ページのためのビットマスクは、どのブロックが使用中でどれが割り当てのためにフリーであるかを追跡します。半分のページ(通常 2048バイト)より大きい場合、割り当ては一度にページ全体まで行われます。

同時アクセスから共有メモリ内のデータを守るためには、ngx_slab_pool_tmutexフィールド内で利用可能なmutexを使ってください。mutexはメモリの割り当てと解放の間にslabプールによって最も一般的に使われますが、共有領域内で割り当てられる他のユーザデータの構造を保護するために使うことができます。mutexをロックあるいはアンロックするために、それぞれngx_shmtx_lock(&shpool->mutex) あるいは ngx_shmtx_unlock(&shpool->mutex)を使ってください。

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

/* allocate shared zone context */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t));
if (ctx == NULL) {
    /* error */
}

/* add an entry for 64k shared zone */
shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module);
if (shm_zone == NULL) {
    /* error */
}

/* register init callback and context */
shm_zone->init = ngx_foo_init_zone;
shm_zone->data = ctx;


...


static ngx_int_t
ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
    ngx_foo_ctx_t  *octx = data;

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

    if (octx) {
        /* reusing a shared zone from old cycle */
        ctx->value = octx->value;
        return NGX_OK;
    }

    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;

    if (shm_zone->shm.exists) {
        /* initialize shared zone context in Windows nginx worker */
        ctx->value = shpool->data;
        return NGX_OK;
    }

    /* initialize shared zone */

    ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t));
    if (ctx->value == NULL) {
        return NGX_ERROR;
    }

    shpool->data = ctx->value;

    return NGX_OK;
}

ログ

ログのためにnginxはngx_log_t オブジェクトを使います。nginxのロガーはいくつかの種類の出力をサポートします:

ロガーのインスタンスは、お互いにnextフィールドを使ってリンクされたロガーのチェインに成り得ます。この場合、各メッセージはチェイン内の全てのロガーに書き込まれます。

各ロガーについて、重大度はどのメッセージがログに書かれるかを制御します (割り当てられたレベルあるいはそれより高いイベントだけが記録されます)。以下の重要度のレベルがサポートされます:

デバッグのログのために、デバッグのマスクが同様にチェックされます。デバッグのマスクは以下の通りです:

通常、ロガーは既存のnginxコードによってerror_log ディレクティブから生成され、サイクル、設定、クライアント接続および他のオブジェクトのほとんど全ての処理のステージで利用可能です。

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() 関数によって生成されます。これは以前のサイクルを引数として取ります。 argument. 関数は以前のサイクルの設定ファイルの場所を特定し、以前のサイクルからできる限り多くのリソースを継承します。nginxが開始する時に"init cycle" と呼ばれるplaceholderサイクルが生成され、その後設定によって構築された実際のサイクルによって置き換えられます。

サイクルのメンバーは以下を含みます:

バッファ

入力/出力オペレーションのために、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;
};

各チェーンリンクはバッファへの参照と次のチェーンリンクへの参照を保持します。

An example of using buffers and chains:

ngx_chain_t *
ngx_get_my_chain(ngx_pool_t *pool)
{
    ngx_buf_t    *b;
    ngx_chain_t  *out, *cl, **ll;

    /* first buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_calloc_buf(pool);
    if (b == NULL) { /* error */ }

    b->start = (u_char *) "foo";
    b->pos = b->start;
    b->end = b->start + 3;
    b->last = b->end;
    b->memory = 1; /* read-only memory */

    cl->buf = b;
    out = cl;
    ll = &cl->next;

    /* second buf */
    cl = ngx_alloc_chain_link(pool);
    if (cl == NULL) { /* error */ }

    b = ngx_create_temp_buf(pool, 3);
    if (b == NULL) { /* error */ }

    b->last = ngx_cpymem(b->last, "foo", 3);

    cl->buf = b;
    cl->next = NULL;
    *ll = cl;

    return out;
}

ネットワーク

接続

接続型 ngx_connection_t はソケットディスクリプタのラッパーです。以下のフィールドを含みます:

nginxの接続は透過的にSSL層をカプセル化します。この場合、接続のssl フィールドはngx_ssl_connection_t 構造へのポインタを保持し、SSL_CTXSSLを含む接続のための全てのSSLに関係するデータを持ち続けます。recv, send, recv_chain および send_chain ハンドラはSSL有効な関数へも設定されます。

nginxの設定内のworker_connectionsディレクティブはnginxワーカー毎の接続の数を制限します。全ての接続の構造は、ワーカーが開始する時にあらかじめ作成され、サイクルオブジェクトのconnectionsフィールドの中に格納されます。接続の構造を取り出すには、ngx_get_connection(s, log) 関数を使ってください。それはs引数をソケットディスクリプタとして取ります。これはconnection構造の中でラップされる必要があります。

ワーカーあたりの接続の数が制限されるため、nginxは現在使われている接続を横取りする方法を提供します。接続の再利用を有効あるいは無効にするために、ngx_reusable_connection(c, reusable)関数を呼んでください。ngx_reusable_connection(c, 1)の呼び出しは接続構造のreuse フラグを設定し、その接続をサイクルのreusable_connections_queue に挿入します。ngx_get_connection()はサイクルのfree_connections リストの中に利用可能な接続が無いと分かるといつでも特定の数の再利用可能な接続lを解放するためにngx_drain_connections()を呼びます。そのようなそれぞれの接続について、close フラグが設定され、ngx_close_connection(c) を呼びだすことで接続を解放することになっている読み込みハンドラが呼ばれ、再利用できるようにします。接続が再利用可能な時に状態を抜けるためにngx_reusable_connection(c, 0)が呼ばれます。HTTPクライアント接続はnginxでの再利用可能な接続の例です; クライアントからの最初のリクエストのバイトが受信されるまでそれらは再利用可能としてマークされます。

イベント

イベント

nginxでのイベント オブジェクト ngx_event_t は特定のイベントが起きたことを伝えるための仕組みを提供します。

ngx_event_t内のフィールドは以下を含みます:

I/O イベント

ngx_get_connection() 関数を呼び出すことで取得される各接続は2つの備え付けのイベントを持ちます。c->readc->write。それらはソケットの読み込みあるいは書き込みの準備ができたという通知を受け取るために使われます。全てのそのようなイベントはEdge-Triggeredモードで操作されます。ソケットの状態の変更時にそれらは通知を引き起こすことを意味します。例えば、ソケット上の部分的な読み込みの実施は、ソケットにもっと多くのデータが到着するまでnginxに繰り返しの読み込み通知を配送しないでしょう。背後で動くI/O通知の仕組みが本質的に Level-Triggered(poll, select)であった場合でも、nginxは通知を Edge-Triggered に変換します。異なるプラットフォーム上の全ての通知システムに渡ってnginxのイベント通知に一貫性を持たせるには、ngx_handle_read_event(rev, flags)ngx_handle_write_event(wev, lowat) がI/Oソケットの通知あるいはソケット上の全てのI/O呼び出し関数の後で呼ばれる必要があります。通常、関数は各読み込みあるいは書き込みイベントハンドラの最後に一度呼ばれます。

タイマー イベント

タイムアウトが期限切れになった時に通知を送信するためにイベントを設定することができます。例えば、ngx_current_msec 変数はEpochから費やされngx_msec_tに切り取られたミリ秒数を保持します。その現在の値はngx_current_msec変数から取得することができます。

関数 ngx_add_timer(ev, timer) はイベントのタイムアウトを設定し、ngx_del_timer(ev) は以前に設定されたタイムアウトを削除します。グローバル タイムアウト 赤黒木 ngx_event_timer_rbtree は現在設定されている全てのタイムアウトを格納します。木の中のキーはngx_msec_tの型で、イベントが発生した時の時間です。木の構造により高速な挿入と削除操作が可能で、最も近いタイムアウトにアクセスします。nginxはI/Oイベントおよびタイムアウトの期限切れイベントまでどれだけ待つかを見つけるために使います。

ポストされたイベント

イベントがポストされるかも知れません。ハンドルが現在のイベントループの繰り返しの中でどこかの時点の後で呼ばれるかも知れないという意味です。イベントのポストはコードを単純化しスタックオーバーフローを避けるための良い練習です。ポストされたイベントはポストキューの中に保持されます。ngx_post_event(ev, q) マクロはイベント ev をポスト キューq に投稿します。ngx_delete_posted_event(ev) マクロはイベントev を現在投稿されているキューから削除します。通常、イベントはngx_posted_eventsキューに投稿されます。これはイベントループの終わりで処理されます — 全てのI/Oとタイマーイベントが既に処理された後。関数 ngx_event_process_posted() はイベントキューを処理するために呼ばれます。キューが空になるまでイベントハンドラを呼びます。このことは、ポストされたイベントハンドラは現在のイベントループの繰り返しの中で処理されるされるようにもっと多くのイベントをポストすることができることを意味します。

例:

void
ngx_my_connection_read(ngx_connection_t *c)
{
    ngx_event_t  *rev;

    rev = c->read;

    ngx_add_timer(rev, 1000);

    rev->handler = ngx_my_read_handler;

    ngx_my_read(rev);
}


void
ngx_my_read_handler(ngx_event_t *rev)
{
    ssize_t            n;
    ngx_connection_t  *c;
    u_char             buf[256];

    if (rev->timedout) { /* timeout expired */ }

    c = rev->data;

    while (rev->ready) {
        n = c->recv(c, buf, sizeof(buf));

        if (n == NGX_AGAIN) {
            break;
        }

        if (n == NGX_ERROR) { /* error */ }

        /* process buf */
    }

    if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ }
}

イベントループ

nginxのマスタープロセスを除いて、全てのnginxプロセスはI/Oを行い、そういうわけでイベントループを持ちます。(nginxのマスタプロセスは代わりにシグナルが到着するのを待つsigsuspend()呼び出しの中でほとんどの時間を過ごします。) nginxのイベントループはngx_process_events_and_timers() 関数の中で実装されます。これはプロセスが終了するまで繰り返し呼ばれます。

イベントループは以下のステージを持ちます:

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

プロセス

nginxには処理の幾つかのタイプがあります:プロセスの型はngx_process グローバル変数の中に保持され、以下のうちの1つです:

nginxプロセスは以下のシグナルを処理します:

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

スレッド

nginxワーカープロセスをブロックしてしまうものを別のスレッドタスクに押し付けることができます。例えば、nginxはfile I/Oをするためにスレッドを使うように設定することができます。他の使い方は、非同期インタフェースを持たないため通常はnginxで使うことができないライブラリです。スレッド インタフェースはクライアント接続を処理する既存の非同期のやり方のためのヘルパーであることを思い出してください。決して置き換えをするつもりではありません。

同期を扱うためには、以下のpthreads プリミティブのラッパーが利用可能です:

各タスクのために新しいスレッドを作成する代わりに、nginxはthread_pool 戦略を実装します。複数のスレッドプールは異なる目的(例えば、ディスクの異なるセット上でのI/Oの実施)のために設定されるかもしれません。各スレッドプールは起動時に生成され、タスクのキューを処理する限られた数のスレッドを含みます。タスクが完了した時に、事前定義された完了ハンドラが呼ばれます。

src/core/ngx_thread_pool.h ヘッダファイルは関連する定義を含みます:

struct ngx_thread_task_s {
    ngx_thread_task_t   *next;
    ngx_uint_t           id;
    void                *ctx;
    void               (*handler)(void *data, ngx_log_t *log);
    ngx_event_t          event;
};

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name);
ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name);

ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size);
ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);

設定時に、スレッドを喜んで使うモジュールはngx_thread_pool_add(cf, name)を呼ぶことでスレッドプールへのリファレンスを取得する必要があります。これは指定された名前の新しいスレッドプールを作成するか、既に存在する場合はその名前を持つプールへのリファレンスを返します。

実行時に特定のスレッドプールtpのキューへタスクを追加するには、ngx_thread_task_post(tp, task) 関数を使ってください。スレッド内で関数を実行するには、ngx_thread_task_t構造を使ってパラメータを渡し、ハンドラを完了ハンドラをセットアップします:

typedef struct {
    int    foo;
} my_thread_ctx_t;


static void
my_thread_func(void *data, ngx_log_t *log)
{
    my_thread_ctx_t *ctx = data;

    /* this function is executed in a separate thread */
}


static void
my_thread_completion(ngx_event_t *ev)
{
    my_thread_ctx_t *ctx = ev->data;

    /* executed in nginx event loop */
}


ngx_int_t
my_task_offload(my_conf_t *conf)
{
    my_thread_ctx_t    *ctx;
    ngx_thread_task_t  *task;

    task = ngx_thread_task_alloc(conf->pool, sizeof(my_thread_ctx_t));
    if (task == NULL) {
        return NGX_ERROR;
    }

    ctx = task->ctx;

    ctx->foo = 42;

    task->handler = my_thread_func;
    task->event.handler = my_thread_completion;
    task->event.data = ctx;

    if (ngx_thread_task_post(conf->thread_pool, task) != NGX_OK) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

モジュール

新しいモジュールの追加

各スタンドアローンのnginxモジュールは少なくとも2つのファイルを含む別個のディレクトリ内にあります: config とモジュールソースコードを持つファイル。config ファイルはnginxがモジュールを統合するために必要な全ての情報を含みます。例えば:

ngx_module_type=CORE
ngx_module_name=ngx_foo_module
ngx_module_srcs="$ngx_addon_dir/ngx_foo_module.c"

. auto/module

ngx_addon_name=$ngx_module_name

config ファイルは以下の変数を設定およびアクセスすることができるPOSIXシェルスクリプトです:

モジュールをnginxに静的にコンパイルするには、設定スクリプトへ--add-module=/path/to/module引数を使ってください。後でnginxに動的にロードするには、--add-dynamic-module=/path/to/module 引数を使ってください。

コア モジュール

モジュールはnginxの構築ブロックで、機能のほとんどはモジュールとして実装されます。モジュールのソースファイルは型ngx_module_tのグローバル変数を含まなければなりません。これは以下のように定義されます:

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    /* stubs for future extensions are omitted */
};

省略されたprivate部分はモジュールのバージョン、シグネチャーを含み、事前定義されたマクロNGX_MODULE_V1を使って埋められます。

各モジュールはprivateデータをctx フィールドに保持し、設定ディレクティブを理解し、commands 配列の中で指定され、nginxのライフサイクルの特定のステージで起動することができます。モジュールのライフサイクルは以下のイベントから成ります:

スレッドはnginx内で独自のAPIを使って捕捉のI/O機能としてのみ使われるため、init_threadexit_thread ハンドラは正しく呼ばれません。init_masterハンドラは不必要なオーバーヘッドがあるため、init_master ハンドラもありません。

モジュールのtype 型はctx フィールド内に何が保持されるかを正確に定義します。その値は以下の型の1つです:

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_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 型は1つの設置ディレクティブを定義します。設定をサポートする各モジュールは、引数をどうやって処理するか、どのハンドラを呼ぶかを記述する、そのような構造の配列を提供します:

typedef struct ngx_command_s  ngx_command_t;

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

特殊な値 ngx_null_command を使って配列を終えます。name は設定ファイルの中で現れるようなディレクティブの名前です。例えば "worker_processes" あるいは "listen" です。type はディレクティブが取る引数の数、その型、およびそれが現れるコンテキストを指定するフラグのビット フィールドです。フラグは以下の通りです:

ディレクティブの型のためのフラグは以下の通りです:

ディレクティブのコンテキストはそれが設定内のどこに現れるかを定義します:

設定パーサは間違ったディレクティブの場合にエラーを投げるためにこのフラグを使用し、適切な設定ポインタによって提供されるディレクティブハンドラを呼び出します。そしやって、異なるlocation内の同じディレクティブが個々の場所でそれらの値を格納できるようにします。

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

conf フィールドはどの設定構造がディレクトリ ハンドラに渡されるかを定義します。コア モジュールはグローバル設定のみを持ち、それにアクセスするためにNGX_DIRECT_CONF フラグを設定します。HTTP、ストリームあるいはメールのようなモジュールは設定の階層構造を持ちます。例えば、モジュールの設定はserver, locationif スコープのために生成されます。

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つの種類の設定を持つかも知れません:

  • この設定はhttpブロック全体に適用されます。モジュールのためのグローバル設定としての関数。
  • サーバ設定 — 1つのserver ブロックに適用されます。モジュールのためのサーバ固有の設定としての関数。
  • location 設定 — 1つの location, if あるいは limit_except ブロックへ適用されます。モジュールのためのlocation固有の設定としての関数。

設定構造は関数の呼び出しによってnginx設定ステージで生成されます。これらの構造を割り当て、初期化し、そして合併します。以下の例はモジュールのための簡単な location 設定を生成する方法を示します。設定はunsigned integer型の1つの設定foo を持ちます。

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


static ngx_http_module_t  ngx_http_foo_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_foo_create_loc_conf,          /* create location configuration */
    ngx_http_foo_merge_loc_conf            /* merge location configuration */
};


static void *
ngx_http_foo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_foo_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_foo_loc_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


static char *
ngx_http_foo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_foo_loc_conf_t *prev = parent;
    ngx_http_foo_loc_conf_t *conf = child;

    ngx_conf_merge_uint_value(conf->foo, prev->foo, 1);
}

例にあるように、ngx_http_foo_create_loc_conf() 関数は新しい設定構造を生成し、ngx_http_foo_merge_loc_conf() は設定を高レベルからの他の設定とマージします。実際、serverとlocation設定はserverとlocationレベルだけに存在するだけでなく、その上の全ての設定のためにも生成されます。特に、server設定はmainレベルに生成され、location設定はmain, server および location レベルのために生成されます。これらの設定によりnginx設定ファイルの任意のレベルでserverおよびlocation固有の設定を指定することができます。結果的に設定がマージされます。NGX_CONF_UNSET および NGX_CONF_UNSET_UINT のような多くのマクロが、失われた設定を示しマージの間無視するために提供されます。ngx_conf_merge_value() およびngx_conf_merge_uint_value()のような標準のnginxマージ マクロ は、明示的な値で設定がされない場合に設定をマージしデフォルト値を設定する簡単な方法を提供します。異なる型のための完全なマクロのリストについては、src/core/ngx_conf_file.hを見てください。

以下のマクロが利用可能です。設定時のHTTPモジュールのための設定へアクセスするため。それら全て最初の引数としてngx_conf_t の参照を受け取ります。

  • ngx_http_conf_get_module_main_conf(cf, module)
  • ngx_http_conf_get_module_srv_conf(cf, module)
  • ngx_http_conf_get_module_loc_conf(cf, module)

以下の例は標準nginxコアモジュール ngx_http_core_module のlocation設定へのポインタを取得し、構造の handler フィールド内に保持されたlocationコンテントハンドラを置き換えます。

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);


static ngx_command_t  ngx_http_foo_commands[] = {

    { ngx_string("foo"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_foo,
      0,
      0,
      NULL },

      ngx_null_command
};


static char *
ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_bar_handler;

    return NGX_CONF_OK;
}

以下のマクロが実行時にHTTPモジュールのための設定にアクセスするために利用可能です。

  • ngx_http_get_module_main_conf(r, module)
  • ngx_http_get_module_srv_conf(r, module)
  • ngx_http_get_module_loc_conf(r, module)

これらのマクロはHTTPリクエスト ngx_http_request_t へのリファレンスを受け取ります。リクエストのメインの設定は変更されません。server 設定は、リクエストのためのバーチャルサーバを選択した後で、デフォルトのものから変わるかもしれません。リクエストの処理のために選択されたlocationの設定は、rewrite操作あるいは内部のリダイレクトの結果として複数回変更されるかもしれません。以下の例は実行時にモジュールのHTTP設定へアクセスする方法を示します。

static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_http_foo_loc_conf_t  *flcf;

    flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);

    ...
}

フェーズ

各HTTPリクエストはHTTPフェーズのシーケンスを通過します。各フェーズの中で、別個の種類の処理がリクエスト上で行われます。ほとんどのフェーズ内でモジュール固有のハンドラが登録可能で、多くの標準的なnginxモジュールがそれらのフェーズハンドラをリクエスト処理の特定のステージで呼ばれる方法として登録します。一旦リクエストがフェーズに到着すると、フェーズの処理が成功し、フェーズハンドラが呼ばれます。以下はnginx HTTPフェーズのリストです。

  • NGX_HTTP_POST_READ_PHASE — 最初のフェーズ。ngx_http_realip_module は他のモジュールが起動される前にクライアントのアドレスの置き換えを有効にするために、このフェーズでハンドラを登録します。
  • NGX_HTTP_SERVER_REWRITE_PHASE — server ブロック内 (しかしlocationブロックの外側) で定義されたrewriteディレクティブが処理されるフェーズ。ngx_http_rewrite_module はこのフェーズにハンドラを差し込みます。
  • NGX_HTTP_FIND_CONFIG_PHASE — リクエストURIに基づいてlocationが選択される特別なふぇjーズ。このフェーズの前に、関連するバーチャルサーバのためのデフォルトのlocationがリクエストに割り当てられ、location設定をリクエストしている全てのモジュールはデフォルトのserver locationのための設定を受け取ります。このフェーズは新しいlocationをリクエストに割り当てます。このフェーズでは追加のハンドラは登録することができません。
  • NGX_HTTP_REWRITE_PHASE — NGX_HTTP_SERVER_REWRITE_PHASEと同じだが、前のフェーズで選択された、locationで定義されたrewrite ruleのためのもの。
  • NGX_HTTP_POST_REWRITE_PHASE — URIがrewriteの間に変更された場合、リクエストが新しいlocationにリダイレクトされる特別なフェーズ。これはNGX_HTTP_FIND_CONFIG_PHASEを再び通過するリクエストによって実装されます。このフェーズでは追加のハンドラは登録することができません。
  • NGX_HTTP_PREACCESS_PHASE — アクセス制御に関係しない、ハンドラの異なる型のための共通のフェーズ。標準のnginxモジュールngx_http_limit_conn_module および ngx_http_limit_req_module はこのフェーズにハンドラを登録します
  • NGX_HTTP_ACCESS_PHASE — クライアントがリクエストを作成する許可があるかを検証するフェーズ。ngx_http_access_module および ngx_http_auth_basic_module のような標準のnginxモジュールはこのフェーズにハンドラを登録します。デフォルトでは、リクエストが次のフェーズを続けるためにクライアントはこのフェーズで登録された全てのハンドラの認証チェックを通過しなければなりません。satisfy ディレクテイブは他のフェーズハンドラがクライアントを認証する場合処理の継続を許可するために使うことができます。
  • NGX_HTTP_POST_ACCESS_PHASE — satisfy any ディレクティブが処理される特別なフェーズ。もし幾つかのアクセス フェーズ ハンドラがアクセスを拒否し、明示的に許可しない場合は、リクエストは終了されます。このフェーズでは追加のハンドラは登録することができません。
  • NGX_HTTP_PRECONTENT_PHASE — ハンドラがコンテントの生成の前に呼ばれるフェーズ。 ngx_http_try_files_modulengx_http_mirror_module のような標準的なモジュールはこのフェーズでそれらのハンドラを登録します。
  • NGX_HTTP_CONTENT_PHASE — 応答が通常生成されるフェーズ。ngx_http_index_module あるいは ngx_http_static_moduleを含む、複数のnginxの標準モジュールがこのフェーズにハンドラを登録します。それらの1つが出力を生成するまで連続して呼ばれます。locationごとにコンテントハンドラを設定することもできます。ngx_http_core_moduleの location 設定がhandler セットを持つ場合、このハンドラはコンテント ハンドラとして呼ばれ、このフェーズで差し込まれたハンドラは無視されます。
  • NGX_HTTP_LOG_PHASE — リクエストの記録が行われるフェーズ。現在のところ ngx_http_log_module だけがアクセスログのためにこのステージでハンドラを登録します。ログフェーズハンドラはリクエスト処理のかなり最後の方で、リクエストを解放する直前に呼ばれます。

以下はアクセス前のフェーズハンドラの例です。

static ngx_http_module_t  ngx_http_foo_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_foo_init,                     /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};


static ngx_int_t
ngx_http_foo_handler(ngx_http_request_t *r)
{
    ngx_str_t  *ua;

    ua = r->headers_in->user_agent;

    if (ua == NULL) {
        return NGX_DECLINED;
    }

    /* reject requests with "User-Agent: foo" */
    if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) {
        return NGX_HTTP_FORBIDDEN;
    }

    return NGX_DECLINED;
}


static ngx_int_t
ngx_http_foo_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_foo_handler;

    return NGX_OK;
}

フェーズハンドラは特定のコードを返すことを期待されます:

  • NGX_OK — 次のフェーズに進みます。
  • NGX_DECLINED — 現在のフェーズの次のハンドラに進みます。現在のハンドラが現在のフェーズの最後の場合、次のフェーズに進みます。
  • NGX_AGAIN, NGX_DONE — 例えば、非同期 I/O操作あるいは単なる遅延に成り得る何らかの将来のイベントまで、フェーズの操作を延期します。ngx_http_core_run_phases() の呼び出しによってフェーズの処理が後で再開されると見なされます。
  • フェーズハンドラによって返される他のどのような値もリクエストの終了コード、特にHTTP応答コードとして扱われます。リクエストは与えられたコードで終了されます。

幾つかのフェーズについては、返り値は少し異なる方法で扱われます。コンテントフェーズでは、NGX_DECLINED以外のどのようなコードも終了コードと見なされます。locaion コンテント ハンドラからの返り値は終了コードと見なされます。satisfy anyモードのアクセスフェーズでは、NGX_OK, NGX_DECLINED, NGX_AGAIN, NGX_DONE 意外の全ての返り値は拒否と見なされます。後に続くアクセスハンドラのいずれもが異なるコードでアクセスを許可あるいは拒否しない場合は、拒否コードが終了コードになるでしょう。

変数

既存の変数へのアクセス

変数はインデックス (これが最も一般的な方法です)あるいは名前(以下を見てください)を使って参照することができます。インデックスは変数が設定に追加される時に設定ステージで生成されます。変数のインデックスを取得するには、ngx_http_get_variable_index()を使ってください:

ngx_str_t  name;  /* ngx_string("foo") */
ngx_int_t  index;

index = ngx_http_get_variable_index(cf, &name);

ここで、cf はnginxの設定へのポインタで、name は変数名を持つ文字列へのポインタです。関数はエラー時にNGX_ERROR を返すか、そうでなければ有効なインデックスを返します。これは一般的に後で使うためにモジュール内のどこかに格納されます。

全てのHTTP変数はHTTPリクエストのコンテキスト内で評価され、結果はHTTPリクエストに固有でその中にキャッシュされます。変数を評価する全ての関数はngx_http_variable_value_t型を返し、変数の値を表します:

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;
    unsigned    escape:1;

    u_char     *data;
} ngx_variable_value_t;

ここで:

  • len — 値の長さ
  • data — 値そのもの
  • valid — 値が有効
  • not_found — 変数が見つからず、従ってdatalen フィールドは無関係です; 例えば、対応する引数がリクエスト内で渡されなかった場合の$arg_foo のような変数で起こるかも知れません
  • no_cacheable — 結果をキャッシュしません
  • escape — 値が出力でエスケープされる必要があると値にマークをするためにロギングモジュールによって内部的に使われます

ngx_http_get_flushed_variable()ngx_http_get_indexed_variable() 関数は変数の値を取得するために使われます。それらは同じインタフェースを持ちます - HTTP リクエスト r を変数を評価するためのコンテキストとして受け取り、それを識別するためのindexを受け取ります。一般的な使い方の例:

ngx_http_variable_value_t  *v;

v = ngx_http_get_flushed_variable(r, index);

if (v == NULL || v->not_found) {
    /* we failed to get value or there is no such variable, handle it */
    return NGX_ERROR;
}

/* some meaningful value is found */

関数間の違いはngx_http_get_indexed_variable() はキャッシュされた値を返し、ngx_http_get_flushed_variable() はキャッシュできない変数のためにキャッシュをフラッシュすることです。

SSIおよびPerlのような幾つかのモジュールは、設定時に名前が知られていない変数を扱う必要があります。従ってインデックスはそれらにアクセスするために使うことができませんが、ngx_http_get_variable(r, name, key)関数が利用可能です。それは指定された名前name と名前に由来するハッシュキーを使って変数を検索します。

変数の作成

変数を作成するには、ngx_http_add_variable() 関数を使ってください。それは引数として、関数の挙動を制御する設定 (変数が登録された場所)、変数名およびフラグを取ります。

  • NGX_HTTP_VAR_CHANGEABLE — 変数の再定義を可能にします: 他のモジュールが同じ名前で変数を定義しない場合は衝突はありません。これにより set ディレクティブが変数を上書きすることができます。
  • NGX_HTTP_VAR_NOCACHEABLE  — キャッシングを無効にします。$time_localのような変数にとって便利です
  • NGX_HTTP_VAR_NOHASH  — この変数が名前ではなくインデックスによってのみアクセス可能であることを示します。これはSSIあるいはPerlのようなモジュール内で変数が必要とされないことが分かっている場合に、使われるかも知れない小さな最適化です。
  • NGX_HTTP_VAR_PREFIX  — この変数の名前のプリフィックスです。この場合、ハンドラは特定の変数の値を取得するための追加のロジックを実装する必要があります。例えば、リクエストの引数を検索し特定の引数の値を返す同じハンドラによって、全ての“arg_” 変数が処理されます。

関数は、エラー時にはNULL、そうでなければngx_http_variable_tへのポインタを返します:

struct ngx_http_variable_s {
    ngx_str_t                     name;
    ngx_http_set_variable_pt      set_handler;
    ngx_http_get_variable_pt      get_handler;
    uintptr_t                     data;
    ngx_uint_t                    flags;
    ngx_uint_t                    index;
};

get および set ハンドラは変数値を取得あるいは設定するために呼ばれ、data は変数ハンドラに渡され、index 変数を参照するために使われる割り当てられた可変のインデックスを持つでしょう。

通常、ngx_http_variable_t構造のnullで終わる静的な配列はモジュールによって生成され、変数を設定に追加するために事前設定のステージで処理されます。例えば:

static ngx_http_variable_t  ngx_http_foo_vars[] = {

    { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 },

      ngx_http_null_variable
};

static ngx_int_t
ngx_http_foo_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var, *v;

    for (v = ngx_http_foo_vars; v->name.len; v++) {
        var = ngx_http_add_variable(cf, &v->name, v->flags);
        if (var == NULL) {
            return NGX_ERROR;
        }

        var->get_handler = v->get_handler;
        var->data = v->data;
    }

    return NGX_OK;
}

例でのこの関数はHTTPモジュールのコンテキストのpreconfiguration フィールドを初期化するために使われ、パーサーがこれらの変数を参照できるようにHTTP設定をパースする前に呼ばれます。

get ハンドラは特定のリクエストのコンテキストの中で変数を評価する責任があります。例えば:

static ngx_int_t
ngx_http_variable_connection(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char  *p;

    p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->len = ngx_sprintf(p, "%uA", r->connection->number) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;
    v->data = p;

    return NGX_OK;
}

内部エラー(例えば、メモリ割り当ての失敗)の場合はNGX_ERROR を返し、そうでなければNGX_OK を返します。変数の評価の状態を学ぶには、ngx_http_variable_value_t内のフラグを調べてください (の説明を見てください)。

set ハンドラによって変数によるプロパティの参照を設定することができます。例えば、$limit_rate 変数の設定ハンドラはリクエストの limit_rate フィールドを修正します:

...
{ ngx_string("limit_rate"), ngx_http_variable_request_set_size,
  ngx_http_variable_request_get_size,
  offsetof(ngx_http_request_t, limit_rate),
  NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
...

static void
ngx_http_variable_request_set_size(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    ssize_t    s, *sp;
    ngx_str_t  val;

    val.len = v->len;
    val.data = v->data;

    s = ngx_parse_size(&val);

    if (s == NGX_ERROR) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "invalid size \"%V\"", &val);
        return;
    }

    sp = (ssize_t *) ((char *) r + data);

    *sp = s;

    return;
}

comoplex値

複雑な値という名前にも関わらず、それはテキスト、変数およびそれらの組み合わせを含む表現の評価を簡単にする方法を提供します。

ngx_http_compile_complex_value 内のcomplex値の概要は設定ステージにおいて、表現の評価の結果を取得するために実行時に使われるngx_http_complex_value_t へコンパイルされます。

ngx_str_t                         *value;
ngx_http_complex_value_t           cv;
ngx_http_compile_complex_value_t   ccv;

value = cf->args->elts; /* directive arguments */

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
ccv.zero = 1;
ccv.conf_prefix = 1;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
    return NGX_CONF_ERROR;
}

ここで、ccv はcomplex値 cvを初期化することを必要とする全てのパラメータを保持します:

  • cf — 設定ポインタ
  • value — パースする文字列(入力)
  • complex_value — コンパイルされた値(出力)
  • zero — 0で終了する値を有効にするフラグ
  • conf_prefix — 結果の前に構成プリフィックスを付けます(nginxが現在のところ構成を探しているディレクトリ)
  • root_prefix — 結果の前にルート プリフィックスを付けます (通常のnginxのインストレーション プリフィックス)

ゼロで終了する文字列を必要とするライブラリに結果が渡される場合はzero フラグが利用可能です。ファイル名を扱う場合にはプリフィックスが便利です。

コンパイルが成功すると、cv.lengths は表現内の変数の存在についての情報を含みます。NULL値は表現が静的なテキストのみを含むことを意味し、複雑な値としてではなく単純な文字列の中に格納できます。

ngx_http_set_complex_value_slot()はディレクティブ定義の中でcomplex値を完全に正しく初期化するために使われる便利な関数です。

実行時には、complex値はngx_http_complex_value() 関数を使って計算されるかも知れません:

ngx_str_t  res;

if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) {
    return NGX_ERROR;
}

リクエストr と以前にコンパイルされた値 cvが与えられると、関数は表現を評価し結果をresに書きます。

リクエストのリダイレクト

HTTPリクエストはngx_http_request_t 構造のloc_conf フィールドを経由して常にlocationに接続します。どのモジュールのどのlocation設定の場所でも ngx_http_get_module_loc_conf(r, module)を呼び出すことでリクエストから検索されることができることを意味します。リクエストのlocationはリクエストの生存期間の中で何度か変更することができます。最初に、デフォルトのサーバのデフォルトのserver locationはリクエストに割り当てられます。リクエストが異なるサーバ(HTTP “Host” ヘッダあるいはSSL SNI拡張によって選択されます)に切り替えた場合、リクエストはそのサーバのデフォルトのlocationにも切り替えます。次のlocationの変更はNGX_HTTP_FIND_CONFIG_PHASEリクエスト フェーズで起こります。このフェーズでlocationはサーバのために設定された全ての名前無しのlocationの間でリクエストのURIによって選択されます。ngx_http_rewrite_modulerewrite ディレクティブの結果としてNGX_HTTP_REWRITE_PHASE リクエスト フェーズでリクエストURIを変更することができ、新しいURIに基づいた新しいlocationの選択のためにリクエストを NGX_HTTP_FIND_CONFIG_PHASEフェーズに送り返します。

どの時点でもngx_http_internal_redirect(r, uri, args) あるいは ngx_http_named_location(r, name)のうちの1つを呼び出すことでリクエストを新しいlocationにリダイレクトすることもできます。

ngx_http_internal_redirect(r, uri, args) 関数はリクエストURIを変更し、リクエストを NGX_HTTP_SERVER_REWRITE_PHASE フェーズに返します。リクエストはサーバのデフォルトのlocationを使って進みます。NGX_HTTP_FIND_CONFIG_PHASEの後で新しいリクエストURIに基づいて新しいlocationが選択されます。

以下の例は新しいリクエスト引数を使って内部的なリダイレクトを行います。

ngx_int_t
ngx_http_foo_redirect(ngx_http_request_t *r)
{
    ngx_str_t  uri, args;

    ngx_str_set(&uri, "/foo");
    ngx_str_set(&args, "bar=1");

    return ngx_http_internal_redirect(r, &uri, &args);
}

関数ngx_http_named_location(r, name)はリクエストを名前付きのlocationにリダイレクトします。locationの名前は引数として渡されます。リクエストは NGX_HTTP_REWRITE_PHASEフェーズに切り替えた後で、locationは現在のサーバの全ての名前付きのlocationの中を検索します。

以下の例は名前付きの location @foo へのリダイレクトを行います。

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

両方の関数 - ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name) はnginxモジュールが既にいくつかのコンテキストをリクエストの ctx フィールドに格納している時に呼ばれるかもしれません。これらのコンテキストが新しいlocationの設置と矛盾するようになるかもしれません。矛盾を避けるために、全てのリクエストのコンテキストが両方のリダイレクト関数によって削除されます。

ngx_http_internal_redirect(r, uri, args) あるいは ngx_http_named_location(r, name) の呼び出しはリクエストの countを増やします。一貫性のあるリクエストの参照カウントのために、リクエストをリダイレクトした後でngx_http_finalize_request(r, NGX_DONE)を呼んでください。これは現在のリクエストコードのパスを終了し、カウンタを減らします。

リダイレクトおよびrewriteされたリクエストは内部的になり、internal locationをアクセスすることができます。内部リクエストは internal フラグセットを持ちます。

サブリクエスト

サブリクエストは主におそらく他のデータと混ぜるために1つのリクエストの他への出力を挿入するために使われます。サブリクエストは通常のリクエストのように見えますが、幾らかのデータを親と共有します。サブリクエストはクライアントから他に何も受け取らないため、特にクライアントの入力に関係するすべてのフィールドは共有されます。サブリクエストのためのリクエストフィールドparentは親のリクエストへのリンクを含み、メインリクエストについてはNULLです。フィールド main はリクエストのグループ内のメインリクエストへのリンクを含みます。

サブリクエストは NGX_HTTP_SERVER_REWRITE_PHASE フェーズ内で開始します。それは通常のリクエストとして同じサブリクエストのフェーズを通過し、その独自のURIに基づいてlocationを割り当てられます。

サブリクエストの出力ヘッダは常に無視されます。ngx_http_postpone_filter はサブリクエストの出力ボディを親リクエストによって生成された他のデータに関連して正しい位置に置きます。

サブリクエストはアクティブなリクエストの概念と関係があります。リクエスト r は、もし c->data == rc はクライアントの接続オブジェクト、の時にアクティブと見なされます。どの時点においても、リクエストグループ内のアクティブなリクエストはバッファをクライアントに出力することができます。アクティブでは無いリクエストは出力をまだフィルターチェインに送信しますが、それはngx_http_postpone_filterを超えて渡すことはなく、リクエストがアクティブになるまでフィルタによってバッファされたままです。リクエストの有効化には幾つかのルールがあります:

  • 初めのうちはメインリクエストはアクティブです。
  • アクティブリクエストの最初のサブリクエストは生成のすぐ後でアクティブになります。
  • リクエストより前の全てのデータがいったん送信されると、ngx_http_postpone_filter はアクティブ リクエストのサブリクエストのリストの中で次のリクエストを活性化します。
  • リクエストが終了すると、その親が活性化されます。

関数ngx_http_subrequest(r, uri, args, psr, ps, flags)を呼ぶことでサブリクエストを生成します。ここでrは親リクエスト、uriargs はサブリクエストのURIと引数、psrは新しく生成されたサブリクエストの参照を受け取る出力パラメータ、psはサブリクエストが終了される時に親リクエストに通知するためのコールバックオブジェクト、そしてflags はフラグのビットマスク。以下のフラグが利用可能です:

  • NGX_HTTP_SUBREQUEST_IN_MEMORY - 出六はクライアントに送信されませんがメモリ内に格納されます。フラグはプロキシされるモジュールのうちの1つによって処理されるサブリクエストにのみ影響します。サブリクエストが終了された後で、その出力は型ngx_buf_tr->out内で利用可能です。
  • NGX_HTTP_SUBREQUEST_WAITED - サブリクエストの doneフラグは、たとえサブリクエストが終了時にアクティブでは無くても、設定されます。このサブリクエストフラグはSSIフィルタによって使われます。
  • NGX_HTTP_SUBREQUEST_CLONE - サブリクエストはその親のクローンとして生成されます。それは同じlocationで開始され、親リクエストとして同じフェーズから進みます。

以下の例は/fooのURIを持つサブリクエストを作成します。

ngx_int_t            rc;
ngx_str_t            uri;
ngx_http_request_t  *sr;

...

ngx_str_set(&uri, "/foo");

rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0);
if (rc == NGX_ERROR) {
    /* error */
}

この例は現在のリクエストをクローンし、サブリクエストのための終了コールバックを設定します。

ngx_int_t
ngx_http_foo_clone(ngx_http_request_t *r)
{
    ngx_http_request_t          *sr;
    ngx_http_post_subrequest_t  *ps;

    ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
    if (ps == NULL) {
        return NGX_ERROR;
    }

    ps->handler = ngx_http_foo_subrequest_done;
    ps->data = "foo";

    return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps,
                               NGX_HTTP_SUBREQUEST_CLONE);
}


ngx_int_t
ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc)
{
    char  *msg = (char *) data;

    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                  "done subrequest r:%p msg:%s rc:%i", r, msg, rc);

    return rc;
}

サブリクエストは通常ボディフィルタ内で生成されます。その場合それらの出力は任意の明示的なリクエストからの出力のように取り扱われることができます。つまり、最終的にサブリクエストの出力はサブリクエストの作成前に渡される全ての明示的なバッファの後、および作成後に渡される全てのバッファの前に、クライアントに送信されます。この順番はサブリクエストの大規模な構造についてさえも保持されます。以下の例では、全てのリクエストデータバッファの後だが、last_bufフラグを持つ最後のバッファの前に、サブリクエストからの出力を挿入します。

ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_uint_t                  last;
    ngx_chain_t                *cl, out;
    ngx_http_request_t         *sr;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_body_filter(r, in);
    }

    last = 0;

    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {
            cl->buf->last_buf = 0;
            cl->buf->last_in_chain = 1;
            cl->buf->sync = 1;
            last = 1;
        }
    }

    /* Output explicit output buffers */

    rc = ngx_http_next_body_filter(r, in);

    if (rc == NGX_ERROR || !last) {
        return rc;
    }

    /*
     * Create the subrequest.  The output of the subrequest
     * will automatically be sent after all preceding buffers,
     * but before the last_buf buffer passed later in this function.
     */

    if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);

    /* Output the final buffer with the last_buf flag */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = 1;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

サブリクエストはデータの出力以外の他の目的のために生成することもできます。例えば、 ngx_http_auth_request_module モジュールはNGX_HTTP_ACCESS_PHASEフェーズでサブリクエストを生成します。この時点で出力を無効にするには、サブリクエスト上でheader_only フラグを設定します。これはサブリクエストボディがクライアントに送信されることを防ぎます。サブリクエストのヘッダは決してクライアントに送信されないことに注意してください。サブリクエストの結果はコールバック ハンドラ内で解析することができます。

リクエストの終了

HTTPリクエストは関数ngx_http_finalize_request(r, rc)を呼ぶことで終了されます。全ての出力バッファがフィルタチェインに送信された後で通常はコンテント ハンドラによって終了されます。出力の幾つかはファルタチェインと一緒にどこかにバッファされたまま、この時点で出力の全てはクライアントに送信されないかもしれません。そうであれば、ngx_http_finalize_request(r, rc) 関数は出力の送信を終了するために自動的に特別なハンドラngx_http_writer(r)を導入します。エラーの場合、またはもし標準的なHTTP応答コードがクライアントへ返される必要がある場合、リクエストも終了されます。

関数ngx_http_finalize_request(r, rc)は以下のrc 値を期待します:

  • NGX_DONE - 高速な終了。リクエストのcountを減少し、それが0になったらリクエストを破棄します。現在のリクエストが破棄された後で、クライアントの接続はもっと多くのリクエストのために使うことができます。
  • NGX_ERROR, NGX_HTTP_REQUEST_TIME_OUT (408), NGX_HTTP_CLIENT_CLOSED_REQUEST (499) - エラーの終了。可能な限りリクエストを終了し、クライアントの接続を閉じます。
  • NGX_HTTP_CREATED (201), NGX_HTTP_NO_CONTENT (204), NGX_HTTP_SPECIAL_RESPONSE (300)以上のコード - 特別な応答の終了。これらの値については、nginxはクライアントにそのコードのためのデフォルトの応答ページを送信するか、そのコードのために設置された場合はerror_page location への内部的なリダイレクトを行うかをします。
  • 他のコードは終了コードが成功したと見なされ、応答ボディの送信を終了するためにリクエスト書き込みを有効化するかもしれません。一旦ボディが完全に送信されると、リクエストのcount は減らされます。それが0になると、リクエストは破棄されますが、クライアント接続は他のリクエストのためにまだ使われるかもしれません。もしcountが正の場合は、リクエスト内に完了していない活動があります。これは後の時点で終了されるでしょう。

リクエスト ボディ

クライアント リクエストのボディを扱うために、nginxはngx_http_read_client_request_body(r, post_handler)ngx_http_discard_request_body(r) 関数を提供します。最初の関数はリクエストボディを読み込み、request_body リクエスト フィールドを使って利用可能にします。2つ目の関数はnginxにリクエスト ボディを破棄(読み込みそして無視する)ように指示します。これらの関数のうちの1つは各リクエストごとに呼ばれる必要があります。通常は、コンテキストハンドラが呼びます。

サブリクエストからのクライアント リクエスト ボディの読み込みあるいは破棄は許可されません。それは常にメイン リクエストの中で行われる必要があります。サブリクエストが作成された時に、メイン リクエストが以前リクエストボディを読み込んだ場合はサブリクエストによって使うことができる親のrequest_body オブジェクトを継承します。

関数 ngx_http_read_client_request_body(r, post_handler) リクエストボディの読み込みの処理を開始します。ボディが完全に読み込まれた場合、リクエストの処理を続けるためにpost_handler コールバックが呼ばれます。もしリクエストボディが失われたか既に読み込まれていた場合は、コールバックがすぐに呼ばれます。関数 ngx_http_read_client_request_body(r, post_handler) は型ngx_http_request_body_trequest_bodyリクエストフィールドを割り当てます。このオブジェクトのフィールドbufs はバッファチェインとして結果を保持します。もしclient_body_buffer_sizeディレクティブによって指定される容量がメモリ内にボディ全体を収めるのに十分では無い場合は、ボディはメモリバッファあるいはファイルバッファ内に保存することができます。

以下の例はクライアント リクエスト ボディを読み込み、そのサイズを返します。

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        /* error */
        return rc;
    }

    return NGX_DONE;
}


void
ngx_http_foo_init(ngx_http_request_t *r)
{
    off_t         len;
    ngx_buf_t    *b;
    ngx_int_t     rc;
    ngx_chain_t  *in, out;

    if (r->request_body == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    len = 0;

    for (in = r->request_body->bufs; in; in = in->next) {
        len += ngx_buf_size(in->buf);
    }

    b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN);
    if (b == NULL) {
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    b->last = ngx_sprintf(b->pos, "%O", len);
    b->last_buf = (r == r->main) ? 1: 0;
    b->last_in_chain = 1;

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->last - b->pos;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        ngx_http_finalize_request(r, rc);
        return;
    }

    out.buf = b;
    out.next = NULL;

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

以下のリクエストのフィールドはリクエストボディがどのように読み込まれるかを決定します:

  • request_body_in_single_buf - ボディを1つのメモリバッファに読み込みます。
  • request_body_in_file_only - たとえメモリバッファ内に収まる場合でも、常にボディをファイルに読み込みます。
  • request_body_in_persistent_file - 作成の後ですぐにファイルをunlinkしません。このフラグを持つファイルは他のディレクトリに移動することができます。
  • request_body_in_clean_file - リクエストが終了されない場合、ファイルをunlinkします。ファイルが他のディレクトリに移動すると考えられていたが何かの理由で移動されなかった場合、これは便利かもしれません。
  • request_body_file_group_access - デフォルトの 0600 アクセス マスクを 0660 に置き換えることでファイルへのグループアクセスを有効にします。
  • request_body_file_log_level - ファイルエラーを記憶する重要度レベル。
  • request_body_no_buffering - バッファリング無しにリクエストボディを読み込みます。

request_body_no_buffering フラグはリクエストボディの読み込みのバッファ無しモードを有効にします。このモードでは、ngx_http_read_client_request_body()の呼び出しの後で、bufsチェインはボディの一部だけを保持するかもしれません。次の部分を読み込むには、ngx_http_read_unbuffered_request_body(r) 関数を呼びます。返り値 NGX_AGAIN とリクエストフラグreading_bodyはもっと多くのデータが利用可能であることを示します。この関数を呼んだあとで、もしbufsがNULLであれば、今のところは何も読み込むものはありません。リクエスト コールバック read_event_handlerはリクエストボディの次の部分が利用可能な時に呼ばれるでしょう。

リクエストボディフィルタ

リクエストボディ部分が読まれた後で、ngx_http_top_request_body_filter変数に格納されている最初のボディフィルタハンドラを呼び出すことで、リクエストボディフィルタに渡されます。最後のハンドラngx_http_request_body_save_filter(r, cl)が呼ばれるまで、各ボディハンドラがチェーン内の次のハンドラを呼ぶと仮定します。このハンドラはr->request_body->bufs内のバッファを収集し、必要に応じてファイルに書き込みます。最後のリクエストボディバッファにはゼロ以外のlast_bufフラグがあります。

フィルタがデータバッファを遅延させることを計画している場合は、初めて呼び出される時にr->request_body->filter_need_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) 関数は出力ヘッダを送信します。r->headers_out がHTTP応答ヘッダが生成するために必要な全てのデータを含むまで、この関数を呼ばないでください。r->headers_out内のstatusフィールドは常に設定されなければなりません。もし応答ステータスがヘッダに続く応答ボディを示す場合、content_length_n も同様に設定することができます。このフィールドのデフォルトの値は-1です。このことはボディサイズが未知であることを意味します。この場合、chunked transfer encoding が使われます。任意のヘッダを出力するには、headersリストを追加します。

static ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t         rc;
    ngx_table_elt_t  *h;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    /* X-Foo: foo */

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    ...
}

ヘッダフィルタ

ngx_http_send_header(r) 関数はngx_http_top_header_filter変数に格納されている最初のフィルタハンドラを呼び出すことでヘッダフィルタチェーンを起動します。最後のハンドラngx_http_header_filter(r) が呼ばれるまで、各ヘッダハンドラがチェーン内の次のハンドラを呼ぶと仮定します。最後のヘッダハンドラはr->headers_out に基づいたHTTP応答を構築し、それを出力のためにngx_http_writer_filter に渡します。

ヘッダ フィルターチェインにハンドラを追加するために、設定時にそのアドレスをグローバル変数ngx_http_top_header_filterに格納します。前のハンドラのアドレスは通常モジュール内の静的変数に格納され、終了する前に新しく追加されたハンドラによって呼ばれます。

ヘッダーフィルターモジュールの以下の例はHTTPヘッダ "X-Foo: foo" をステータス 200を持つ各応答に追加します。

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


static ngx_int_t ngx_http_foo_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_foo_header_filter_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_foo_header_filter_module_ctx = {
    NULL,                                   /* preconfiguration */
    ngx_http_foo_header_filter_init,        /* postconfiguration */

    NULL,                                   /* create main configuration */
    NULL,                                   /* init main configuration */

    NULL,                                   /* create server configuration */
    NULL,                                   /* merge server configuration */

    NULL,                                   /* create location configuration */
    NULL                                    /* merge location configuration */
};


ngx_module_t  ngx_http_foo_header_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_foo_header_filter_module_ctx, /* module context */
    NULL,                                   /* module directives */
    NGX_HTTP_MODULE,                        /* module type */
    NULL,                                   /* init master */
    NULL,                                   /* init module */
    NULL,                                   /* init process */
    NULL,                                   /* init thread */
    NULL,                                   /* exit thread */
    NULL,                                   /* exit process */
    NULL,                                   /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;


static ngx_int_t
ngx_http_foo_header_filter(ngx_http_request_t *r)
{
    ngx_table_elt_t  *h;

    /*
     * The filter handler adds "X-Foo: foo" header
     * to every HTTP 200 response
     */

    if (r->headers_out.status != NGX_HTTP_OK) {
        return ngx_http_next_header_filter(r);
    }

    h = ngx_list_push(&r->headers_out.headers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    h->hash = 1;
    ngx_str_set(&h->key, "X-Foo");
    ngx_str_set(&h->value, "foo");

    return ngx_http_next_header_filter(r);
}


static ngx_int_t
ngx_http_foo_header_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_foo_header_filter;

    return NGX_OK;
}

応答ボディ

応答ボディを送信するには、ngx_http_output_filter(r, cl) 関数を呼びます。関数は複数回呼び出すことができます。毎回、それは応答ボディの部分をバッファチェインの形式で送信します。最後のボディバッファ内のlast_bufフラグを設定します。

以下の例はボディとして "foo" を持つ完全なHTTP応答を生成します。例がサブリクエストとメインリクエストとして動作するために、出力の最後のバッファ内でlast_in_chainフラグが設定されます。サブリクエストのための最後のバッファは出力全体を終らせないため、last_buf フラグはメインのリクエストのためだけに設定されます。

static ngx_int_t
ngx_http_bar_content_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;

    /* send header */

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = 3;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    /* send body */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    b->last_buf = (r == r->main) ? 1: 0;
    b->last_in_chain = 1;

    b->memory = 1;

    b->pos = (u_char *) "foo";
    b->last = b->pos + 3;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}

Response body filters

関数 ngx_http_output_filter(r, cl)ngx_http_top_body_filter に格納されている最初のボディ フィルタ ハンドラを呼び出すことでボディフィルタを起動します。最後のハンドラngx_http_write_filter(r, cl) が呼ばれるまで、各ボディハンドラがチェーン内の次のハンドラを呼ぶと仮定します。

ボディフィルタハンドラはバッファのチェーンを受け取ります。ハンドラはバッファを処理し、できる限り新しいチェーンを次のハンドラに渡すとされています。着信チェーンのチェーンリンクngx_chain_tは呼び出し元に所属し、再利用または変更してはならないことに注意してください。ハンドラが完了するとすぐに、呼び出し元はそれを送信したバッファの追跡を続けるために出力チェーンのリンクを使うことができます。バッファチェインを保存、あるいは次のフィルタへ渡す前にバッファを代用するために、ハンドラは独自のチェインリンクを割り当てる必要があります。

以下はボディのバイト数をカウントする簡単なボディフィルタの例です。結果はアクセスログ内で使うことができる $counter 変数として利用可能です。

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


typedef struct {
    off_t  count;
} ngx_http_counter_filter_ctx_t;


static ngx_int_t ngx_http_counter_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static ngx_int_t ngx_http_counter_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_counter_add_variables(ngx_conf_t *cf);
static ngx_int_t ngx_http_counter_filter_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_counter_filter_module_ctx = {
    ngx_http_counter_add_variables,        /* preconfiguration */
    ngx_http_counter_filter_init,          /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};


ngx_module_t  ngx_http_counter_filter_module = {
    NGX_MODULE_V1,
    &ngx_http_counter_filter_module_ctx,   /* module context */
    NULL,                                  /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};


static ngx_http_output_body_filter_pt  ngx_http_next_body_filter;

static ngx_str_t  ngx_http_counter_name = ngx_string("counter");


static ngx_int_t
ngx_http_counter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_chain_t                    *cl;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_counter_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
    }

    for (cl = in; cl; cl = cl->next) {
        ctx->count += ngx_buf_size(cl->buf);
    }

    return ngx_http_next_body_filter(r, in);
}


static ngx_int_t
ngx_http_counter_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
    uintptr_t data)
{
    u_char                         *p;
    ngx_http_counter_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_counter_filter_module);
    if (ctx == NULL) {
        v->not_found = 1;
        return NGX_OK;
    }

    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
    if (p == NULL) {
        return NGX_ERROR;
    }

    v->data = p;
    v->len = ngx_sprintf(p, "%O", ctx->count) - p;
    v->valid = 1;
    v->no_cacheable = 0;
    v->not_found = 0;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var;

    var = ngx_http_add_variable(cf, &ngx_http_counter_name, 0);
    if (var == NULL) {
        return NGX_ERROR;
    }

    var->get_handler = ngx_http_counter_variable;

    return NGX_OK;
}


static ngx_int_t
ngx_http_counter_filter_init(ngx_conf_t *cf)
{
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_counter_body_filter;

    return NGX_OK;
}

フィルタモジュールのビルド

ボディあるいはヘッダフィルタを書く時、フィルタの順番内のフィルタの位置に特に注意してください。nginxの標準モジュールによって登録された多くのヘッダとボディフィルタがあります。nginxの標準モジュールは多くのヘッドおよびボディフィルタを登録します。それらに関して正しい場所に新しいフィルタモジュールを登録することが重要です。通常は、モジュールは後設定ハンドラにフィルタを登録します。処理の間にフィルタが呼ばれる順番は明確にそれらが登録された逆の順番です。

サードパーティのフィルタモジュールについては、nginxは特別なスロット HTTP_AUX_FILTER_MODULESを提供します。スロット内でフィルタモジュールを登録するには、モジュールの設定の中で ngx_module_type 変数を HTTP_AUX_FILTERに設定します。

以下の例は、たった1つのソースファイル ngx_http_foo_filter_module.c を持つモジュールを仮定した、ファイルたモジュールの設定ファイルを示します。

ngx_module_type=HTTP_AUX_FILTER
ngx_module_name=ngx_http_foo_filter_module
ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c"

. auto/module

バッファの再利用

バッファのストリームを発行あるいは変更する時、割り当てられたバッファを再利用することがしばしば望ましいです。nginxのコードの中で標準的で広範囲に適用されるやり方は、この目的のために二つのバッファのチェーンを保持します: freebusy. free チェインは全てのフリーなバッファを保持します。これは再利用することができます。busy チェーンは幾つかの他のフィルタハンドラによってまだ使われている現在のモジュールによって送信される全てのバッファを保持します。もしサイズが0より大きければ、バッファは使用中だと見なされます。通常、バッファがフィルタによって消費された場合、そのpos (あるいはファイルバッファについては file_pos ) は last (ファイルバッファについてはfile_last )に進みます。いったんバッファが完全に消費されると、再利用することができます。free チェーンに新しくfreeにされたバッファを追加するには、busy チェーンの一番上でサイズが0のバッファをfreeに移動するのを繰り返すことで十分です。この操作は一般的で、これを行う専用の関数ngx_chain_update_chains(free, busy, out, tag)があります。関数は出力チェーンoutbusy に追加し、freeなバッファをbusy の一番上から freeに移動します。指定されたtagを持つバッファだけが再利用されます。これはモジュールによって割り当てられたバッファだけがモジュールによって再利用されます。

以下の例は各入力のバッファの前に “foo” 文字列を挿入するボディフィルタの例です。モジュールによって割り当てられた新しいバッファは可能であれば再利用されます。この例を適切に動作させるには、header filter を設定しcontent_length_n-1 に再設定することも必要ですが、関係するコードがここでは提供されないことに注意してください。

typedef struct {
    ngx_chain_t  *free;
    ngx_chain_t  *busy;
}  ngx_http_foo_filter_ctx_t;


ngx_int_t
ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_buf_t                  *b;
    ngx_chain_t                *cl, *tl, *out, **ll;
    ngx_http_foo_filter_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

    /* create a new chain "out" from "in" with all the changes */

    ll = &out;

    for (cl = in; cl; cl = cl->next) {

        /* append "foo" in a reused buffer if possible */

        tl = ngx_chain_get_free_buf(r->pool, &ctx->free);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        b = tl->buf;
        b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module;
        b->memory = 1;
        b->pos = (u_char *) "foo";
        b->last = b->pos + 3;

        *ll = tl;
        ll = &tl->next;

        /* append the next incoming buffer */

        tl = ngx_alloc_chain_link(r->pool);
        if (tl == NULL) {
            return NGX_ERROR;
        }

        tl->buf = cl->buf;
        *ll = tl;
        ll = &tl->next;
    }

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

    /* update "busy" and "free" chains for reuse */

    ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out,
                            (ngx_buf_tag_t) &ngx_http_foo_filter_module);

    return rc;
}

ロードバランシング

ngx_http_upstream_module はリクエストをリモートサーバに渡すために必要な基本的な機能を提供します。HTTPあるいはFastCGIのような特定のプロトコルを実装するモジュールはこの機能を使います。モジュールは独自のロードバランシングモジュールを作成するためのインタフェースも提供し、デフォルトのラウンドロビンバランシングメソッドを実装します。

least_connhash モジュールは二者択一のロードバランシング方法を実装しますが、実際にはupstreamラウンドロビン モジュールの拡張として実装されており、サーバグループの表現のような多くのコードをそれと共有します。keepalive モジュールはupstream機能を拡張する独立したモジュールです。

ngx_http_upstream_module は対応するupstream ブロックを設定ファイルに置くことで明示的に、あるいはどこかの時点でサーバのリストへ評価されるURLを受け付けるproxy_pass のようなディレクティブを使うことで暗黙的に設定することができます。明示的なupstream設定を使って、別のロードバランシング方法が利用可能です。upstreamモジュールの設定は独自のディレクティブコンテキスト NGX_HTTP_UPS_CONFを持ちます。その構造は以下のように定義されます:

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

    ngx_uint_t                       flags;
    ngx_str_t                        host;
    u_char                          *file_name;
    ngx_uint_t                       line;
    in_port_t                        port;
    ngx_uint_t                       no_port;  /* unsigned no_port:1 */

#if (NGX_HTTP_UPSTREAM_ZONE)
    ngx_shm_zone_t                  *shm_zone;
#endif
};

  • srv_conf — upstreamモジュールの設定コンテキスト。
  • servers — upstreamブロック内のserverディレクティブの配列をパースした結果である、ngx_http_upstream_server_tの配列。
  • flags — 主に、ロードバランシング方法によってどの機能がサポートされるかをマークするフラグ。機能は server ディレクティブのパラメータとして設定されます:
    • NGX_HTTP_UPSTREAM_CREATE — proxy_pass ディレクティブと “friends” (FastCGI, SCGI など)によって自動的に生成されたupstreamと、明示的に定義されたupstreamを区別する。
    • NGX_HTTP_UPSTREAM_WEIGHT — “weight” パラメータがサポートされます
    • NGX_HTTP_UPSTREAM_MAX_FAILS — “max_fails” パラメータがサポートされます
    • NGX_HTTP_UPSTREAM_FAIL_TIMEOUT — “fail_timeout” パラメータがサポートされます
    • NGX_HTTP_UPSTREAM_DOWN — “down” パラメータがサポートされます
    • NGX_HTTP_UPSTREAM_BACKUP — “backup” パラメータがサポートされます
    • NGX_HTTP_UPSTREAM_MAX_CONNS — “max_conns” パラメータがサポートされます
  • host — upstreamの名前。
  • file_name, line — 設定ファイルの名前とupstream ブロックが置かれている行。
  • portno_port — 明示的に定義されたupstreamグループのためには使われません。
  • shm_zone — もしあれば、このupstreamグループによって使われる共有メモリ領域。
  • peer — upstream設定の初期化のための一般的なメソッドを保持するオブジェクト:
    typedef struct {
        ngx_http_upstream_init_pt        init_upstream;
        ngx_http_upstream_init_peer_pt   init;
        void                            *data;
    } ngx_http_upstream_peer_t;
    
    ロードバランシングアルゴリズムを実装するモジュールは、これらのメソッドを設定しprivatedataを初期化しなければなりません。もしinit_upstream が設定のパースの間に初期化されなかった場合は、ngx_http_upstream_module はそれをデフォルトの ngx_http_upstream_init_round_robinアルゴリズムに設定します。
    • init_upstream(cf, us) — 成功時には、サーバのグループの初期化と init() メソッドの初期化に責任がある設定時のメソッド。代表的なロードバランシングモジュールは、独自の設定をdataフィールドに保持し使用する幾つかの有効なデータ構造を作成するために、upstreamブロック内のサーバのリストを利用します。
    • init(r, us) — ロードバランシングのために使われるリクエスト毎のngx_http_upstream_peer_t.peer 構造を初期化します (上で説明されたupstream毎のngx_http_upstream_srv_conf_t.peerと混同しないでください)。data 引数としてサーバ選択を扱う全てのコールバックに渡されます。

nginxがリクエストを処理のために他のホストに渡す必要がある場合は、接続先のアドレスを取得するために設定されたロードバランシングメソッドを使います。メソッドは型ngx_peer_connection_tngx_http_upstream_t.peer オブジェクトから取得されます。

struct ngx_peer_connection_s {
    ...

    struct sockaddr                 *sockaddr;
    socklen_t                        socklen;
    ngx_str_t                       *name;

    ngx_uint_t                       tries;

    ngx_event_get_peer_pt            get;
    ngx_event_free_peer_pt           free;
    ngx_event_notify_peer_pt         notify;
    void                            *data;

#if (NGX_SSL || NGX_COMPAT)
    ngx_event_set_peer_session_pt    set_session;
    ngx_event_save_peer_session_pt   save_session;
#endif

    ...
};

構造は以下のフィールドを持ちます:

  • sockaddr, socklen, name — 接続先のupstreamサーバのアドレス; これはロードバランシング メソッドの出力パラメータです。
  • data — ロードバランシング メソッドのリクエスト毎のデータ; 選択アルゴリズムの状態を保持し、通常はupstream設定へのリンクを含みます。引数としてサーバ選択を扱う全てのメソッドへ渡されるでしょう( belowを見てください)。
  • tries — upstreamサーバへ接続できる試行の
  • get, free, notify, set_session およびsave_session - ロードバランシングモジュールのメソッド。以下で説明されます。

全てのメソッドは少なくとも二つの引数を受け付けます; ngx_http_upstream_srv_conf_t.peer.init()によって生成された peer接続オブジェクト pcdata。ロードバランシング モジュールの“chaining”により、pc.data とは異なるかも知れないことに注意してください。

  • get(pc, data) — メソッドは、upstreamモジュールがリクエストをupstreamサーバに渡す準備ができ、そのアドレスを知る必要がある時に呼ばれます。メソッドはngx_peer_connection_t構造の sockaddr, socklen および nameフィールドを埋める必要があります。返り値は以下のうちの1つです:
    • NGX_OK — サーバが選択された
    • NGX_ERROR — 内部エラーが発生した
    • NGX_BUSY — 現時点で利用可能なサーバが無い。これは以下のような多くの理由で起こり得ます: 動的なサーバグループが空、グループ内の全てのサーバが失敗状態にある、グループ内の全てのサーバが接続の最大数を既に扱っている、など。
    • NGX_DONE — 背後にある接続が再利用され、upstreamサーバへの新しい接続を作成する必要がありません。この値はkeepalive モジュールによって設定されます。
  • free(pc, data, state) — upstreamモジュールが特定のサーバを使った作業が完了したした時にメソッドが呼ばれます。state 引数はupstream接続の完了状態です。ビットマスクは以下の可能な値です:
    • NGX_PEER_FAILED — 試行がunsuccessfulだった
    • NGX_PEER_NEXT — upstreamサーバがコード403 あるいは 404を返す特別な場合。これは failureとは見なされません。
    • NGX_PEER_KEEPALIVE — 現在のところ使われていない
    このメソッドはtries カウンタも減らします。
  • notify(pc, data, type) — OSSバージョンでは、現在のところ使われていません。
  • set_session(pc, data)save_session(pc, data) — upstreamサーバへセッションをキャッシュすることができるSSL固有のメソッド。実装はラウンドロビンバランシングメソッドによって提供されます。

nginx-dev-examplesリポジトリはnginxモジュールの例を提供します。

コードスタイル

一般的なルール

  • テキストの幅の最大は80文字です
  • インデントは4つのスペース
  • タブは無し、後に続く空白は無し
  • 同じ行上の要素の並びは空白で分割されます
  • 16進数文字列は小文字
  • ファイル名、関数と型名、およびグローバル変数は ngx_ あるいはngx_http_ngx_mail_のようなもっと特定のプリフィックスを持ちます

size_t
ngx_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

    for (len = 0; p < last; len++) {

        c = *p;

        if (c < 0x80) {
            p++;
            continue;
        }

        if (ngx_utf8_decode(&p, last - p) > 0x10ffff) {
            /* invalid UTF-8 */
            return n;
        }
    }

    return len;
}

ファイル

一般的なソースファイルは2つの空行で分割される以下の章を持ちます:

  • コピーライト文
  • includes
  • プリプロセッサ定義
  • 型定義
  • 関数のプロトタイプ
  • 変数の定義
  • 関数の定義

コピーライト文はこのようなものです:

/*
 * Copyright (C) Author Name
 * Copyright (C) Organization, Inc.
 */

ファイルが大きく書き換えられた場合は、著者のリストが更新され、新しい著者が一番上に追加されます。

ngx_config.hngx_core.h ファイルは常に最初に含まれ、ngx_http.h, ngx_stream.h あるいは ngx_mail.hの1つが続きます。そして、オプションの外部ヘッダファイルに従います:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxslt/xslt.h>

#if (NGX_HAVE_EXSLT)
#include <libexslt/exslt.h>
#endif

ヘッダファイルは "header protection" と呼ばれるものを含まなければなりません:

#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
#define _NGX_PROCESS_CYCLE_H_INCLUDED_
...
#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */

コメント

  • //” comments are not used
  • テキストは英語で書かれ、アメリカ語の綴りが望ましいです
  • 複数行のコメントは以下のように整形されます:
    /*
     * The red-black tree code is based on the algorithm described in
     * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
     */
    
    /* find the server configuration for the address:port */
    

プリプロセッサ

マクロ名はngx_ あるいは NGX_ (あるいはもっと具体的な)プリフィックスから始まります。定数のためのマクロ名は大文字です。パラメータ化されたマクロとイニシャライザのためのマクロは小文字です。マクロ名と値は少なくとも2つの空白で分離されます:

#define NGX_CONF_BUFFER  4096

#define ngx_buf_in_memory(b)  (b->temporary || b->memory || b->mmap)

#define ngx_buf_size(b)                                                      \
    (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos):                      \
                            (b->file_last - b->file_pos))

#define ngx_null_string  { 0, NULL }

条件は丸括弧の中で、否定は外です:

#if (NGX_HAVE_KQUEUE)
...
#elif ((NGX_HAVE_DEVPOLL && !(NGX_TEST_BUILD_DEVPOLL)) \
       || (NGX_HAVE_EVENTPORT && !(NGX_TEST_BUILD_EVENTPORT)))
...
#elif (NGX_HAVE_EPOLL && !(NGX_TEST_BUILD_EPOLL))
...
#elif (NGX_HAVE_POLL)
...
#else /* select */
...
#endif /* NGX_HAVE_KQUEUE */

タイプ

型名は “_t” サフィックスで終わります。定義された型名は少なくとも2つの空白で分離されます:

typedef ngx_uint_t  ngx_rbtree_key_t;

構造型は typedefを使って定義されます。構造の中で、メンバー型と名前が並べられます:

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

ファイル内の異なる構造の間で割り当てを同じにしてください。自信を示す構造は “_s”で終わる名前を持ちます。隣接する構造の定義は2つの空尾行で分離されます:

typedef struct ngx_list_part_s  ngx_list_part_t;

struct ngx_list_part_s {
    void             *elts;
    ngx_uint_t        nelts;
    ngx_list_part_t  *next;
};


typedef struct {
    ngx_list_part_t  *last;
    ngx_list_part_t   part;
    size_t            size;
    ngx_uint_t        nalloc;
    ngx_pool_t       *pool;
} ngx_list_t;

各構造のメンバはそれ自身の行の上で定義されます:

typedef struct {
    ngx_uint_t        hash;
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;
} ngx_table_elt_t;

構造内の関数ポインタは “_pt” で終わる定義された型を持ちます:

typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);
typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
    off_t limit);

typedef struct {
    ngx_recv_pt        recv;
    ngx_recv_chain_pt  recv_chain;
    ngx_recv_pt        udp_recv;
    ngx_send_pt        send;
    ngx_send_pt        udp_send;
    ngx_send_chain_pt  udp_send_chain;
    ngx_send_chain_pt  send_chain;
    ngx_uint_t         flags;
} ngx_os_io_t;

Enumerationは “_e” で終わる型を持ちます:

typedef enum {
    ngx_http_fastcgi_st_version = 0,
    ngx_http_fastcgi_st_type,
    ...
    ngx_http_fastcgi_st_padding
} ngx_http_fastcgi_state_e;

変数

変数は基本型の長さ、そしてアルファベット順でソートされて定義されます。型名と変数名は並べられます。型と名前の “columns” は2つの空白で分離されます。大きな配列は宣言ブロックの最後に配置されます:

u_char                      |  | *rv, *p;
ngx_conf_t                  |  | *cf;
ngx_uint_t                  |  |  i, j, k;
unsigned int                |  |  len;
struct sockaddr             |  | *sa;
const unsigned char         |  | *data;
ngx_peer_connection_t       |  | *pc;
ngx_http_core_srv_conf_t    |  |**cscfp;
ngx_http_upstream_srv_conf_t|  | *us, *uscf;
u_char                      |  |  text[NGX_SOCKADDR_STRLEN];

静的およびグローバルは変数は宣言上で初期化されるかもしれません:

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");

static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static uint32_t  ngx_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

一般的に使われる型/名前 の組み合わせの1群があります。

u_char                        *rv;
ngx_int_t                      rc;
ngx_conf_t                    *cf;
ngx_connection_t              *c;
ngx_http_request_t            *r;
ngx_peer_connection_t         *pc;
ngx_http_upstream_srv_conf_t  *us, *uscf;

機能

全ての関数 (静的なものであっても)はプロトタイプを持つ必要があります。プロトタイプは引数名を含みます。長いプロトタイプは1つの連続する行上の1つのインデントでラップされます:

static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf);

static char *ngx_http_merge_servers(ngx_conf_t *cf,
    ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module,
    ngx_uint_t ctx_index);

定義内の関数名は新しい行で始まります。関数の本文の開始および終了の括弧は別の行にあります。関数の本文はインデントされます。関数の間には2つの空の行があります:

static ngx_int_t
ngx_http_find_virtual_server(ngx_http_request_t *r, u_char *host, size_t len)
{
    ...
}


static ngx_int_t
ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
    ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt)
{
    ...
}

関数名と開始括弧の間には空白はありません。長い関数の呼び出しは連続する行が最初の関数の引数の位置から始まるようにラップされます。可能であれば最初の連続する行を79の位置で終わるように整形します:

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
               "http header: \"%V: %V\"",
               &h->key, &h->value);

hc->busy = ngx_palloc(r->connection->pool,
                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));

ngx_inline マクロは inlineの代わりに使われなければなりません:

static ngx_inline void ngx_cpuid(uint32_t i, uint32_t *buf);

.” と “−>” を期待する二項演算子はそれらのオペランドから1つの空白で分離されなければなりません。単項演算子と添え字はそれらオペランドから空白によって分離されません:

width = width * 10 + (*fmt++ - '0');

ch = (u_char) ((decoded << 4) + (ch - '0'));

r->exten.data = &r->uri.data[i + 1];

型のキャストは1つの空白によってキャストされた表現から分離されます。型キャスト内のアスタリスクは1つの空白によって型名から分離されます:

len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);

表現が1つの行に収まらない場合は、ラップされます。行を区切る望ましい箇所は、二項演算子です。連続する行は表現の開始を使って整列されます:

if (status == NGX_HTTP_MOVED_PERMANENTLY
    || status == NGX_HTTP_MOVED_TEMPORARILY
    || status == NGX_HTTP_SEE_OTHER
    || status == NGX_HTTP_TEMPORARY_REDIRECT
    || status == NGX_HTTP_PERMANENT_REDIRECT)
{
    ...
}

p->temp_file->warn = "an upstream response is buffered "
                     "to a temporary file";

最後の手段として、連続する行が79の位置で終わるように表現をラップすることができます:

hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
                                     + size * sizeof(ngx_hash_elt_t *));

上の規則は副表現にも適用されます。各副表現は独自のインデントレベルを持ちます:

if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING)
     || c->stale_updating) && !r->background
    && u->conf->cache_background_update)
{
    ...
}

時には、キャストの後で表現をラップすることが便利です。この場合、連続する行はインデントされます:

node = (ngx_rbtree_node_t *)
           ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));

ポインタはNULL (0ではない)に対して明示的に比較されます:

if (ptr != NULL) {
    ...
}

条件とループ

if” キーワードは1つの空白によって条件から分離されます。開始の括弧は同じ行、あるいは条件が幾つかの行を取る場合は専用の行に置かれます。終了の括弧は専用の行、任意で“else if / else”が続く行に置かれます。通常は “else if / else” 部分の前に空の行があります:

if (node->left == sentinel) {
    temp = node->right;
    subst = node;

} else if (node->right == sentinel) {
    temp = node->left;
    subst = node;

} else {
    subst = ngx_rbtree_min(node->right, sentinel);

    if (subst->left != sentinel) {
        temp = subst->left;

    } else {
        temp = subst->right;
    }
}

同じような整形規則が “do” と “while” ループに適用されます:

while (p < last && *p == ' ') {
    p++;
}

do {
    ctx->node = rn;
    ctx = ctx->next;
} while (ctx);

switch” キーワードは1つの空白によって条件から分離されます。開始の括弧は同じ行に置かれます。終了の括弧は専用の行に置かれます。“case” キーワードは “switch” を使って整列されます:

switch (ch) {
case '!':
    looked = 2;
    state = ssi_comment0_state;
    break;

case '<':
    copy_end = p;
    break;

default:
    copy_end = p;
    looked = 0;
    state = ssi_start_state;
    break;
}

ほとんどの “for” ループはこのように整形されます:

for (i = 0; i < ccf->env.nelts; i++) {
    ...
}

for (q = ngx_queue_head(locations);
     q != ngx_queue_sentinel(locations);
     q = ngx_queue_next(q))
{
    ...
}

for” の幾つかの部分が省略される場合、これは “/* void */” コメントによって示されます:

for (i = 0; /* void */ ; i++) {
    ...
}

空の本文を持つループも同じ行に置かれるかもしれない “/* void */” コメントによって示されます:

for (cl = *busy; cl->next; cl = cl->next) { /* void */ }

終わりのないループはこのように見えます:

for ( ;; ) {
    ...
}

ラベル

ラベルは空の行で囲まれ、前のレベルにインデントされます:

    if (i == 0) {
        u->err = "host not found";
        goto failed;
    }

    u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t));
    if (u->addrs == NULL) {
        goto failed;
    }

    u->naddrs = i;

    ...

    return NGX_OK;

failed:

    freeaddrinfo(res);
    return NGX_ERROR;

メモリ問題のデバッグ

バッファオーバーランや解放後の使用エラーなどのメモリの問題をデバッグするために、最新のコンパイラでサポートされている AddressSanitizer (ASan)を使えます。gccclangでASanを有効にするには、コンパイラやリンカの-fsanitize=addressオプションを使います。これは、nginxをビルドする時に、configureスクリプトの--with-cc-opt--with-ld-optパラメータにオプションを追加することで行えます。

nginxのほとんどの割り当てはnginx内部のpoolから行われるため、メモリの問題をデバッグにするにはASanを有効にするだけでは不十分な場合があります。内部プールは、システムからメモリの大きなチャンクを割り当て、そこから小さな割り当てを切り取ります。ただし、このメカニズムはNGX_DEBUG_PALLOCマクロを1に設定することで無効にできます。この場合、割り当てはシステムアロケータに直接渡され、バッファ境界を完全に制御できます。

以下の設定業は、上記の情報をまとめたものです。サードパーティモジュールを開発し、様々なプラットフォームでnginxをテストする際に推奨されます。

auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1'
               --with-ld-opt=-fsanitize=address

共通の落とし穴

Cモジュールの書き方

最も一般的な落とし穴は、回避できる場合に本格的なCモジュールを書こうとすることです。ほとんどの場合、そのタスクは適切な設定を作成することで達成できます。モジュールを書くことが避けられない場合は、できるかぎり小さくシンプルにしてください。例えば、モジュールは一部の変数のみをエクスポートできます。

モジュールを開始する前に、以下の質問を考慮してください:

  • 既に利用可能なモジュールを使って目的の機能を実装できますか?
  • Perlまたはnjsのような組み込みのスクリプト言語を使って問題を解決できますか?

C文字列

nginxで最もよく使われている文字列、ngx_str_t は、C形式のゼロで終わる文字列ではありません。strlen()またはstrstr()のような標準Cライブラリ関数にデータを渡せません。代わりに、ngx_str_tを受け付けるnginxの対応物か、データへのポインタと長さを使う必要があります。ただし、ngx_str_tが是ゼロで終了する文字列へのポインタを保持する場合があります: 設定ファイルのパースの結果として得られる文字列はゼロで終了します。

グローバル変数

モジュールでグローバル変数を使わないでください。ほとんどの場合、これはグローバル変数を持つエラーです。グローバルデータは、設定サイクルに関連付けて、対応するメモリプールから割り当てる必要があります。これによりnginxはグレースフル設定再読み込みを実行できます。グローバル変数を使おうとすると、この機能が壊れる可能性があります。これは、2つの設定を同時に取得してそれらを取り除くことができないためです。グローバル変数が必要な時があります。この場合、再設定を適切に管理するには、特別な注意が必要です。また、コードで使われているライブラリにリロード時に壊れる可能性がある暗黙的なグローバル状態があるかを確認します。

手動のメモリ管理

エラーが起きやすいmalloc/freeのやり方を扱う代わりに、nginxのプールの使い方を学んでください。プールは作成され、オブジェクトに関連付けられます - 設定サイクル接続HTTPリクエスト。オブジェクトが破棄されると、関連するプールも破棄されます。オブジェクトを扱う場合、対応するプールから必要な量を割り当てることができ、エラーが発生した場合でもメモリを解放する必要はありません。

スレッド

nginxでスレッドを使わないことをお勧めします。これにより確実に問題が発生します: ほとんどのnginx関数はスレッドセーフではありません。スレッドは、システムコールとスレッドセーフなライブラリ関数のみを実行することが望まれます。クライアント要求処理に関連しないコードを実行する必要がある場合、適切な方法はinit_processモジュールハンドラでタイマーをスケジュールし、タイマーハンドラで必要なアクションを実行することです。内部的に、nginxはIO関連の操作を強化するためにスレッドを利用しますが、これは多くの制限がある特殊なケースです。

ブロッキングライブラリ

よくある間違いは、内部でブロックするライブラリを使うことです。ほとんどのライブラリは本質的に同期型でブロックします。つまり、一度に1つの操作を実行し、他のピアからの応答を待機する時間を浪費します。その結果、そのようなライブラリでリクエストが処理される場合、nginxのワーカー全体がブロックされ、パフォーマンスが低下します。非同期インタフェースを提供し、プロセス全体をブロックしないライブラリのみを使ってください。

外部サービスへのHTTPリクエスト

多くの場合、モジュールは外部サービスへのHTTP呼び出しを実行する必要があります。よくある間違いは、HTTPリクエストを行うためにlibcurlのような外部ライブラリを使うことです。nginx自体で実行できるタスクに大量の外部(おそらくブロックする!)コードを用意する必要は全くありません。

外部リクエストが必要な場合、2つの基本的な使用シナリオがあります:

  • クライアント要求の処理のコンテキスト(例えばコンテントハンドラ)
  • ワーカープロセスのコンテキスト(例えば、タイマーハンドラ)

最初のケースでは、サブリクエストAPIを使うのが最適です。外部サービスに直接アクセスする代わりに、nginx設定でlocationを宣言し、サブリクエストをこのlocationに送信します。このlocationはプロキシリクエストに限定されていませんが、他のnginxディレクティブが含まれる可能性があります。そのようなやり方の例は、ngx_http_auth_request moduleで実装されるauth_requestディレクティブです。

2つ目のケースでは、nginxで利用可能な基本的なHTTPクライアント機能を使うことができます。例えば、OCSPモジュールは単純なHTTPクライアントを実装します。

TOP
inserted by FC2 system