開發指南
簡介
程式碼佈局
-
auto
— 建置腳本 -
src
-
core
— 基本類型和函式 — 字串、陣列、日誌、記憶池等。 -
event
— 事件核心-
modules
— 事件通知模組:epoll
、kqueue
、select
等。
-
-
http
— 核心 HTTP 模組和通用程式碼-
modules
— 其他 HTTP 模組 -
v2
— HTTP/2
-
-
mail
— 郵件模組 -
os
— 平台特定程式碼-
unix
-
win32
-
-
stream
— 串流模組
-
包含檔案
以下兩個 #include
陳述式必須出現在每個 nginx 檔案的開頭
#include <ngx_config.h> #include <ngx_core.h>
除此之外,HTTP 程式碼應包含
#include <ngx_http.h>
郵件程式碼應包含
#include <ngx_mail.h>
串流程式碼應包含
#include <ngx_stream.h>
整數
針對一般用途,nginx 程式碼使用兩種整數類型:ngx_int_t
和 ngx_uint_t
,它們分別是 intptr_t
和 uintptr_t
的 typedef。
常見回傳碼
nginx 中的大多數函式會回傳下列程式碼
-
NGX_OK
— 操作成功。 -
NGX_ERROR
— 操作失敗。 -
NGX_AGAIN
— 操作未完成;再次呼叫函式。 -
NGX_DECLINED
— 操作遭拒,例如,因為它在組態中停用。這絕不是錯誤。 -
NGX_BUSY
— 資源不可用。 -
NGX_DONE
— 操作已完成或在其他地方繼續。也用作替代的成功程式碼。 -
NGX_ABORT
— 函式已中止。也用作替代的錯誤程式碼。
錯誤處理
ngx_errno
巨集會回傳最後的系統錯誤程式碼。它在 POSIX 平台上對應到 errno
,在 Windows 中則對應到 GetLastError()
呼叫。ngx_socket_errno
巨集會回傳最後的 socket 錯誤編號。如同 ngx_errno
巨集,它在 POSIX 平台上對應到 errno
。它在 Windows 上對應到 WSAGetLastError()
呼叫。連續多次存取 ngx_errno
或 ngx_socket_errno
的值可能會造成效能問題。如果錯誤值可能會多次使用,請將它儲存在 ngx_err_t
類型的區域變數中。若要設定錯誤,請使用 ngx_set_errno(errno)
和 ngx_set_socket_errno(errno)
巨集。
ngx_errno
和 ngx_socket_errno
的值可以傳遞至日誌記錄函式 ngx_log_error()
和 ngx_log_debugX()
,在這種情況下,系統錯誤文字會新增至日誌訊息。
使用 ngx_errno
的範例
ngx_int_t ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo) { ngx_err_t err; if (kill(pid, signo) == -1) { err = ngx_errno; ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo); if (err == NGX_ESRCH) { return 2; } return 1; } return 0; }
字串
概觀
針對 C 字串,nginx 使用 unsigned char 類型指標 u_char *
。
nginx 字串類型 ngx_str_t
的定義如下
typedef struct { size_t len; u_char *data; } ngx_str_t;
len
欄位會保留字串長度,而 data
會保留字串資料。在 ngx_str_t
中保留的字串可能在 len
個位元組後以 null 結尾,也可能不會。在大多數情況下,它不會。然而,在程式碼的某些部分 (例如,在剖析組態時),已知 ngx_str_t
物件會以 null 結尾,這可簡化字串比較並讓字串更容易傳遞至系統呼叫。
nginx 中的字串操作會在 src/core/ngx_string.h
中宣告。其中部分是標準 C 函式的包裝函式
-
ngx_strcmp()
-
ngx_strncmp()
-
ngx_strstr()
-
ngx_strlen()
-
ngx_strchr()
-
ngx_memcmp()
-
ngx_memset()
-
ngx_memcpy()
-
ngx_memmove()
其他字串函式是 nginx 特有的
-
ngx_memzero()
— 以零填滿記憶體。 -
ngx_explicit_memzero()
— 其作用與ngx_memzero()
相同,但編譯器的無效儲存消除最佳化永遠不會移除此呼叫。此函式可用於清除機密資料,例如密碼和金鑰。 -
ngx_cpymem()
— 其作用與ngx_memcpy()
相同,但會回傳最終目的地位址。這適用於依序附加多個字串。 -
ngx_movemem()
— 其作用與ngx_memmove()
相同,但會回傳最終目的地位址。 -
ngx_strlchr()
— 在以兩個指標分隔的字串中搜尋字元。
下列函式會執行大小寫轉換和比較
-
ngx_tolower()
-
ngx_toupper()
-
ngx_strlow()
-
ngx_strcasecmp()
-
ngx_strncasecmp()
下列巨集可簡化字串初始化
-
ngx_string(text)
— 從 C 字串常值text
初始化ngx_str_t
類型。 -
ngx_null_string
—ngx_str_t
類型的靜態空白字串初始化器 -
ngx_str_set(str, text)
— 使用 C 字串常值text
初始化ngx_str_t *
類型的字串str
。 -
ngx_str_null(str)
— 使用空白字串初始化ngx_str_t *
類型的字串str
。
格式化
下列格式化函式支援 nginx 特有類型
-
ngx_sprintf(buf, fmt, ...)
-
ngx_snprintf(buf, max, fmt, ...)
-
ngx_slprintf(buf, last, fmt, ...)
-
ngx_vslprintf(buf, last, fmt, args)
-
ngx_vsnprintf(buf, max, fmt, args)
這些函式支援的格式化選項完整清單位於 src/core/ngx_string.c
中。其中部分如下
-
%O
—off_t
-
%T
—time_t
-
%z
—ssize_t
-
%i
—ngx_int_t
-
%p
—void *
-
%V
—ngx_str_t *
-
%s
—u_char *
(以 null 結尾) -
%*s
—size_t + u_char *
您可以在大多數類型前面加上 u
,使其成為無正負號。若要將輸出轉換為十六進位,請使用 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 中實作了數個數值轉換函式。前四個函式各會將指定長度的字串轉換為指定類型的正整數。它們會在錯誤時回傳 NGX_ERROR
。
-
ngx_atoi(line, n)
—ngx_int_t
-
ngx_atosz(line, n)
—ssize_t
-
ngx_atoof(line, n)
—off_t
-
ngx_atotm(line, n)
—time_t
還有兩個額外的數值轉換函式。如同前四個函式,它們會在錯誤時回傳 NGX_ERROR
。
-
ngx_atofp(line, n, point)
— 將指定長度的固定點浮點數轉換為ngx_int_t
類型的正整數。結果會向左移動point
個小數位數。數字的字串表示法預期不會超過points
個小數。例如,ngx_atofp("10.5", 4, 2)
會回傳1050
。 -
ngx_hextoi(line, n)
— 將正整數的十六進位表示法轉換為ngx_int_t
。
正規表示式
nginx 中的正規表示式介面是 PCRE 函式庫的包裝函式。對應的標頭檔是 src/core/ngx_regex.h
。
若要使用正規表示式進行字串比對,首先必須編譯正規表示式,通常會在組態階段進行。請注意,由於 PCRE 支援是選用的,因此所有使用介面的程式碼都必須受到周圍的 NGX_PCRE
巨集保護
#if (NGX_PCRE) ngx_regex_t *re; ngx_regex_compile_t rc; u_char errstr[NGX_MAX_CONF_ERRSTR]; ngx_str_t value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'"); ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value; rc.pool = cf->pool; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; /* rc.options can be set to NGX_REGEX_CASELESS */ if (ngx_regex_compile(&rc) != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err); return NGX_CONF_ERROR; } re = rc.regex; #endif
在成功編譯後,ngx_regex_compile_t
結構中的 captures
和 named_captures
欄位分別包含在正規表示式中找到的所有擷取和命名擷取的計數。
接著可使用編譯過的正規表示式來比對字串
ngx_int_t n; int captures[(1 + rc.captures) * 3]; ngx_str_t input = ngx_string("This is message 123. Codeword is 'foobar'."); n = ngx_regex_exec(re, &input, captures, (1 + rc.captures) * 3); if (n >= 0) { /* string matches expression */ } else if (n == NGX_REGEX_NO_MATCHED) { /* no match was found */ } else { /* some error */ ngx_log_error(NGX_LOG_ALERT, log, 0, ngx_regex_exec_n " failed: %i", n); }
ngx_regex_exec()
的引數是編譯過的正規表示式 re
、要比對的字串 input
、用以保留任何找到的 captures
的選擇性整數陣列,以及陣列的 size
。captures
陣列的大小必須是三的倍數,這是 PCRE API 的要求。在此範例中,大小是從擷取總數加上比對字串本身來計算的。
如果有比對,則可以如下所示存取擷取
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 時差類型來表示時間
typedef struct { time_t sec; ngx_uint_t msec; ngx_int_t gmtoff; } ngx_time_t;
ngx_tm_t
結構是 UNIX 平台上的 struct tm
和 Windows 上的 SYSTEMTIME
的別名。
若要取得目前時間,通常只需存取代表所需格式快取時間值的其中一個可用全域變數即可。
可用的字串表示法如下
-
ngx_cached_err_log_time
— 用於錯誤日誌項目:"1970/09/28 12:00:00"
-
ngx_cached_http_log_time
— 用於 HTTP 存取日誌項目:"28/Sep/1970:12:00:00 +0600"
-
ngx_cached_syslog_time
— 用於 syslog 項目:"Sep 28 12:00:00"
-
ngx_cached_http_time
— 用於 HTTP 標頭:"Mon, 28 Sep 1970 06:00:00 GMT"
-
ngx_cached_http_log_iso8601
— ISO 8601 標準格式:"1970-09-28T12:00:00+06:00"
ngx_time()
和 ngx_timeofday()
巨集會回傳目前時間值 (以秒為單位),是存取快取時間值的偏好方式。
若要明確取得時間,請使用 ngx_gettimeofday()
,這會更新其引數 (指向 struct timeval
的指標)。當 nginx 從系統呼叫傳回事件迴圈時,一律會更新時間。若要立即更新時間,請呼叫 ngx_time_update()
,或是在訊號處理常式內容中更新時間時呼叫 ngx_time_sigsafe_update()
。
下列函式會將 time_t
轉換為指示的分解時間表示法。每對中的第一個函式會將 time_t
轉換為 ngx_tm_t
,而第二個函式 (包含 _libc_
中綴) 則會轉換為 struct tm
-
ngx_gmtime(), ngx_libc_gmtime()
— 以 UTC 表示的時間 -
ngx_localtime(), ngx_libc_localtime()
— 相對於當地時區表示的時間
ngx_http_time(buf, time)
函式會回傳適用於 HTTP 標頭的字串表示法 (例如,"Mon, 28 Sep 1970 06:00:00 GMT"
)。ngx_http_cookie_time(buf, time)
函式會回傳適用於 HTTP Cookie 的字串表示法 ("Thu, 31-Dec-37 23:55:55 GMT"
)。
容器
陣列
nginx 陣列類型 ngx_array_t
的定義如下
typedef struct { void *elts; ngx_uint_t nelts; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_array_t;
陣列的元素儲存在 elts
欄位中。nelts
欄位保存元素的數量。size
欄位保存單個元素的大小,並在陣列初始化時設定。
使用 ngx_array_create(pool, n, size)
呼叫在記憶體池中建立一個陣列,並使用 ngx_array_init(array, pool, n, size)
呼叫來初始化已配置的陣列物件。
ngx_array_t *a, b; /* create an array of strings with preallocated memory for 10 elements */ a = ngx_array_create(pool, 10, sizeof(ngx_str_t)); /* initialize string array for 10 elements */ ngx_array_init(&b, pool, 10, sizeof(ngx_str_t));
使用以下函式向陣列新增元素
-
ngx_array_push(a)
新增一個尾端元素並傳回指向它的指標 -
ngx_array_push_n(a, n)
新增n
個尾端元素並傳回指向第一個元素的指標
如果目前配置的記憶體量不足以容納新的元素,則會配置新的記憶體區塊,並將現有元素複製到其中。新的記憶體區塊通常是現有區塊的兩倍大。
s = ngx_array_push(a); ss = ngx_array_push_n(&b, 3);
列表
在 Nginx 中,列表是一系列陣列,針對插入大量項目進行了最佳化。ngx_list_t
列表類型定義如下
typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
實際項目儲存在列表的各部分中,其定義如下
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; };
在使用之前,必須呼叫 ngx_list_init(list, pool, n, size)
初始化列表,或呼叫 ngx_list_create(pool, n, size)
建立列表。這兩個函式都將單個項目的大小和每個列表部分的項目數作為引數。要將項目新增到列表中,請使用 ngx_list_push(list)
函式。要迭代項目,請直接存取列表欄位,如範例所示
ngx_str_t *v; ngx_uint_t i; ngx_list_t *list; ngx_list_part_t *part; list = ngx_list_create(pool, 100, sizeof(ngx_str_t)); if (list == NULL) { /* error */ } /* add items to the list */ v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "foo"); v = ngx_list_push(list); if (v == NULL) { /* error */ } ngx_str_set(v, "bar"); /* iterate over the list */ part = &list->part; v = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; v = part->elts; i = 0; } ngx_do_smth(&v[i]); }
列表主要用於 HTTP 輸入和輸出標頭。
列表不支援移除項目。但是,在需要時,可以將項目在內部標記為遺失,而無需實際從列表中移除。例如,要將 HTTP 輸出標頭(儲存為 ngx_table_elt_t
物件)標記為遺失,請將 ngx_table_elt_t
中的 hash
欄位設定為零。當迭代標頭時,會明確跳過以這種方式標記的項目。
佇列
在 Nginx 中,佇列是一種侵入式雙向鏈結串列,每個節點定義如下
typedef struct ngx_queue_s ngx_queue_t; struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next; };
頭部佇列節點未與任何資料連結。在使用之前,請使用 ngx_queue_init(q)
呼叫來初始化列表頭部。佇列支援以下操作
-
ngx_queue_insert_head(h, x)
、ngx_queue_insert_tail(h, x)
— 插入一個新節點 -
ngx_queue_remove(x)
— 移除一個佇列節點 -
ngx_queue_split(h, q, n)
— 在一個節點處分割佇列,並在單獨的佇列中傳回佇列尾部 -
ngx_queue_add(h, n)
— 將第二個佇列新增到第一個佇列 -
ngx_queue_head(h)
、ngx_queue_last(h)
— 取得第一個或最後一個佇列節點 -
ngx_queue_sentinel(h)
- 取得佇列哨兵物件,以在此物件結束迭代 -
ngx_queue_data(q, type, link)
— 取得對佇列節點資料結構開頭的參考,並考慮其中的佇列欄位偏移量
一個範例
typedef struct { ngx_str_t value; ngx_queue_t queue; } ngx_foo_t; ngx_foo_t *f; ngx_queue_t values, *q; ngx_queue_init(&values); f = ngx_palloc(pool, sizeof(ngx_foo_t)); if (f == NULL) { /* error */ } ngx_str_set(&f->value, "foo"); ngx_queue_insert_tail(&values, &f->queue); /* insert more nodes here */ for (q = ngx_queue_head(&values); q != ngx_queue_sentinel(&values); q = ngx_queue_next(q)) { f = ngx_queue_data(q, ngx_foo_t, queue); ngx_do_smth(&f->value); }
紅黑樹
src/core/ngx_rbtree.h
標頭檔案提供對紅黑樹有效實作的存取。
typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; /* custom per-tree data here */ } my_tree_t; typedef struct { ngx_rbtree_node_t rbnode; /* custom per-node data */ foo_t val; } my_node_t;
若要將樹狀結構視為整體進行處理,您需要兩個節點:根節點和哨兵節點。通常,它們會新增到自訂結構中,讓您能夠將資料組織成一個樹狀結構,其中葉子包含指向或嵌入資料的連結。
若要初始化樹狀結構
my_tree_t root; ngx_rbtree_init(&root.rbtree, &root.sentinel, insert_value_function);
若要遍歷樹狀結構並插入新值,請使用 "insert_value
" 函式。例如,ngx_str_rbtree_insert_value
函式處理 ngx_str_t
類型。其引數是指向插入的根節點、要新增的新建立節點和樹狀結構哨兵的指標。
void ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
遍歷非常簡單,可以使用以下查找函式模式示範
my_node_t * my_rbtree_lookup(ngx_rbtree_t *rbtree, foo_t *val, uint32_t hash) { ngx_int_t rc; my_node_t *n; ngx_rbtree_node_t *node, *sentinel; node = rbtree->root; sentinel = rbtree->sentinel; while (node != sentinel) { n = (my_node_t *) node; if (hash != node->key) { node = (hash < node->key) ? node->left : node->right; continue; } rc = compare(val, node->val); if (rc < 0) { node = node->left; continue; } if (rc > 0) { node = node->right; continue; } return n; } return NULL; }
compare()
函式是一個典型的比較器函式,它傳回小於、等於或大於零的值。為了加快查找速度並避免比較可能很大的使用者物件,會使用整數雜湊欄位。
若要將節點新增到樹狀結構,請配置新節點、初始化該節點並呼叫 ngx_rbtree_insert()
my_node_t *my_node; ngx_rbtree_node_t *node; my_node = ngx_palloc(...); init_custom_data(&my_node->val); node = &my_node->rbnode; node->key = create_key(my_node->val); ngx_rbtree_insert(&root->rbtree, node);
若要移除節點,請呼叫 ngx_rbtree_delete()
函式
ngx_rbtree_delete(&root->rbtree, node);
雜湊
雜湊表函式在 src/core/ngx_hash.h
中宣告。支援精確比對和萬用字元比對。後者需要額外的設定,並在下面的單獨章節中描述。
在初始化雜湊之前,您需要知道它將保存的元素數量,以便 Nginx 可以以最佳方式建構它。需要設定的兩個參數是 max_size
和 bucket_size
,詳情請參閱單獨的文件。它們通常可由使用者設定。雜湊初始化設定儲存在 ngx_hash_init_t
類型中,雜湊本身為 ngx_hash_t
ngx_hash_t foo_hash; ngx_hash_init_t hash; hash.hash = &foo_hash; hash.key = ngx_hash_key; hash.max_size = 512; hash.bucket_size = ngx_align(64, ngx_cacheline_size); hash.name = "foo_hash"; hash.pool = cf->pool; hash.temp_pool = cf->temp_pool;
key
是指向函式的指標,該函式會從字串建立雜湊整數金鑰。有兩個通用的金鑰建立函式: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)
初始化:第二個參數 (type
) 控制為雜湊預先配置的資源量,可以是 NGX_HASH_SMALL
或 NGX_HASH_LARGE
。如果您預期雜湊包含數千個元素,則後者適用。
ngx_hash_keys_arrays_t foo_keys; foo_keys.pool = cf->pool; foo_keys.temp_pool = cf->temp_pool; ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);
若要將金鑰插入雜湊金鑰陣列中,請使用 ngx_hash_add_key(keys_array, key, value, flags)
函式
ngx_str_t k1 = ngx_string("key1"); ngx_str_t k2 = ngx_string("key2"); ngx_hash_add_key(&foo_keys, &k1, &my_data_ptr_1, NGX_HASH_READONLY_KEY); ngx_hash_add_key(&foo_keys, &k2, &my_data_ptr_2, NGX_HASH_READONLY_KEY);
若要建構雜湊表,請呼叫 ngx_hash_init(hinit, key_names, nelts)
函式
ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);
如果 max_size
或 bucket_size
參數不夠大,則函式會失敗。
當建構雜湊時,請使用 ngx_hash_find(hash, key, name, len)
函式來查找元素
my_data_t *data; ngx_uint_t key; key = ngx_hash_key(k1.data, k1.len); data = ngx_hash_find(&foo_hash, key, k1.data, k1.len); if (data == NULL) { /* key not found */ }
萬用字元比對
若要建立使用萬用字元的雜湊,請使用 ngx_hash_combined_t
類型。它包括上述的雜湊類型,並具有兩個額外的金鑰陣列:dns_wc_head
和 dns_wc_tail
。基本屬性的初始化類似於一般雜湊
ngx_hash_init_t hash ngx_hash_combined_t foo_hash; hash.hash = &foo_hash.hash; hash.key = ...;
可以使用 NGX_HASH_WILDCARD_KEY
旗標新增萬用字元金鑰
/* k1 = ".example.org"; */ /* k2 = "foo.*"; */ ngx_hash_add_key(&foo_keys, &k1, &data1, NGX_HASH_WILDCARD_KEY); ngx_hash_add_key(&foo_keys, &k2, &data2, NGX_HASH_WILDCARD_KEY);
該函式會識別萬用字元,並將金鑰新增到相應的陣列中。有關萬用字元語法和比對演算法的描述,請參閱 map 模組文件。
根據新增金鑰的內容,您可能需要初始化最多三個金鑰陣列:一個用於精確比對(如上所述),另外兩個用於啟用從字串開頭或尾端開始的比對
if (foo_keys.dns_wc_head.nelts) { ngx_qsort(foo_keys.dns_wc_head.elts, (size_t) foo_keys.dns_wc_head.nelts, sizeof(ngx_hash_key_t), cmp_dns_wildcards); hash.hash = NULL; hash.temp_pool = pool; if (ngx_hash_wildcard_init(&hash, foo_keys.dns_wc_head.elts, foo_keys.dns_wc_head.nelts) != NGX_OK) { return NGX_ERROR; } foo_hash.wc_head = (ngx_hash_wildcard_t *) hash.hash; }
需要排序金鑰陣列,並且必須將初始化結果新增到合併雜湊。dns_wc_tail
陣列的初始化以類似的方式完成。
合併雜湊中的查找由 ngx_hash_find_combined(chash, key, name, len)
處理
/* key = "bar.example.org"; — will match ".example.org" */ /* key = "foo.example.com"; — will match "foo.*" */ hkey = ngx_hash_key(key.data, key.len); res = ngx_hash_find_combined(&foo_hash, hkey, key.data, key.len);
記憶體管理
堆積
若要從系統堆積配置記憶體,請使用下列函式
-
ngx_alloc(size, log)
— 從系統堆積配置記憶體。這是malloc()
的包裝函式,具有記錄支援。配置錯誤和偵錯資訊會記錄到log
。 -
ngx_calloc(size, log)
— 從系統堆積配置記憶體,如ngx_alloc()
,但在配置後將記憶體填滿零。 -
ngx_memalign(alignment, size, log)
— 從系統堆積配置對齊的記憶體。這是那些提供該函式的平台上的posix_memalign()
包裝函式。否則,實作會回退到提供最大對齊的ngx_alloc()
。 -
ngx_free(p)
— 釋放配置的記憶體。這是free()
的包裝函式
記憶池
大多數 Nginx 配置都在記憶體池中完成。在 Nginx 記憶體池中配置的記憶體會在記憶體池銷毀時自動釋放。這提供了良好的配置效能,並使記憶體控制變得容易。
記憶體池會在內部以連續的記憶體區塊配置物件。一旦區塊已滿,則會配置新的區塊並將其新增到記憶體池區塊清單。當請求的配置太大而無法放入區塊時,則會將請求轉發到系統配置器,並且將傳回的指標儲存在記憶體池中以供進一步解除配置。
Nginx 記憶體池的類型為 ngx_pool_t
。支援以下操作
-
ngx_create_pool(size, log)
— 建立具有指定區塊大小的記憶體池。傳回的記憶體池物件也會在記憶體池中配置。size
應該至少為NGX_MIN_POOL_SIZE
且為NGX_POOL_ALIGNMENT
的倍數。 -
ngx_destroy_pool(pool)
— 釋放所有記憶體池記憶體,包括記憶體池物件本身。 -
ngx_palloc(pool, size)
— 從指定的記憶體池配置對齊的記憶體。 -
ngx_pcalloc(pool, size)
— 從指定的記憶體池配置對齊的記憶體,並使用零填滿。 -
ngx_pnalloc(pool, size)
— 從指定的記憶體池配置未對齊的記憶體。主要用於配置字串。 -
ngx_pfree(pool, p)
— 釋放先前在指定的記憶體池中配置的記憶體。只有轉發到系統配置器的請求所產生的配置才能釋放。
u_char *p; ngx_str_t *s; ngx_pool_t *pool; pool = ngx_create_pool(1024, log); if (pool == NULL) { /* error */ } s = ngx_palloc(pool, sizeof(ngx_str_t)); if (s == NULL) { /* error */ } ngx_str_set(s, "foo"); p = ngx_pnalloc(pool, 3); if (p == NULL) { /* error */ } ngx_memcpy(p, "foo", 3);
鏈結連結 (ngx_chain_t
) 在 Nginx 中積極使用,因此 Nginx 記憶體池實作提供了一種重複使用它們的方法。ngx_pool_t
的 chain
欄位保留了先前配置的連結清單,這些連結已準備好重複使用。若要在記憶體池中有效配置鏈結連結,請使用 ngx_alloc_chain_link(pool)
函式。此函式會在記憶體池清單中尋找可用的鏈結連結,如果記憶體池清單為空,則會配置新的鏈結連結。若要釋放連結,請呼叫 ngx_free_chain(pool, cl)
函式。
可以在記憶體池中註冊清除處理常式。清除處理常式是在銷毀記憶體池時呼叫的回呼函式,其中包含一個引數。記憶體池通常與特定的 Nginx 物件(例如 HTTP 請求)繫結,並在物件達到其生命週期結束時銷毀。註冊記憶體池清除是釋放資源、關閉檔案描述符或對與主要物件相關聯的共用資料進行最終調整的便捷方法。
若要註冊記憶體池清除,請呼叫 ngx_pool_cleanup_add(pool, size)
,這會傳回要由呼叫者填寫的 ngx_pool_cleanup_t
指標。使用 size
引數為清除處理常式配置內容。
ngx_pool_cleanup_t *cln; cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { /* error */ } cln->handler = ngx_my_cleanup; cln->data = "foo"; ... static void ngx_my_cleanup(void *data) { u_char *msg = data; ngx_do_smth(msg); }
共享記憶體
Nginx 使用共用記憶體在程序之間共用通用資料。ngx_shared_memory_add(cf, name, size, tag)
函式會將新的共用記憶體項目 ngx_shm_zone_t
新增到循環中。該函式會接收區域的 name
和 size
。每個共用區域都必須具有唯一的名稱。如果已存在具有所提供 name
和 tag
的共用區域項目,則會重複使用現有的區域項目。如果現有項目的名稱相同但標籤不同,則函式會失敗並顯示錯誤。通常,模組結構的位址會作為 tag
傳遞,以便在一個 Nginx 模組中按名稱重複使用共用區域。
共用記憶體項目結構 ngx_shm_zone_t
具有以下欄位
-
init
— 初始化回呼函式,在將共用區域對應到實際記憶體後呼叫 -
data
— 資料上下文,用於將任意資料傳遞給init
回呼函式 -
noreuse
— 標記,用於停用從舊循環中重複使用共享區塊 -
tag
— 共享區塊標籤 -
shm
— 平台特定的ngx_shm_t
型別物件,至少具有以下欄位-
addr
— 已對應的共享記憶體位址,初始值為 NULL -
size
— 共享記憶體大小 -
name
— 共享記憶體名稱 -
log
— 共享記憶體日誌 -
exists
— 標記,指示共享記憶體繼承自主進程(Windows 特有)
-
在解析配置後,共享區塊條目會對應到 ngx_init_cycle()
中的實際記憶體。在 POSIX 系統上,會使用 mmap()
系統呼叫來建立共享匿名對應。在 Windows 上,則使用 CreateFileMapping()
/MapViewOfFileEx()
配對。
為了在共享記憶體中分配空間,nginx 提供了 slab pool ngx_slab_pool_t
型別。每個 nginx 共享區塊中都會自動建立一個用於分配記憶體的 slab pool。該 pool 位於共享區塊的開頭,可以使用表示式 (ngx_slab_pool_t *) shm_zone->shm.addr
來存取。要在共享區塊中分配記憶體,請呼叫 ngx_slab_alloc(pool, size)
或 ngx_slab_calloc(pool, size)
。要釋放記憶體,請呼叫 ngx_slab_free(pool, p)
。
Slab pool 將所有共享區塊劃分為頁面。每個頁面都用於分配相同大小的物件。指定的大小必須是 2 的冪次方,且大於 8 位元組的最小值。不符合的值會被向上捨入。每個頁面的位元遮罩會追蹤哪些區塊正在使用,以及哪些區塊可供分配。對於大於半頁(通常為 2048 位元組)的大小,一次會分配整個頁面。
為了保護共享記憶體中的資料免於並行存取,請使用 ngx_slab_pool_t
的 mutex
欄位中提供的互斥鎖。互斥鎖最常被 slab pool 在分配和釋放記憶體時使用,但也可以用來保護在共享區塊中分配的任何其他使用者資料結構。要鎖定或解鎖互斥鎖,請分別呼叫 ngx_shmtx_lock(&shpool->mutex)
或 ngx_shmtx_unlock(&shpool->mutex)
。
ngx_str_t name; ngx_foo_ctx_t *ctx; ngx_shm_zone_t *shm_zone; ngx_str_set(&name, "foo"); /* allocate shared zone context */ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_foo_ctx_t)); if (ctx == NULL) { /* error */ } /* add an entry for 64k shared zone */ shm_zone = ngx_shared_memory_add(cf, &name, 65536, &ngx_foo_module); if (shm_zone == NULL) { /* error */ } /* register init callback and context */ shm_zone->init = ngx_foo_init_zone; shm_zone->data = ctx; ... static ngx_int_t ngx_foo_init_zone(ngx_shm_zone_t *shm_zone, void *data) { ngx_foo_ctx_t *octx = data; size_t len; ngx_foo_ctx_t *ctx; ngx_slab_pool_t *shpool; value = shm_zone->data; if (octx) { /* reusing a shared zone from old cycle */ ctx->value = octx->value; return NGX_OK; } shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; if (shm_zone->shm.exists) { /* initialize shared zone context in Windows nginx worker */ ctx->value = shpool->data; return NGX_OK; } /* initialize shared zone */ ctx->value = ngx_slab_alloc(shpool, sizeof(ngx_uint_t)); if (ctx->value == NULL) { return NGX_ERROR; } shpool->data = ctx->value; return NGX_OK; }
日誌記錄
對於日誌記錄,nginx 使用 ngx_log_t
物件。nginx 日誌記錄器支援數種類型的輸出
- stderr — 記錄到標準錯誤輸出 (stderr)
- file — 記錄到檔案
- syslog — 記錄到 syslog
- memory — 記錄到內部記憶體儲存,用於開發目的;稍後可以使用偵錯工具存取記憶體
一個日誌記錄器實例可以是一個日誌記錄器鏈,它們之間透過 next
欄位連結。在這種情況下,每個訊息都會寫入到鏈中的所有日誌記錄器。
對於每個日誌記錄器,嚴重性級別會控制哪些訊息會寫入到日誌(只會記錄分配了該級別或更高層級的事件)。支援以下嚴重性級別
-
NGX_LOG_EMERG
-
NGX_LOG_ALERT
-
NGX_LOG_CRIT
-
NGX_LOG_ERR
-
NGX_LOG_WARN
-
NGX_LOG_NOTICE
-
NGX_LOG_INFO
-
NGX_LOG_DEBUG
對於偵錯日誌記錄,也會檢查偵錯遮罩。偵錯遮罩如下
-
NGX_LOG_DEBUG_CORE
-
NGX_LOG_DEBUG_ALLOC
-
NGX_LOG_DEBUG_MUTEX
-
NGX_LOG_DEBUG_EVENT
-
NGX_LOG_DEBUG_HTTP
-
NGX_LOG_DEBUG_MAIL
-
NGX_LOG_DEBUG_STREAM
通常,日誌記錄器是由現有的 nginx 程式碼從 error_log
指令建立,並且幾乎可以在循環、配置、用戶端連線和其他物件的每個處理階段中使用。
Nginx 提供以下日誌記錄巨集
-
ngx_log_error(level, log, err, fmt, ...)
— 錯誤日誌記錄 -
ngx_log_debug0(level, log, err, fmt)
、ngx_log_debug1(level, log, err, fmt, arg1)
等 — 偵錯日誌記錄,最多支援 8 個格式化參數
日誌訊息會在堆疊上大小為 NGX_MAX_ERROR_STR
(目前為 2048 位元組)的緩衝區中格式化。訊息會以嚴重性級別、進程 ID (PID)、連線 ID(儲存在 log->connection
中)和系統錯誤文字作為前綴。對於非偵錯訊息,也會呼叫 log->handler
以在日誌訊息中加上更多特定資訊。HTTP 模組將 ngx_http_log_error()
函式設定為日誌處理程式,以記錄用戶端和伺服器位址、目前動作(儲存在 log->action
中)、用戶端請求行、伺服器名稱等。
/* specify what is currently done */ log->action = "sending mp4 to client"; /* error and debug log */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
上面的範例會產生如下的日誌條目
2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1" 2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000
週期
循環物件儲存從特定配置建立的 nginx 執行階段上下文。其型別為 ngx_cycle_t
。目前的循環由 ngx_cycle
全域變數參考,並由 nginx 工作程式在啟動時繼承。每次重新載入 nginx 配置時,都會從新的 nginx 配置建立一個新的循環;通常在成功建立新的循環後,舊的循環會被刪除。
循環是由 ngx_init_cycle()
函式建立,該函式會將先前的循環作為其引數。該函式會找到先前循環的設定檔,並從先前的循環繼承盡可能多的資源。一個名為「初始化循環」的預留位置循環會在 nginx 啟動時建立,然後由從配置建立的實際循環取代。
循環的成員包括
-
pool
— 循環池。為每個新的循環建立。 -
log
— 循環日誌。最初從舊循環繼承,在讀取配置後,會設定為指向new_log
。 -
new_log
— 循環日誌,由配置建立。它會受到根範圍的error_log
指令影響。 -
connections
、connection_n
—ngx_connection_t
型別的連線陣列,由事件模組在初始化每個 nginx 工作程式時建立。nginx 配置中的worker_connections
指令會設定連線數connection_n
。 -
free_connections
、free_connection_n
— 目前可用的連線清單和數量。如果沒有可用的連線,nginx 工作程式會拒絕接受新的用戶端或連線到上游伺服器。 -
files
、files_n
— 將檔案描述符對應到 nginx 連線的陣列。此對應由具有NGX_USE_FD_EVENT
標記的事件模組使用(目前為poll
和devpoll
)。 -
conf_ctx
— 核心模組配置的陣列。配置會在讀取 nginx 設定檔期間建立和填寫。 -
modules
、modules_n
—ngx_module_t
型別的模組陣列,包括靜態和動態模組,由目前的配置載入。 -
listening
—ngx_listening_t
型別的監聽物件陣列。監聽物件通常由呼叫ngx_create_listening()
函式的不同模組的listen
指令新增。監聽 socket 會根據監聽物件建立。 -
paths
—ngx_path_t
型別的路徑陣列。路徑是由要對特定目錄進行操作的模組呼叫函式ngx_add_path()
新增。如果遺失這些目錄,nginx 會在讀取配置後建立它們。此外,可以為每個路徑新增兩個處理常式- 路徑載入器 — 在啟動或重新載入 nginx 後,每 60 秒執行一次。通常,載入器會讀取目錄並將資料儲存在 nginx 共享記憶體中。該處理常式是從專用的 nginx 程序「nginx 快取載入器」呼叫。
- 路徑管理員 — 定期執行。通常,管理員會從目錄中移除舊檔案,並更新 nginx 記憶體以反映變更。該處理常式是從專用的「nginx 快取管理員」程序呼叫。
-
open_files
—ngx_open_file_t
型別的開啟檔案物件清單,這些物件是透過呼叫函式ngx_conf_open_file()
建立。目前,nginx 使用此類開啟檔案進行日誌記錄。讀取配置後,nginx 會開啟open_files
清單中的所有檔案,並將每個檔案描述符儲存在物件的fd
欄位中。這些檔案會以附加模式開啟,如果遺失則會建立。在接收到重新開啟訊號(通常是USR1
)時,nginx 工作程式會重新開啟清單中的檔案。在這種情況下,fd
欄位中的描述符會變更為新值。 -
shared_memory
— 共享記憶體區塊的清單,每個區塊都是透過呼叫ngx_shared_memory_add()
函式新增。共享區塊會對應到所有 nginx 程序中的相同位址範圍,並用於共享通用資料,例如 HTTP 快取的記憶體內樹狀結構。
緩衝區
對於輸入/輸出操作,nginx 提供緩衝區類型 ngx_buf_t
。通常,它用於儲存要寫入目的地或從來源讀取的資料。緩衝區可以參考記憶體或檔案中的資料,並且技術上可以讓緩衝區同時參考兩者。緩衝區的記憶體會單獨分配,且與緩衝區結構 ngx_buf_t
無關。
ngx_buf_t
結構具有以下欄位
-
start
、end
— 為緩衝區分配的記憶體區塊的邊界。 -
pos
、last
— 記憶體緩衝區的邊界;通常是start
..end
的子範圍。 -
file_pos
、file_last
— 檔案緩衝區的邊界,以檔案開頭的位移表示。 -
tag
— 用於區分緩衝區的唯一值;由不同的 nginx 模組建立,通常用於緩衝區重複使用。 -
file
— 檔案物件。 -
temporary
— 標記,指示緩衝區參考可寫入的記憶體。 -
memory
— 標記,指示緩衝區參考唯讀記憶體。 -
in_file
— 標記,指示緩衝區參考檔案中的資料。 -
flush
— 標記,指示需要在緩衝區之前清除所有資料。 -
recycled
— 標記,指示緩衝區可以重複使用,且需要盡快使用。 -
sync
— 標記,指示緩衝區不帶有資料或特殊訊號(例如flush
或last_buf
)。依預設,nginx 會將此類緩衝區視為錯誤條件,但此標記會告知 nginx 跳過錯誤檢查。 -
last_buf
— 標記,指示緩衝區是輸出的最後一個。 -
last_in_chain
— 標記,指示請求或子請求中不再有更多資料緩衝區。 -
shadow
— 指向與目前緩衝區相關的另一個(「影子」)緩衝區的參考,通常表示該緩衝區使用來自影子的資料。當緩衝區被消耗時,影子緩衝區通常也會被標記為已消耗。 -
last_shadow
— 旗標,指示該緩衝區是最後一個參考特定影子緩衝區的緩衝區。 -
temp_file
— 旗標,指示該緩衝區位於臨時檔案中。
對於輸入和輸出操作,緩衝區以鏈狀方式連結。鏈是類型為 ngx_chain_t
的鏈結序列,定義如下:
typedef struct ngx_chain_s ngx_chain_t; struct ngx_chain_s { ngx_buf_t *buf; ngx_chain_t *next; };
每個鏈結都保留對其緩衝區的參考,以及對下一個鏈結的參考。
使用緩衝區和鏈的範例
ngx_chain_t * ngx_get_my_chain(ngx_pool_t *pool) { ngx_buf_t *b; ngx_chain_t *out, *cl, **ll; /* first buf */ cl = ngx_alloc_chain_link(pool); if (cl == NULL) { /* error */ } b = ngx_calloc_buf(pool); if (b == NULL) { /* error */ } b->start = (u_char *) "foo"; b->pos = b->start; b->end = b->start + 3; b->last = b->end; b->memory = 1; /* read-only memory */ cl->buf = b; out = cl; ll = &cl->next; /* second buf */ cl = ngx_alloc_chain_link(pool); if (cl == NULL) { /* error */ } b = ngx_create_temp_buf(pool, 3); if (b == NULL) { /* error */ } b->last = ngx_cpymem(b->last, "foo", 3); cl->buf = b; cl->next = NULL; *ll = cl; return out; }
網路
連線
連線類型 ngx_connection_t
是套接字描述符的包裝器。它包含以下欄位:
-
fd
— 套接字描述符 -
data
— 任意連線上下文。通常,它是一個指向建立在連線之上的較高層級物件的指標,例如 HTTP 請求或 Stream 會話。 -
read
,write
— 連線的讀取和寫入事件。 -
recv
,send
,recv_chain
,send_chain
— 連線的 I/O 操作。 -
pool
— 連線池。 -
log
— 連線日誌。 -
sockaddr
,socklen
,addr_text
— 遠端套接字位址的二進制和文字形式。 -
local_sockaddr
,local_socklen
— 本機套接字位址的二進制形式。最初,這些欄位是空的。使用ngx_connection_local_sockaddr()
函式取得本機套接字位址。 -
proxy_protocol_addr
,proxy_protocol_port
- PROXY 協定用戶端位址和埠,如果為連線啟用 PROXY 協定的話。 -
ssl
— 連線的 SSL 上下文。 -
reusable
— 旗標,指示連線處於可重複使用的狀態。 -
close
— 旗標,指示連線正在重複使用且需要關閉。
nginx 連線可以透明地封裝 SSL 層。在這種情況下,連線的 ssl
欄位會保留指向 ngx_ssl_connection_t
結構的指標,其中保存了連線的所有 SSL 相關資料,包括 SSL_CTX
和 SSL
。recv
、send
、recv_chain
和 send_chain
處理常式也會設定為啟用 SSL 的函式。
nginx 設定中的 worker_connections
指令限制了每個 nginx 工作程序的最大連線數。所有連線結構都會在工作程序啟動時預先建立,並儲存在循環物件的 connections
欄位中。若要檢索連線結構,請使用 ngx_get_connection(s, log)
函式。它將套接字描述符作為 s
參數,此套接字描述符需要包裝在連線結構中。
由於每個工作程序的連線數有限,nginx 提供了一種方法來抓取目前正在使用的連線。若要啟用或停用連線的重複使用,請呼叫 ngx_reusable_connection(c, reusable)
函式。呼叫 ngx_reusable_connection(c, 1)
會在連線結構中設定 reuse
旗標,並將連線插入循環的 reusable_connections_queue
中。每當 ngx_get_connection()
發現循環的 free_connections
清單中沒有可用的連線時,它會呼叫 ngx_drain_connections()
來釋放特定數量的可重複使用連線。對於每個此類連線,都會設定 close
旗標,並呼叫其讀取處理常式,該處理常式應透過呼叫 ngx_close_connection(c)
來釋放連線,並使其可用於重複使用。若要退出連線可重複使用的狀態,請呼叫 ngx_reusable_connection(c, 0)
。HTTP 用戶端連線是 nginx 中可重複使用連線的範例;它們會被標記為可重複使用,直到收到來自用戶端的第一個請求位元組。
事件
事件
nginx 中的事件物件 ngx_event_t
提供了一種機制,用於通知特定事件已發生。
ngx_event_t
中的欄位包括:
-
data
— 事件處理常式中使用的任意事件上下文,通常是指向與事件相關的連線的指標。 -
handler
— 事件發生時要呼叫的回呼函式。 -
write
— 指示寫入事件的旗標。沒有此旗標則表示讀取事件。 -
active
— 旗標,指示事件已註冊接收 I/O 通知,通常來自epoll
、kqueue
、poll
等通知機制。 -
ready
— 旗標,指示事件已收到 I/O 通知。 -
delayed
— 旗標,指示由於速率限制,I/O 已延遲。 -
timer
— 用於將事件插入計時器樹的紅黑樹節點。 -
timer_set
— 旗標,指示事件計時器已設定且尚未過期。 -
timedout
— 旗標,指示事件計時器已過期。 -
eof
— 旗標,指示在讀取資料時發生 EOF。 -
pending_eof
— 旗標,指示套接字上正在等待 EOF,即使在它之前可能有一些資料可用。此旗標透過EPOLLRDHUP
epoll
事件或EV_EOF
kqueue
旗標傳遞。 -
error
— 旗標,指示在讀取(對於讀取事件)或寫入(對於寫入事件)期間發生錯誤。 -
cancelable
— 計時器事件旗標,指示在關閉工作程序時應忽略該事件。延遲正常關閉工作程序,直到沒有排程任何不可取消的計時器事件。 -
posted
— 旗標,指示事件已張貼到佇列。 -
queue
— 用於將事件張貼到佇列的佇列節點。
I/O 事件
每個透過呼叫 ngx_get_connection()
函式取得的連線都有兩個附加事件,c->read
和 c->write
,它們用於接收套接字已準備好讀取或寫入的通知。所有此類事件都以邊緣觸發模式運作,這表示它們僅在套接字的狀態變更時才會觸發通知。例如,對套接字進行部分讀取不會使 nginx 發出重複的讀取通知,直到更多資料到達套接字。即使底層 I/O 通知機制本質上是層級觸發(poll
、select
等),nginx 也會將通知轉換為邊緣觸發。為了使 nginx 事件通知在不同平台上的所有通知系統中保持一致,在處理 I/O 套接字通知或對該套接字呼叫任何 I/O 函式之後,必須呼叫函式 ngx_handle_read_event(rev, flags)
和 ngx_handle_write_event(wev, lowat)
。通常,這些函式會在每個讀取或寫入事件處理常式的末尾呼叫一次。
計時器事件
可以設定事件,以便在逾時到期時傳送通知。事件使用的計時器會計算自過去某個未指定時間點以來經過的毫秒數,截斷為 ngx_msec_t
類型。其目前值可以從 ngx_current_msec
變數取得。
函式 ngx_add_timer(ev, timer)
會設定事件的逾時,而 ngx_del_timer(ev)
會刪除先前設定的逾時。全域逾時紅黑樹 ngx_event_timer_rbtree
會儲存目前設定的所有逾時。樹中的鍵類型為 ngx_msec_t
,是事件發生的時間。樹狀結構可以快速執行插入和刪除操作,以及存取最接近的逾時,nginx 會使用這些逾時來找出等待 I/O 事件和逾時事件到期的時間長度。
張貼事件
可以張貼事件,這表示其處理常式將在目前事件迴圈迭代的稍後某個時間點呼叫。張貼事件是簡化程式碼和避免堆疊溢位的良好做法。已張貼的事件會保留在張貼佇列中。ngx_post_event(ev, q)
巨集會將事件 ev
張貼到張貼佇列 q
。ngx_delete_posted_event(ev)
巨集會從目前張貼的佇列中刪除事件 ev
。通常,事件會張貼到 ngx_posted_events
佇列,該佇列會在事件迴圈稍後處理,也就是在所有 I/O 和計時器事件都已處理之後。呼叫函式 ngx_event_process_posted()
來處理事件佇列。它會呼叫事件處理常式,直到佇列不為空。這表示已張貼的事件處理常式可以張貼更多事件,以便在目前的事件迴圈迭代中處理。
一個範例
void ngx_my_connection_read(ngx_connection_t *c) { ngx_event_t *rev; rev = c->read; ngx_add_timer(rev, 1000); rev->handler = ngx_my_read_handler; ngx_my_read(rev); } void ngx_my_read_handler(ngx_event_t *rev) { ssize_t n; ngx_connection_t *c; u_char buf[256]; if (rev->timedout) { /* timeout expired */ } c = rev->data; while (rev->ready) { n = c->recv(c, buf, sizeof(buf)); if (n == NGX_AGAIN) { break; } if (n == NGX_ERROR) { /* error */ } /* process buf */ } if (ngx_handle_read_event(rev, 0) != NGX_OK) { /* error */ } }
事件迴圈
除了 nginx 主程序之外,所有 nginx 程序都會執行 I/O,因此都有一個事件迴圈。(nginx 主程序反而大部分時間都花在 sigsuspend()
呼叫中,等待訊號到達。)nginx 事件迴圈是在 ngx_process_events_and_timers()
函式中實作的,此函式會重複呼叫,直到程序結束。
事件迴圈具有以下階段:
- 透過呼叫
ngx_event_find_timer()
找出最接近到期的逾時。此函式會在計時器樹中找到最左邊的節點,並傳回節點到期之前的毫秒數。 - 透過呼叫處理常式來處理 I/O 事件,此處理常式特定於 nginx 設定所選取的事件通知機制。此處理常式會等待至少一個 I/O 事件發生,但只會等待到下一個逾時到期。當發生讀取或寫入事件時,會設定
ready
旗標,並呼叫事件的處理常式。對於 Linux,通常會使用ngx_epoll_process_events()
處理常式,它會呼叫epoll_wait()
以等待 I/O 事件。 - 透過呼叫
ngx_event_expire_timers()
使計時器到期。計時器樹會從最左邊的元素迭代到右邊,直到找到未過期的逾時。對於每個過期的節點,都會設定timedout
事件旗標、重設timer_set
旗標,並呼叫事件處理常式。 - 透過呼叫
ngx_event_process_posted()
來處理已張貼的事件。此函式會重複從已張貼的事件佇列中移除第一個元素,並呼叫元素的處理常式,直到佇列為空。
所有 nginx 程序也會處理訊號。訊號處理常式只會設定在 ngx_process_events_and_timers()
呼叫之後檢查的全域變數。
程序
nginx 中有數種程序類型。程序的類型保留在全域變數 ngx_process
中,並且是下列其中之一:
-
NGX_PROCESS_MASTER
— 主程序,它會讀取 NGINX 設定、建立循環,並啟動和控制子程序。它不會執行任何 I/O,只會回應訊號。其循環函式為ngx_master_process_cycle()
。 -
NGX_PROCESS_WORKER
— 工作程序,它會處理用戶端連線。它由主程序啟動,並且也會回應其訊號和通道命令。其循環函式為ngx_worker_process_cycle()
。可以有多個工作程序,如worker_processes
指令所設定。 -
NGX_PROCESS_SINGLE
— 單一程序,僅在master_process off
模式下存在,並且是該模式下唯一執行的程序。它會建立循環(如同主程序所做),並處理用戶端連線(如同工作程序所做)。其循環函式為ngx_single_process_cycle()
。 -
NGX_PROCESS_HELPER
— 協助程序,目前有兩種:快取管理員和快取載入器。兩者的循環函式都是ngx_cache_manager_process_cycle()
。
nginx 程序會處理下列訊號:
-
NGX_SHUTDOWN_SIGNAL
(在大多數系統上為SIGQUIT
)— 正常關閉。收到此訊號時,主程序會向所有子程序傳送關閉訊號。當沒有子程序時,主程序會銷毀循環池並結束。當工作程序收到此訊號時,它會關閉所有監聽的 socket,並等待直到沒有安排無法取消的事件,然後銷毀循環池並結束。當快取管理器或快取載入器程序收到此訊號時,它會立即結束。當程序收到此訊號時,ngx_quit
變數會被設為1
,並在處理後立即重設。當工作程序處於關閉狀態時,ngx_exiting
變數會被設為1
。 -
NGX_TERMINATE_SIGNAL
(在大多數系統上為SIGTERM
)— 終止。收到此訊號時,主程序會向所有子程序傳送終止訊號。如果子程序在 1 秒內沒有結束,主程序會傳送SIGKILL
訊號來強制終止它。當沒有子程序時,主程序會銷毀循環池並結束。當工作程序、快取管理器程序或快取載入器程序收到此訊號時,它會銷毀循環池並結束。當收到此訊號時,變數ngx_terminate
會被設為1
。 -
NGX_NOACCEPT_SIGNAL
(在大多數系統上為SIGWINCH
)- 關閉所有工作程序和輔助程序。收到此訊號時,主程序會關閉其子程序。如果先前啟動的新 nginx 二進制檔案結束,舊主程序的子程序會再次啟動。當工作程序收到此訊號時,它會以debug_points
指令設定的除錯模式關閉。 -
NGX_RECONFIGURE_SIGNAL
(在大多數系統上為SIGHUP
)- 重新設定。收到此訊號時,主程序會重新讀取設定,並根據它建立新的循環。如果新的循環建立成功,舊的循環會被刪除,並啟動新的子程序。同時,舊的子程序會收到NGX_SHUTDOWN_SIGNAL
訊號。在單程序模式下,nginx 會建立新的循環,但會保留舊的循環,直到沒有客戶端與其有活動連線。工作程序和輔助程序會忽略此訊號。 -
NGX_REOPEN_SIGNAL
(在大多數系統上為SIGUSR1
)— 重新開啟檔案。主程序會將此訊號傳送給工作程序,工作程序會重新開啟所有與循環相關的open_files
。 -
NGX_CHANGEBIN_SIGNAL
(在大多數系統上為SIGUSR2
)— 變更 nginx 二進制檔案。主程序會啟動新的 nginx 二進制檔案,並傳入所有監聽 socket 的列表。以文字格式傳遞,在“NGINX”
環境變數中,由分號分隔的描述符號碼組成。新的 nginx 二進制檔案會讀取“NGINX”
變數,並將 socket 加入其初始化循環中。其他程序會忽略此訊號。
雖然所有 nginx 工作程序都能接收並正確處理 POSIX 訊號,但主程序不會使用標準的 kill()
系統呼叫將訊號傳遞給工作程序和輔助程序。相反地,nginx 使用程序間 socket 配對,允許在所有 nginx 程序之間傳送訊息。然而,目前訊息僅從主程序傳送到其子程序。訊息攜帶標準訊號。
執行緒
可以將會阻塞 nginx 工作程序的任務卸載到單獨的執行緒中。例如,可以設定 nginx 使用執行緒來執行檔案 I/O。另一個使用案例是沒有非同步介面的程式庫,因此無法正常與 nginx 一起使用。請記住,執行緒介面是現有非同步處理用戶端連線方法的輔助工具,絕非作為替代方案。
為了處理同步,可以使用以下 pthreads
基本元素的包裝器
-
typedef pthread_mutex_t ngx_thread_mutex_t;
-
ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
-
typedef pthread_cond_t ngx_thread_cond_t;
-
ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);
-
ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);
-
nginx 並非為每個任務建立新的執行緒,而是實作了 執行緒池 策略。可以為不同的目的設定多個執行緒池(例如,在不同的磁碟集上執行 I/O)。每個執行緒池在啟動時建立,並包含有限數量的執行緒來處理任務佇列。當任務完成時,會呼叫預定義的完成處理常式。
src/core/ngx_thread_pool.h
標頭檔包含相關定義
struct ngx_thread_task_s { ngx_thread_task_t *next; ngx_uint_t id; void *ctx; void (*handler)(void *data, ngx_log_t *log); ngx_event_t event; }; typedef struct ngx_thread_pool_s ngx_thread_pool_t; ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name); ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name); ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size); ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);
在設定時,想要使用執行緒的模組必須透過呼叫 ngx_thread_pool_add(cf, name)
來取得執行緒池的參考,這會建立一個具有指定 name
的新執行緒池,如果該名稱的池已存在,則會傳回該池的參考。
若要在執行時將 task
加入指定執行緒池 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 模組都位於一個單獨的目錄中,其中至少包含兩個檔案: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 shell 腳本,可以設定和存取以下變數
-
ngx_module_type
— 要建置的模組類型。可能的值為CORE
、HTTP
、HTTP_FILTER
、HTTP_INIT_FILTER
、HTTP_AUX_FILTER
、MAIL
、STREAM
或MISC
。 -
ngx_module_name
— 模組名稱。若要從一組原始碼檔案建置多個模組,請指定以空格分隔的名稱列表。第一個名稱表示動態模組的輸出二進制檔案的名稱。列表中的名稱必須與原始碼中使用的名稱相符。 -
ngx_addon_name
— 模組在設定腳本的控制台輸出中顯示的名稱。 -
ngx_module_srcs
— 用於編譯模組的原始碼檔案的空格分隔列表。可以使用$ngx_addon_dir
變數來表示模組目錄的路徑。 -
ngx_module_incs
— 建置模組所需的包含路徑 -
ngx_module_deps
— 模組的依賴關係的空格分隔列表。通常,它是標頭檔的列表。 -
ngx_module_libs
— 要與模組連結的程式庫的空格分隔列表。例如,使用ngx_module_libs=-lpthread
來連結libpthread
程式庫。可以使用以下巨集來連結與 nginx 相同的程式庫:LIBXSLT
、LIBGD
、GEOIP
、PCRE
、OPENSSL
、MD5
、SHA1
、ZLIB
和PERL
。 -
ngx_module_link
— 建置系統為動態模組設定為DYNAMIC
的變數,或是為靜態模組設定為ADDON
,用於判斷根據連結類型要執行的不同動作。 -
ngx_module_order
— 模組的載入順序;對於HTTP_FILTER
和HTTP_AUX_FILTER
模組類型很有用。此選項的格式是一個空格分隔的模組列表。列表中當前模組名稱後面的所有模組都會在模組的全域列表中排在其後,這會設定模組初始化的順序。對於篩選模組,稍後初始化表示較早執行。以下模組通常用作參考。
ngx_http_copy_filter_module
會讀取其他篩選模組的資料,並放置在列表的底部附近,以便它是最早執行的模組之一。ngx_http_write_filter_module
會將資料寫入用戶端 socket,並放置在列表的頂部附近,而且是最後執行的模組。預設情況下,篩選模組會放置在模組列表中的
ngx_http_copy_filter
之前,以便在複製篩選器處理常式之後執行篩選器處理常式。對於其他模組類型,預設值為空字串。
若要將模組靜態編譯到 nginx 中,請使用設定腳本的 --add-module=/path/to/module
參數。若要編譯模組以便稍後動態載入到 nginx 中,請使用 --add-dynamic-module=/path/to/module
參數。
核心模組
模組是 nginx 的建構區塊,其大部分功能都以模組的形式實作。模組原始碼檔案必須包含 ngx_module_t
類型的全域變數,其定義如下
struct ngx_module_s { /* private part is omitted */ void *ctx; ngx_command_t *commands; ngx_uint_t type; ngx_int_t (*init_master)(ngx_log_t *log); ngx_int_t (*init_module)(ngx_cycle_t *cycle); ngx_int_t (*init_process)(ngx_cycle_t *cycle); ngx_int_t (*init_thread)(ngx_cycle_t *cycle); void (*exit_thread)(ngx_cycle_t *cycle); void (*exit_process)(ngx_cycle_t *cycle); void (*exit_master)(ngx_cycle_t *cycle); /* stubs for future extensions are omitted */ };
省略的私有部分包含模組版本和簽名,並使用預定義的巨集 NGX_MODULE_V1
填寫。
每個模組都會將其私有資料保留在 ctx
欄位中,識別在 commands
陣列中指定的設定指令,並可以在 nginx 生命週期的特定階段呼叫。模組生命週期包含以下事件
- 當設定指令出現在設定檔案中時,會以主程序的內容呼叫設定指令處理常式。
- 成功剖析設定後,會在主程序的內容中呼叫
init_module
處理常式。每次載入設定時,都會在主程序中呼叫init_module
處理常式。 - 主程序會建立一個或多個工作程序,並在每個程序中呼叫
init_process
處理常式。 - 當工作程序收到來自主程序的關閉或終止命令時,它會呼叫
exit_process
處理常式。 - 主程序會在結束之前呼叫
exit_master
處理常式。
由於執行緒在 nginx 中僅作為具有其自己 API 的輔助 I/O 工具使用,因此目前不會呼叫 init_thread
和 exit_thread
處理常式。也沒有 init_master
處理常式,因為這會是不必要的負擔。
模組 type
精確定義 ctx
欄位中儲存的內容。其值為下列類型之一
NGX_CORE_MODULE
NGX_EVENT_MODULE
NGX_HTTP_MODULE
NGX_MAIL_MODULE
NGX_STREAM_MODULE
NGX_CORE_MODULE
是最基本的,因此也是最通用的和最低層級的模組類型。其他模組類型是在其之上實作的,並提供更方便的方式來處理相應的網域,例如處理事件或 HTTP 請求。
核心模組的集合包括 ngx_core_module
、ngx_errlog_module
、ngx_regex_module
、ngx_thread_pool_module
和 ngx_openssl_module
模組。HTTP 模組、串流模組、郵件模組和事件模組也是核心模組。核心模組的內容定義如下
typedef struct { ngx_str_t name; void *(*create_conf)(ngx_cycle_t *cycle); char *(*init_conf)(ngx_cycle_t *cycle, void *conf); } ngx_core_module_t;
其中 name
是一個模組名稱字串,create_conf
和 init_conf
是指向函數的指標,分別用於建立和初始化模組配置。對於核心模組,nginx 會在解析新配置之前呼叫 create_conf
,並在所有配置解析成功後呼叫 init_conf
。典型的 create_conf
函數會為配置分配記憶體並設定預設值。
例如,一個簡單的模組稱為 ngx_foo_module
可能會像這樣:
/* * Copyright (C) Author. */ #include <ngx_config.h> #include <ngx_core.h> typedef struct { ngx_flag_t enable; } ngx_foo_conf_t; static void *ngx_foo_create_conf(ngx_cycle_t *cycle); static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf); static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_foo_enable_post = { ngx_foo_enable }; static ngx_command_t ngx_foo_commands[] = { { ngx_string("foo_enabled"), NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, 0, offsetof(ngx_foo_conf_t, enable), &ngx_foo_enable_post }, ngx_null_command }; static ngx_core_module_t ngx_foo_module_ctx = { ngx_string("foo"), ngx_foo_create_conf, ngx_foo_init_conf }; ngx_module_t ngx_foo_module = { NGX_MODULE_V1, &ngx_foo_module_ctx, /* module context */ ngx_foo_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void * ngx_foo_create_conf(ngx_cycle_t *cycle) { ngx_foo_conf_t *fcf; fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t)); if (fcf == NULL) { return NULL; } fcf->enable = NGX_CONF_UNSET; return fcf; } static char * ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_foo_conf_t *fcf = conf; ngx_conf_init_value(fcf->enable, 0); return NGX_CONF_OK; } static char * ngx_foo_enable(ngx_conf_t *cf, void *post, void *data) { ngx_flag_t *fp = data; if (*fp == 0) { return NGX_CONF_OK; } ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled"); return NGX_CONF_OK; }
組態指令
ngx_command_t
型別定義單一的配置指令。每個支援配置的模組都會提供一個此類結構的陣列,描述如何處理參數以及要呼叫哪些處理常式。
typedef struct ngx_command_s ngx_command_t; struct ngx_command_s { ngx_str_t name; ngx_uint_t type; char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); ngx_uint_t conf; ngx_uint_t offset; void *post; };
使用特殊值 ngx_null_command
終止陣列。name
是指令在設定檔中顯示的名稱,例如 "worker_processes" 或 "listen"。type
是旗標的位元欄位,指定指令接受的參數數量、其類型以及它出現的上下文。這些旗標包括:
-
NGX_CONF_NOARGS
— 指令不接受任何參數。 -
NGX_CONF_1MORE
— 指令接受一個或多個參數。 -
NGX_CONF_2MORE
— 指令接受兩個或多個參數。 -
NGX_CONF_TAKE1
..NGX_CONF_TAKE7
— 指令恰好接受指定數量的參數。 -
NGX_CONF_TAKE12
,NGX_CONF_TAKE13
,NGX_CONF_TAKE23
,NGX_CONF_TAKE123
,NGX_CONF_TAKE1234
— 指令可能接受不同數量的參數。選項限制為給定的數字。例如,NGX_CONF_TAKE12
表示它接受一個或兩個參數。
指令類型的旗標包括:
-
NGX_CONF_BLOCK
— 指令是一個區塊,也就是說,它可以在其開頭和結尾的大括號內包含其他指令,甚至可以實作自己的解析器來處理裡面的內容。 -
NGX_CONF_FLAG
— 指令接受布林值,on
或off
。
指令的上下文定義了它可能出現在配置中的位置。
-
NGX_MAIN_CONF
— 在頂層上下文。 -
NGX_HTTP_MAIN_CONF
— 在http
區塊中。 -
NGX_HTTP_SRV_CONF
— 在http
區塊中的server
區塊中。 -
NGX_HTTP_LOC_CONF
— 在http
區塊中的location
區塊中。 -
NGX_HTTP_UPS_CONF
— 在http
區塊中的upstream
區塊中。 -
NGX_HTTP_SIF_CONF
— 在http
區塊中的server
區塊內的if
區塊中。 -
NGX_HTTP_LIF_CONF
— 在http
區塊中的location
區塊內的if
區塊中。 -
NGX_HTTP_LMT_CONF
— 在http
區塊中的limit_except
區塊中。 -
NGX_STREAM_MAIN_CONF
— 在stream
區塊中。 -
NGX_STREAM_SRV_CONF
— 在stream
區塊中的server
區塊中。 -
NGX_STREAM_UPS_CONF
— 在stream
區塊中的upstream
區塊中。 -
NGX_MAIL_MAIN_CONF
— 在mail
區塊中。 -
NGX_MAIL_SRV_CONF
— 在mail
區塊中的server
區塊中。 -
NGX_EVENT_CONF
— 在event
區塊中。 -
NGX_DIRECT_CONF
— 由不建立上下文階層且只有一個全域配置的模組使用。此配置會作為conf
參數傳遞給處理常式。
配置解析器會使用這些旗標,在指令位置不正確時拋出錯誤,並呼叫提供正確配置指標的指令處理常式,以便不同位置的相同指令可以將它們的值儲存在不同的位置。
set
欄位定義一個處理指令並將解析的值儲存到相應配置中的處理常式。有一些函數可以執行常見的轉換:
-
ngx_conf_set_flag_slot
— 將字串文字on
和off
轉換為ngx_flag_t
值,值分別為 1 或 0。 -
ngx_conf_set_str_slot
— 將字串儲存為ngx_str_t
類型的值。 -
ngx_conf_set_str_array_slot
— 將值附加到字串ngx_str_t
的陣列ngx_array_t
。如果陣列不存在,則會建立該陣列。 -
ngx_conf_set_keyval_slot
— 將鍵值對附加到鍵值對ngx_keyval_t
的陣列ngx_array_t
。第一個字串成為鍵,第二個字串成為值。如果陣列不存在,則會建立該陣列。 -
ngx_conf_set_num_slot
— 將指令的參數轉換為ngx_int_t
值。 -
ngx_conf_set_size_slot
— 將大小轉換為以位元組表示的size_t
值。 -
ngx_conf_set_off_slot
— 將偏移量轉換為以位元組表示的off_t
值。 -
ngx_conf_set_msec_slot
— 將時間轉換為以毫秒表示的ngx_msec_t
值。 -
ngx_conf_set_sec_slot
— 將時間轉換為以秒表示的time_t
值。 -
ngx_conf_set_bufs_slot
— 將提供的兩個參數轉換為ngx_bufs_t
物件,該物件保存緩衝區的數量和大小。 -
ngx_conf_set_enum_slot
— 將提供的參數轉換為ngx_uint_t
值。在post
欄位中傳遞的以 null 終止的ngx_conf_enum_t
陣列定義了可接受的字串和對應的整數值。 -
ngx_conf_set_bitmask_slot
— 將提供的參數轉換為ngx_uint_t
值。每個參數的遮罩值會進行 OR 運算以產生結果。在post
欄位中傳遞的以 null 終止的ngx_conf_bitmask_t
陣列定義了可接受的字串和對應的遮罩值。 -
set_path_slot
— 將提供的參數轉換為ngx_path_t
值並執行所有必需的初始化。如需詳細資訊,請參閱 proxy_temp_path 指令的文件。 -
set_access_slot
— 將提供的參數轉換為檔案權限遮罩。如需詳細資訊,請參閱 proxy_store_access 指令的文件。
conf
欄位定義將哪個配置結構傳遞給目錄處理常式。核心模組只有全域配置,並設定 NGX_DIRECT_CONF
旗標以存取它。像 HTTP、Stream 或 Mail 這樣的模組會建立配置的階層。例如,會為 server
、location
和 if
範圍建立模組的配置。
-
NGX_HTTP_MAIN_CONF_OFFSET
—http
區塊的配置。 -
NGX_HTTP_SRV_CONF_OFFSET
—http
區塊中server
區塊的配置。 -
NGX_HTTP_LOC_CONF_OFFSET
—http
區塊中location
區塊的配置。 -
NGX_STREAM_MAIN_CONF_OFFSET
—stream
區塊的配置。 -
NGX_STREAM_SRV_CONF_OFFSET
—stream
區塊中server
區塊的配置。 -
NGX_MAIL_MAIN_CONF_OFFSET
—mail
區塊的配置。 -
NGX_MAIL_SRV_CONF_OFFSET
—mail
區塊中server
區塊的配置。
offset
定義模組配置結構中欄位的偏移量,該欄位保存此特定指令的值。典型的用法是使用 offsetof()
巨集。
post
欄位有兩個用途:它可以用於定義在主要處理常式完成後要呼叫的處理常式,或將其他資料傳遞給主要處理常式。在第一種情況下,需要使用指向處理常式的指標來初始化 ngx_conf_post_t
結構,例如:
static char *ngx_do_foo(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_foo_post = { ngx_do_foo };
post
參數是 ngx_conf_post_t
物件本身,而 data
是指向該值的指標,該值由主處理常式使用適當的類型從參數轉換而來。
HTTP
連線
每個 HTTP 用戶端連線都會經過以下階段:
-
ngx_event_accept()
接受用戶端 TCP 連線。此處理常式會在接聽 socket 上收到讀取通知時呼叫。此階段會建立一個新的ngx_connection_t
物件來封裝新接受的用戶端 socket。每個 nginx 接聽器都會提供一個處理常式來將新的連線物件傳遞給它。對於 HTTP 連線,它是ngx_http_init_connection(c)
。 -
ngx_http_init_connection()
會執行 HTTP 連線的早期初始化。在此階段,會為連線建立一個ngx_http_connection_t
物件,其參考會儲存在連線的data
欄位中。稍後它將被 HTTP 要求物件取代。此階段也會啟動 PROXY 通訊協定解析器和 SSL 交握。 -
當用戶端 socket 上有資料可用時,會呼叫
ngx_http_wait_request_handler()
讀取事件處理常式。在此階段,會建立一個 HTTP 要求物件ngx_http_request_t
並將其設定為連線的data
欄位。 -
ngx_http_process_request_line()
讀取事件處理常式會讀取用戶端要求行。此處理常式由ngx_http_wait_request_handler()
設定。資料會讀入連線的buffer
。緩衝區的大小最初由 client_header_buffer_size 指令設定。整個用戶端標頭應該適合緩衝區。如果初始大小不足,則會分配一個更大的緩衝區,其容量由large_client_header_buffers
指令設定。 -
ngx_http_process_request_headers()
讀取事件處理常式會在ngx_http_process_request_line()
之後設定,以讀取用戶端要求標頭。 -
當要求標頭完全讀取和解析後,會呼叫
ngx_http_core_run_phases()
。此函數會執行從NGX_HTTP_POST_READ_PHASE
到NGX_HTTP_CONTENT_PHASE
的要求階段。最後一個階段旨在產生回應並將其沿著篩選器鏈傳遞。回應不一定在此階段傳送給用戶端。它可能會保持緩衝並在最終確定階段傳送。 -
ngx_http_finalize_request()
通常在要求產生所有輸出或產生錯誤時呼叫。在後一種情況下,會查閱適當的錯誤頁面並將其用作回應。如果此時回應尚未完全傳送給用戶端,則會啟動 HTTP 寫入器ngx_http_writer()
以完成傳送未處理的資料。 -
當完整的回應已傳送給用戶端且可以銷毀要求時,會呼叫
ngx_http_finalize_connection()
。如果已啟用用戶端連線保持啟用功能,則會呼叫ngx_http_set_keepalive()
,這會銷毀目前的要求並等待連線上的下一個要求。否則,ngx_http_close_request()
會銷毀要求和連線。
請求
對於每個用戶端 HTTP 要求,都會建立 ngx_http_request_t
物件。此物件的一些欄位包括:
-
connection
— 指向ngx_connection_t
用戶端連線物件的指標。多個要求可以同時參考同一個連線物件 - 一個主要要求及其子要求。刪除要求後,可以在同一連線上建立新的要求。請注意,對於 HTTP 連線,
ngx_connection_t
的data
欄位會指向該請求。這些請求被稱為「活動」請求,與繫結於該連線的其他請求相對。活動請求用於處理客戶端連線事件,並允許將其回應輸出到客戶端。通常,每個請求都會在某個時間點變成活動狀態,以便可以發送其輸出。 -
ctx
— HTTP 模組內容的陣列。類型為NGX_HTTP_MODULE
的每個模組都可以在請求中儲存任何值(通常是指向結構的指標)。該值會儲存在模組的ctx_index
位置的ctx
陣列中。以下巨集提供了一種方便的方式來取得和設定請求內容-
ngx_http_get_module_ctx(r, module)
— 傳回module
的內容 -
ngx_http_set_ctx(r, c, module)
— 將c
設定為module
的內容
-
-
main_conf
、srv_conf
、loc_conf
— 目前請求設定的陣列。設定儲存在模組的ctx_index
位置。 -
read_event_handler
、write_event_handler
- 請求的讀取和寫入事件處理常式。通常,HTTP 連線的讀取和寫入事件處理常式都會設定為ngx_http_request_handler()
。此函式會呼叫目前活動請求的read_event_handler
和write_event_handler
處理常式。 -
cache
— 用於快取上游回應的請求快取物件。 -
upstream
— 用於代理的請求上游物件。 -
pool
— 請求池。請求物件本身會在此池中配置,並在請求刪除時銷毀。對於需要在客戶端連線生命週期內可用的配置,請改用ngx_connection_t
的池。 -
header_in
— 用於讀取客戶端 HTTP 請求標頭的緩衝區。 -
headers_in
、headers_out
— 輸入和輸出 HTTP 標頭物件。這兩個物件都包含ngx_list_t
類型的headers
欄位,用於保存標頭的原始清單。除此之外,還可以取得和設定特定標頭作為個別欄位,例如content_length_n
、status
等。 -
request_body
— 客戶端請求主體物件。 -
start_sec
、start_msec
— 請求建立的時間點,用於追蹤請求持續時間。 -
method
、method_name
— 客戶端 HTTP 請求方法的數字和文字表示法。方法的數值定義在src/http/ngx_http_request.h
中,巨集為NGX_HTTP_GET
、NGX_HTTP_HEAD
、NGX_HTTP_POST
等。 -
http_protocol
— 客戶端 HTTP 通訊協定版本,以原始文字格式表示(「HTTP/1.0」、「HTTP/1.1」等)。 -
http_version
— 客戶端 HTTP 通訊協定版本,以數值格式表示 (NGX_HTTP_VERSION_10
、NGX_HTTP_VERSION_11
等)。 -
http_major
、http_minor
— 客戶端 HTTP 通訊協定版本,以數值格式表示,並分成主要和次要部分。 -
request_line
、unparsed_uri
— 原始客戶端請求中的請求行和 URI。 -
uri
、args
、exten
— 目前請求的 URI、引數和檔案副檔名。此處的 URI 值可能與客戶端傳送的原始 URI 不同,因為已進行標準化。在整個請求處理過程中,這些值會隨著執行內部重新導向而變更。 -
main
— 指向主要請求物件的指標。此物件用於處理客戶端 HTTP 請求,而非子請求,後者是為了在主要請求中執行特定子任務而建立的。 -
parent
— 指向子請求的父請求的指標。 -
postponed
— 輸出緩衝區和子請求的清單,按照它們的傳送和建立順序排列。延後篩選器會使用此清單,以便在子請求建立部分輸出時,提供一致的請求輸出。 -
post_subrequest
— 指向處理常式的指標,其中包含在子請求完成時要呼叫的內容。主要請求未使用。 -
posted_requests
— 要開始或恢復的請求清單,透過呼叫請求的write_event_handler
來完成。通常,此處理常式會保留請求主要函式,該函式首先會執行請求階段,然後產生輸出。請求通常會透過呼叫
ngx_http_post_request(r, NULL)
來發佈。它總是會發佈到主要請求的posted_requests
清單。函式ngx_http_run_posted_requests(c)
會執行在傳遞連線的活動請求的主要請求中發佈的所有請求。所有事件處理常式都會呼叫ngx_http_run_posted_requests
,這可能會導致新的發佈請求。通常,它會在叫用請求的讀取或寫入處理常式之後呼叫。 -
phase_handler
— 目前請求階段的索引。 -
ncaptures
、captures
、captures_data
— 請求上次正規表示式比對產生的正規表示式擷取。正規表示式比對可能會在請求處理期間的多個位置發生:對應查閱、依 SNI 或 HTTP Host 的伺服器查閱、重寫、proxy_redirect 等。查閱產生的擷取會儲存在上述欄位中。欄位ncaptures
保存擷取的數量,captures
保存擷取的界限,而captures_data
保存比對正規表示式的字串,並用於擷取擷取。在每次新的正規表示式比對之後,請求擷取會重設以保存新值。 -
count
— 請求參考計數器。此欄位僅對主要請求有意義。遞增計數器是透過簡單的r->main->count++
來完成的。若要遞減計數器,請呼叫ngx_http_finalize_request(r, rc)
。建立子請求和執行請求主體讀取程序都會遞增計數器。 -
subrequests
— 目前子請求的巢狀層級。每個子請求都會繼承其父請求的巢狀層級,並減 1。如果該值達到零,則會產生錯誤。主要請求的值由NGX_HTTP_MAX_SUBREQUESTS
常數定義。 -
uri_changes
— 請求剩餘的 URI 變更次數。請求可以變更其 URI 的總次數受到NGX_HTTP_MAX_URI_CHANGES
常數的限制。每次變更時,該值都會遞減,直到達到零為止,屆時會產生錯誤。重寫和內部重新導向到一般或具名位置會被視為 URI 變更。 -
blocked
— 請求持有的區塊計數器。當此值不為零時,請求無法終止。目前,此值會由擱置的 AIO 作業(POSIX AIO 和執行緒作業)和活動快取鎖定遞增。 -
buffered
— 位元遮罩,顯示哪些模組已緩衝請求產生的輸出。許多篩選器可以緩衝輸出;例如,sub_filter 可能會因為部分字串比對而緩衝資料,copy 篩選器可能會因為缺少可用的輸出緩衝區而緩衝資料等等。只要此值不為零,請求就無法在刷新之前完成。 -
header_only
— 指示輸出不需要主體的旗標。例如,此旗標由 HTTP HEAD 請求使用。 -
keepalive
— 指示是否支援客戶端連線保持活動的旗標。該值是從 HTTP 版本和「Connection」標頭的值推斷而來。 -
header_sent
— 指示請求已傳送輸出標頭的旗標。 -
internal
— 指示目前請求為內部的旗標。若要進入內部狀態,請求必須通過內部重新導向或成為子請求。內部請求允許進入內部位置。 -
allow_ranges
— 指示可以將部分回應傳送至客戶端的旗標,如 HTTP Range 標頭所要求。 -
subrequest_ranges
— 指示在處理子請求時可以傳送部分回應的旗標。 -
single_range
— 指示只能將單一連續範圍的輸出資料傳送至客戶端的旗標。此旗標通常在傳送資料流時設定,例如從代理伺服器傳送,且整個回應無法在一個緩衝區中取得。 -
main_filter_need_in_memory
、filter_need_in_memory
— 旗標要求在記憶體緩衝區而非檔案中產生輸出。這是傳送給複製篩選器的訊號,即使啟用 sendfile,也從檔案緩衝區讀取資料。兩個旗標之間的差異在於設定它們的篩選器模組的位置。在篩選器鏈結中延後篩選器之前呼叫的篩選器會設定filter_need_in_memory
,要求只有目前請求輸出才進入記憶體緩衝區。在篩選器鏈結中稍後呼叫的篩選器會設定main_filter_need_in_memory
,要求主要請求和所有子請求在傳送輸出時從記憶體讀取檔案。 -
filter_need_temporary
— 旗標要求在暫存緩衝區中產生請求輸出,而不是在唯讀記憶體緩衝區或檔案緩衝區中產生。這由可能會直接在傳送的緩衝區中變更輸出的篩選器使用。
組態
每個 HTTP 模組都可以有三種類型的設定
- 主要設定 — 適用於整個
http
區塊。作為模組的全域設定。 - 伺服器設定 — 適用於單一
server
區塊。作為模組的伺服器特定設定。 - 位置設定 — 適用於單一
location
、if
或limit_except
區塊。作為模組的位置特定設定。
設定結構會在 nginx 設定階段透過呼叫函式來建立,這些函式會配置結構、初始化它們並合併它們。以下範例顯示如何為模組建立簡單的位置設定。該設定具有一個類型為不帶正負號的整數的設定 foo
。
typedef struct { ngx_uint_t foo; } ngx_http_foo_loc_conf_t; static ngx_http_module_t ngx_http_foo_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_foo_create_loc_conf, /* create location configuration */ ngx_http_foo_merge_loc_conf /* merge location configuration */ }; static void * ngx_http_foo_create_loc_conf(ngx_conf_t *cf) { ngx_http_foo_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_foo_loc_conf_t)); if (conf == NULL) { return NULL; } conf->foo = NGX_CONF_UNSET_UINT; return conf; } static char * ngx_http_foo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_foo_loc_conf_t *prev = parent; ngx_http_foo_loc_conf_t *conf = child; ngx_conf_merge_uint_value(conf->foo, prev->foo, 1); }
如範例所示,ngx_http_foo_create_loc_conf()
函式會建立新的設定結構,而 ngx_http_foo_merge_loc_conf()
會將設定與較高層級的設定合併。事實上,伺服器和位置設定不僅存在於伺服器和位置層級,而且也會為其上的所有層級建立。具體來說,伺服器設定也會在主要層級建立,而位置設定會在主要、伺服器和位置層級建立。這些設定可以讓您在 nginx 設定檔的任何層級指定伺服器和位置特定的設定。最終設定會向下合併。提供了一些巨集,例如 NGX_CONF_UNSET
和 NGX_CONF_UNSET_UINT
,用於指示遺失的設定,並在合併時忽略它。標準 nginx 合併巨集,例如 ngx_conf_merge_value()
和 ngx_conf_merge_uint_value()
,提供了一種方便的方式來合併設定,並在沒有任何設定提供明確值時設定預設值。如需不同類型巨集的完整清單,請參閱 src/core/ngx_conf_file.h
。
可以使用以下巨集。用於在設定時存取 HTTP 模組的設定。它們都採用 ngx_conf_t
參考作為第一個引數。
-
ngx_http_conf_get_module_main_conf(cf, module)
-
ngx_http_conf_get_module_srv_conf(cf, module)
-
ngx_http_conf_get_module_loc_conf(cf, module)
以下範例會取得標準 nginx 核心模組 ngx_http_core_module 的位置設定指標,並取代結構的 handler
欄位中保留的位置內容處理常式。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r); static ngx_command_t ngx_http_foo_commands[] = { { ngx_string("foo"), NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, ngx_http_foo, 0, 0, NULL }, ngx_null_command }; static char * ngx_http_foo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_bar_handler; return NGX_CONF_OK; }
可以使用以下巨集來存取執行階段 HTTP 模組的設定。
-
ngx_http_get_module_main_conf(r, module)
-
ngx_http_get_module_srv_conf(r, module)
-
ngx_http_get_module_loc_conf(r, module)
這些巨集會接收對 HTTP 請求 ngx_http_request_t
的參考。請求的主要設定永遠不會變更。在選擇請求的虛擬伺服器之後,伺服器設定可以從預設值變更。為處理請求而選取的位置設定可能會因為重寫操作或內部重新導向而變更多次。以下範例顯示如何在執行階段存取模組的 HTTP 設定。
static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_http_foo_loc_conf_t *flcf; flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module); ... }
階段
每個 HTTP 請求都會經歷一系列階段。在每個階段,都會對請求執行不同的處理類型。模組特定的處理常式可以在大多數階段中註冊,許多標準 nginx 模組會註冊它們的階段處理常式,作為在請求處理的特定階段被呼叫的方式。階段會依序處理,並且一旦請求到達該階段,就會呼叫階段處理常式。以下是 nginx HTTP 階段的清單。
-
NGX_HTTP_POST_READ_PHASE
— 第一階段。 ngx_http_realip_module 會在此階段註冊其處理常式,以便在叫用任何其他模組之前啟用客戶端位址的替代。 -
NGX_HTTP_SERVER_REWRITE_PHASE
— 處理定義在server
區塊(但在location
區塊之外)的重寫指令的階段。ngx_http_rewrite_module 模組在此階段安裝其處理常式。 -
NGX_HTTP_FIND_CONFIG_PHASE
— 根據請求 URI 選擇 location 的特殊階段。在此階段之前,會將相關虛擬伺服器的預設 location 指派給請求,並且任何請求 location 配置的模組都會收到預設伺服器 location 的配置。此階段會為請求指派新的 location。在此階段無法註冊額外的處理常式。 -
NGX_HTTP_REWRITE_PHASE
— 與NGX_HTTP_SERVER_REWRITE_PHASE
相同,但用於定義在先前階段選定的 location 中的重寫規則。 -
NGX_HTTP_POST_REWRITE_PHASE
— 特殊階段,如果請求的 URI 在重寫期間發生變更,則會將請求重新導向至新的 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
— 驗證用戶端是否有權發出請求的階段。標準 nginx 模組,例如 ngx_http_access_module 和 ngx_http_auth_basic_module 在此階段註冊其處理常式。預設情況下,用戶端必須通過在此階段註冊的所有處理常式的授權檢查,請求才能繼續到下一個階段。可以使用 satisfy 指令,允許在任何階段處理常式授權用戶端時繼續處理。 -
NGX_HTTP_POST_ACCESS_PHASE
— 處理 satisfy any 指令的特殊階段。如果某些存取階段處理常式拒絕存取,且沒有任何明確允許存取,則會完成請求。在此階段無法註冊額外的處理常式。 -
NGX_HTTP_PRECONTENT_PHASE
— 在產生內容之前呼叫處理常式的階段。標準模組,例如 ngx_http_try_files_module 和 ngx_http_mirror_module 在此階段註冊其處理常式。 -
NGX_HTTP_CONTENT_PHASE
— 通常在此階段產生回應。多個 nginx 標準模組在此階段註冊其處理常式,包括 ngx_http_index_module 或ngx_http_static_module
。它們會依序呼叫,直到其中一個產生輸出為止。也可以在每個 location 的基礎上設定內容處理常式。如果 ngx_http_core_module 的 location 配置設定了handler
,則會將其呼叫為內容處理常式,而忽略在此階段安裝的處理常式。 -
NGX_HTTP_LOG_PHASE
— 執行請求記錄的階段。目前,只有 ngx_http_log_module 在此階段註冊其處理常式以進行存取記錄。記錄階段處理常式會在請求處理的最後,就在釋放請求之前呼叫。
以下是 preaccess 階段處理常式的範例。
static ngx_http_module_t ngx_http_foo_module_ctx = { NULL, /* preconfiguration */ ngx_http_foo_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r) { ngx_table_elt_t *ua; ua = r->headers_in.user_agent; if (ua == NULL) { return NGX_DECLINED; } /* reject requests with "User-Agent: foo" */ if (ua->value.len == 3 && ngx_strncmp(ua->value.data, "foo", 3) == 0) { return NGX_HTTP_FORBIDDEN; } return NGX_DECLINED; } static ngx_int_t ngx_http_foo_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } *h = ngx_http_foo_handler; return NGX_OK; }
階段處理常式預期會傳回特定的程式碼
-
NGX_OK
— 繼續到下一個階段。 -
NGX_DECLINED
— 繼續到目前階段的下一個處理常式。如果目前的處理常式是目前階段的最後一個,則移至下一個階段。 -
NGX_AGAIN
、NGX_DONE
— 暫停階段處理,直到將來發生某些事件,例如非同步 I/O 操作或僅是延遲。假設之後會透過呼叫ngx_http_core_run_phases()
來恢復階段處理。 - 階段處理常式傳回的任何其他值都會被視為請求完成程式碼,特別是 HTTP 回應程式碼。請求會以提供的程式碼完成。
對於某些階段,傳回程式碼的處理方式略有不同。在內容階段,除了 NGX_DECLINED
之外的任何傳回程式碼都視為完成程式碼。來自 location 內容處理常式的任何傳回程式碼都視為完成程式碼。在存取階段,在 satisfy any 模式中,除了 NGX_OK
、NGX_DECLINED
、NGX_AGAIN
、NGX_DONE
之外的任何傳回程式碼都視為拒絕。如果沒有後續的存取處理常式允許或拒絕具有不同程式碼的存取,則拒絕程式碼將成為完成程式碼。
變數
存取現有變數
變數可以透過索引(這是最常用的方法)或名稱(請參閱下方)來參照。索引是在組態階段建立的,當變數新增至組態時。若要取得變數索引,請使用 ngx_http_get_variable_index()
ngx_str_t name; /* ngx_string("foo") */ ngx_int_t index; index = ngx_http_get_variable_index(cf, &name);
此處,cf
是指向 nginx 組態的指標,而 name
指向包含變數名稱的字串。該函式在錯誤時傳回 NGX_ERROR
,否則傳回有效索引,通常會將其儲存在模組的組態中的某處以供將來使用。
所有 HTTP 變數都會在給定的 HTTP 請求的內容中評估,結果特定於該 HTTP 請求並快取在其中。所有評估變數的函式都會傳回 ngx_http_variable_value_t
類型,表示變數值
typedef ngx_variable_value_t ngx_http_variable_value_t; typedef struct { unsigned len:28; unsigned valid:1; unsigned no_cacheable:1; unsigned not_found:1; unsigned escape:1; u_char *data; } ngx_variable_value_t;
其中
-
len
— 值的長度 -
data
— 值本身 -
valid
— 值有效 -
not_found
— 未找到變數,因此data
和len
欄位不相關;例如,當請求中未傳遞對應的引數時,變數(例如$arg_foo
)可能會發生這種情況 -
no_cacheable
— 請勿快取結果 -
escape
— 由記錄模組在內部使用,以標記輸出時需要逸出的值。
ngx_http_get_flushed_variable()
和 ngx_http_get_indexed_variable()
函式用於取得變數的值。它們具有相同的介面 - 接受 HTTP 請求 r
作為評估變數的內容和識別它的 index
。典型用法的範例
ngx_http_variable_value_t *v; v = ngx_http_get_flushed_variable(r, index); if (v == NULL || v->not_found) { /* we failed to get value or there is no such variable, handle it */ return NGX_ERROR; } /* some meaningful value is found */
函式之間的差異在於 ngx_http_get_indexed_variable()
傳回快取值,而 ngx_http_get_flushed_variable()
則會清除不可快取變數的快取。
某些模組(例如 SSI 和 Perl)需要處理在組態時名稱未知的變數。因此,無法使用索引來存取它們,但可以使用 ngx_http_get_variable(r, name, key)
函式。它會搜尋具有給定 name
和其雜湊值 key
(從名稱衍生)的變數。
建立變數
若要建立變數,請使用 ngx_http_add_variable()
函式。它會將組態(註冊變數的位置)、變數名稱和控制函式行為的旗標作為引數
NGX_HTTP_VAR_CHANGEABLE
— 啟用變數的重新定義:如果另一個模組定義了具有相同名稱的變數,則不會發生衝突。這允許 set 指令覆寫變數。NGX_HTTP_VAR_NOCACHEABLE
— 停用快取,這對於諸如$time_local
之類的變數很有用。NGX_HTTP_VAR_NOHASH
— 表示此變數只能透過索引存取,而不能透過名稱存取。這是一個小優化,適用於已知不需要在 SSI 或 Perl 等模組中使用變數的情況。NGX_HTTP_VAR_PREFIX
— 變數的名稱是前置詞。在這種情況下,處理常式必須實作額外的邏輯才能取得特定變數的值。例如,所有「arg_
」變數都由相同的處理常式處理,該處理常式會在請求引數中執行查閱並傳回特定引數的值。
如果發生錯誤,該函式會傳回 NULL,否則傳回指向 ngx_http_variable_t
的指標
struct ngx_http_variable_s { ngx_str_t name; ngx_http_set_variable_pt set_handler; ngx_http_get_variable_pt get_handler; uintptr_t data; ngx_uint_t flags; ngx_uint_t index; };
呼叫 get
和 set
處理常式以取得或設定變數值,data
會傳遞至變數處理常式,而 index
會保留用於參照變數的已指派變數索引。
通常,會由模組建立 ngx_http_variable_t
結構的以 null 終止的靜態陣列,並在預先組態階段進行處理,以將變數新增至組態中,例如
static ngx_http_variable_t ngx_http_foo_vars[] = { { ngx_string("foo_v1"), NULL, ngx_http_foo_v1_variable, 0, 0, 0 }, ngx_http_null_variable }; static ngx_int_t ngx_http_foo_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_foo_vars; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); if (var == NULL) { return NGX_ERROR; } var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; }
範例中的此函式用於初始化 HTTP 模組內容的 preconfiguration
欄位,並在剖析 HTTP 組態之前呼叫,以便剖析器可以參照這些變數。
get
處理常式負責在特定請求的內容中評估變數,例如
static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; p = ngx_pnalloc(r->pool, NGX_ATOMIC_T_LEN); if (p == NULL) { return NGX_ERROR; } v->len = ngx_sprintf(p, "%uA", r->connection->number) - p; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->data = p; return NGX_OK; }
如果發生內部錯誤(例如,記憶體配置失敗),則傳回 NGX_ERROR
,否則傳回 NGX_OK
。若要瞭解變數評估的狀態,請檢查 ngx_http_variable_value_t
中的旗標(請參閱上方的描述)。
set
處理常式允許設定由變數參照的屬性。例如,$limit_rate
變數的 set 處理常式會修改請求的 limit_rate
欄位
... { ngx_string("limit_rate"), ngx_http_variable_request_set_size, ngx_http_variable_request_get_size, offsetof(ngx_http_request_t, limit_rate), NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... static void ngx_http_variable_request_set_size(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ssize_t s, *sp; ngx_str_t val; val.len = v->len; val.data = v->data; s = ngx_parse_size(&val); if (s == NGX_ERROR) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid size \"%V\"", &val); return; } sp = (ssize_t *) ((char *) r + data); *sp = s; return; }
複雜值
複合值雖然名稱如此,但它提供了一種簡單的方法來評估可以包含文字、變數及其組合的表達式。
ngx_http_compile_complex_value
中的複合值描述會在組態階段編譯成 ngx_http_complex_value_t
,該值在執行階段用於取得表達式評估的結果。
ngx_str_t *value; ngx_http_complex_value_t cv; ngx_http_compile_complex_value_t ccv; value = cf->args->elts; /* directive arguments */ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; ccv.complex_value = &cv; ccv.zero = 1; ccv.conf_prefix = 1; if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { return NGX_CONF_ERROR; }
此處,ccv
保留了初始化複合值 cv
所需的所有參數
-
cf
— 組態指標 -
value
— 要剖析的字串(輸入) -
complex_value
— 已編譯的值(輸出) -
zero
— 啟用以零終止值的旗標 -
conf_prefix
— 將組態前置詞(nginx 目前正在搜尋組態的目錄)加到結果的前面 -
root_prefix
— 將根前置詞(正常的 nginx 安裝前置詞)加到結果的前面
當結果要傳遞給需要以零終止字串的程式庫時,zero
旗標很有用,而處理檔案名稱時,前置詞很方便。
成功編譯後,cv.lengths
會包含有關表達式中是否存在變數的資訊。NULL 值表示表達式僅包含靜態文字,因此可以將其儲存在簡單字串中,而不是儲存為複合值。
ngx_http_set_complex_value_slot()
是一個方便的函式,用於在指令宣告本身中完整初始化一個複雜的值。
在執行階段,可以使用 ngx_http_complex_value()
函式計算複雜的值。
ngx_str_t res; if (ngx_http_complex_value(r, &cv, &res) != NGX_OK) { return NGX_ERROR; }
給定請求 r
和先前編譯的值 cv
,此函式會評估運算式,並將結果寫入 res
。
請求重新導向
HTTP 請求總是透過 ngx_http_request_t
結構的 loc_conf
欄位連結到一個 location。這表示在任何時候,都可以透過呼叫 ngx_http_get_module_loc_conf(r, module)
從請求中取得任何模組的 location 設定。請求的 location 在請求的生命週期中可能會變更數次。最初,會將預設伺服器的預設 location 指派給請求。如果請求切換到不同的伺服器(由 HTTP “Host” 標頭或 SSL SNI 擴充功能選擇),則請求也會切換到該伺服器的預設 location。location 的下一個變更發生在 NGX_HTTP_FIND_CONFIG_PHASE
請求階段。在此階段,會根據請求 URI 從為伺服器配置的所有未命名 location 中選擇一個 location。ngx_http_rewrite_module 可以在 NGX_HTTP_REWRITE_PHASE
請求階段,因為 rewrite 指令而變更請求 URI,並將請求送回 NGX_HTTP_FIND_CONFIG_PHASE
階段,以便根據新的 URI 選擇新的 location。
也可以透過呼叫 ngx_http_internal_redirect(r, uri, args)
或 ngx_http_named_location(r, name)
中的其中一個函式,將請求重新導向到新的 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 的名稱會以引數傳遞。會在目前伺服器的所有已命名 location 中查詢 location,之後請求會切換到 NGX_HTTP_REWRITE_PHASE
階段。
以下範例會執行重新導向到已命名 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); }
當 nginx 模組已在請求的 ctx
欄位中儲存某些內容時,可以呼叫這兩個函式 - ngx_http_internal_redirect(r, uri, args)
和 ngx_http_named_location(r, name)
。這些內容可能會與新的 location 設定不一致。為了防止不一致,兩個重新導向函式都會清除所有請求內容。
呼叫 ngx_http_internal_redirect(r, uri, args)
或 ngx_http_named_location(r, name)
會增加請求的 count
。為了確保請求參考計數的一致性,請在重新導向請求後呼叫 ngx_http_finalize_request(r, NGX_DONE)
。這將會完成目前的請求程式碼路徑並減少計數器。
重新導向和重新寫入的請求會變成內部請求,並且可以存取 internal location。內部請求會設定 internal
旗標。
子請求
子請求主要用於將一個請求的輸出插入到另一個請求中,並可能與其他資料混合。子請求看起來像是一個正常的請求,但會與其父請求共用一些資料。特別是,所有與用戶端輸入相關的欄位都會共用,因為子請求不會從用戶端接收任何其他輸入。子請求的請求欄位 parent
包含指向其父請求的連結,而主請求則為 NULL。欄位 main
包含指向請求群組中主要請求的連結。
子請求會在 NGX_HTTP_SERVER_REWRITE_PHASE
階段開始。它會通過與正常請求相同的後續階段,並根據其自身的 URI 指派一個 location。
子請求中的輸出標頭一律會被忽略。ngx_http_postpone_filter
會將子請求的輸出主體放置在相對於父請求產生的其他資料的正確位置。
子請求與作用中請求的概念相關。如果 c->data == r
,則請求 r
會被視為作用中,其中 c
是用戶端連線物件。在任何給定的時間點,只允許請求群組中的作用中請求將其緩衝區輸出到用戶端。非作用中請求仍然可以將其輸出傳送到篩選器鏈,但它不會通過 ngx_http_postpone_filter
,並且會由該篩選器緩衝,直到請求變成作用中為止。以下是請求啟動的一些規則
- 最初,主請求是作用中的。
- 作用中請求的第一個子請求會在建立後立即變成作用中。
ngx_http_postpone_filter
會在傳送完該請求之前的所有資料後,啟動作用中請求的子請求清單中的下一個請求。- 當請求完成時,其父請求會被啟動。
呼叫函式 ngx_http_subrequest(r, uri, args, psr, ps, flags)
來建立子請求,其中 r
是父請求,uri
和 args
是子請求的 URI 和引數,psr
是輸出參數,它接收新建立的子請求參考,ps
是用於通知父請求子請求正在完成的回呼物件,而 flags
是旗標的位元遮罩。以下旗標可用
-
NGX_HTTP_SUBREQUEST_IN_MEMORY
- 輸出不會傳送到用戶端,而是儲存在記憶體中。此旗標只會影響由其中一個 Proxy 模組處理的子請求。在子請求完成後,其輸出可在r->out
中使用,其類型為ngx_buf_t
。 -
NGX_HTTP_SUBREQUEST_WAITED
- 即使子請求在完成時不是作用中的,也會設定子請求的done
旗標。此子請求旗標由 SSI 篩選器使用。 -
NGX_HTTP_SUBREQUEST_CLONE
- 子請求會建立為其父請求的複本。它會從與父請求相同的 location 開始,並從相同的階段繼續。
以下範例會建立一個 URI 為 /foo
的子請求。
ngx_int_t rc; ngx_str_t uri; ngx_http_request_t *sr; ... ngx_str_set(&uri, "/foo"); rc = ngx_http_subrequest(r, &uri, NULL, &sr, NULL, 0); if (rc == NGX_ERROR) { /* error */ }
此範例會複製目前的請求,並為子請求設定完成回呼。
ngx_int_t ngx_http_foo_clone(ngx_http_request_t *r) { ngx_http_request_t *sr; ngx_http_post_subrequest_t *ps; ps = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); if (ps == NULL) { return NGX_ERROR; } ps->handler = ngx_http_foo_subrequest_done; ps->data = "foo"; return ngx_http_subrequest(r, &r->uri, &r->args, &sr, ps, NGX_HTTP_SUBREQUEST_CLONE); } ngx_int_t ngx_http_foo_subrequest_done(ngx_http_request_t *r, void *data, ngx_int_t rc) { char *msg = (char *) data; ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "done subrequest r:%p msg:%s rc:%i", r, msg, rc); return rc; }
子請求通常會在主體篩選器中建立,在這種情況下,它們的輸出可以像任何明確請求的輸出一樣處理。這表示最終子請求的輸出會傳送到用戶端,在所有明確緩衝區(在子請求建立之前傳遞)以及在建立之後傳遞的任何緩衝區之後。即使對於大型子請求階層,也會保留此順序。以下範例會在所有請求資料緩衝區之後,但在具有 last_buf
旗標的最後一個緩衝區之前,插入子請求的輸出。
ngx_int_t ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_buf_t *b; ngx_uint_t last; ngx_chain_t *cl, out; ngx_http_request_t *sr; ngx_http_foo_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } last = 0; for (cl = in; cl; cl = cl->next) { if (cl->buf->last_buf) { cl->buf->last_buf = 0; cl->buf->last_in_chain = 1; cl->buf->sync = 1; last = 1; } } /* Output explicit output buffers */ rc = ngx_http_next_body_filter(r, in); if (rc == NGX_ERROR || !last) { return rc; } /* * Create the subrequest. The output of the subrequest * will automatically be sent after all preceding buffers, * but before the last_buf buffer passed later in this function. */ if (ngx_http_subrequest(r, ctx->uri, NULL, &sr, NULL, 0) != NGX_OK) { return NGX_ERROR; } ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module); /* Output the final buffer with the last_buf flag */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->last_buf = 1; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
也可以為了資料輸出以外的其他目的而建立子請求。例如, ngx_http_auth_request_module 模組會在 NGX_HTTP_ACCESS_PHASE
階段建立一個子請求。若要在此點停用輸出,會在子請求上設定 header_only
旗標。這會阻止將子請求主體傳送到用戶端。請注意,子請求的標頭永遠不會傳送到用戶端。可以在回呼處理常式中分析子請求的結果。
請求最終化
透過呼叫函式 ngx_http_finalize_request(r, rc)
來完成 HTTP 請求。它通常會在所有輸出緩衝區都傳送到篩選器鏈之後,由內容處理常式完成。在這一點上,所有輸出可能尚未傳送到用戶端,其中一些輸出仍會緩衝在篩選器鏈上的某個位置。如果是這樣,ngx_http_finalize_request(r, rc)
函式會自動安裝一個特殊的處理常式 ngx_http_writer(r)
來完成傳送輸出。如果發生錯誤或需要將標準 HTTP 回應碼傳回用戶端,也會完成請求。
ngx_http_finalize_request(r, rc)
函式預期下列 rc
值
-
NGX_DONE
- 快速完成。減少請求count
,並在請求達到零時銷毀請求。在目前的請求被銷毀後,用戶端連線可以用於更多請求。 -
NGX_ERROR
、NGX_HTTP_REQUEST_TIME_OUT
(408
)、NGX_HTTP_CLIENT_CLOSED_REQUEST
(499
) - 錯誤完成。盡快終止請求並關閉用戶端連線。 -
NGX_HTTP_CREATED
(201
)、NGX_HTTP_NO_CONTENT
(204
)、大於或等於NGX_HTTP_SPECIAL_RESPONSE
(300
) 的程式碼 - 特殊回應完成。對於這些值,nginx 會傳送用戶端預設的回應頁面,或執行內部重新導向到 error_page location(如果針對該程式碼配置)。 - 其他程式碼會被視為成功的完成程式碼,並且可能會啟動請求寫入器以完成傳送回應主體。一旦完全傳送主體,請求
count
會減少。如果達到零,則會銷毀請求,但用戶端連線仍然可以用於其他請求。如果count
為正數,則請求中會有未完成的活動,這些活動會在稍後完成。
請求主體
為了處理用戶端請求的主體,nginx 提供 ngx_http_read_client_request_body(r, post_handler)
和 ngx_http_discard_request_body(r)
函式。第一個函式會讀取請求主體,並透過 request_body
請求欄位提供。第二個函式會指示 nginx 捨棄(讀取並忽略)請求主體。每個請求都必須呼叫其中一個函式。通常,內容處理常式會進行呼叫。
不允許從子請求讀取或捨棄用戶端請求主體。必須始終在主請求中執行。當建立子請求時,它會繼承父請求的 request_body
物件,如果主請求先前已讀取請求主體,則子請求可以使用此物件。
ngx_http_read_client_request_body(r, post_handler)
函式會開始讀取請求主體的程序。當完全讀取主體時,會呼叫 post_handler
回呼以繼續處理請求。如果缺少請求主體或已讀取,則會立即呼叫回呼。ngx_http_read_client_request_body(r, post_handler)
函式會配置 ngx_http_request_body_t
類型的 request_body
請求欄位。此物件的欄位 bufs
會將結果保留為緩衝區鏈。如果 client_body_buffer_size 指令指定的容量不足以將整個主體放入記憶體中,則主體可以儲存在記憶體緩衝區或檔案緩衝區中。
以下範例會讀取用戶端請求主體並傳回其大小。
ngx_int_t ngx_http_foo_content_handler(ngx_http_request_t *r) { ngx_int_t rc; rc = ngx_http_read_client_request_body(r, ngx_http_foo_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { /* error */ return rc; } return NGX_DONE; } void ngx_http_foo_init(ngx_http_request_t *r) { off_t len; ngx_buf_t *b; ngx_int_t rc; ngx_chain_t *in, out; if (r->request_body == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } len = 0; for (in = r->request_body->bufs; in; in = in->next) { len += ngx_buf_size(in->buf); } b = ngx_create_temp_buf(r->pool, NGX_OFF_T_LEN); if (b == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } b->last = ngx_sprintf(b->pos, "%O", len); b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = b->last - b->pos; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { ngx_http_finalize_request(r, rc); return; } out.buf = b; out.next = NULL; rc = ngx_http_output_filter(r, &out); ngx_http_finalize_request(r, rc); }
請求的下列欄位決定如何讀取請求主體
-
request_body_in_single_buf
- 將主體讀取到單一記憶體緩衝區。 -
request_body_in_file_only
- 即使可以放入記憶體緩衝區,也始終將主體讀取到檔案中。 -
request_body_in_persistent_file
- 請勿在建立後立即取消連結檔案。具有此旗標的檔案可以移動到另一個目錄。 -
request_body_in_clean_file
- 當請求完成時,取消連結檔案。這在檔案本應被移動到另一個目錄,但由於某些原因沒有移動時很有用。 -
request_body_file_group_access
- 啟用群組對檔案的存取權限,將預設的 0600 存取遮罩替換為 0660。 -
request_body_file_log_level
- 記錄檔案錯誤的嚴重程度級別。 -
request_body_no_buffering
- 在不緩衝的情況下讀取請求主體。
request_body_no_buffering
標誌啟用讀取請求主體的非緩衝模式。在此模式下,在呼叫 ngx_http_read_client_request_body()
之後,bufs
鏈可能只保留主體的一部分。要讀取下一部分,請呼叫 ngx_http_read_unbuffered_request_body(r)
函數。返回值 NGX_AGAIN
和請求標誌 reading_body
表示有更多數據可用。如果在呼叫此函數後 bufs
為 NULL,則表示目前沒有任何內容可讀。當請求主體的下一部分可用時,將呼叫請求回調函數 read_event_handler
。
請求主體篩選器
讀取請求主體的一部分後,它會透過呼叫儲存在 ngx_http_top_request_body_filter
變數中的第一個主體篩選器處理程式,將其傳遞到請求主體篩選器鏈。假設每個主體處理程式都會呼叫鏈中的下一個處理程式,直到呼叫最終處理程式 ngx_http_request_body_save_filter(r, cl)
為止。此處理程式會收集 r->request_body->bufs
中的緩衝區,並在必要時將它們寫入檔案。最後一個請求主體緩衝區具有非零的 last_buf
標誌。
如果篩選器計劃延遲數據緩衝區,則應在第一次呼叫時將 r->request_body->filter_need_buffering
標誌設定為 1
。
以下是一個簡單的請求主體篩選器範例,該篩選器會將請求主體延遲一秒。
#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
,表示主體大小未知。在這種情況下,將使用分塊傳輸編碼。要輸出任意標頭,請附加 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
標誌。
以下範例會產生一個完整的 HTTP 回應,其主體為「foo」。為了使此範例也能作為子請求和主請求運作,會在輸出的最後一個緩衝區中設定 last_in_chain
標誌。last_buf
標誌僅針對主請求設定,因為子請求的最後一個緩衝區不會結束整個輸出。
static ngx_int_t ngx_http_bar_content_handler(ngx_http_request_t *r) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t out; /* send header */ r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = 3; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } /* send body */ b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->memory = 1; b->pos = (u_char *) "foo"; b->last = b->pos + 3; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
回應主體篩選器
函數 ngx_http_output_filter(r, cl)
會呼叫主體篩選器鏈,方法是呼叫儲存在 ngx_http_top_body_filter
變數中的第一個主體篩選器處理程式。假設每個主體處理程式都會呼叫鏈中的下一個處理程式,直到呼叫最終處理程式 ngx_http_write_filter(r, cl)
為止。
主體篩選器處理程式會接收緩衝區鏈。處理程式應該處理緩衝區,並將可能的新鏈傳遞給下一個處理程式。值得注意的是,傳入鏈的鏈接 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
。
以下範例顯示了篩選器模組的設定檔,假設該模組只有一個來源檔案 ngx_http_foo_filter_module.c
。
ngx_module_type=HTTP_AUX_FILTER ngx_module_name=ngx_http_foo_filter_module ngx_module_srcs="$ngx_addon_dir/ngx_http_foo_filter_module.c" . auto/module
緩衝區重複使用
在發出或變更緩衝區串流時,通常希望重複使用已配置的緩衝區。在 nginx 程式碼中,標準且廣泛採用的方法是為此目的保留兩個緩衝區鏈:free
和 busy
。free
鏈會保留所有可重複使用的可用緩衝區。busy
鏈會保留目前模組發送的所有緩衝區,這些緩衝區仍被其他篩選器處理程式使用。如果緩衝區的大小大於零,則視為正在使用中。通常,當篩選器取用緩衝區時,其 pos
(或檔案緩衝區的 file_pos
)會朝向 last
(檔案緩衝區的 file_last
)移動。一旦緩衝區完全被取用,就可以重複使用。若要將新釋放的緩衝區新增至 free
鏈,只需反覆運算 busy
鏈,並將其頭部的零大小緩衝區移至 free
即可。此操作非常常見,因此有一個特殊的函數 ngx_chain_update_chains(free, busy, out, tag)
可以執行此操作。此函數會將輸出鏈 out
附加到 busy
,並將 busy
頂端的可用緩衝區移至 free
。只會重複使用具有指定 tag
的緩衝區。這讓模組只能重複使用其自行配置的緩衝區。
以下範例是一個主體篩選器,它會在每個傳入緩衝區之前插入字串「foo」。如果可能,會重複使用模組配置的新緩衝區。請注意,為了使此範例正常運作,還需要設定標頭篩選器並將 content_length_n
重設為 -1
,但此處未提供相關程式碼。
typedef struct { ngx_chain_t *free; ngx_chain_t *busy; } ngx_http_foo_filter_ctx_t; ngx_int_t ngx_http_foo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *cl, *tl, *out, **ll; ngx_http_foo_filter_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_foo_filter_module); if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_foo_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module); } /* create a new chain "out" from "in" with all the changes */ ll = &out; for (cl = in; cl; cl = cl->next) { /* append "foo" in a reused buffer if possible */ tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_foo_filter_module; b->memory = 1; b->pos = (u_char *) "foo"; b->last = b->pos + 3; *ll = tl; ll = &tl->next; /* append the next incoming buffer */ tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = cl->buf; *ll = tl; ll = &tl->next; } *ll = NULL; /* send the new chain */ rc = ngx_http_next_body_filter(r, out); /* update "busy" and "free" chains for reuse */ ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_foo_filter_module); return rc; }
負載平衡
ngx_http_upstream_module 提供了將請求傳遞到遠端伺服器所需的基本功能。實作特定協定(例如 HTTP 或 FastCGI)的模組會使用此功能。此模組還提供用於建立自訂負載平衡模組的介面,並實作預設的循環配置方法。
least_conn 和 hash 模組實作了替代的負載平衡方法,但實際上是作為上游循環配置模組的延伸而實作,並與其共用許多程式碼,例如伺服器群組的表示方式。keepalive 模組是一個獨立的模組,可延伸上游功能。
ngx_http_upstream_module 可以透過將對應的 upstream 區塊放置到設定檔中來明確設定,或者透過使用諸如 proxy_pass 之類的指令來隱含設定,這些指令會接受在某個時間點評估為伺服器清單的 URL。替代的負載平衡方法僅適用於明確的上游設定。上游模組設定有其自己的指令內容 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
— 上游模組的設定內容。 -
servers
—ngx_http_upstream_server_t
的陣列,這是剖析upstream
區塊中一組 server 指令的結果。 -
flags
— 主要標記負載平衡方法支援哪些功能的標誌。這些功能設定為 server 指令的參數-
NGX_HTTP_UPSTREAM_CREATE
— 將明確定義的上游與由 proxy_pass 指令和「朋友」(FastCGI、SCGI 等)自動建立的上游區分開來。 -
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
— 上游伺服器的名稱。 -
file_name, line
— 配置檔的名稱以及upstream
區塊所在的行數。 -
port
和no_port
— 不用於明確定義的上游群組。 -
shm_zone
— 此上游群組使用的共享記憶體區,如果有的話。 -
peer
— 用於初始化上游配置的通用方法的物件。
實作負載平衡演算法的模組必須設定這些方法並初始化私有的typedef struct { ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void *data; } ngx_http_upstream_peer_t;
data
。如果在配置解析期間未初始化init_upstream
,ngx_http_upstream_module
會將其設定為預設的ngx_http_upstream_init_round_robin
演算法。-
init_upstream(cf, us)
— 配置時方法,負責初始化伺服器群組,並在成功時初始化init()
方法。典型的負載平衡模組會使用upstream
區塊中的伺服器列表來建立它使用的有效率資料結構,並將自己的配置儲存到data
欄位中。 -
init(r, us)
— 初始化每個請求的ngx_http_upstream_peer_t.peer
結構,該結構用於負載平衡(不要與上述每個上游伺服器的ngx_http_upstream_srv_conf_t.peer
混淆)。它作為data
引數傳遞給所有處理伺服器選擇的回呼。
-
當 nginx 必須將請求傳遞給另一個主機進行處理時,它會使用配置的負載平衡方法來取得要連接的位址。該方法是從 ngx_http_upstream_t.peer
物件中取得,其類型為 ngx_peer_connection_t
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
— 要連接的上游伺服器的位址;這是負載平衡方法的輸出參數。 -
data
— 負載平衡方法的每個請求資料;保持選擇演算法的狀態,通常包括到上游配置的連結。它作為引數傳遞給所有處理伺服器選擇的方法(請參閱下方的說明)。 -
tries
— 允許的連接到上游伺服器的嘗試次數。 -
get
、free
、notify
、set_session
和save_session
- 負載平衡模組的方法,如下所述。
所有方法都接受至少兩個引數:一個對等連線物件 pc
和由 ngx_http_upstream_srv_conf_t.peer.init()
建立的 data
。請注意,由於負載平衡模組的「鏈接」,它可能與 pc.data
不同。
-
get(pc, data)
— 當上游模組準備將請求傳遞到上游伺服器並且需要知道其位址時,會呼叫此方法。該方法必須填寫ngx_peer_connection_t
結構的sockaddr
、socklen
和name
欄位。傳回值為下列其中之一:-
NGX_OK
— 已選取伺服器。 -
NGX_ERROR
— 發生內部錯誤。 -
NGX_BUSY
— 目前沒有可用的伺服器。這可能由於多種原因而發生,包括:動態伺服器群組為空,群組中的所有伺服器都處於失敗狀態,或群組中的所有伺服器都已經處理了最大連線數。 -
NGX_DONE
— 底層連線已重複使用,無需建立與上游伺服器的新連線。此值由keepalive
模組設定。
-
-
free(pc, data, state)
— 當上游模組完成與特定伺服器的工作時,會呼叫此方法。state
引數是上游連線的完成狀態,是一個位元遮罩,具有以下可能的值:-
NGX_PEER_FAILED
— 嘗試失敗。 -
NGX_PEER_NEXT
— 當上游伺服器傳回程式碼403
或404
時的特殊情況,這些程式碼不被視為失敗。 -
NGX_PEER_KEEPALIVE
— 目前未使用。
tries
計數器。 -
-
notify(pc, data, type)
— 目前在 OSS 版本中未使用。 -
set_session(pc, data)
和save_session(pc, data)
— SSL 專用方法,可讓快取到上游伺服器的連線階段。實作是由循環配置負載平衡方法提供。
範例
nginx-dev-examples 儲存庫提供 nginx 模組範例。
程式碼風格
一般規則
- 最大文字寬度為 80 個字元
- 縮排為 4 個空格
- 沒有 Tab,沒有尾隨空格
- 同一行上的列表元素以空格分隔
- 十六進位文字為小寫
- 檔案名稱、函式和類型名稱以及全域變數具有
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; }
檔案
典型的原始程式碼檔案可能包含以下以兩個空行分隔的章節
- 著作權聲明
- 包含
- 前置處理器定義
- 類型定義
- 函式原型
- 變數定義
- 函式定義
著作權聲明如下所示
/* * Copyright (C) Author Name * Copyright (C) Organization, Inc. */
如果檔案經過大幅修改,則應更新作者列表,新的作者會新增至頂端。
ngx_config.h
和 ngx_core.h
檔案始終會先包含,接著包含 ngx_http.h
、ngx_stream.h
或 ngx_mail.h
其中之一。然後是選用的外部標頭檔
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #include <libxml/parser.h> #include <libxml/tree.h> #include <libxslt/xslt.h> #if (NGX_HAVE_EXSLT) #include <libexslt/exslt.h> #endif
標頭檔應包含所謂的「標頭保護」
#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_ #define _NGX_PROCESS_CYCLE_H_INCLUDED_ ... #endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
註解
- 不使用「
//
」註解 - 文字以英文撰寫,偏好美式拼法
- 多行註解的格式如下
/* * The red-black tree code is based on the algorithm described in * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest. */
/* find the server configuration for the address:port */
預處理器
巨集名稱以 ngx_
或 NGX_
(或更具體)前置詞開頭。常數的巨集名稱為大寫。參數化的巨集和用於初始化程式的巨集為小寫。巨集名稱和值之間至少以兩個空格分隔
#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
」字尾結尾。定義的類型名稱至少以兩個空格分隔
typedef ngx_uint_t ngx_rbtree_key_t;
結構類型使用 typedef
定義。在結構內部,成員類型和名稱會對齊
typedef struct { size_t len; u_char *data; } ngx_str_t;
在檔案中不同結構之間保持對齊方式相同。指向自身的結構具有以「_s
」結尾的名稱。相鄰的結構定義以兩個空行分隔
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; }; typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
每個結構成員都會在其自己的行上宣告
typedef struct { ngx_uint_t hash; ngx_str_t key; ngx_str_t value; u_char *lowcase_key; } ngx_table_elt_t;
結構內的函式指標具有以「_pt
」結尾的定義類型
typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size); typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit); typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size); typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, off_t limit); typedef struct { ngx_recv_pt recv; ngx_recv_chain_pt recv_chain; ngx_recv_pt udp_recv; ngx_send_pt send; ngx_send_pt udp_send; ngx_send_chain_pt udp_send_chain; ngx_send_chain_pt send_chain; ngx_uint_t flags; } ngx_os_io_t;
列舉的類型以「_e
」結尾
typedef enum { ngx_http_fastcgi_st_version = 0, ngx_http_fastcgi_st_type, ... ngx_http_fastcgi_st_padding } ngx_http_fastcgi_state_e;
變數
變數會按照基本類型的長度排序,然後按字母順序宣告。類型名稱和變數名稱會對齊。「類型」和「名稱」欄會以兩個空格分隔。大型陣列會放在宣告區塊的結尾
u_char | | *rv, *p; ngx_conf_t | | *cf; ngx_uint_t | | i, j, k; unsigned int | | len; struct sockaddr | | *sa; const unsigned char | | *data; ngx_peer_connection_t | | *pc; ngx_http_core_srv_conf_t | |**cscfp; ngx_http_upstream_srv_conf_t| | *us, *uscf; u_char | | text[NGX_SOCKADDR_STRLEN];
靜態和全域變數可以在宣告時初始化
static ngx_str_t ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t ngx_crc32_table16[] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, ... 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
有一組常用的類型/名稱組合
u_char *rv; ngx_int_t rc; ngx_conf_t *cf; ngx_connection_t *c; ngx_http_request_t *r; ngx_peer_connection_t *pc; ngx_http_upstream_srv_conf_t *us, *uscf;
函式
所有函式(甚至是靜態函式)都應具有原型。原型包含引數名稱。長原型會在新行上以單一縮排換行
static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_init_phases(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf); static char *ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf, ngx_http_module_t *module, ngx_uint_t ctx_index);
定義中的函式名稱以新行開頭。函式主體的開頭和結尾大括號位於不同的行上。函式的主體會縮排。函式之間有兩個空行
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);
表達式
二元運算子(「.
」和「−>
」除外)應與其運算元以一個空格分隔。一元運算子和下標不會以空格與其運算元分隔
width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];
類型轉換與轉換後的運算式以一個空格分隔。類型轉換內的星號會以空格與類型名稱分隔
len = ngx_sock_ntop((struct sockaddr *) sin6, p, len, 1);
如果運算式不適合單行,則會換行。換行的慣用點是二元運算子。後續行會與運算式的開頭對齊
if (status == NGX_HTTP_MOVED_PERMANENTLY || status == NGX_HTTP_MOVED_TEMPORARILY || status == NGX_HTTP_SEE_OTHER || status == NGX_HTTP_TEMPORARY_REDIRECT || status == NGX_HTTP_PERMANENT_REDIRECT) { ... }
p->temp_file->warn = "an upstream response is buffered " "to a temporary file";
在不得已的情況下,可以換行運算式,使後續行在位置 79 結束
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t) + size * sizeof(ngx_hash_elt_t *));
上述規則也適用於子運算式,其中每個子運算式都有自己的縮排層級
if (((u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) || c->stale_updating) && !r->background && u->conf->cache_background_update) { ... }
有時,在轉換後換行運算式會很方便。在這種情況下,後續行會縮排
node = (ngx_rbtree_node_t *) ((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
指標會明確與 NULL
比較(而不是 0
)
if (ptr != NULL) { ... }
條件式和迴圈
「if
」關鍵字與條件以一個空格分隔。開頭大括號位於同一行,或如果條件佔用多行,則位於專用行上。結尾大括號位於專用行上,選擇性地後面接著「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
」關鍵字與條件以一個空格分隔。開頭大括號位於同一行。結尾大括號位於專用行上。「case
」關鍵字與「switch
」對齊
switch (ch) { case '!': looked = 2; state = ssi_comment0_state; break; case '<': copy_end = p; break; default: copy_end = p; looked = 0; state = ssi_start_state; break; }
大多數「for
」迴圈的格式如下
for (i = 0; i < ccf->env.nelts; i++) { ... }
for (q = ngx_queue_head(locations); q != ngx_queue_sentinel(locations); q = ngx_queue_next(q)) { ... }
如果省略「for
」陳述式的一部分,則會以「/* void */
」註解表示
for (i = 0; /* void */ ; i++) { ... }
具有空主體的迴圈也會以「/* void */
」註解表示,可以放在同一行
for (cl = *busy; cl->next; cl = cl->next) { /* void */ }
無限迴圈如下所示
for ( ;; ) { ... }
標籤
標籤會以空行包圍,並在前一個層級縮排
if (i == 0) { u->err = "host not found"; goto failed; } u->addrs = ngx_pcalloc(pool, i * sizeof(ngx_addr_t)); if (u->addrs == NULL) { goto failed; } u->naddrs = i; ... return NGX_OK; failed: freeaddrinfo(res); return NGX_ERROR;
除錯記憶體問題
若要偵錯記憶體問題,例如緩衝區溢位或釋放後使用錯誤,您可以使用某些現代編譯器支援的 AddressSanitizer (ASan)。若要使用 gcc
和 clang
啟用 ASan,請使用 -fsanitize=address
編譯器和連結器選項。在建置 nginx 時,可以將選項新增至 configure
指令碼的 --with-cc-opt
和 --with-ld-opt
參數中。
由於 nginx 中的大多數配置都是從 nginx 內部集區進行的,因此啟用 ASan 可能並不總是足以偵錯記憶體問題。內部集區會從系統配置一大部分記憶體,並從中切出較小的配置。不過,可以將 NGX_DEBUG_PALLOC
巨集設定為 1
來停用此機制。在這種情況下,配置會直接傳遞給系統配置器,使其完全控制緩衝區邊界。
以下配置行會總結以上提供的資訊。建議在開發協力廠商模組和在不同平台上測試 nginx 時使用。
auto/configure --with-cc-opt='-fsanitize=address -DNGX_DEBUG_PALLOC=1' --with-ld-opt=-fsanitize=address
常見陷阱
撰寫 C 模組
最常見的陷阱是嘗試撰寫完整的 C 模組,而這本來可以避免。在大多數情況下,您可以藉由建立正確的配置來完成工作。如果撰寫模組是不可避免的,請嘗試使其盡可能小且簡單。例如,模組只能匯出一些變數。
在開始模組之前,請考慮以下問題
C 字串
在 nginx 中最常用的字串類型 ngx_str_t 並非 C 風格的以零結尾的字串。您無法將此資料傳遞給標準 C 函式庫,例如 strlen()
或 strstr()
。相反地,您應該使用 nginx 的 對應函式,這些函式接受 ngx_str_t
或資料指標以及長度。然而,在某些情況下,ngx_str_t
會持有指向以零結尾的字串的指標:從組態檔解析而來的字串就是以零結尾的。
全域變數
避免在您的模組中使用全域變數。擁有全域變數很可能是錯誤的。任何全域資料都應該與組態週期綁定,並且從相對應的記憶體池中分配。這允許 nginx 執行優雅的組態重新載入。嘗試使用全域變數可能會破壞此功能,因為它將無法同時擁有兩個組態並擺脫它們。有時確實需要全域變數。在這種情況下,需要特別注意如何正確地管理重新組態。此外,請檢查您的程式碼使用的函式庫是否有隱含的全域狀態,這些狀態可能會在重新載入時被破壞。
手動記憶體管理
與其使用容易出錯的 malloc/free 方法,不如學習如何使用 nginx 的記憶體池。記憶體池會被建立並綁定到一個物件 - 組態、週期、連線或HTTP 請求。當物件被銷毀時,相關的記憶體池也會被銷毀。因此,當您使用某個物件時,您可以從相對應的記憶體池中分配所需的記憶體量,即使發生錯誤也不必擔心釋放記憶體。
執行緒
建議避免在 nginx 中使用執行緒,因為這肯定會破壞某些東西:大多數 nginx 函式都不是執行緒安全的。預期執行緒只會執行系統呼叫和執行緒安全的函式庫函式。如果您需要執行一些與客戶端請求處理無關的程式碼,正確的方法是在 init_process
模組處理器中排程一個計時器,並在計時器處理器中執行所需的動作。在內部,nginx 利用執行緒來加速 IO 相關的操作,但這是一個有許多限制的特殊情況。
阻塞函式庫
一個常見的錯誤是使用內部會阻塞的函式庫。大多數函式庫本質上是同步且阻塞的。換句話說,它們一次執行一個操作,並浪費時間等待其他對等方的回應。因此,當使用這樣的函式庫處理請求時,整個 nginx 工作進程會被阻塞,從而破壞效能。請僅使用提供非同步介面且不會阻塞整個進程的函式庫。
對外部服務的 HTTP 請求
模組經常需要對某些外部服務執行 HTTP 呼叫。一個常見的錯誤是使用一些外部函式庫,例如 libcurl,來執行 HTTP 請求。對於可以用 nginx 本身完成的任務,完全沒有必要引入大量外部(可能會阻塞!)的程式碼。
需要外部請求時,有兩種基本的使用情境
- 在處理客戶端請求的環境中(例如,在內容處理器中)
- 在工作進程的環境中(例如,計時器處理器)
在第一種情況下,最好使用子請求 API。您不必直接存取外部服務,而是在 nginx 組態中宣告一個位置,並將您的子請求導向到該位置。此位置不僅限於代理請求,還可以包含其他 nginx 指令。此方法的範例是 auth_request 指令,該指令在 ngx_http_auth_request 模組中實作。
對於第二種情況,可以使用 nginx 中提供的基本 HTTP 客戶端功能。例如,OCSP 模組實作了簡單的 HTTP 客戶端。