开发指南

简介
代码布局
包括文件
整数
常见返回代码
错误处理
字符串
概述
格式设置
数字转换
正则表达式
时间
容器
数组
列表
队列
红黑树
哈希
内存管理


共享内存
日志记录
周期
缓冲区
网络
连接
事件
事件
I/O 事件
计时器事件
已发布的事件
事件循环
进程
线程
模块
添加新模块
核心模块
配置指令
HTTP
连接
请求 配置
阶段

变量
复杂值
请求重定向
子请求
请求终结
请求正文
请求正文过滤器
响应
正文 响应正文
过滤器
构建过滤器模块
缓冲区重用
负载均衡
示例
代码样式
通用规则
文件
注释
预处理器
类型
变量
函数 表达式

条件语句和循环
标签
调试内存问题
常见陷阱
编写 C 语言模块
C 字符串
全局变量
手动内存管理
线程
阻止库
对外部服务的 HTTP 请求

介绍

代码布局

  • auto— 构建脚本
  • src
    • core— 基本类型和函数— 字符串、数组、日志、 池等。
    • event— 事件核心
      • modules— 事件通知模块:、 等epollkqueueselect
    • http— 核心 HTTP 模块和通用代码
      • modules— 其他 HTTP 模块
      • v2— HTTP/2
    • mail— 邮件模块
    • os— 特定于平台的代码
      • unix
      • win32
    • stream— 流模块

包含文件

以下两个语句必须出现在 每个 nginx 文件的开头:#include

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

除此之外,HTTP 代码还应包括

#include <ngx_http.h>

邮件代码应包括

#include <ngx_mail.h>

Stream 代码应包括

#include <ngx_stream.h>

整数

对于一般用途,nginx 代码使用两种整数类型,即 和 ,它们是 typedef 的 和 。ngx_int_tngx_uint_tintptr_tuintptr_t

常见返回代码

nginx 中的大多数函数都返回以下代码:

  • NGX_OK—作成功。
  • NGX_ERROR—作失败。
  • NGX_AGAIN—作未完成;再次调用该函数。
  • NGX_DECLINED—作被拒绝,例如,因为它是 disabled。这绝不是错误。
  • NGX_BUSY— 资源不可用。
  • NGX_DONE— 行动完成或在其他地方继续。 也用作替代成功代码。
  • NGX_ABORT— 函数已中止。 也用作替代错误代码。

错误处理

宏返回最后一个系统错误代码。 它在 POSIX 平台上映射到 Windows 并调用 宏返回最后一个套接字错误 数。 与宏一样,它也被映射到 POSIX 平台上。 它映射到 Windows 上的调用。 连续多次访问 或 的值可能会导致 性能问题。 如果错误值可能被多次使用,请将其存储在局部变量中 的类型 。 要设置错误,请使用 和 宏。ngx_errnoerrnoGetLastError()ngx_socket_errnongx_errnoerrnoWSAGetLastError()ngx_errnongx_socket_errnongx_err_tngx_set_errno(errno)ngx_set_socket_errno(errno)

和 的值可以传递给日志记录函数和 ,在 哪种大小写的系统错误文本将添加到日志消息中。ngx_errnongx_socket_errnongx_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 character type pointer 。u_char *

nginx 字符串类型定义如下:ngx_str_t

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

该字段保存字符串长度并保存字符串数据。 字符串 (held in ) 可能是也可能不是 以 null 结尾的字节。 在大多数情况下,情况并非如此。 但是,在代码的某些部分(例如,解析配置时),已知对象以 null 结尾,这 简化了字符串比较,并使其更容易将字符串传递给 sys调用。lendatangx_str_tlenngx_str_t

nginx 中的字符串作在 其中一些是标准 C 函数的包装器:src/core/ngx_string.h

  • ngx_strcmp()
  • ngx_strncmp()
  • ngx_strstr()
  • ngx_strlen()
  • ngx_strchr()
  • ngx_memcmp()
  • ngx_memset()
  • ngx_memcpy()
  • ngx_memmove()

其他字符串函数是特定于 nginx 的

  • ngx_memzero()— 用零填充内存。
  • ngx_explicit_memzero()— 与 相同,但此调用永远不会被 编译器的 Dead Store Elimination Optimization。 该功能可用于清除密码、密钥等敏感数据。ngx_memzero()
  • ngx_cpymem()— 与 相同,但返回最终目标地址 这个选项对于在一行中附加多个字符串很方便。ngx_memcpy()
  • ngx_movemem()— 与 相同,但返回最终目标地址。ngx_memmove()
  • ngx_strlchr()— 在字符串中搜索字符, 由 2 个指针分隔。

以下函数执行大小写转换和比较:

  • ngx_tolower()
  • ngx_toupper()
  • ngx_strlow()
  • ngx_strcasecmp()
  • ngx_strncasecmp()

以下宏简化了字符串初始化:

  • ngx_string(text)— C 字符串文字中类型的 static initializerngx_str_ttext
  • ngx_null_string— type 的 static empty string initializerngx_str_t
  • ngx_str_set(str, text)— 使用 C 字符串初始化 string 类型的字符串 字面strngx_str_t *text
  • ngx_str_null(str)— 使用空字符串初始化 String 类型strngx_str_t *

格式

以下格式化函数支持 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

  • %Ooff_t
  • %Ttime_t
  • %zssize_t
  • %ingx_int_t
  • %pvoid *
  • %Vngx_str_t *
  • %s— (以 null 结尾)u_char *
  • %*ssize_t + u_char *

您可以在大多数类型前面加上前缀以使其无符号。 要将输出转换为十六进制,请使用 或 。uXx

例如:

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)- 转换固定点浮点数 number 的给定长度转换为 . 类型的正整数。 结果将左移十进制 位置。 数字的字符串表示形式不应有更多 比小数位。 例如,返回 .ngx_int_tpointpointsngx_atofp("10.5", 4, 2)1050
  • ngx_hextoi(line, n)— 转换十六进制表示 的正整数设置为 。ngx_int_t

正则表达式

nginx 中的正则表达式接口是一个包装器 PCRE 库。 相应的头文件为 .src/core/ngx_regex.h

要使用正则表达式进行字符串匹配,它首先需要是 compiled,这通常在配置阶段完成。 请注意,由于 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

编译成功后,结构体中的 and 字段包含所有 captures 和 named captures,分别在正则表达式中找到。capturesnamed_capturesngx_regex_compile_t

然后,编译后的正则表达式可用于匹配字符串:

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

参数是编译后的常规 expression 、 要匹配的字符串 、 一个可选的整数数组,用于保存任何 found,数组的 . 数组的大小必须是 3 的倍数, 根据 PCRE API 的要求。 在此示例中,大小是根据捕获总数加上 一个用于匹配的字符串本身。ngx_regex_exec()reinputcapturessizecaptures

如果存在匹配项,则可以按如下方式访问捕获:

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_tNGX_OKNGX_DECLINEDNGX_ERROR

时间

该结构用三个单独的 类型表示秒、毫秒和 GMT 偏移量:ngx_time_t

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

该结构是 UNIX 平台和 Windows 上的别名。ngx_tm_tstruct tmSYSTEMTIME

要获取当前时间,通常只需访问 available global variables,表示所需 格式。

可用的字符串表示形式包括:

  • 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"

的 and 宏 返回当前时间值(以秒为单位),是访问 缓存时间值。ngx_time()ngx_timeofday()

要显式获取时间,请使用 , 这会更新其参数(指向 的指针)。 当 nginx 从系统返回到事件循环时,时间始终更新 调用。 要立即更新时间,请调用 , 或者,如果更新 信号处理程序上下文。ngx_gettimeofday()struct timevalngx_time_update()ngx_time_sigsafe_update()

以下函数转换为指示的 分解时间表示。 每对中的第一个函数转换为 ,第二个函数(带有中缀)转换为 :time_ttime_tngx_tm_t_libc_struct tm

  • ngx_gmtime(), ngx_libc_gmtime()— 以 UTC 表示的时间
  • ngx_localtime(), ngx_libc_localtime()— 时间表示 相对于本地时区

该函数返回一个字符串 适合在 HTTP 标头中使用的表示形式(例如 )。 返回一个字符串 表示函数返回一个适合的字符串表示 对于 HTTP Cookie () 。ngx_http_time(buf, time)"Mon, 28 Sep 1970 06:00:00 GMT"ngx_http_cookie_time(buf, time)"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;

数组的元素在字段中可用。 该字段包含元素的数量。 该字段保存单个元素的大小,并已设置 初始化数组时。eltsneltssize

使用调用创建一个 数组,以及用于初始化已分配的数组对象的调用。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)添加一个 tail 元素并返回 pointer 到
  • ngx_array_push_n(a, n)添加 tail 元素 并返回指向第一个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;
};

使用前,必须通过调用初始化列表或通过调用创建列表。 这两个函数都将单个项目的大小和数量 items per list part 的 items (每个列表部分的项数)。 若要将项添加到列表,请使用 函数。 要迭代项目,请直接访问列表字段,如 例: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 输出标头(存储为对象)标记为缺失,请将 in 中的字段设置为 零。 在迭代标头时,将显式跳过以这种方式标记的项 多。ngx_table_elt_thashngx_table_elt_t

队列

在 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)- 获取要结束的队列 sentinel 对象 iteration at
  • ngx_queue_data(q, type, link)— 获取对 queue 节点数据结构的开头,考虑 它

一个例子:

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;

要将树作为一个整体进行处理,您需要两个节点:root 和 sentinel。 通常,它们会添加到自定义结构中,从而允许您 将数据组织到一个树中,其中的叶子包含指向或嵌入的链接 您的数据。

要初始化树:

my_tree_t  root;

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

要遍历树并插入新值,请使用 “” 函数。 例如,函数 dealing 替换为 type. 它的参数是指向插入的根节点的指针,即新创建的 node 和 tree sentinel。insert_valuengx_str_rbtree_insert_valuengx_str_t

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

遍历非常简单,可以用 以下 lookup 函数模式:

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

该函数是一个经典的比较函数,它 返回小于、等于或大于零的值。 为了加快查找速度并避免比较可能很大的用户对象,可以使用整数 hash 字段。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

在初始化哈希之前,您需要知道它将要初始化的元素数 hold 以便 nginx 可以最佳地构建它。 需要配置的两个参数是 和 ,详见单独的文档。 它们通常由用户配置。 哈希初始化设置与类型一起存储,哈希本身为:max_sizebucket_sizengx_hash_init_tngx_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;

这是指向创建哈希的函数的指针 字符串的 Integer 键。 有两个通用的密钥创建函数:和 。 后者将字符串转换为所有小写字符,因此传递的字符串 必须是可写的。 如果不是真的,请传递标志 添加到函数中,初始化 Key 数组(见下文)。keyngx_hash_key(data, len)ngx_hash_key_lc(data, len)NGX_HASH_READONLY_KEY

哈希键存储在 和 初始化为 : 第二个参数 () 控制资源量 preallocated 的 ,可以是 或 。 如果您希望哈希包含数千个 元素。ngx_hash_keys_arrays_tngx_hash_keys_array_init(arr, type)typeNGX_HASH_SMALLNGX_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);

如果 or 参数不够大,则函数将失败。max_sizebucket_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 */
}

通配符匹配

要创建使用通配符的哈希,请使用 type. 它包括上述哈希类型,并具有两个额外的键数组:和 . 基本属性的初始化类似于常规哈希:ngx_hash_combined_tdns_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 模块 通配符语法描述的文档和 匹配算法。

根据添加的键的内容,您最多可能需要初始化三个 键数组:一个用于精确匹配(如上所述),另外两个用于启用 从字符串的 head 或 tail 开始匹配:

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

需要对 keys 数组进行排序,并且必须添加初始化结果 添加到组合哈希值中。 数组的初始化也类似。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)— 从系统堆分配内存 like ,但在 分配。ngx_alloc()
  • ngx_memalign(alignment, size, log)— 分配对齐的内存 从系统堆中。 这是提供该功能的那些平台上的包装器。 否则,实现将回退到哪个 提供最大对齐方式。posix_memalign()ngx_alloc()
  • ngx_free(p)— 释放分配的内存。 这是一个包装器free()

大多数 nginx 分配都是在池中完成的。 当 nginx 池中分配的内存 摧毁。 这提供了良好的分配性能,并使内存控制变得容易。

池在内部以连续的内存块为单位分配对象。 一旦一个块满了,就会分配一个新的块并添加到池内存中 阻止列表。 当请求的分配太大而无法放入区块时,请求 转发到系统分配器,并且 返回的 Pointer 存储在池中以供进一步释放。

nginx 池的类型为 . 支持以下作:ngx_pool_t

  • ngx_create_pool(size, log)- 创建具有指定 块大小。 返回的 pool 对象也在池中分配。 应至少为 和 的倍数。sizeNGX_MIN_POOL_SIZENGX_POOL_ALIGNMENT
  • ngx_destroy_pool(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);

链式链接 () 在 nginx 中被积极使用, 因此,nginx 池实现提供了一种重用它们的方法。 的字段保留 以前分配的可供重用的链接列表。 为了在池中有效分配链环,请使用函数。 此函数在矿池列表中查找空闲的链链接,并分配一个新的 链链接(如果池列表为空)。 要释放链接,请调用该函数。ngx_chain_tchainngx_pool_tngx_alloc_chain_link(pool)ngx_free_chain(pool, cl)

清理处理程序可以在池中注册。 清理处理程序是一个带有参数的回调,当 pool 为 摧毁。 池通常与特定的 nginx 对象(如 HTTP 请求)相关联,并且 在对象达到其生命周期结束时销毁。 注册池清理是释放资源的便捷方法,请关闭 文件描述符或对与 main 对象。

要注册池清理,请调用 ,这将返回指向 由调用方填写。 使用参数为清理分配上下文 处理器。ngx_pool_cleanup_add(pool, size)ngx_pool_cleanup_tsize

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 使用共享内存在进程之间共享公共数据。 该函数将 Cycle 的新 Shared Memory 条目。 该函数接收区域的 and。 每个共享区域必须具有唯一的名称。 如果具有 provided 且已存在的共享区域条目,则重用现有区域条目。 如果具有相同名称的现有条目具有 different 标签。 通常,模块结构的地址作为 传递,从而可以在 1 个 nginx 模块。ngx_shared_memory_add(cf, name, size, tag)ngx_shm_zone_tnamesizenametagtag

共享内存条目结构具有 以下字段:ngx_shm_zone_t

  • init— 初始化回调,在共享区域之后调用 映射到实际内存
  • data— 数据上下文,用于将任意数据传递给回调init
  • noreuse— 禁止从 旧周期
  • tag— 共享区域标签
  • shm— 类型为 Platform specific object ,至少具有以下字段:ngx_shm_t
    • addr— 映射的共享内存地址,最初为 NULL
    • size— 共享内存大小
    • name— 共享内存名称
    • log— 共享内存日志
    • exists— 指示共享内存已继承的标志 从主进程(特定于 Windows)

共享区域条目在解析配置后映射到实际内存。 在 POSIX 系统上,syscall 用于创建 共享匿名映射。 在 Windows 上,使用 / 对。ngx_init_cycle()mmap()CreateFileMapping()MapViewOfFileEx()

为了在共享内存中分配,nginx 提供了 slab 池类型。 在每个共享的 nginx 中自动创建一个用于分配内存的 slab 池 区。 该池位于共享区域的开头,可通过以下方式访问 表达式 . 要在共享区域中分配内存,请调用 或 。 要释放内存,请调用 。ngx_slab_pool_t(ngx_slab_pool_t *) shm_zone->shm.addrngx_slab_alloc(pool, size)ngx_slab_calloc(pool, size)ngx_slab_free(pool, p)

Slab 池将所有共享区域划分为多个页面。 每个页面都用于分配相同大小的对象。 指定的大小必须是 2 的幂,并且大于 8 字节。 不符合项的值将向上舍入。 每个页面的位掩码跟踪哪些块正在使用,哪些块是免费的 分配。 对于大于半页(通常为 2048 字节)的大小,分配为 一次完成一整页

要保护共享内存中的数据免受并发访问,请使用互斥锁 在 字段中可用。 slab 池在分配和释放时最常使用互斥锁 memory,但它可用于保护分配的任何其他用户数据结构 在共享区域中。 要锁定或解锁互斥锁,请分别调用 或 。mutexngx_slab_pool_tngx_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 使用对象。 nginx logger 支持多种类型的输出:ngx_log_t

  • stderr — 记录到标准错误 (stderr)
  • file — 记录到文件
  • syslog — 记录到 syslog
  • memory — 记录到内部内存存储以进行开发;记忆 稍后可以使用调试器访问

Logger 实例可以是 Logger 链,通过 字段。 在这种情况下,每条消息都会写入链中的所有 Logger。next

对于每个记录器,严重性级别控制将哪些消息写入 log (仅记录分配了该级别或更高级别的事件)。 支持以下严重性级别:

  • 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

通常,Logger 是由现有的 nginx 代码从指令创建的,并且几乎在每个阶段都可用 在 cycle、configuration、client connection 和其他对象中处理。error_log

Nginx 提供了以下日志宏:

  • ngx_log_error(level, log, err, fmt, ...)— 错误日志记录
  • ngx_log_debug0(level, log, err, fmt)等 — 调试 使用最多 8 个支持的格式化参数进行日志记录ngx_log_debug1(level, log, err, fmt, arg1)

日志消息在堆栈大小为 (当前为 2048 字节) 的缓冲区中格式化。 消息前面有严重性级别、进程 ID (PID)、连接 ID(存储在 )和系统错误文本。 对于非调试消息,还会调用 在日志消息前面加上更具体的信息。 HTTP 模块将函数设置为日志 handler 记录客户端和服务器地址、当前作(存储在 )、客户端请求行、服务器名称等。NGX_MAX_ERROR_STRlog->connectionlog->handlerngx_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

周期

cycle 对象存储从特定 配置。 其类型为 。 当前周期由全局 变量,并由 nginx worker 在启动时继承。 每次重新加载 nginx 配置时,都会从 新的 nginx 配置;旧的 cycle 通常在新的 cycle 之后被删除 已成功创建。ngx_cycle_tngx_cycle

一个循环由函数创建,该函数 将前一个循环作为其参数。 该函数定位到上一个 cycle 的配置文件,并继承为 尽可能多地使用上一个周期的资源。 一个名为 “init cycle” 的占位符循环被创建为 nginx start,然后是 替换为根据 Configuration 构建的实际周期。ngx_init_cycle()

该周期的成员包括:

  • pool— 自行车池。 为每个新周期创建。
  • log— 循环日志。 最初继承自 old cycle,它被设置为在读取 configuration 后指向。new_log
  • new_log— 由配置创建的周期日志。 它受 root-scope 指令的影响。error_log
  • connections, — 类型的连接数组 ,由 创建 事件模块。 nginx 配置中的指令 设置 Number of Connections 。connection_nngx_connection_tworker_connectionsconnection_n
  • free_connections, — 当前可用的列表和数量 连接。 如果没有可用的连接,nginx worker 将拒绝接受新客户端 或连接到上游服务器。free_connection_n
  • files, — 映射文件的数组 Descriptors 添加到 nginx 连接。 此映射由事件模块使用,具有标志(当前为 和 )。files_nNGX_USE_FD_EVENTpolldevpoll
  • conf_ctx— 核心模块配置的数组。 配置是在读取 nginx 配置期间创建和填充的 文件。
  • modules, — 模块数组 类型 ,包括 static 和 dynamic,由 当前配置。modules_nngx_module_t
  • listening— 类型的侦听对象数组。 侦听对象通常由调用该函数的不同模块的指令添加。 侦听套接字是基于侦听对象创建的。ngx_listening_tlistenngx_create_listening()
  • paths— 类型的路径数组 。 通过从 模块,这些模块将对某些目录进行作。 这些目录是 nginx 在读取配置后创建的(如果缺少)。 此外,可以为每个路径添加两个处理程序:ngx_path_tngx_add_path()
    • path loader — 在启动或重新加载后 60 秒内仅执行一次 nginx 的 正常情况下,loader 会读取目录并将数据存储在 nginx 共享 记忆。 该处理程序是从专用的 nginx 进程 “nginx cache loader” 中调用的。
    • path manager — 定期执行。 通常,管理器会从目录中删除旧文件并更新 nginx memory 来反映更改。 处理程序是从专用的 “nginx cache manager” 进程调用的。
  • open_files— 通过调用函数 创建的类型为 的打开文件对象的列表。 目前,nginx 使用这种打开的文件进行日志记录。 读取配置后,nginx 会打开列表中的所有文件,并将每个文件描述符存储在 object 的字段。 文件以追加模式打开,如果缺少,则创建这些文件。 列表中的文件在收到 nginx worker 的 reopen 信号(最常见)。 在这种情况下,字段中的描述符将更改为 new 值。ngx_open_file_tngx_conf_open_file()open_filesfdUSR1fd
  • shared_memory— 共享内存区域列表,每个区域由 调用函数。 共享区域在所有 nginx 进程中都映射到相同的地址范围,并且 用于共享公共数据,例如 HTTP 缓存内存树。ngx_shared_memory_add()

缓冲区

对于输入/输出作,nginx 提供 buffer type 。 通常,它用于保存要写入目标或从 源。 缓冲区可以引用内存或文件中的数据,从技术上讲,它是 缓冲区可以同时引用两者。 缓冲区的内存是单独分配的,与缓冲区无关 结构。ngx_buf_tngx_buf_t

该结构包含以下字段:ngx_buf_t

  • start, — 内存的边界 块。end
  • pos, — 内存的边界 缓冲区;通常是 ...laststartend
  • file_pos, — 的边界 file 缓冲区,表示为距文件开头的偏移量。file_last
  • tag— 用于区分缓冲区的唯一值;创建者 不同的 nginx 模块,通常是为了缓冲区重用。
  • file— 文件对象。
  • temporary— 指示缓冲区引用 可写内存。
  • memory— 指示缓冲区引用只读的标志 记忆。
  • in_file- 指示缓冲区引用数据的标志 在文件中。
  • flush— 指示缓冲区之前的所有数据的标志 需要冲洗。
  • recycled— 指示缓冲区可以重复使用的标志,以及 需要尽快食用。
  • sync— 指示缓冲区不携带任何数据或 特殊信号,如 或 。 默认情况下,nginx 将此类缓冲区视为错误条件,但此标志告诉 nginx 跳过错误检查。flushlast_buf
  • last_buf— 指示缓冲区是 中的最后一个 输出。
  • last_in_chain— 指示没有更多数据的标志 buffers 中的请求或子请求。
  • shadow— 对另一个(“阴影”)缓冲区的引用 当前缓冲区,通常是指缓冲区使用来自 影子。 当缓冲区被消耗时,阴影缓冲区通常也被标记为 消耗。
  • last_shadow— 指示缓冲区是最后一个缓冲区的标志 一个引用特定阴影缓冲区的缓冲区。
  • temp_file— 指示缓冲区位于临时 文件。

对于 input 和 output作,缓冲区以链的形式链接。 链是 类型的链节序列 , 定义如下: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, , , — I/O作 对于连接。sendrecv_chainsend_chain
  • pool— 连接池。
  • log— 连接日志。
  • sockaddr, , — 二进制和文本形式的远程套接字地址。socklenaddr_text
  • local_sockaddr, — 本地 套接字地址。 最初,这些字段为空。 使用该函数获取 local 套接字地址。local_socklenngx_connection_local_sockaddr()
  • proxy_protocol_addr, - PROXY 协议客户端地址和端口(如果启用了 PROXY 协议) 联系。proxy_protocol_port
  • ssl— 连接的 SSL 上下文。
  • reusable— 指示连接处于 使其符合重用条件。
  • close— 指示正在重复使用连接的标志 并且需要关闭。

nginx 连接可以透明地封装 SSL 层。 在这种情况下,连接的字段包含指向结构的指针,保留所有与 SSL 相关的数据 对于连接,包括 和 。 、 和 处理程序 是 设置为 SSL 启用的函数。sslngx_ssl_connection_tSSL_CTXSSLrecvsendrecv_chainsend_chain

nginx 配置中的指令 限制每个 nginx 工作线程的连接数。 所有连接结构都是在 worker 启动时预先创建的,并存储在 Cycle 对象的字段。 要检索连接结构,请使用 function. 它采用一个套接字描述符作为其参数,该描述符需要 包装在连接结构中。worker_connectionsconnectionsngx_get_connection(s, log)s

由于每个 worker 的连接数是有限的,因此 nginx 提供了一个 获取当前正在使用的连接的方法。 要启用或禁用连接的重用,请调用该函数。 调用 在连接结构中设置标志,并将 连接进入的循环。 每当发现没有 循环列表中的可用连接, 它调用以释放 特定数量的可重用连接。 对于每个这样的连接,都会设置标志并读取其 handler 被调用,它应该通过调用来释放连接并使其可供重用。 调用 To exit 状态,当连接可以重用时。 HTTP 客户端连接是 nginx 中可重用连接的一个示例;他们 标记为可重用,直到从客户端收到第一个请求字节。ngx_reusable_connection(c, reusable)ngx_reusable_connection(c, 1)reusereusable_connections_queuengx_get_connection()free_connectionsngx_drain_connections()closengx_close_connection(c)ngx_reusable_connection(c, 0)

事件

事件

nginx 中的 Event 对象提供了一种机制 用于通知已发生特定事件。ngx_event_t

中的字段包括:ngx_event_t

  • data— 事件处理程序中使用的任意事件上下文, 通常作为指向与事件相关的连接的指针。
  • handler— 事件发生时要调用的回调函数 发生。
  • write— 指示写入事件的标志。 缺少标志表示读取事件。
  • active— 指示事件已注册的标志 接收 I/O 通知,通常来自通知机制,如 、 、 。epollkqueuepoll
  • ready— 指示事件已收到 I/O 通知。
  • delayed— 指示 I/O 因速率而延迟的标志 限制。
  • timer— 用于将事件插入到的红黑树节点 计时器树。
  • timer_set— 指示已设置事件计时器的标志,以及 尚未过期。
  • timedout— 指示事件计时器已过期的标志。
  • eof— 指示读取数据时发生 EOF 的标志。
  • pending_eof— 指示 EOF 在 套接字,即使之前可能有一些可用的数据。 标志通过 event 或 flag 传递。EPOLLRDHUPepollEV_EOFkqueue
  • error— 指示在 读取(对于读取事件)或写入(对于写入事件)。
  • cancelable— 计时器事件标志,指示事件 在关闭 worker 时应该忽略。 正常 worker 关闭被延迟,直到没有不可取消的计时器 活动安排。
  • posted— 指示事件已发布到队列的标志。
  • queue— 用于将事件发布到队列的 Queue 节点。

I/O 事件

通过调用函数获得的每个连接都有两个附加事件 和 ,用于接收 套接字已准备好进行读取或写入。 所有这些事件都在 Edge-Triggered 模式下运行,这意味着它们只触发 通知。 例如,对套接字进行部分读取不会使 nginx 提供 重复读取通知,直到更多数据到达套接字。 即使底层 I/O 通知机制本质上是 Level-Triggered (等)、nginx 将通知转换为 Edge-Triggered。 使 nginx 事件通知在所有通知系统中保持一致 在不同平台上,函数 和 必须在 处理 I/O 套接字通知或调用该套接字上的任何 I/O 函数。 通常,这些函数在每次读取或写入结束时调用一次 事件处理程序。ngx_get_connection()c->readc->writepollselectngx_handle_read_event(rev, flags)ngx_handle_write_event(wev, lowat)

计时器事件

可以将事件设置为在超时到期时发送通知。 事件使用的计时器计算自某个未指定点以来的毫秒数 在过去截断为 type。 其当前值可以从变量中获取。ngx_msec_tngx_current_msec

该函数为 事件,删除以前设置的超时。 全局超时 red-black 树存储当前设置的所有超时。 树中的键是 type 和 time 事件发生时。 树结构支持快速插入和删除作,以及 访问最近的超时,nginx 使用它来了解等待多长时间 用于 I/O 事件和过期超时事件。ngx_add_timer(ev, timer)ngx_del_timer(ev)ngx_event_timer_rbtreengx_msec_t

已发布的事件

可以发布事件,这意味着其处理程序将在某个 point 的 intent 值。 发布事件是简化代码和转义堆栈的好做法 溢出。 已发布的事件保存在 post 队列中。 宏将事件发布到 post queue 。 该宏将从当前发布的队列中删除事件。 通常,事件会发布到队列中。 它在事件循环的后期处理 — 在所有 I/O 和 timer 之后 事件已处理。 调用该函数以处理 事件队列。 它调用事件处理程序,直到队列不为空。 这意味着 posted 事件处理程序可以发布更多要处理的事件 在当前事件循环迭代中。ngx_post_event(ev, q)evqngx_delete_posted_event(ev)evngx_posted_eventsngx_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 主进程将大部分时间花在调用中,等待信号到达。 nginx 事件循环在函数中实现,该函数称为 重复,直到进程退出。sigsuspend()ngx_process_events_and_timers()

事件循环包含以下阶段:

  • 通过调用 查找最接近过期的超时。 此函数在计时器树中找到最左侧的节点,并返回 节点过期前的毫秒数。ngx_event_find_timer()
  • 通过调用特定于事件通知的处理程序来处理 I/O 事件 机制,由 nginx 配置选择。 此处理程序等待至少一个 I/O 事件发生,但只等待下一个 I/O 事件发生 timeout 过期。 当读取或写入事件发生时,将设置标志并调用事件的处理程序。 对于 Linux,处理程序 ,它调用等待 I/O 事件。readyngx_epoll_process_events()epoll_wait()
  • 通过调用 使计时器过期。 计时器树从最左侧的元素迭代到右侧,直到 找到未过期的超时。 对于每个过期的节点,都会设置事件标志, 该标志被重置,事件处理程序被调用ngx_event_expire_timers()timedouttimer_set
  • 通过调用 处理已发布的事件。 该函数会重复从已发布的事件中删除第一个元素 queue 并调用元素的处理程序,直到队列为空。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— 处理客户端 连接。 它由主进程启动并响应其信号和通道 命令。 它的循环函数是 。 可以有多个 worker 进程,如指令所配置的那样。ngx_worker_process_cycle()worker_processes

  • NGX_PROCESS_SINGLE— 单个进程,它仅存在于 mode 中,并且是唯一在 那个模式。 它创建周期(就像主进程一样)并处理客户端连接 (就像 worker 进程一样)。 它的循环函数是 。master_process offngx_single_process_cycle()

  • NGX_PROCESS_HELPER— 帮助程序进程,当前为 有两种类型:缓存管理器和缓存加载器。 两者的循环函数都是 。ngx_cache_manager_process_cycle()

nginx 进程处理以下信号:

  • NGX_SHUTDOWN_SIGNAL (SIGQUIT在大多数 systems) — 正常关闭。 收到此信号后,主进程向所有 子进程。 当没有剩余的子进程时,主进程销毁循环池并退出。 当 worker 进程收到此信号时,它会关闭所有侦听套接字,并且 等待没有安排不可取消的事件,然后销毁 cycle pool 并退出。 当缓存管理器或缓存加载器进程收到此信号时,它会 立即退出。 该变量设置为 process 接收到此信号,并在处理后立即重置。 该变量设置为 while 工作进程处于 SHUTDOWN 状态。ngx_quit1ngx_exiting1

  • NGX_TERMINATE_SIGNAL (SIGTERM在大多数 systems) — 终止。 收到此信号后,主进程会向所有 子进程。 如果子进程在 1 秒内未退出,则主进程会发送信号以终止它。 当没有子进程时,主进程销毁循环池,并且 出口。 当 worker 进程、cache manager 进程或 cache loader 进程 收到这个信号后,它会销毁 Cycle Pool 并退出。 该变量设置为接收到此信号的时间。SIGKILLngx_terminate1

  • NGX_NOACCEPT_SIGNAL (SIGWINCH在大多数 systems) - 关闭所有工作进程和帮助程序进程。 收到此信号后,主进程将关闭其子进程。 如果之前启动的新 nginx 二进制文件退出,则旧 master 的 master 实例。 当工作进程收到此信号时,它会在调试模式下关闭 由指令设置。debug_points

  • NGX_RECONFIGURE_SIGNAL (SIGHUP在大多数 systems) - 重新配置。 收到此信号后,主进程会重新读取配置,并且 基于它创建一个新的循环。 如果新周期创建成功,则旧周期将被删除,新周期 子进程启动。 同时,旧的子进程接收到信号。 在单进程模式下,nginx 会创建一个新的循环,但会保留旧的循环,直到 不再有与之绑定的 Active Connections 的客户端。 worker 和 helper 进程会忽略此信号。NGX_SHUTDOWN_SIGNAL

  • NGX_REOPEN_SIGNAL (SIGUSR1在大多数 systems) — 重新打开文件。 主进程将此信号发送给 worker,worker 会重新打开与周期相关的所有内容。open_files

  • NGX_CHANGEBIN_SIGNAL (SIGUSR2在大多数 systems) — 更改 nginx 二进制文件。 主进程启动一个新的 nginx 二进制文件,并传入一个 all listen 插座。 在环境中传递的 text-format 列表 variable,由用分号分隔的描述符编号组成。 新的 nginx 二进制文件读取变量并添加 sockets 添加到其 init 循环中。 其他进程会忽略此信号。“NGINX”“NGINX”

虽然所有 nginx 工作进程都能够接收并正确处理 POSIX signals,则主进程不会使用标准 syscall 将信号传递给 worker 和 helpers。 相反,nginx 使用允许发送消息的进程间套接字对 在所有 nginx 进程之间。 但是,目前,消息仅从 master 发送到其子项。 这些消息携带标准信号。kill()

线程

可以将任务卸载到单独的线程中,否则 阻止 nginx 工作进程。 例如,可以将 nginx 配置为使用线程来执行文件 I/O。 另一个用例是没有异步接口的库 因此通常不能与 nginx 一起使用。 请记住,threads 接口是现有 异步处理客户端连接的方法,绝不是 旨在作为替代品。

为了处理同步,可以使用以下 primitives 包装器:pthreads

  • typedef pthread_mutex_t ngx_thread_mutex_t;
    • ngx_int_t ngx_thread_mutex_create(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_destroy(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_lock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
    • ngx_int_t ngx_thread_mutex_unlock(ngx_thread_mutex_t *mtx, ngx_log_t *log);
  • typedef pthread_cond_t ngx_thread_cond_t;
    • ngx_int_t ngx_thread_cond_create(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_destroy(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_signal(ngx_thread_cond_t *cond, ngx_log_t *log);
    • ngx_int_t ngx_thread_cond_wait(ngx_thread_cond_t *cond, ngx_thread_mutex_t *mtx, ngx_log_t *log);

nginx 不是为每个任务创建一个新线程,而是实现 thread_pool策略。 可以针对不同目的配置多个线程池 (例如,在不同的磁盘集上执行 I/O)。 每个线程池都是在启动时创建的,并且包含有限数量的线程 处理任务队列。 任务完成后,将调用预定义的完成处理程序。

头文件包含 相关定义:src/core/ngx_thread_pool.h

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

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

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

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

在配置时,愿意使用 threads 的模块必须获取 通过调用 来引用线程池,这会创建一个 new thread pool 替换为给定的 or 返回一个引用 添加到具有该名称的池(如果已存在)。ngx_thread_pool_add(cf, name)name

要在运行时将 a 添加到指定线程池的队列中,请使用函数。 要在线程中执行函数,请传递参数并设置完成 handler 使用结构:tasktpngx_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 模块都位于一个单独的目录中,其中包含 至少两个文件:和一个包含模块源代码的文件。 该文件包含 nginx 所需的所有信息 集成模块,例如:configconfig

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

该文件是一个 POSIX shell 脚本,可以设置 并访问以下变量:config

  • ngx_module_type— 要构建的模块类型。 可能的值为 、 、 、 、 或 。COREHTTPHTTP_FILTERHTTP_INIT_FILTERHTTP_AUX_FILTERMAILSTREAMMISC
  • ngx_module_name- 模块名称。 要从一组源文件构建多个模块,请指定 以空格分隔的名称列表。 first name 表示动态模块的输出二进制文件的名称。 列表中的名称必须与源代码中使用的名称匹配。
  • ngx_addon_name- 输出中显示的模块名称 在控制台上,从 configure 脚本。
  • ngx_module_srcs— 以空格分隔的源列表 用于编译模块的文件。 该变量可用于表示路径 添加到模块目录中。$ngx_addon_dir
  • ngx_module_incs— 包括构建模块所需的路径
  • ngx_module_deps— 模块的 依赖。 通常,它是头文件列表。
  • ngx_module_libs— 以空格分隔的库列表 与模块链接。 例如,用于链接库。 以下宏可用于针对与 nginx:、、、、 和 .ngx_module_libs=-lpthreadlibpthreadLIBXSLTLIBGDGEOIPPCREOPENSSLMD5SHA1ZLIBPERL
  • ngx_module_link— 由构建系统设置为动态模块或静态模块的变量,用于确定要执行的不同作 取决于链接类型。DYNAMICADDON
  • ngx_module_order— 模块的加载顺序; 对 和 模块类型很有用。 此选项的格式是以空格分隔的模块列表。 列表中当前模块名称后面的所有模块都位于 模块的全局列表,用于设置模块初始化的顺序。 对于 filter 模块,较晚的初始化意味着较早的执行。HTTP_FILTERHTTP_AUX_FILTER

    以下模块通常用作参考。 读取其他 filter modules 的 fragment 模块,并放置在列表底部附近,以便它是 第一个被执行的。 将数据写入 client 套接字,它位于列表顶部附近,并且是最后一个 执行。ngx_http_copy_filter_modulengx_http_write_filter_module

    默认情况下,过滤器模块放在模块列表的 之前,以便过滤器 handler 在 Copy Filter 处理程序之后执行。 对于其他模块类型,默认值为空字符串。ngx_http_copy_filter

要将模块静态编译到 nginx 中,请使用 configure 脚本。 要编译模块以便以后动态加载到 nginx 中,请使用参数。--add-module=/path/to/module--add-dynamic-module=/path/to/module

核心模块

模块是 nginx 的构建块,它的大部分功能是 作为模块实现。 模块源文件必须包含 type 的全局变量,其定义如下: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

每个模块都将其私有数据保存在字段中, 识别数组中指定的配置指令,并且可以在 nginx 生命周期。 模块生命周期由以下事件组成:ctxcommands

  • 配置指令处理程序在出现时调用 在主进程上下文中的配置文件中。
  • 成功解析配置后,将在主进程的上下文中调用 handler。 处理程序在每个主进程中调用 加载配置的时间。init_moduleinit_module
  • 主进程创建一个或多个工作进程,并在每个工作进程中调用处理程序。init_process
  • 当工作进程收到来自 master,它会调用处理程序。exit_process
  • 主进程在 退出。exit_master

因为线程在 nginx 中仅用作补充 I/O 工具,其 自己的 API,并且当前未调用处理程序。 也没有处理程序,因为它将是 不必要的开销。init_threadexit_threadinit_master

该模块准确定义了字段中存储的内容。 其值为以下类型之一:typectx

  • NGX_CORE_MODULE
  • NGX_EVENT_MODULE
  • NGX_HTTP_MODULE
  • NGX_MAIL_MODULE
  • NGX_STREAM_MODULE

这是最基本的,因此也是最 通用和大多数最低级类型的模块。 其他模块类型是在它之上实现的,并提供了更多的 处理相应域的便捷方式,如处理事件或 HTTP 请求。NGX_CORE_MODULE

核心模块集包括 、 、 和 模块。 HTTP 模块、stream 模块、mail 模块和 event 模块是核心模块 模块也是如此。 核心模块的上下文定义为:ngx_core_modulengx_errlog_modulengx_regex_modulengx_thread_pool_modulengx_openssl_module

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;

其中 this 是模块名称字符串,是指向创建和初始化模块配置的函数的指针 分别。 对于核心模块,nginx 在解析之前调用 新配置和毕竟配置 解析成功。 典型的函数为 configuration 并设置默认值。namecreate_confinit_confcreate_confinit_confcreate_conf

例如,一个名为 might 的简单模块 如下所示: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;
}

配置指令

type 定义单个配置 命令。 每个支持配置的模块都提供了一组这样的结构 描述如何处理参数以及要调用哪些处理程序: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;
};

用特殊值 . 终止数组 . 这是指令的名称 在配置文件中,例如 “worker_processes” 或 “listen”。 是标志的位字段,用于指定 指令采用的参数、其类型以及它出现的上下文。 标志是:ngx_null_commandnametype

  • NGX_CONF_NOARGS— 指令不接受任何参数。
  • NGX_CONF_1MORE— 指令接受一个或多个参数。
  • NGX_CONF_2MORE— 指令接受两个或多个参数。
  • NGX_CONF_TAKE1..— Directive 正好接受指定数量的参数。NGX_CONF_TAKE7
  • NGX_CONF_TAKE12、、、、— 指令可以采用不同数量的 参数。 选项仅限于给定的数字。 例如,表示需要 1 或 2 个 参数。NGX_CONF_TAKE13NGX_CONF_TAKE23NGX_CONF_TAKE123NGX_CONF_TAKE1234NGX_CONF_TAKE12

指令类型的标志是:

  • NGX_CONF_BLOCK— 指令是一个区块,也就是说,它可以 在其左括号和右括号中包含其他指令,甚至 实现自己的解析器来处理其中的内容。
  • NGX_CONF_FLAG— 指令采用布尔值 或 .onoff

指令的 context 定义了它可能出现在配置中的位置:

  • NGX_MAIN_CONF— 在顶级上下文中。
  • NGX_HTTP_MAIN_CONF— 在街区。http
  • NGX_HTTP_SRV_CONF— 在区块中 在区块内。serverhttp
  • NGX_HTTP_LOC_CONF— 在区块中 在区块内。locationhttp
  • NGX_HTTP_UPS_CONF— 在区块中 在区块内。upstreamhttp
  • NGX_HTTP_SIF_CONF— 在 块中的块。ifserverhttp
  • NGX_HTTP_LIF_CONF— 在 块中的块。iflocationhttp
  • NGX_HTTP_LMT_CONF— 在块内的块中。limit_excepthttp
  • NGX_STREAM_MAIN_CONF— 在街区。stream
  • NGX_STREAM_SRV_CONF— 在区块中 在区块内。serverstream
  • NGX_STREAM_UPS_CONF— 在区块中 在区块内。upstreamstream
  • NGX_MAIL_MAIN_CONF— 在街区。mail
  • NGX_MAIL_SRV_CONF— 在区块中 在区块内。servermail
  • NGX_EVENT_CONF— 在街区。event
  • NGX_DIRECT_CONF— 由不 创建上下文的层次结构,并且只有一个全局配置。 此配置作为参数传递给处理程序。conf

配置解析器使用这些标志在 放错位置的指令并调用指令处理程序,并提供适当的 configuration 指针,以便不同位置的相同指令可以 将它们的值存储在不同的地方。

该字段定义处理指令的处理程序 并将解析的值存储到相应的配置中。 有许多函数可以执行常见的转换:set

  • ngx_conf_set_flag_slot— 将文本字符串 和 分别转换为值为 1 或 0 的值。onoffngx_flag_t
  • ngx_conf_set_str_slot— 将字符串存储为类型的值。ngx_str_t
  • ngx_conf_set_str_array_slot— 将值附加到字符串数组 。 如果 尚不存在,则创建该数组。ngx_array_tngx_str_t
  • ngx_conf_set_keyval_slot— 将键值对附加到 键值对数组 。 第一个字符串成为键,第二个字符串成为值。 如果数组尚不存在,则创建该数组。ngx_array_tngx_keyval_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— 转换提供的参数 转换为值。 字段中传入的以 null 结尾的数组定义可接受的字符串和相应的 整数值。ngx_uint_tngx_conf_enum_tpost
  • ngx_conf_set_bitmask_slot— 转换提供的参数 转换为值。 每个参数的掩码值都经过 OR 运算,从而生成结果。 字段中传入的以 null 结尾的数组定义可接受的字符串和相应的 mask 值。ngx_uint_tngx_conf_bitmask_tpost
  • set_path_slot— 将提供的参数转换为值并执行所有必需的初始化。 有关详细信息,请参阅 proxy_temp_path 指令的文档。ngx_path_t
  • set_access_slot— 将提供的参数转换为文件 权限掩码。 有关详细信息,请参阅 proxy_store_access 指令的文档。

该字段定义哪个配置结构 传递给目录处理程序。 核心模块只有全局配置和 set 标志来访问它。 HTTP、Stream 或 Mail 等模块会创建配置的层次结构。 例如,为 和 范围创建模块的配置。confNGX_DIRECT_CONFserverlocationif

  • NGX_HTTP_MAIN_CONF_OFFSET— 块的配置。http
  • NGX_HTTP_SRV_CONF_OFFSET— 块中块的配置。serverhttp
  • NGX_HTTP_LOC_CONF_OFFSET— 块的配置 .locationhttp
  • NGX_STREAM_MAIN_CONF_OFFSET— 块的配置。stream
  • NGX_STREAM_SRV_CONF_OFFSET— 块中块的配置。serverstream
  • NGX_MAIL_MAIN_CONF_OFFSET— 块的配置。mail
  • NGX_MAIL_SRV_CONF_OFFSET— 块中块的配置。servermail

定义模块中字段的偏移量 配置结构,该结构保存此特定指令的值。 典型用途是使用宏。offsetoffsetof()

该字段有两个用途:它可以用来定义 在主处理程序完成后调用的处理程序,或将 additional data 添加到主处理程序。 在第一种情况下,结构需要 使用指向处理程序的指针进行初始化,例如:postngx_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 };

参数是对象本身,而 the 是指向值 由具有适当类型的主处理程序从参数转换而来。postngx_conf_post_tdata

HTTP 协议

连接

每个 HTTP 客户端连接都经历以下阶段:

  • ngx_event_accept()接受客户端 TCP 连接。 调用此处理程序是为了响应 listen 套接字上的读取通知。 在此阶段创建新对象 包装新接受的客户端套接字。 每个 nginx 侦听器都提供了一个处理程序,用于将新的 Connection 对象传递给它。 对于 HTTP 连接,它是 .ngx_connection_tngx_http_init_connection(c)
  • ngx_http_init_connection()执行 HTTP 连接。 在此阶段,将为 连接及其引用存储在连接的字段中。 稍后,它将替换为 HTTP 请求对象。 PROXY 协议解析器和 SSL 握手从 这个阶段也是如此。ngx_http_connection_tdata
  • ngx_http_wait_request_handler()读取事件处理程序 当 Client 端套接字上有可用数据时调用。 在此阶段,HTTP 请求对象为 created 并设置为 connection 的字段。ngx_http_request_tdata
  • ngx_http_process_request_line()读取事件处理程序 读取客户端请求行。 处理程序由 设置。 数据被读入连接的 . 缓冲区的大小最初由指令 client_header_buffer_size 设置。 整个 Client 端标头应该适合缓冲区。 如果初始大小不够,则分配更大的缓冲区, 替换为指令设置的容量。ngx_http_wait_request_handler()bufferlarge_client_header_buffers
  • ngx_http_process_request_headers()read 事件处理程序、 设置为 read 客户端请求标头。ngx_http_process_request_line()
  • ngx_http_core_run_phases()在请求头 完全读取和解析。 此函数从 到 运行请求阶段。 最后一个阶段旨在生成响应并将其传递给过滤器 链。 在此阶段,响应不一定发送到客户端。 它可能保持缓冲状态,并在最终确定阶段发送。NGX_HTTP_POST_READ_PHASENGX_HTTP_CONTENT_PHASE
  • ngx_http_finalize_request()通常在 请求已生成所有输出或产生错误。 在后一种情况下,会查找适当的错误页面并将其用作 响应。 如果此时响应尚未完全发送到客户端,则 激活 HTTP 写入器以完成 发送出色的数据。ngx_http_writer()
  • ngx_http_finalize_connection()在完成 响应已发送到客户端,并且可以销毁请求。 如果启用了客户端连接 keepalive 功能,则会调用 ,这会销毁 current 请求,并等待连接上的下一个请求。 否则,会同时销毁 请求和连接。ngx_http_set_keepalive()ngx_http_close_request()

请求

对于每个客户端 HTTP 请求,对象为 创建。此对象的一些字段包括:ngx_http_request_t

  • connection— 指向客户端连接对象的指针。 多个请求可以同时引用同一个连接对象 - 一个主请求及其子请求。 删除请求后,可以在同一连接上创建新请求。ngx_connection_t

    请注意,对于 HTTP connections 的字段指向请求。 此类请求称为 active,而不是与 连接。 活动请求用于处理客户端连接事件,并允许 将其响应输出给客户端。 通常,每个请求都会在某个时候变为活动状态,以便它可以将其 输出。ngx_connection_tdata

  • ctx— HTTP 模块上下文数组。 每个类型的模块都可以存储任何值 (通常是指向结构的指针)。 该值存储在数组中的模块位置。 以下宏提供了一种获取和设置请求上下文的便捷方法:NGX_HTTP_MODULEctxctx_index

    • ngx_http_get_module_ctx(r, module)— 返回 的上下文module
    • ngx_http_set_ctx(r, c, module)— 设置为 的上下文cmodule
  • main_conf, , — 当前请求的数组 配置。 配置存储在模块的位置。srv_confloc_confctx_index
  • read_event_handler, - 读取和写入请求的事件处理程序。 通常,HTTP 连接的 read 和 write 事件处理程序 设置为 . 此函数调用当前 active 请求。write_event_handlerngx_http_request_handler()read_event_handlerwrite_event_handler
  • cache— 请求缓存对象以缓存 upstream 响应。
  • upstream— 请求上游对象以进行代理。
  • pool— 请求池。 请求对象本身在此池中分配,该池在 请求将被删除。 对于需要在整个客户端连接的 lifetime 的 Bean 中,请改用 的 pool。ngx_connection_t
  • header_in— 客户端 HTTP 请求进入的缓冲区 header 被读取。
  • headers_in, — 输入和 output HTTP headers 对象。 这两个对象都包含用于保存 Headers 原始列表的 type 字段。 除此之外,还可以使用特定的标头来获取和设置为 单独的字段,例如 ,等。headers_outheadersngx_list_tcontent_length_nstatus
  • request_body— 客户端请求正文对象。
  • start_sec, — 时间点 请求已创建,用于跟踪请求持续时间。start_msec
  • method, — 数字和文本 客户端 HTTP 请求方法的表示形式。 方法的数值在 中使用宏 、 、 等定义。method_namesrc/http/ngx_http_request.hNGX_HTTP_GETNGX_HTTP_HEADNGX_HTTP_POST
  • http_protocol— 客户端 HTTP 协议版本在其 原始文本形式(“HTTP/1.0”、“HTTP/1.1”等)。
  • http_version— 客户端 HTTP 协议版本 数字形式 (, , etc.)NGX_HTTP_VERSION_10NGX_HTTP_VERSION_11
  • http_major, — 客户端 HTTP 协议版本以数字形式拆分为主要部分和次要部分。http_minor
  • request_line, — 请求行 和 URI 中的请求。unparsed_uri
  • uri, , — 当前请求的 URI、参数和文件扩展名。 此处的 URI 值可能与客户端发送的原始 URI 不同,因为 正常化。 在整个请求处理过程中,这些值可能会作为内部重定向而更改 执行。argsexten
  • main— 指向主请求对象的指针。 创建此对象是为了处理客户端 HTTP 请求,而不是 subrequest 创建,这些请求是为了在主 请求。
  • parent— 指向子请求的父请求的指针。
  • postponed— 输出缓冲区和子请求的列表,位于 发送和创建它们的顺序。 该列表由 delaypone 过滤器用于提供一致的请求输出 当它的一部分是由 subrequest 创建时。
  • post_subrequest— 指向具有上下文的处理程序的指针 在子请求完成时调用。 不可用于主请求。
  • posted_requests— 要启动的请求列表或 resumed,这是通过调用请求的 . 通常,此处理程序保存请求 main 函数,该函数首先运行 请求阶段,然后生成输出。write_event_handler

    请求通常由调用发布。 它始终发布到主请求列表。 该函数运行所有 在传递的 Connection 的活动请求。 所有事件处理程序都调用 , 这可能会导致新的已发布请求。 通常,它是在调用请求的读取或写入处理程序后调用的。ngx_http_post_request(r, NULL)posted_requestsngx_http_run_posted_requests(c)ngx_http_run_posted_requests

  • phase_handler— 当前请求阶段的索引。
  • ncaptures, , — 生成的正则表达式捕获 按请求的最后一个正则表达式匹配项。 在请求处理期间,正则表达式匹配可能发生在多个位置: 映射查找、通过 SNI 或 HTTP Host 查找服务器、重写、proxy_redirect等。 查找生成的捕获存储在上述字段中。 该字段保存捕获的数量,保存捕获边界,并保存正则表达式所针对的字符串 matched 的 和 用于提取捕获。 在每个新的正则表达式匹配后,请求捕获将重置以保存新值。capturescaptures_datancapturescapturescaptures_data
  • count— 请求引用计数器。 该字段仅对主请求有意义。 增加计数器是通过 simple 完成的。 要减小计数器,请调用 。 创建子请求并运行请求正文读取过程 递增计数器。r->main->count++ngx_http_finalize_request(r, rc)
  • subrequests— 当前子请求嵌套级别。 每个子请求都继承其父级的嵌套级别,减少 1。 如果该值达到零,则会生成错误。 主请求的值由 constant 定义。NGX_HTTP_MAX_SUBREQUESTS
  • uri_changes— 剩余的 URI 更改数 请求。 请求可以更改其 URI 的总次数受常量限制。 每次更改时,该值都会递减,直到达到零,此时 生成错误。 重写和内部重定向到正常位置或命名位置被视为 URI 变化。NGX_HTTP_MAX_URI_CHANGES
  • blocked— 请求中保留的区块计数器。 当此值不为零时,无法终止请求。 目前,此值由待处理的 AIO作(POSIX AIO 和 thread作)和 Active Cache Lock 的 Zip S作。
  • buffered— 位掩码显示哪些模块缓冲了 请求生成的输出。 许多过滤器可以缓冲输出;例如,sub_filter 可以缓冲数据 由于部分字符串匹配,复制过滤器可以缓冲数据,因为 缺少空闲输出缓冲区等。 只要此值不为零,请求就不会完成 等待 flush。
  • header_only— 指示输出没有 需要一个 body。 例如,此标志由 HTTP HEAD 请求使用。
  • keepalive— 指示客户端连接是否 支持 keepalive。 该值是从 HTTP 版本和 “Connection” 标头。

  • header_sent— 指示输出标头的标志 已由请求发送。
  • internal— 指示当前请求的标志 是内部的。 要进入 internal 状态,请求必须通过 internal redirect 或 be a subrequest 进行访问。 允许内部请求进入内部位置。
  • allow_ranges— 指示部分响应的标志 可以根据 HTTP Range 标头的请求发送到客户端。
  • subrequest_ranges— 指示部分响应的标志 可以在处理子请求时发送。
  • single_range— 表示只有一个连续的 的输出数据范围可以发送到客户端。 此标志通常在发送数据流时设置,例如从 代理服务器,并且整个响应在一个缓冲区中不可用。
  • main_filter_need_in_memory, — 标志 请求在内存缓冲区而不是文件中生成的输出。 这是向 copy 过滤器发送的信号,用于从文件缓冲区读取数据,即使 sendfile 已启用。 这两个标志之间的区别在于过滤器模块的位置,这些 设置它们。 在过滤器链 set 中的 postpone 过滤器之前调用的过滤器,请求仅当前 请求输出来自内存缓冲区。 稍后在过滤器链 set 中调用的过滤器 ,请求 主请求和所有子请求都读取内存中的文件 在发送输出时。filter_need_in_memoryfilter_need_in_memorymain_filter_need_in_memory
  • filter_need_temporary— 请求请求的标志 output 在临时缓冲区中生成,但不在 readonly 内存缓冲区中生成,或者 文件缓冲区。 这被过滤器使用,过滤器可以直接在缓冲区中更改输出,其中 它已发送。

配置

每个 HTTP 模块可以有三种类型的配置:

  • Main configuration (主配置) - 适用于整个数据块。 用作模块的全局设置。http
  • Server configuration (服务器配置) — 适用于单个数据块。 用作模块的服务器特定设置。server
  • 位置配置 — 适用于单个 或 块。 用作模块的特定位置设置。locationiflimit_except

配置结构是在 nginx 配置阶段由 调用分配结构的函数,初始化它们 并合并它们。 以下示例说明如何创建简单位置 模块的配置。 配置具有一个类型为 的设置 , unsigned 整数。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);
}

如示例中所示,该函数创建一个新的配置结构,并将一个配置与 配置。 事实上,服务器和位置配置不仅仅存在于 server 和 location 级别,但也为它们上方的所有级别创建。 具体来说,还会在主级别创建服务器配置,并且 位置配置是在 Main、Server 和 Location 级别创建的。 这些配置可以指定特定于服务器和位置的 nginx 配置文件的任意级别的设置。 最终,配置被合并下来。 提供了许多宏,例如 和 用于指示缺少的设置并在合并时忽略它。 标准的 nginx 合并宏,如 和 提供了一种方便的方式 合并设置并设置默认值(如果没有任何配置) 提供了一个显式值。 有关不同类型的宏的完整列表,请参阅。ngx_http_foo_create_loc_conf()ngx_http_foo_merge_loc_conf()NGX_CONF_UNSETNGX_CONF_UNSET_UINTngx_conf_merge_value()ngx_conf_merge_uint_value()src/core/ngx_conf_file.h

可以使用以下宏。 用于在配置时访问 HTTP 模块的配置。 它们都将 reference 作为第一个参数。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

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 模块都注册了他们的 phase handlers 作为一种方法 在请求处理的特定阶段被调用。 阶段是连续处理的,阶段处理程序称为 一旦请求到达阶段。 以下是 nginx HTTP 阶段的列表。

  • NGX_HTTP_POST_READ_PHASE— 第一阶段。 ngx_http_realip_module在此阶段注册其处理程序以启用 在调用任何其他模块之前替换客户端地址。
  • NGX_HTTP_SERVER_REWRITE_PHASE— 阶段 重写块中定义的指令 (但在块外部)被处理。 ngx_http_rewrite_module 在此阶段安装其处理程序。serverlocation
  • NGX_HTTP_FIND_CONFIG_PHASE— 特殊阶段 其中,位置是根据请求 URI 选择的。 在此阶段之前,相关虚拟服务器的默认位置 分配给请求,并且请求位置配置的任何模块 接收默认服务器位置的配置。 此阶段为请求分配新位置。 在此阶段不能注册其他处理程序。
  • NGX_HTTP_REWRITE_PHASE— 与 相同,但对于 重写在上一阶段中选择的位置中定义的规则。NGX_HTTP_SERVER_REWRITE_PHASE
  • NGX_HTTP_POST_REWRITE_PHASE— 特殊阶段 其中,如果请求的 URI 发生更改,则请求将重定向到新位置 在重写期间。 这是通过 又。 在此阶段不能注册其他处理程序。NGX_HTTP_FIND_CONFIG_PHASE
  • NGX_HTTP_PREACCESS_PHASE— 不同 类型的处理程序,与访问控制无关。 标准 nginx 模块 ngx_http_limit_conn_module ngx_http_limit_req_module 在此阶段注册其处理程序。
  • NGX_HTTP_ACCESS_PHASE— 验证的阶段 客户端有权发出请求。 标准 nginx 模块(如 ngx_http_access_modulengx_http_auth_basic_module)在此阶段注册其处理程序。 默认情况下,客户端必须通过所有处理程序的授权检查 在此阶段注册,以便请求继续进入下一阶段。 satisfy 指令 可用于允许处理在任何阶段处理程序 授权客户端。
  • NGX_HTTP_POST_ACCESS_PHASE— 处理 satisfy any 指令的特殊阶段。 如果某些访问阶段处理程序拒绝访问,并且没有一个处理程序明确允许访问,则 请求完成。 在此阶段不能注册其他处理程序。
  • NGX_HTTP_PRECONTENT_PHASE— 要调用处理程序的阶段 在生成内容之前。 标准模块(如 ngx_http_try_files_modulengx_http_mirror_module 在此阶段注册其处理程序。
  • NGX_HTTP_CONTENT_PHASE— 响应的阶段 正常生成。 多个 nginx 标准模块在这个阶段注册了它们的处理程序, 包括 ngx_http_index_module 或 . 它们按顺序调用,直到其中一个产生 输出。 还可以基于每个位置设置内容处理程序。 如果ngx_http_core_module的 location 配置已设置,则为 作为内容处理程序调用,并在此阶段安装处理程序 被忽略。ngx_http_static_modulehandler
  • NGX_HTTP_LOG_PHASE— 请求日志记录的阶段 执行。 目前,只有 ngx_http_log_module 注册其处理程序 在此阶段进行访问日志记录。 日志阶段处理程序在请求处理的最后调用 right, right 在释放请求之前。

以下是 preaccess phase 处理程序的示例。

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, — 暂停 阶段处理,直到某个 future 事件(可以是 例如,异步 I/O作或只是延迟。 假设稍后将通过调用 来恢复阶段处理。NGX_DONEngx_http_core_run_phases()
  • 阶段处理程序返回的任何其他值都被视为请求 finalization 代码,特别是 HTTP 响应代码。 使用提供的代码完成请求。

对于某些阶段,返回代码的处理方式略有不同。 在内容阶段,除被视为终结代码之外的任何返回代码。 来自位置内容处理程序的任何返回代码都被视为 finalization 代码。 在 access 阶段,在 satisfy any 模式下, 除 、 、 之外的任何返回代码都被视为拒绝。 如果没有,后续访问处理程序将允许或拒绝使用其他 code,则拒绝代码将成为 finalization code。NGX_DECLINEDNGX_OKNGX_DECLINEDNGX_AGAINNGX_DONE

变量

访问现有变量

变量可以通过 index 引用(这是最常见的方法) 或名称(见下文)。 索引是在配置阶段添加变量时创建的 添加到配置中。 要获取变量索引,请使用 :ngx_http_get_variable_index()

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

index = ngx_http_get_variable_index(cf, &name);

这里是指向 nginx 配置的指针,并指向包含变量 name 的字符串。 该函数返回 on error 或有效索引 否则,它通常存储在模块的 配置以供将来使用。cfnameNGX_ERROR

所有 HTTP 变量都在给定 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— 由日志记录模块内部用于标记 需要在输出时转义的值。

和 函数 用于获取变量的值。 它们具有相同的接口 - 接受 HTTP 请求作为评估变量的上下文和标识变量的上下文。 典型用法示例:ngx_http_get_flushed_variable()ngx_http_get_indexed_variable()rindex

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,需要处理 name 在配置时未知。 因此,索引不能用于访问它们,但函数 可用。 它搜索具有 given 及其派生的哈希的变量 从名称中。ngx_http_get_variable(r, name, key)namekey

创建变量

要创建变量,请使用 function. 它接受一个配置(变量注册的地方)作为参数, 控制函数行为的变量名称和标志:ngx_http_add_variable()

  • NGX_HTTP_VAR_CHANGEABLE— 支持重新定义 变量:如果另一个模块使用 相同的名称。 这允许 set 指令 以覆盖变量。
  • NGX_HTTP_VAR_NOCACHEABLE— 禁用缓存, 这对于 等变量很有用。$time_local
  • NGX_HTTP_VAR_NOHASH— 表示 此变量只能通过 index 访问,而不能通过 name 访问。 这是一个小的优化,当已知 变量在 SSI 或 Perl 等模块中不需要。
  • NGX_HTTP_VAR_PREFIX— 的名称 variable 是前缀。 在这种情况下,处理程序必须实现其他逻辑才能获取值 的特定变量。 例如,所有 “” 变量都由 相同的处理程序,它在请求参数中执行查找并返回值 的特定参数。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;
};

和 处理程序 被调用以获取或设置变量值,传递给变量处理程序,并保存用于引用的已分配变量索引 变量。getsetdataindex

通常,会创建一个以 null 结尾的结构静态数组 由模块进行处理,并在预配置阶段进行处理以添加变量 导入到配置中,例如:ngx_http_variable_t

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 模块上下文的字段,并在解析 HTTP 之前调用 配置,以便解析器可以引用这些变量。preconfiguration

处理程序负责评估变量 在特定请求的上下文中,例如: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;
}

它在出现内部错误时返回 (例如,内存分配失败)或其他方式。 要了解变量评估的状态,请检查标志 in(请参阅上面的描述)。NGX_ERRORNGX_OKngx_http_variable_value_t

处理程序允许设置属性 引用。 例如,变量 修改请求的字段:set$limit_ratelimit_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_valuengx_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;
}

这里,包含所需的所有参数 初始化 complex 值 :ccvcv

  • cf— 配置指针
  • value— 要解析的字符串(输入)
  • complex_value— 编译值(输出)
  • zero— 启用零终止值的标志
  • conf_prefix- 在结果前面加上 configuration prefix (nginx 当前正在查找的目录 配置)
  • root_prefix— 在结果前面加上根前缀 (正常的 nginx 安装前缀)

当结果要传递给 需要以零结尾的字符串和前缀的库在 处理文件名。zero

编译成功后,包含有关变量存在的信息 在表达式中。 NULL 值表示表达式仅包含静态文本 因此可以存储在简单的字符串中,而不是作为复杂的值。cv.lengths

这是一个方便的 用于在指令中完全初始化复杂值的函数 声明本身。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;
}

给定请求和之前编译的 value 时,该函数会计算 expression 并将结果写入 .rcvres

请求重定向

HTTP 请求始终通过结构的字段连接到位置。 这意味着在任何时候,任何模块的位置配置都可以 通过调用 从请求中检索。 请求位置在请求的生命周期内可以多次更改。 最初,默认服务器的默认服务器位置分配给 请求。 如果请求切换到其他服务器(由 HTTP “Host” 标头或 SSL SNI 扩展),则请求会切换到 该服务器的默认位置。 位置的下一次更改发生在请求阶段。 在此阶段,通过请求 URI 在所有非命名位置中选择一个位置 配置。 ngx_http_rewrite_module 可以在请求阶段更改请求 URI,因为 rewrite 指令并将请求发回 到阶段以选择 基于新 URI 的新位置。loc_confngx_http_request_tngx_http_get_module_loc_conf(r, module)NGX_HTTP_FIND_CONFIG_PHASENGX_HTTP_REWRITE_PHASENGX_HTTP_FIND_CONFIG_PHASE

还可以通过以下方式随时将请求重定向到新位置 调用 或 之一。ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name)

函数更改 请求 URI 并将请求返回到阶段。 请求将继续使用服务器默认位置。 稍后在新的位置选择 基于新的请求 URI。ngx_http_internal_redirect(r, uri, args)NGX_HTTP_SERVER_REWRITE_PHASENGX_HTTP_FIND_CONFIG_PHASE

以下示例使用新请求执行内部重定向 参数。

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

函数 redirects 对指定位置的请求。位置的名称作为 论点。 在当前 server,之后请求将切换到该阶段。ngx_http_named_location(r, name)NGX_HTTP_REWRITE_PHASE

以下示例执行到命名位置@foo的重定向。

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

这两个函数 - 和 都可以在 nginx 模块已经在 request 的 field 中存储了一些上下文。 这些上下文可能会与新的 location 配置。 为了防止不一致,所有请求上下文都是 被两个重定向函数擦除。ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name)ctx

调用 或 增加请求 。 为了获得一致的请求引用计数,请在重定向 请求。 这将完成当前请求代码路径并减少计数器。ngx_http_internal_redirect(r, uri, args)ngx_http_named_location(r, name)countngx_http_finalize_request(r, NGX_DONE)

重定向和重写的请求将成为内部请求,并且可以访问内部位置。 内部请求设置了标志。internal

子请求

子请求主要用于将一个请求的输出插入到另一个请求中。 可能与其他数据混合。 子请求看起来与普通请求类似,但与父请求共享一些数据。 特别是,与 Client 端输入相关的所有字段都是共享的 因为 SubRequest 不会从客户端接收任何其他 Input。 子请求的 request 字段包含一个链接 添加到其父请求中,并且对于主请求为 NULL。 该字段包含指向 一组请求。parentmain

子请求在阶段中启动。 它通过与正常请求相同的后续阶段,并且 根据其自己的 URI 分配位置。NGX_HTTP_SERVER_REWRITE_PHASE

子请求中的输出标头始终被忽略。 将子请求的 输出主体相对于生成的其他数据处于正确的位置 通过父请求。ngx_http_postpone_filter

子请求与活动请求的概念相关。 如果满足以下条件,则认为请求处于活动状态,其中 是客户端 connection 对象。 在任何给定时间点,只允许请求组中的活动请求 将其缓冲区输出到客户端。 非活动请求仍然可以将其输出发送到过滤器链,但它 不会超出 和 保持由该过滤器缓冲,直到请求变为活动状态。 以下是请求激活的一些规则:rc->data == rcngx_http_postpone_filter

  • 最初,主请求处于活动状态。
  • 活动请求的第一个子请求在创建后立即变为活动状态。
  • 激活下一个请求 在活动请求的 SubRequest 列表中,该请求之前的所有数据 被发送。ngx_http_postpone_filter
  • 当请求完成时,其父级将被激活。

通过调用函数创建子请求,其中 是父请求,是 subrequest 是 output 参数,它接收 新创建的 SubRequest 引用,是一个回调对象 用于通知父请求子请求正在完成,并且是 flags 的位掩码。 以下标志可用:ngx_http_subrequest(r, uri, args, psr, ps, flags)ruriargspsrpsflags

  • NGX_HTTP_SUBREQUEST_IN_MEMORY- 输出不是 发送到客户端,但存储在内存中。 该标志仅影响由其中一个代理处理的子请求 模块。 完成子请求后,其输出以 type .r->outngx_buf_t
  • NGX_HTTP_SUBREQUEST_WAITED- 在以下情况下,即使子请求未处于活动状态,也会设置子请求的标志 它已经完成。 此子请求标志由 SSI 筛选器使用。done
  • NGX_HTTP_SUBREQUEST_CLONE- 子请求创建为 clone 的 clone 的 clone 的 Clone 的 Clone 的 它从相同的位置开始,并从与 parent 请求。

以下示例使用 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 */
}

此示例克隆当前请求并为 sub请求。

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

子请求通常在 body 过滤器中创建,在这种情况下,它们的输出 可以被视为任何显式请求的输出。 这意味着子请求的输出最终会发送到客户端 在创建子请求之前传递的所有显式缓冲区之后,以及 在创建后传递的任何缓冲区之前。 即使对于子请求的大型层次结构,也会保留此顺序。 以下示例在所有请求数据之后插入子请求的输出 buffers,但在带有标志的最终缓冲区之前。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 模块 在该阶段创建 SubRequest。 要在此时禁用输出,请在 subrequest 上设置该标志。 这可以防止将子请求正文发送到客户端。 请注意,子请求的 Headers 永远不会发送到 client。 可以在回调处理程序中分析子请求的结果。NGX_HTTP_ACCESS_PHASEheader_only

请求最终确定

通过调用函数 . 它通常由内容处理程序在所有输出缓冲区之后完成 发送到过滤器链。 此时,所有输出可能不会发送到客户端。 其中一些仍然缓冲在过滤器链中的某个地方。 如果是,则函数 自动安装特殊处理程序以完成输出发送。 如果出现错误或标准 HTTP 响应,也会完成请求 代码需要返回给客户端。ngx_http_finalize_request(r, rc)ngx_http_finalize_request(r, rc)ngx_http_writer(r)

该函数需要 以下值:ngx_http_finalize_request(r, rc)rc

  • NGX_DONE- 快速定稿。 递减请求并销毁请求,如果它 达到 0。 客户端连接可用于当前请求之后的更多请求 被销毁。count
  • NGX_ERROR、 () - 错误终结。 尽快终止请求并关闭客户端连接。NGX_HTTP_REQUEST_TIME_OUT408NGX_HTTP_CLIENT_CLOSED_REQUEST499
  • NGX_HTTP_CREATED (201)、()、代码更大 than or equal to () - 特殊响应终结。 对于这些值,nginx 会向客户端发送一个默认响应页面 代码或执行内部重定向到error_page位置(如果 配置代码。NGX_HTTP_NO_CONTENT204NGX_HTTP_SPECIAL_RESPONSE300
  • 其他代码被视为成功的完成代码,可能会激活 请求写入器完成响应正文的发送。 正文完全发送后,请求将递减。 如果达到零,则销毁请求,但客户端连接可以 仍用于其他请求。 如果为正数,则表示有未完成的活动 ,稍后将最终确定。countcount

请求正文

为了处理客户端请求的正文,nginx 提供了 and 函数。 第一个函数读取请求正文,并通过 request 字段使其可用。 第二个函数指示 nginx 丢弃(读取和忽略)请求 身体。 必须为每个请求调用其中一个函数。 通常,内容处理程序会进行调用。ngx_http_read_client_request_body(r, post_handler)ngx_http_discard_request_body(r)request_body

不允许从子请求中读取或丢弃客户端请求正文。 它必须始终在主请求中完成。 当创建子请求时,它会继承父级的对象,如果 主请求之前已读取请求正文。request_body

函数启动 读取请求正文的过程。 当正文被完全读取时,回调 以继续处理请求。 如果请求正文缺失或已被读取,则调用 callback 马上。 该函数分配 type 为 的 request 字段。 此对象的字段将结果保留为缓冲区 链。 如果容量 由 client_body_buffer_size 指令指定的值不足以将整个 body 放入内存中。ngx_http_read_client_request_body(r, post_handler)post_handlerngx_http_read_client_request_body(r, post_handler)request_bodyngx_http_request_body_tbufs

以下示例读取客户端请求正文并返回其大小。

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- 读取请求正文,而不使用 缓冲。

该标志启用 读取请求正文的 unbuffered 模式。 在此模式下,调用 后,链可能只保留正文的一部分。 要阅读下一部分,请调用该函数。 返回值和请求标志指示有更多数据可用。 如果调用此函数后为 NULL,则存在 目前没有什么可读的。 请求回调将在 请求正文的下一部分可用。request_body_no_bufferingngx_http_read_client_request_body()bufsngx_http_read_unbuffered_request_body(r)NGX_AGAINreading_bodybufsread_event_handler

请求正文筛选器

读取请求正文部分后,它会传递给请求 body filter 链,方法是调用存储在 变量。 假设每个 body 处理程序都调用链中的下一个处理程序,直到 调用最终处理程序。 此处理程序收集缓冲区并在必要时将它们写入文件。 最后一个请求正文缓冲区具有非零标志。ngx_http_top_request_body_filterngx_http_request_body_save_filter(r, cl)r->request_body->bufslast_buf

如果 filter 计划延迟数据缓冲区,则应将 flag 设置为 when time 第一次调用。r->request_body->filter_need_buffering1

下面是一个延迟请求的简单请求正文筛选器的示例 body 的 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 响应是通过发送响应标头后跟 可选的响应正文。 header 和 body 都通过一系列过滤器传递,最终得到 写入客户端套接字。 nginx 模块可以将其处理程序安装到 header 或 body 过滤器链中 并处理来自前一个处理程序的输出。

响应标头

该函数发送输出标头。 在包含生成 HTTP 响应标头所需的所有数据之前,请勿调用此函数。 必须始终设置 field in。 如果响应状态指示响应正文遵循标头,也可以设置。 此字段的默认值为 , 这意味着体型未知。 在这种情况下,使用分块传输编码。 要输出任意标头,请附加列表。ngx_http_send_header(r)r->headers_outstatusr->headers_outcontent_length_n-1headers

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 */

    ...
}

标头筛选器

该函数调用 header filter 链中存储的 变量。 假设每个标头处理程序都调用链中的下一个处理程序 直到调用最终处理程序。 最终标头处理程序基于该响应构建 HTTP 响应并将其传递给 for 输出。ngx_http_send_header(r)ngx_http_top_header_filterngx_http_header_filter(r)r->headers_outngx_http_writer_filter

要将处理程序添加到标头过滤器链中,请将其地址存储在 配置时的全局变量。 前一个处理程序地址通常存储在模块的静态变量中 并在退出之前由新添加的处理程序调用。ngx_http_top_header_filter

以下标头筛选器模块示例添加了 HTTP 标头 “” 对状态为 的每个响应。X-Foo: foo200

#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;
}

响应正文

要发送响应正文,请调用该函数。 该函数可以多次调用。 每次,它都会以缓冲区链的形式发送响应正文的一部分。 在最后一个 body 缓冲区中设置标志。ngx_http_output_filter(r, cl)last_buf

以下示例生成一个完整的 HTTP 响应,其中 “foo” 作为其正文。 为了将示例用作 subrequest 和 main request, 标志在 last buffer 中设置 的输出。 该标志仅为主请求设置,因为 子请求的最后一个缓冲区不会结束整个输出。last_in_chainlast_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);
}

响应正文筛选器

该函数调用 body filter 链,方法是调用存储在 变量。 假设每个 body 处理程序都调用链中的下一个处理程序,直到 调用最终处理程序。ngx_http_output_filter(r, cl)ngx_http_top_body_filterngx_http_write_filter(r, cl)

主体筛选器处理程序接收缓冲区链。 处理程序应该处理缓冲区并将可能的新链传递给 下一个处理程序。 值得注意的是, 传入链属于调用方,不得重复使用或更改。 处理程序完成后,调用方可以使用其输出链链接 来跟踪它发送的缓冲区。 要保存缓冲区链或在传递给 next 过滤器,处理程序需要分配自己的链环。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;
}

构建过滤器模块

在编写 body 或 header 过滤器时,请特别注意过滤器的 position (按筛选条件顺序排列)。 nginx 标准注册了许多 header 和 body 过滤器 模块。 nginx 标准模块注册了许多 head 和 body 过滤器,它是 在正确的位置注册一个新的过滤器模块很重要 他们。 通常,模块在其后配置处理程序中注册过滤器。 在处理过程中调用过滤器的顺序显然是 与它们的注册顺序相反。

对于第三方过滤器模块, nginx 提供了一个特殊的 slot 。 要在此插槽中注册过滤器模块,请将 变量 to 在模块的配置中。HTTP_AUX_FILTER_MODULESngx_module_typeHTTP_AUX_FILTER

以下示例显示了一个 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 代码中一个标准且广泛采用的方法是将 用于此目的的两个缓冲链: 和 . 该链保留所有空闲缓冲区, 可以重复使用。 该链保留当前 模块,这些模块仍在被其他过滤器处理程序使用。 如果缓冲区的大小大于零,则认为缓冲区正在使用中。 通常,当过滤器消耗缓冲区时,它(或对于文件缓冲区)会向 ( for a file buffer) 移动。 缓冲区完全用完后,就可以重复使用了。 将新释放的缓冲区添加到链中 迭代链并移动 0 就足够了 size 缓冲区设置为 . 此作非常常见,以至于有一个特殊的函数 。 该函数将输出链附加到 的顶部,并将 的空闲缓冲区从 的顶部移动到 。 只有具有指定 the 的缓冲区才会被重用。 这允许模块仅重用它自己分配的缓冲区。freebusyfreebusyposfile_poslastfile_lastfreebusyfreengx_chain_update_chains(free, busy, out, tag)outbusybusyfreetag

以下示例是一个 body 过滤器,它在每个 incoming buffer 的 intent 缓冲区。 如果可能,将重用模块分配的新缓冲区。 请注意,要使此示例正常工作,还需要设置报头过滤器并重置为,但此处未提供相关代码。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)的模块使用 此功能。 该模块还提供了一个用于创建自定义 load-balancing 模块并实现默认的轮询方法。

least_connhash 模块实现了替代的负载均衡方法,但是 实际上是作为上游循环的扩展实现的 module 并与之共享大量代码,例如表示 的服务器组。 keepalive 模块 是一个扩展上游功能的独立模块。

ngx_http_upstream_module可以通过将相应的上游块放入 配置文件,或者通过 using 指令隐式 例如接受 URL 的 proxy_pass,该 URL 在某个时间点被评估到服务器列表中。 替代负载均衡方法仅适用于显式 upstream 配置。 上游模块配置有其自己的 directive context 。 结构定义如下: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_tupstream
  • 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;
    
    实现负载均衡算法的模块必须设置这些 方法并初始化 private . 如果在配置期间未初始化 parses,则将其设置为默认算法。datainit_upstreamngx_http_upstream_modulengx_http_upstream_init_round_robin
    • init_upstream(cf, us)— 配置时间 方法负责初始化一组服务器,而 如果成功,则初始化方法。 典型的负载均衡模块使用块中的服务器列表 创建一个高效的数据结构,它使用并保存自己的 configuration 添加到字段中。init()upstreamdata
    • init(r, us)— 初始化用于 负载平衡(不要与上述 是每个上游的)。 它作为参数传递给所有回调 处理服务器选择。ngx_http_upstream_peer_t.peerngx_http_upstream_srv_conf_t.peerdata

当 nginx 必须将请求传递给另一台主机进行处理时,它使用 用于获取要连接的地址的已配置负载均衡方法。 该方法是从对象 类型 :ngx_http_upstream_t.peerngx_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, , — 要连接的上游服务器的地址; 这是负载均衡方法的 output 参数。socklenname
  • data— 负载均衡方法的每个请求数据; 保持选择算法的状态,通常包含链接 添加到上游配置中。 它作为参数传递给处理服务器选择的所有方法 (见下文)。
  • tries— 允许尝试连接到上游服务器的次数
  • get、、、 和 - 负载均衡模块的方法,如下所述。freenotifyset_sessionsave_session

所有方法都至少接受两个参数:对等连接对象和由 . 请注意,它可能与 due 不同 到负载均衡模块的 “链接”。pcdatangx_http_upstream_srv_conf_t.peer.init()pc.data

  • get(pc, data)— 当上游 module 已准备好将请求传递给上游服务器,并且需要知道 它的地址。 该方法必须填充结构体的 、 、 和 字段。 返回值是以下值之一:sockaddrsocklennamengx_peer_connection_t
    • NGX_OK— 已选择服务器。
    • NGX_ERROR— 发生内部错误。
    • NGX_BUSY— 当前没有可用的服务器。 发生这种情况的原因有很多,包括:动态服务器组是 empty,则组中的所有服务器都处于 failed 状态,或者 组中的所有服务器都已 处理最大连接数。
    • NGX_DONE— 底层连接被重用,并且 无需创建与上游服务器的新连接。 此值由模块设置。keepalive
  • free(pc, data, state)— 当 upstream 模块已完成与特定服务器的工作。 参数是上游的完成状态 connection,一个具有以下可能值的位掩码:state
    • NGX_PEER_FAILED— 尝试未成功
    • NGX_PEER_NEXT— 上游服务器 返回代码或 , 不被视为失败403404
    • NGX_PEER_KEEPALIVE— 目前未使用
    此方法还会递减计数器。tries
  • notify(pc, data, type)— 目前未使用 在 OSS 版本中。
  • set_session(pc, data)和 — 特定于 SSL 的方法,使缓存会话能够向上游发送 服务器。 该实现由循环平衡方法提供。save_session(pc, data)

例子

nginx-dev-examples 仓库提供了 nginx 模块示例。

代码样式

一般规则

  • 最大文本宽度为 80 个字符
  • 缩进为 4 个空格
  • 没有制表符,没有尾随空格
  • 同一行上的 list 元素用空格分隔
  • 十六进制文本为小写
  • 文件名、函数和类型名称以及全局变量具有 OR 更具体的前缀,例如 和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.
 */

如果文件被显著修改,则应更新作者列表, 新作者将添加到顶部。

的 and 文件 总是首先包含,后跟 、 、 或。 然后遵循可选的外部头文件:ngx_config.hngx_core.hngx_http.hngx_stream.hngx_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

头文件应包括所谓的 “header protection”:

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

变量

变量声明为按基本类型的长度排序,然后按字母顺序排序。 类型名称和变量名称对齐。 type 和 name “columns” 用两个空格分隔。 大型数组放在声明块的末尾:

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

应使用 macro 而不是 :ngx_inlineinline

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

类型强制转换与强制转换表达式之间用一个空格分隔。 type cast 中的星号与类型 name 之间用空格分隔:

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

如果表达式不适合单行,则将其换行。 换行的首选点是二元运算符。 延续行与 expression 的开头对齐:

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

指针明确与 (not ):NULL0

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

条件语句和循环

“” 关键字与条件之间用 一个空格。 左大括号位于同一行上,或者位于 dedicated line (如果条件为多行)。 右大括号位于专用线路上,可选择后跟 由 “ / ”。 通常,在 “ / ” 部分:ifelse ifelseelse ifelse

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

类似的格式规则也适用于 “” 和 “” 循环:dowhile

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

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

“” 关键字与条件之间用 一个空格。 左大括号位于同一行上。 右大括号位于专用线路上。 “” 关键字与 “”:switchcaseswitch

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++) {
    ...
}

具有空主体的 loop 也由 “” 注释,该注释可以放在同一行中:/* 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)。 要使用 和 启用 ASan,请执行以下作: 使用 Compiler and Linker 选项。 在构建 nginx 时,可以通过向脚本添加 option 和 parameters 来完成。gccclang-fsanitize=address--with-cc-opt--with-ld-optconfigure

由于 nginx 中的大多数分配都是从 nginx 内部进行的,因此启用 ASan 可能并不总是足以进行调试 内存问题。 内部池从系统分配一大块内存并削减 较小的分配。 但是,可以通过将宏设置为 来禁用此机制。 在这种情况下,分配将直接传递给为其分配的系统分配器 完全控制缓冲区边界。NGX_DEBUG_PALLOC1

以下配置行总结了上面提供的信息。 建议在开发第三方模块和测试 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 样式 以 0 结尾的字符串。 您不能将数据传递给标准 C 库函数 例如 或 。 相反,应该使用接受其中任何一个的 nginx 对应项 或指向 data 的指针和长度。 但是,在某些情况下,如果 指向以零结尾的字符串的指针:作为 配置文件解析以零结尾。strlen()strstr()ngx_str_tngx_str_t

全局变量

避免在模块中使用全局变量。 这很可能是具有全局变量的错误。 任何全局数据都应该绑定到一个 configuration cycle,并从相应的 memory pool 中分配。 这允许 nginx 执行正常的配置重新加载。 尝试使用全局变量可能会破坏此功能, 因为不可能在 同时并摆脱它们。 有时需要全局变量。 在这种情况下,需要特别注意管理重新配置 适当地。 此外,请检查您的代码使用的库是否具有隐式的 全局状态,该状态可能会在 reload 时中断。

手动内存管理

与其处理容易出错的 malloc/free 方法, 了解如何使用 NGINX 。 创建一个池并将其绑定到一个对象 - 配置循环连接、 或 HTTP 请求。 销毁对象时,关联的池也会被销毁。 因此,在使用对象时,可以分配金额 需要,并且不关心释放内存 即使出现错误。

线程

建议避免在 nginx 中使用线程,因为它会 绝对是坏事:大多数 nginx 函数都不是线程安全的。 预计线程将仅执行系统调用和 线程安全的库函数。 如果您需要运行一些与 Client 端请求处理无关的代码, 正确的方法是在 Module Handler 中调度一个 Timer 并在 Timer Handler 中执行所需的作。 在内部,nginx 利用线程来 boost IO 相关的作,但这是一个特例,有很多 的限制。init_process

阻止库

一个常见的错误是使用内部阻塞的库。 大多数库本质上都是同步和阻塞的。 换句话说,他们一次执行一项作,浪费 等待其他对等体响应的时间。 因此,当使用此类库处理请求时,整个 nginx worker 被阻塞,从而破坏性能。 仅使用提供异步接口且不提供异步接口的库 全程受阻。

对外部服务的 HTTP 请求

通常,模块需要对某些外部服务执行 HTTP 调用。 一个常见的错误是使用一些外部库,比如 libcurl、 以执行 HTTP 请求。 绝对没有必要带上大量的外部 (可能是阻塞!法典 对于可以由 nginx 本身完成的任务。

需要外部请求时,有两种基本使用方案:

  • 在处理客户端请求的上下文中(例如,在内容处理程序中)
  • 在工作进程的上下文中(例如,计时器处理程序)

在第一种情况下,最好使用 subrequests API。 您无需直接访问外部服务,而是声明一个位置 在 nginx 配置中,并将您的子请求定向到此位置。 此位置不仅限于代理请求,还可能包含其他 nginx 指令。 这种方法的一个例子是在 ngx_http_auth_request 模块中实现的 auth_request 指令。

对于第二种情况,可以使用基本的 HTTP 客户端功能 在 nginx 中可用。 例如,OCSP 模块实现简单的 HTTP 客户端。