开发指南

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


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

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

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

介绍

代码布局

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

包含文件

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

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

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

#include <ngx_http.h>

邮件代码应包括

#include <ngx_mail.h>

Stream 代码应包括

#include <ngx_stream.h>

整数

对于一般用途,nginx 代码使用两种整数类型,ngx_int_tngx_uint_t,它们是 typedefs 的intptr_tuintptr_t分别。

常见返回代码

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

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

错误处理

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

的值ngx_errnongx_socket_errno可以传递给日志记录函数ngx_log_error()ngx_log_debugX()在 哪种大小写的系统错误文本将添加到日志消息中。

使用ngx_errno:

ngx_int_t
ngx_my_kill(ngx_pid_t pid, ngx_log_t *log, int signo)
{
    ngx_err_t  err;

    if (kill(pid, signo) == -1) {
        err = ngx_errno;

        ngx_log_error(NGX_LOG_ALERT, log, err, "kill(%P, %d) failed", pid, signo);

        if (err == NGX_ESRCH) {
            return 2;
        }

        return 1;
    }

    return 0;
}

字符串

概述

对于 C 字符串,nginx 使用 unsigned 字符类型指针u_char *.

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

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

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

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

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

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

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

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

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

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

  • ngx_string(text)— static 初始化器ngx_str_ttype 从 C 字符串文本text
  • ngx_null_string— static 空字符串初始化器ngx_str_t类型
  • ngx_str_set(str, text)— 初始化字符串strngx_str_t *type 替换为 C 字符串 字面text
  • ngx_str_null(str)— 初始化字符串strngx_str_t *type 替换为空字符串

格式

以下格式化函数支持 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 *
  • %su_char *(以 null 结尾)
  • %*ssize_t + u_char *

您可以在前面添加u以使其无符号。 要将输出转换为十六进制,请使用Xx.

例如:

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 of given length 到 a type of a positive integerngx_int_t. 结果左移point十进制 位置。 数字的字符串表示形式不应有更多 比points小数位。 例如ngx_atofp("10.5", 4, 2)返回1050.
  • ngx_hextoi(line, n)— 转换十六进制表示 的正整数更改为ngx_int_t.

正则表达式

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

要使用正则表达式进行字符串匹配,它首先需要是 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

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

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

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

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

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

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

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

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

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

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

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

/* accessing named captures */

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

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

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

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

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

ngx_regex_exec_array()function 接受ngx_regex_elt_t元素(只是编译为常规的 表达式、要匹配的字符串和日志。 该函数将数组中的表达式应用于字符串,直到 找到匹配项或没有更多表达式。 返回值为NGX_OK当存在匹配项时,并且NGX_DECLINED否则,或NGX_ERROR如果出现错误。

时间

ngx_time_tstructure 表示时间,具有三个单独的 类型表示秒、毫秒和 GMT 偏移量:

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

ngx_tm_tstructure 是struct tm在 UNIX 平台上,以及SYSTEMTIME在 Windows 上。

要获取当前时间,通常只需访问 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"

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

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

以下函数将time_t进入指示的 分解时间表示。 每对中的第一个函数将转换time_tngx_tm_t第二个(使用_libc_infix 设置为struct tm:

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

ngx_http_time(buf, time)function 返回一个字符串 适合在 HTTP 标头中使用的表示形式(例如"Mon, 28 Sep 1970 06:00:00 GMT"). 这ngx_http_cookie_time(buf, time)返回一个字符串 表示函数返回一个适合的字符串表示 对于 HTTP Cookie ("Thu, 31-Dec-37 23:55:55 GMT").

器皿

数组

nginx 数组类型ngx_array_t定义如下

typedef struct {
    void        *elts;
    ngx_uint_t   nelts;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *pool;
} ngx_array_t;

数组的元素位于elts田。 这neltsfield 包含元素的数量。 这sizefield 保存单个元素的大小,并设置 初始化数组时。

使用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)增加n尾部元素 并返回指向第一个

如果当前分配的内存量不足以容纳 新元素、分配新的内存块和现有元素 将复制到该数据库。 新内存块通常是现有内存块的两倍。

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

列表

在 nginx 中,列表是一系列数组,针对插入潜在的 大量项目。 这ngx_list_tlist 类型定义如下:

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

实际项目存储在列表部分,其定义如下:

typedef struct ngx_list_part_s  ngx_list_part_t;

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

在使用之前,必须通过调用ngx_list_init(list, pool, n, size)或通过调用ngx_list_create(pool, n, size). 这两个函数都将单个项目的大小和数量 items per list part 的 items (每个列表部分的项数)。 要将项目添加到列表中,请使用ngx_list_push(list)功能。 要迭代项目,请直接访问列表字段,如 例:

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

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

/* add items to the list */

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

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

/* iterate over the list */

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

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

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

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

    ngx_do_smth(&v[i]);
}

列表主要用于 HTTP 输入和输出标头。

列表不支持项目删除。 但是,如果需要,可以在内部将项目标记为缺失,而无需实际 正在从列表中删除。 例如,要标记 HTTP 输出标头(存储为ngx_table_elt_t对象)设置为 missing,请将hash字段输入ngx_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)调用 name 来初始化 list head 使用前。 队列支持以下作:

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

要遍历树并插入新值,请使用 "insert_value“ 函数。 例如,ngx_str_rbtree_insert_value功能交易 使用ngx_str_t类型。 它的参数是指向插入的根节点的指针,即新创建的 node 和 tree sentinel。

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

compare()function 是一个经典的比较器函数, 返回小于、等于或大于零的值。 为了加快查找速度并避免比较可能很大的用户对象,可以使用整数 hash 字段。

要将节点添加到树中,请分配一个新节点,对其进行初始化并调用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()功能