非常教程

Nginx参考手册

指南 | Guides

Development guide

  • 介绍
  • 代码布局
  • 包含文件
  • 整型
  • 常用的返回码
  • 错误处理
  • 字符串
  • 概观
  • 格式化
  • 数字转换
  • 常用表达
  • 时间
  • 集装箱
  • 排列
  • 名单
  • 队列
  • 红黑树
  • 哈希
  • 内存管理
  • 共享内存
  • 记录
  • 周期
  • 缓冲
  • 联网
  • 连接
  • 活动
  • 事件
  • I / O事件
  • 计时器事件
  • 发布活动
  • 事件循环
  • 流程
  • 主题
  • 模块
  • 添加新模块
  • 核心模块
  • 配置指令
  • HTTP
  • 连接
  • 请求
  • 组态
  • 变量
  • 复杂的值
  • 请求重定向
  • 子请求
  • 请求完成
  • 请求正文
  • 响应
  • 响应机构
  • 身体过滤器
  • 建立过滤器模块
  • 缓冲区重用
  • 负载均衡

介绍

代码布局

  • auto - 建立脚本
  • src
    • core - 基本类型和函数 - 字符串,数组,日志,池等
    • event - 事件核心
      • modules-事件通知模块:epollkqueueselect等。
-  `http` — Core HTTP module and common code 
    -  `modules` — Other HTTP modules 
    -  `v2` — HTTP/2 
-  `mail` — Mail modules 
-  `os` — Platform-specific code 
    -  `unix` 
    -  `win32` 
-  `stream` — Stream modules 

包含文件

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

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

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

#include <ngx_http.h>

Mail 代码应该包括

#include <ngx_mail.h>

流代码应包括

#include <ngx_stream.h>

整型

一般用途,nginx的代码使用了两个整数类型,ngx_int_tngx_uint_t,这分别是类型定义intptr_tuintptr_t

常用的返回码

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

  • NGX_OK - 操作成功。
  • NGX_ERROR - 操作失败。
  • NGX_AGAIN - 操作不完整; 再次调用该函数。
  • NGX_DECLINED - 例如,操作被拒绝,因为它在配置中被禁用。这绝不是一个错误。
  • NGX_BUSY - 资源不可用。
  • NGX_DONE - 操作完成或在别处继续。也用作备选成功代码。
  • NGX_ABORT - 功能被中止。也用作替代错误代码。

错误处理

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

ngx_errnongx_socket_errno可以传递到记录功能ngx_log_error()ngx_log_debugX(),在这种情况下,系统错误文本被添加到日志信息。

使用示例ngx_errno

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

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

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

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

        return 1;
    }

    return 0;
}

字符串

概观

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

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

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

len字段保存字符串长度,数据保存字符串数据。 该字符串保存在ngx_str_t中,在len字节之后可以或不可以以null结尾。 在大多数情况下不是。 但是,在代码的某些部分(例如解析配置时),已知ngx_str_t对象以空字符结尾,这简化了字符串比较,并使字符串传递到系统调用变得更简单。

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_cpymem()- 与之相同ngx_memcpy(),但返回最终的目标地址这一个方便在一行中追加多个字符串。
  • ngx_movemem()- 与之相同ngx_memmove(),但返回最终的目的地地址。
  • ngx_strlchr() - 搜索字符串中的一个字符,由两个指针分隔。

以下功能执行大小写转换和比较:

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

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

  • ngx_string(text) - 来自C字符串文本文本的ngx_str_t类型的静态初始值设定项
  • ngx_null_string - ngx_str_t类型的静态空字符串初始值设定项
  • ngx_str_set(str,text) - 用C字符串文字初始化ngx_str_t *类型的字符串st
  • ngx_str_null(str) - 用空字符串初始化ngx_str_t *类型的字符串str

格式化

以下格式化函数支持nginx特定的类型:

  • ngx_sprintf(buf, fmt, ...)
  • ngx_snprintf(buf, max, fmt, ...)
  • ngx_slprintf(buf, last, fmt, ...)
  • ngx_vslprint(buf, last, fmt, args)
  • ngx_vsnprint(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-terminated)
  • %*ssize_t + u_char *

你可以在大多数类型上加上前缀来使它们无符号。 要将输出转换为十六进制,请使用X或x。

例如:

u_char      buf[NGX_INT_T_LEN];
size_t      len;
ngx_uint_t  n;

/* set n here */

len = ngx_sprintf(buf, "%ui", n) — buf;

数字转换

用于数字转换的几个函数在nginx中实现。前四个将每个给定长度的字符串转换为指定类型的正整数。NGX_ERROR返回错误。

  • ngx_atoi(line, n)ngx_int_t
  • ngx_atosz(line, n)ssize_t
  • ngx_atoof(line, n)off_t
  • ngx_atotm(line, n)time_t

还有两个额外的数字转换功能。像前四个一样,NGX_ERROR返回错误。

  • ngx_atofp(line, n, point)- 将给定长度的定点浮点数转换为类型的正整数ngx_int_t。结果左移point小数位。数字的字符串表示预计不会超过points小数位数。例如,ngx_atofp("10.5", 4, 2)退货1050
  • ngx_hextoi(line, n)- 将正整数的十六进制表示转换为ngx_int_t

常用表达

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

要使用正则表达式进行字符串匹配,首先需要进行编译,通常在配置阶段完成。请注意,由于PCRE支持是可选的,因此使用该接口的所有代码都必须由周围的NGX_PCRE宏保护:

#if (NGX_PCRE)
ngx_regex_t          *re;
ngx_regex_compile_t   rc;

u_char                errstr[NGX_MAX_CONF_ERRSTR];

ngx_str_t  value = ngx_string("message (\\d\\d\\d).*Codeword is '(?<cw>\\w+)'");

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

rc.pattern = value;
rc.pool = cf->pool;
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* rc.options are passed as is to pcre_compile() */

if (ngx_regex_compile(&rc) != NGX_OK) {
    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc.err);
    return NGX_CONF_ERROR;
}

re = rc.regex;
#endif

编译成功后,ngx_regex_compile_t结构中的capture和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,匹配s的字符串,可选的数组整数以保存找到的捕获,以及数组的大小。 根据PCRE API的要求,捕获数组的大小必须是3的倍数。 在该示例中,大小是根据匹配的字符串本身的捕获总数加1one来计算的。

如果有匹配,可以按如下方式访问捕获:

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

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

/* accessing named captures */

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

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

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

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

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

ngx_regex_exec_array()函数接受ngx_regex_elt_t元素的数组(这些元素只是编译的正则表达式和关联名称),一个匹配的字符串和一个日志。 该函数将数组中的表达式应用于字符串,直到找到匹配或不再有更多表达式为止。 如果匹配,则返回值为NGX_OK,否则为NGX_DECLINED;如果发生错误,返回值为NGX_ERROR。

Time

ngx_time_t结构表示具有三种不同类型的时间,包括秒,毫秒和GMT偏移量:

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

ngx_tm_t结构是struct tmUNIX平台和SYSTEMTIMEWindows 上的别名。

要获取当前时间,通常只需访问一个可用的全局变量即可,它表示所需格式的缓存时间值。例如,ngx_current_msec变量保存从Epoch开始并截断到的毫秒数ngx_msec_t

可用的字符串表示是:

  • 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_缀)转换为struct tm

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

ngx_http_time(buf, time)函数返回适用于HTTP标头的字符串表示形式(例如,"Mon, 28 Sep 1970 06:00:00 GMT")。所述ngx_http_cookie_time(buf, time)返回字符串表示函数返回字符串表示适合的HTTP cookies( "Thu, 31-Dec-37 23:55:55 GMT")。

Containers

Array

nginx数组类型ngx_array_t定义如下

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

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

使用该ngx_array_create(pool, n, size)调用在池中创建一个数组,并ngx_array_init(array, pool, n, size)调用以初始化已分配的数组对象。

ngx_array_t  *a, b;

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

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

使用以下功能将元素添加到数组中:

  • ngx_array_push(a) 添加一个尾部元素并返回指向它的指针
  • ngx_array_push_n(a, n)添加n尾部元素并返回指向第一个的指针

如果当前分配的内存量不足以容纳新元素,则会分配新的内存块,并将现有元素复制到内存中。新的内存块通常是现有内存块的两倍。

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

List

在nginx中,列表是一个数组序列,为插入潜在的大量项目进行了优化。该ngx_list_t列表类型定义如下:

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

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

typedef struct ngx_list_part_s  ngx_list_part_t;

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

在使用之前,必须通过调用ngx_list_init(list, pool, n, size)或通过调用来创建列表ngx_list_create(pool, n, size)。两个函数都以单个项目的大小和每个列表部分的项目数作为参数。要将项目添加到列表中,请使用该ngx_list_push(list)功能。要迭代这些项目,请直接访问列表字段,如示例中所示:

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

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

/* add items to the list */

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

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

/* iterate over the list */

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

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

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

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

    ngx_do_smth(&v[i]);
}

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

列表不支持删除项目。但是,如果需要,项目可以在内部被标记为缺失,而实际上并未从列表中移除。例如,要将HTTP输出标头(它们存储为ngx_table_elt_t对象)标记为缺失,请将该hash字段设置ngx_table_elt_t为零。以这种方式标记的项目在迭代标题时显式跳过。

Queue

在nginx中,队列是一个入侵式双向链表,每个节点的定义如下:

typedef struct ngx_queue_s  ngx_queue_t;

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

头部队列节点不与任何数据链接。使用ngx_queue_init(q)呼叫使用前初始化列表头。队列支持以下操作:

  • ngx_queue_insert_head(h, x)ngx_queue_insert_tail(h, x)- 插入一个新节点
  • ngx_queue_remove(x) - 删除一个队列节点
  • ngx_queue_split(h, q, n) - 在节点处拆分队列,将队列尾部返回到单独的队列中
  • ngx_queue_add(h, n) - 向第一个队列添加第二个队列
  • ngx_queue_head(h)ngx_queue_last(h)-获取第一个或最后一个队列节点
  • ngx_queue_sentinel(h) - 获取队列标识对象以结束迭代
  • ngx_queue_data(q, type, link) - 获取对队列节点数据结构开始的引用,考虑队列字段在其中的偏移量

一个例子:

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

ngx_foo_t    *f;
ngx_queue_t   values, *q;

ngx_queue_init(&values);

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

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

/* insert more nodes here */

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

    ngx_do_smth(&f->value);
}

红黑树

src/core/ngx_rbtree.h头文件提供了访问的有效执行红黑树。

typedef struct {
    ngx_rbtree_t       rbtree;
    ngx_rbtree_node_t  sentinel;

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

typedef struct {
    ngx_rbtree_node_t  rbnode;

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

为了处理一棵树,你需要两个节点: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类型。它的参数是指向插入的根节点,新创建的要添加的节点和树标记的指针。

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

遍历非常简单,可以用以下查找函数模式来演示:

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

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

    while (node != sentinel) {

        n = (my_node_t *) node;

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

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

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

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

        return n;
    }

    return NULL;
}

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

要将节点添加到树中,请分配一个新节点,对其进行初始化并调用ngx_rbtree_insert()

    my_node_t          *my_node;
    ngx_rbtree_node_t  *node;

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

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

    ngx_rbtree_insert(&root->rbtree, node);

要删除节点,请调用该ngx_rbtree_delete()函数:

ngx_rbtree_delete(&root->rbtree, node);

哈希

哈希表函数在中声明src/core/ngx_hash.h。支持精确匹配和通配符匹配。后者需要额外的设置,并在下面的单独部分进行介绍。

在初始化散列之前,您需要知道它将保存的元素的数量,以便nginx能够最优地构建它。需要被配置的两个参数是max_sizebucket_size,如在一个单独的文件中详述。它们通常由用户配置。哈希初始化设置与ngx_hash_init_t类型一起存储,哈希本身是ngx_hash_t

ngx_hash_t       foo_hash;
ngx_hash_init_t  hash;

hash.hash = &foo_hash;
hash.key = ngx_hash_key;
hash.max_size = 512;
hash.bucket_size = ngx_align(64, ngx_cacheline_size);
hash.name = "foo_hash";
hash.pool = cf->pool;
hash.temp_pool = cf->temp_pool;

key是一个指向函数的指针,该函数从字符串中创建散列整数键。有两个通用的键创建函数:ngx_hash_key(data, len)ngx_hash_key_lc(data, len)。后者将字符串转换为全部小写字符,因此传递的字符串必须是可写的。如果不是这样,则将NGX_HASH_READONLY_KEY标志传递给函数,初始化关键数组(参见下文)。

散列键存储在中ngx_hash_keys_arrays_t,并用ngx_hash_keys_array_init(arr, type)以下参数进行初始化:第二个参数(type)控制为散列预分配的资源量,可以是NGX_HASH_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);

如果max_size或bucket_size参数不够大,该函数将失败。

当构建哈希时,使用该ngx_hash_find(hash, key, name, len)函数查找元素:

my_data_t   *data;
ngx_uint_t   key;

key = ngx_hash_key(k1.data, k1.len);

data = ngx_hash_find(&foo_hash, key, k1.data, k1.len);
if (data == NULL) {
    /* key not found */
}

通配符匹配

要创建与通配符一起使用的散列,请使用该ngx_hash_combined_t类型。它包含上面描述的散列类型,并有两个附加的键阵列:dns_wc_headdns_wc_tail。基本属性的初始化类似于常规散列:

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

hash.hash = &foo_hash.hash;
hash.key = ...;

可以使用NGX_HASH_WILDCARD_KEY标志添加通配符键:

/* k1 = ".example.org"; */
/* k2 = "foo.*";        */
ngx_hash_add_key(&foo_keys, &k1, &data1, NGX_HASH_WILDCARD_KEY);
ngx_hash_add_key(&foo_keys, &k2, &data2, NGX_HASH_WILDCARD_KEY);

该功能可识别通配符并将键添加到相应的阵列中。有关通配符语法和匹配算法的说明,请参阅地图模块文档。

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

if (foo_keys.dns_wc_head.nelts) {

    ngx_qsort(foo_keys.dns_wc_head.elts,
              (size_t) foo_keys.dns_wc_head.nelts,
              sizeof(ngx_hash_key_t),
              cmp_dns_wildcards);

    hash.hash = NULL;
    hash.temp_pool = pool;

    if (ngx_hash_wildcard_init(&hash, foo_keys.dns_wc_head.elts,
                               foo_keys.dns_wc_head.nelts)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    foo_hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
}

键数组需要排序,并且初始化结果必须添加到组合散列。dns_wc_tail数组的初始化是相似的。

组合哈希中的查找由以下处理ngx_hash_find_combined(chash, key, name, len)

/* key = "bar.example.org"; — will match ".example.org" */
/* key = "foo.example.com"; — will match "foo.*"        */

hkey = ngx_hash_key(key.data, key.len);
res = ngx_hash_find_combined(&foo_hash, hkey, key.data, key.len);

内存管理

Heap

要从系统堆分配内存,请使用以下功能:

  • ngx_alloc(size, log) - 从系统堆分配内存。这是一个malloc()包含日志支持的封装。分配错误和调试信息被记录到log
  • ngx_calloc(size, log)- 像系统堆一样分配内存ngx_alloc(),但分配后用零填充内存。
  • ngx_memalign(alignment, size, log) - 从系统堆分配对齐的内存。这是posix_memalign()提供该功能的平台上的一个包装。否则,实施回落到ngx_alloc()提供最大对齐。
  • ngx_free(p) - 免费分配的内存。这是一个包装free()

Pool

大多数nginx分配在池中完成。在池被销毁时,在nginx池中分配的内存会自动释放。这提供了良好的分配性能并使内存控制变得容易。

池在内部分配连续内存块中的对象。一旦块已满,将分配新块并将其添加到池内存块列表中。当请求的分配太大而无法放入块中时,请求将被转发到系统分配器,并且返回的指针将被存储在池中以供进一步释放。

nginx池的类型是ngx_pool_t。支持以下操作:

  • ngx_create_pool(size, log) - 创建一个具有指定块大小的池。返回的池对象也在池中分配。
  • ngx_destroy_pool(pool) - 释放所有池内存,包括池对象本身。
  • ngx_palloc(pool, size) - 从指定的池中分配对齐的内存。
  • ngx_pcalloc(pool, size) - 从指定的池中分配对齐的内存并用零填充。
  • ngx_pnalloc(pool, size) - 从指定的池中分配未对齐的内存。主要用于分配字符串。
  • ngx_pfree(pool, p) - 先前在指定池中分配的可用内存。只有转发给系统分配器的请求产生的分配才能被释放。
u_char      *p;
ngx_str_t   *s;
ngx_pool_t  *pool;

pool = ngx_create_pool(1024, log);
if (pool == NULL) { /* error */ }

s = ngx_palloc(pool, sizeof(ngx_str_t));
if (s == NULL) { /* error */ }
ngx_str_set(s, "foo");

p = ngx_pnalloc(pool, 3);
if (p == NULL) { /* error */ }
ngx_memcpy(p, "foo", 3);

链接(ngx_chain_t)在nginx中被有效地使用,所以nginx池实现提供了重用它们的方法。该chain字段ngx_pool_t保留了以前分配的链接列表以备重用。为了有效分配池中的链式链接,请使用该ngx_alloc_chain_link(pool)函数。该功能在池列表中查找自由链接,并在池列表为空时分配新的链接链接。要释放链接,请调用该ngx_free_chain(pool, cl)功能。

清理处理程序可以在池中注册。清理处理程序是一个带有参数的回调,当池被销毁时会调用该参数。池通常绑定到特定的nginx对象(如HTTP请求),并在对象达到其生命周期结束时被销毁。注册池清理是释放资源,关闭文件描述符或对与主对象关联的共享数据进行最终调整的便捷方式。

要注册一个池清理,调用ngx_pool_cleanup_add(pool, size),它将返回一个ngx_pool_cleanup_t指针,由调用者填充。使用size参数为清理处理程序分配上下文。

ngx_pool_cleanup_t  *cln;

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

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

...

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

    ngx_do_smth(msg);
}

共享内存

共享内存由nginx用来在进程之间共享公共数据。该ngx_shared_memory_add(cf, name, size, tag)函数ngx_shm_zone_t为循环添加一个新的共享内存条目。该功能接收区域namesize区域。每个共享区域必须具有唯一的名称。如果共享区域条目与提供的name并且tag已存在,则现有区域条目将被重新使用。如果具有相同名称的现有条目具有不同的标签,则该函数将失败并显示错误。通常,将模块结构的地址作为参数传递tag,从而可以在一个nginx模块中按名称重用共享区域。

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

  • init - 初始化回调,在共享区域映射到实际内存后调用
  • data- 数据上下文,用于传递任意数据到init回调
  • noreuse - 禁止重复使用旧循环中共享区域的标志
  • tag - 共享区域标签
  • shm- 平台特定的类型对象,ngx_shm_t至少包含以下字段:
    • addr - 映射共享内存地址,最初为NULL
    • size - 共享内存大小
    • name - 共享内存名称
    • log - 共享内存日志
    • exists - 表示共享内存是从主进程继承的标志(特定于Windows)

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

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

板块池将所有共享区域分成页面。每个页面用于分配相同大小的对象。指定的大小必须是2的幂,并且大于8字节的最小大小。不合格值被四舍五入。每个页面的位掩码用于跟踪哪些块在使用中,哪些可以自由分配。对于大于半页(通常为2048字节)的大小,一次分配整个页面

要保护共享内存中的数据免受并发访问,请使用ngx_slab_pool_t的互斥量字段中提供的互斥量。 在分配和释放内存时,slab池最常使用互斥锁,但它可用于保护在共享区域中分配的任何其他用户数据结构。 要锁定或解锁互斥锁,请分别调用ngx_shmtx_lock(&shpool-> mutex)或ngx_shmtx_unlock(&shpool-> mutex)。

ngx_str_t        name;
ngx_foo_ctx_t   *ctx;
ngx_shm_zone_t  *shm_zone;

ngx_str_set(&name, "foo");

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

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

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


...


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

    size_t            len;
    ngx_foo_ctx_t    *ctx;
    ngx_slab_pool_t  *shpool;

    value = shm_zone->data;

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

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

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

    /* initialize shared zone */

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

    shpool->data = ctx->value;

    return NGX_OK;
}

Logging

对于日志记录,nginx使用ngx_log_t对象。nginx记录器支持多种类型的输出:

  • stderr - 记录到标准错误(stderr)
  • file - 记录到文件
  • syslog - 记录到syslog
  • memory - 为了开发目的而登录到内部存储器存储; 内存可以在稍后用调试器访问

记录器实例可以是一个记录器链,通过该next字段彼此链接。在这种情况下,每条消息都写入链中的所有记录器。

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

  • NGX_LOG_EMERG
  • NGX_LOG_ALERT
  • NGX_LOG_CRIT
  • NGX_LOG_ERR
  • NGX_LOG_WARN
  • NGX_LOG_NOTICE
  • NGX_LOG_INFO
  • NGX_LOG_DEBUG

对于调试日志记录,也会检查调试掩码。调试掩码是:

  • NGX_LOG_DEBUG_CORE
  • NGX_LOG_DEBUG_ALLOC
  • NGX_LOG_DEBUG_MUTEX
  • NGX_LOG_DEBUG_EVENT
  • NGX_LOG_DEBUG_HTTP
  • NGX_LOG_DEBUG_MAIL
  • NGX_LOG_DEBUG_STREAM

通常情况下,记录器是由error_log指令中现有的nginx代码创建的,并且几乎在循环,配置,客户端连接和其他对象的每个处理阶段都可用。

Nginx提供了以下日志记录宏:

  • ngx_log_error(level, log, err, fmt, ...) - 错误记录
  • ngx_log_debug0(level, log, err, fmt)ngx_log_debug1(level, log, err, fmt, arg1)等等-调试记录多达8个支持的格式参数

日志消息格式化为NGX_MAX_ERROR_STR堆栈大小(当前为2048字节)的缓冲区。该消息预先包含严重性级别,进程标识(PID),连接标识(存储在中log->connection)以及系统错误文本。对于非调试消息也log->handler被调用以在日志消息中添加更多特定的信息。HTTP模块设置ngx_http_log_error()为日志处理程序来记录客户端和服务器地址,当前操作(存储log->action),客户端请求行,服务器名称等。

/* specify what is currently done */
log->action = "sending mp4 to client”;

/* error and debug log */
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely
              closed connection”);

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
               "mp4 start:%ui, length:%ui”, mp4->start, mp4->length);

上面的例子会产生这样的日志条目:

2016/09/16 22:08:52 [info] 17445#0: *1 client prematurely closed connection while
sending mp4 to client, client: 127.0.0.1, server: , request: "GET /file.mp4 HTTP/1.1”
2016/09/16 23:28:33 [debug] 22140#0: *1 mp4 start:0, length:10000

周期

循环对象存储从特定配置创建的nginx运行时上下文。它的类型是ngx_cycle_t。当前循环由ngx_cycle全局变量引用,并由nginx工作人员在开始时继承。每次重新加载nginx配置时,都会从新的nginx配置创建一个新的循环; 旧周期通常会在成功创建新周期后被删除。

一个循环由该ngx_init_cycle()函数创建,该循环以前一个循环为参数。该函数查找前一周期的配置文件,并从前一周期继承尽可能多的资源。一个名为“init cycle”的占位符循环被创建为nginx start,然后被一个由配置构建的实际循环替换。

周期的成员包括:

  • pool - 循环池。为每个新周期创建。
  • log - 循环日志。最初从旧周期继承,它被设置为new_log在读取配置之后指向。
  • new_log - 周期日志,由配置创建。它受root-scope error_log指令的影响。
  • connectionsconnection_n- ngx_connection_t初始化每个nginx工作者时由事件模块创建的类型连接数组。worker_connectionsnginx配置中的指令设置连接的数量connection_n
  • free_connectionsfree_connection_n- 当前可用连接的列表和数量。如果没有连接可用,nginx工作者拒绝接受新客户端或连接到上游服务器。
  • filesfiles_n- 用于将文件描述符映射到nginx连接的数组。该映射由事件模块使用,具有NGX_USE_FD_EVENT标志(当前,它polldevpoll)。
  • conf_ctx - 核心模块配置阵列。配置在读取nginx配置文件期间被创建和填充。
  • modulesmodules_n- ngx_module_t由当前配置加载的静态和动态类型的模块数组。
  • listening- 类型的侦听对象数组ngx_listening_t。侦听对象通常由listen调用ngx_create_listening()函数的不同模块的指令添加。侦听套接字是基于侦听对象创建的。
  • paths- 类型路径的数组ngx_path_t。通过调用ngx_add_path()将要在特定目录上运行的模块的功能来添加路径。这些目录是在读取配置后由nginx创建的,如果缺失的话。而且,可以为每个路径添加两个处理程序:
    • 路径加载器 - 在启动或重新加载nginx后的60秒内只执行一次。通常,加载器读取目录并将数据存储在nginx共享内存中。处理程序从专用的nginx进程“nginx缓存加载器”调用。
    • 路径管理器 - 定期执行。通常,管理员从目录中删除旧文件并更新nginx内存以反映更改。处理程序从专用的“nginx缓存管理器”进程中调用。
  • open_files- ngx_open_file_t通过调用该函数创建的打开的文件对象类型列表ngx_conf_open_file()。目前,nginx使用这种打开的文件进行日志记录。读取配置后,nginx将打开open_files列表中的所有文件,并将每个文件描述符存储在对象的fd字段中。这些文件以追加模式打开,并在缺失时创建。接收到重新打开信号(最常见USR1)后,列表中的文件将由nginx工作人员重新打开。在这种情况下,该fd字段中的描述符会更改为新值。
  • shared_memory- 共享内存区域列表,每个共享内存区域都通过调用ngx_shared_memory_add()函数添加。共享区域映射到所有nginx进程中相同的地址范围,并用于共享公共数据,例如HTTP缓存内存树。

缓冲

对于输入/输出操作,nginx提供了缓冲区类型ngx_buf_t。通常,它用于保存要写入目标或从源读取的数据。缓冲区可以引用内存中或文件中的数据,并且技术上缓冲区可以同时引用这两个数据。缓冲区的内存是单独分配的,并且与缓冲区结构ngx_buf_t无关。

ngx_buf_t结构具有以下阶段:

  • startend- 分配给缓冲区的内存块的边界。
  • poslast- 内存缓冲区的边界; 通常的子范围start.. end
  • file_posfile_last- 文件缓冲区的边界,表示为从文件开始的偏移量。
  • tag - 用于区分缓冲区的唯一值; 由不同的nginx模块创建,通常用于缓冲区重用。
  • file - 文件对象。
  • temporary - 指示缓冲区引用可写内存的标志。
  • memory - 指示缓冲区引用只读存储器的标志。
  • in_file - 指示缓冲区引用文件中的数据的标志。
  • flush - 指示缓冲区之前的所有数据需要刷新的标志。
  • recycled - 指示缓冲区可以重新使用并且需要尽快消​​耗的标志。
  • sync- 指示缓冲区不携带数据或特殊信号(例如flush或)的标志last_buf。默认情况下,nginx认为这样的缓冲区是一个错误条件,但是这个标志告诉nginx跳过错误检查。
  • last_buf - 指示缓冲区是输出中最后一个的标志。
  • last_in_chain - 指示请求或子请求中不再有数据缓冲区的标志。
  • shadow - 引用与当前缓冲区相关的另一个(“影子”)缓冲区,通常意味着缓冲区使用来自阴影的数据。当缓冲区被消耗时,影子缓冲区通常也被标记为消耗。
  • last_shadow - 指示缓冲区是引用特定影子缓冲区的最后一个的标志。
  • temp_file - 指示缓冲区位于临时文件中的标志。

对于输入和输出操作,缓冲区链接在一起。一个链是一个链式链的序列ngx_chain_t,定义如下:

typedef struct ngx_chain_s  ngx_chain_t;

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};

每个链节保持对其缓冲区的引用和对下一个链节的引用。

使用缓冲区和链的示例:

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

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

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

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

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

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

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

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

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

    return out;
}

Networking

连接

连接类型ngx_connection_t是一个套接字描述符的包装。它包含以下字段:

  • fd - 套接字描述符
  • data - 任意连接上下文。通常,它是一个指向构建在连接之上的更高级别对象的指针,例如HTTP请求或Stream会话。
  • readwrite-读取和写入连接的事件。
  • recvsendrecv_chainsend_chain-为连接I / O操作。
  • pool - 连接池。
  • log - 连接日志。
  • sockaddrsocklenaddr_text-二进制和文本形式的远程套接字地址。
  • local_sockaddrlocal_socklen- 二进制形式的本地套接字地址。最初,这些字段是空的。使用该ngx_connection_local_sockaddr()函数获取本地套接字地址。
  • proxy_protocol_addrproxy_protocol_port- PROXY协议客户端的地址和端口,如果代理协议为连接启用。
  • ssl - 连接的SSL上下文。
  • reusable - 指示连接处于允许重用的状态的标志。
  • close - 表示连接正在重用并需要关闭的标志。

一个nginx连接可以透明地封装SSL层。 在这种情况下,连接的ssl字段保存一个指向ngx_ssl_connection_t结构的指针,保留连接的所有SSL相关数据,包括SSL_CTX和SSL。 recv,send,recv_chain和send_chain处理程序也设置为启用SSL的函数。

nginx配置中的worker_connections指令限制了每个nginx工作者的连接数量。 所有连接结构都是在工作人员启动时预先创建并存储在循环对象的连接字段中。 要检索连接结构,请使用ngx_get_connection(s,log)函数。 它使用套接字描述符作为其参数,该描述符需要在连接结构中打包。

由于每个工作者的连接数量有限,因此nginx提供了一种方法来获取当前正在使用的连接。要启用或禁用连接的重用,请调用ngx_reusable_connection(c,可重用)函数。调用ngx_reusable_connection(c,1)在连接结构中设置重用标志,并将连接插入到循环的reusable_connections_queue中。只要ngx_get_connection()发现周期的free_connections列表中没有可用的连接,就会调用ngx_drain_connections()来释放特定数量的可重用连接。对于每个这样的连接,关闭标志被设置,并且它的读处理程序被调用,它应该通过调用ngx_close_connection(c)来释放连接并使其可用于重用。在连接可以重用时退出状态ngx_reusable_connection(c,0)被调用。 HTTP客户端连接是nginx中可重用连接的一个例子;它们被标记为可重复使用,直到从客户端收到第一个请求字节。

事件

Event

nginx中的事件对象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在套接字上挂起的标志,即使在它之前可能有一些数据可用。该标志通过EPOLLRDHUP epoll事件或EV_EOF kqueue标志传递。
  • error - 表示在读取(读取事件)或写入(写入事件)期间发生错误的标志。
  • cancelable - 定时器事件标志,指示在关闭工作人员时应忽略该事件。缓慢关机延迟到没有不可取消的计时器事件安排为止。
  • posted - 指示事件发布到队列的标志。
  • queue - 将事件发布到队列的队列节点。

I / O事件

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

计时器事件

可以设置事件以在超时到期时发送通知。 函数ngx_add_timer(ev,timer)设置事件超时,ngx_del_timer(ev)删除先前设置的超时。 全局超时红黑树ngx_event_timer_rbtree存储当前设置的所有超时。 树中的键是ngx_msec_t类型,并且是事件过期的时间,以1970年1月1日午夜以来的毫秒数表示,模数ngx_msec_t最大值。 树结构支持快速插入和删除操作,以及访问最近的超时,nginx使用该超时时间来查找等待I / O事件和超时事件过期的时间。

发布事件

可以发布一个事件,这意味着它的处理程序将在当前事件循环迭代的稍后一段时间被调用。 发布事件是简化代码和避免堆栈溢出的良好实践。 发布的活动在帖子队列中进行。 ngx_post_event(ev,q)mscro将事件ev发布到帖子队列q。 ngx_delete_posted_event(ev)宏从当前发布的队列中删除事件ev。通常情况下,事件发布到ngx_posted_events队列中,在事件循环中处理晚了 - 所有I / O和计时器事件都已处理完毕。 函数ngx_event_process_posted()被调用来处理一个事件队列。 它调用事件处理程序,直到队列不为空。 这意味着发布的事件处理程序可以发布更多事件以在当前事件循环迭代中处理。

一个例子:

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

    rev = c->read;

    ngx_add_timer(rev, 1000);

    rev->handler = ngx_my_read_handler;

    ngx_my_read(rev);
}


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

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

    c = rev->data;

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

        if (n == NGX_AGAIN) {
            break;
        }

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

        /* process buf */
    }

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

事件循环

除nginx主进程外,所有nginx进程都执行I / O,因此有一个事件循环。(nginx主进程将其大部分时间花费在sigsuspend()等待信号到达的呼叫中。)nginx事件循环在ngx_process_events_and_timers()函数中实现,该函数在该进程退出之前被重复调用。

事件循环有以下几个阶段:

  • 通过调用ngx_event_find_timer()来查找最接近即将到期的超时。该函数查找定时器树中最左边的节点,并返回毫秒数,直到节点到期。
  • 通过调用处理程序处理I / O事件,特定于事件通知机制,由nginx配置选择。此处理程序至少会等待一个I / O事件发生,但直到下一个超时过期。当发生读或写事件时,就绪标志被设置并且事件的处理程序被调用。对于Linux,通常使用ngx_epoll_process_events()处理程序,它调用epoll_wait()来等待I / O事件。
  • 通过调用ngx_event_expire_timers()来使计时器过期。定时器树从最左边的元素迭代到右边,直到找到未到期的超时。对于每个过期的节点,设置超时事件标志,重置timer_set标志,并调用事件处理程序
  • 处理通过调用ngx_event_process_posted()发布事件。该函数重复从发布的事件队列中移除第一个元素并调用元素的处理程序,直到队列为空。

所有的nginx进程也处理信号。信号处理程序只设置在ngx_process_events_and_timers()调用之后检查的全局变量。

流程

在nginx中有几种类型的进程。进程的类型保存在ngx_process全局变量中,并且是以下之一:

  • NGX_PROCESS_MASTER - 主进程读取NGINX配置,创建周期并启动和控制子进程。它不执行任何I / O并仅响应信号。它的循环功能是ngx_master_process_cycle()
  • NGX_PROCESS_WORKER - 处理客户端连接的工作进程。它由主进程启动,并响应其信号和通道命令。它的循环功能是ngx_worker_process_cycle()。根据worker_processes指令配置,可以有多个工作进程。
  • NGX_PROCESS_SINGLE- 仅存在于master_process off模式中的单个进程,并且是在该模式下运行的唯一进程。它创建周期(像主进程一样)并处理客户端连接(如工作进程)。它的循环功能是ngx_single_process_cycle()
  • NGX_PROCESS_HELPER - 辅助进程,目前有两种类型:缓存管理器和缓存加载器。两者的循环功能是ngx_cache_manager_process_cycle()

nginx进程处理以下信号:

  • NGX_SHUTDOWN_SIGNAL(在大多数系统上为SIGQUIT) - 正常关机。在接收到该信号后,主进程向所有子进程发送关闭信号。当没有子过程时,主人会破坏循环池并退出。当一个工作进程接收到这个信号时,它会关闭所有监听套接字并等待,直到没有不可取消的事件被调度,然后销毁循环池并退出。当缓存管理器或缓存加载器进程收到该信号时,它立即退出。该ngx_quit变量被设置为1当一个进程接收该信号,并在处理之后立即复位。该ngx_exiting变量设置为1当工作进程处于关闭状态时。
  • NGX_TERMINATE_SIGNALSIGTERM在大多数系统上) - 终止。在接收到该信号后,主进程向所有子进程发送终止信号。如果子进程在1秒内没有退出,主进程会发送SIGKILL信号来终止它。当没有子进程时,主进程会破坏循环池并退出。当工作进程,高速缓存管理器进程或缓存加载器进程收到此信号时,它会销毁循环池并退出。该信号被接收时变量ngx_terminate被设置为1
  • NGX_NOACCEPT_SIGNALSIGWINCH在大多数系统上) - 关闭所有辅助进程和辅助进程。在收到此信号后,主进程关闭其子进程。如果先前启动的新nginx二进制文件退出,则旧的主文件的子进程将再次启动。当工作进程接收到该信号时,它会在debug_points指令设置的调试模式下关闭。
  • NGX_RECONFIGURE_SIGNALSIGHUP在大多数系统上) - 重新配置。在接收到此信号后,主进程将重新读取配置并基于此创建新的周期。如果新周期成功创建,则删除旧周期并启动新的子进程。同时,旧的子进程接收NGX_SHUTDOWN_SIGNAL信号。在单进程模式下,nginx会创建一个新的循环,但保留旧循环,直到不再有与其连接的活动连接的客户端。worker和helper进程忽略这个信号。
  • NGX_REOPEN_SIGNALSIGUSR1在大多数系统上) - 重新打开文件。主进程发送这个信号给工人,重新打开所有open_files与周期有关的信息。
  • NGX_CHANGEBIN_SIGNALSIGUSR2在大多数系统上) - 更改nginx二进制文件。主进程启动一个新的nginx二进制文件并传入所有侦听套接字的列表。在“NGINX”环境变量中传递的文本格式列表由以分号分隔的描述符编号组成。新的nginx二进制读取“NGINX”变量并将套接字添加到其init循环中。其他进程忽略此信号。

虽然所有的nginx工作进程都能够接收并正确处理POSIX信号,但主进程不使用标准的kill()系统调用来向工作者和帮助者传递信号。相反,nginx使用跨进程套接字对,它允许在所有nginx进程之间发送消息。然而,目前,信息只能从主人发送给其子女。这些消息携带标准信号。

线程

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

为了处理同步,以下pthreads原语的包装可用:

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

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

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

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

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

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

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

在配置时,一个愿意使用线程的模块必须通过调用ngx_thread_pool_add(cf,name)来获得对线程池的引用,该线程池或者使用给定的名称创建一个新的线程池,或者如果它存在,返回一个对具有该名称的池的引用。

要在运行时将任务添加到指定线程池tp的队列中,请使用ngx_thread_task_post(tp,task)函数。 要在线程中执行函数,请使用ngx_thread_task_t结构传递参数并设置完成处理程序:

typedef struct {
    int    foo;
} my_thread_ctx_t;


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

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


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

    /* executed in nginx event loop */
}


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

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

    ctx = task->ctx;

    ctx->foo = 42;

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

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

    return NGX_OK;
}

模块

添加新模块

每个独立的nginx模块驻留在一个单独的目录中,该目录至少包含两个文件:config一个包含模块源代码的文件。该config文件包含nginx集成模块所需的所有信息,例如:

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

. auto/module

ngx_addon_name=$ngx_module_name

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

  • ngx_module_type - 要建立的模块类型。可能的值是COREHTTPHTTP_FILTERHTTP_INIT_FILTERHTTP_AUX_FILTERMAILSTREAM,或MISC
  • ngx_module_name - 模块名称。要从一组源文件构建多个模块,请指定一个以空格分隔的名称列表。名字表示动态模块的输出二进制文件的名称。列表中的名称必须与源代码中使用的名称相匹配。
  • ngx_addon_name - 模块在配置脚本中显示在控制台上的输出中的名称。
  • ngx_module_srcs - 用于编译模块的空白分隔的源文件列表。该$ngx_addon_dir变量可以用来表示模块目录的路径。
  • ngx_module_incs - 包含构建模块所需的路径
  • ngx_module_deps - 模块依赖关系的空白分隔列表。通常,它是头文件的列表。
  • ngx_module_libs - 与模块链接的以空格分隔的库列表。例如,用于ngx_module_libs=-lpthread链接libpthread库。下面的宏可以用来对同一库,nginx的链接:LIBXSLTLIBGDGEOIPPCREOPENSSLMD5SHA1ZLIB,和PERL
  • ngx_module_link- 由构建系统DYNAMIC为动态模块或ADDON静态模块设置的变量,用于根据链接类型确定要执行的不同动作。
  • ngx_module_order - 为模块加载订单; 对于有用HTTP_FILTERHTTP_AUX_FILTER模块类型。此选项的格式是以空格分隔的模块列表。当前模块名称后面的列表中的所有模块都会在全局模块列表中出现,这会设置模块初始化的顺序。对于后面的过滤器模块,初始化意味着更早执 以下模块通常用作参考。的ngx_http_copy_filter_module其它的过滤器模块读取数据,并设置在靠近列表的底部,使得它是第一个被执行的一个。在ngx_http_write_filter_module将数据写入到客户端插座,设置在靠近列表的顶部,并且是要执行的最后一次。默认情况下,过滤器模块放置在ngx_http_copy_filter在模块列表中,以便在复制过滤器处理程序之后执行过滤器处理程序。对于其他模块类型,默认为空字符串。

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

核心模块

模块是nginx的构建块,其大部分功能都是作为模块实现的。模块源文件必须包含一个类型的全局变量ngx_module_t,其定义如下:

struct ngx_module_s {

    /* private part is omitted */

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

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

    void                (*exit_master)(ngx_cycle_t *cycle);

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

省略的专用部分包含模块版本和签名,并使用预定义的宏填充NGX_MODULE_V1

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

  • 配置指令处理程序在主进程的上下文中出现在配置文件中时被调用。
  • 配置成功解析后,init_module处理程序将在主进程的上下文中调用。该init_module处理程序是在主过程中的每个的结构被加载时被调用。
  • 主进程创建一个或多个工作进程,并init_process在每个进程中调用该处理程序。
  • 当工作进程从主服务器接收到关闭或终止命令时,它会调用该exit_process处理程序。
  • 主进程exit_master在退出之前调用处理程序。

由于线程仅在nginx中用作具有自己的API的补充I / O工具,因此init_thread和exit_thread处理程序当前未被调用。 也没有init_master处理程序,因为这将是不必要的开销。

该模块type准确定义了ctx现场存储的内容。它的值是以下类型之一:

  • NGX_CORE_MODULE
  • NGX_EVENT_MODULE
  • NGX_HTTP_MODULE
  • NGX_MAIL_MODULE
  • NGX_STREAM_MODULE

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

该组的核心模块包括ngx_core_modulengx_errlog_modulengx_regex_modulengx_thread_pool_modulengx_openssl_module模块。HTTP模块,流模块,邮件模块和事件模块也是核心模块。核心模块的上下文定义为:

typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

其中name是一个模块名称字符串,create_confinit_conf是指向创建和初始化分别模块配置功能。对于核心模块,nginx create_conf在解析新配置之前以及init_conf在所有配置解析成功后调用。典型的create_conf功能为配置分配内存并设置默认值。

例如,一个简单的模块ngx_foo_module可能看起来像这样:

/*
 * Copyright (C) Author.
 */


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


typedef struct {
    ngx_flag_t  enable;
} ngx_foo_conf_t;


static void *ngx_foo_create_conf(ngx_cycle_t *cycle);
static char *ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf);

static char *ngx_foo_enable(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_enable_post = { ngx_foo_enable };


static ngx_command_t  ngx_foo_commands[] = {

    { ngx_string("foo_enabled"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      0,
      offsetof(ngx_foo_conf_t, enable),
      &ngx_foo_enable_post },

      ngx_null_command
};


static ngx_core_module_t  ngx_foo_module_ctx = {
    ngx_string("foo"),
    ngx_foo_create_conf,
    ngx_foo_init_conf
};


ngx_module_t  ngx_foo_module = {
    NGX_MODULE_V1,
    &ngx_foo_module_ctx,                   /* module context */
    ngx_foo_commands,                      /* module directives */
    NGX_CORE_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};


static void *
ngx_foo_create_conf(ngx_cycle_t *cycle)
{
    ngx_foo_conf_t  *fcf;

    fcf = ngx_pcalloc(cycle->pool, sizeof(ngx_foo_conf_t));
    if (fcf == NULL) {
        return NULL;
    }

    fcf->enable = NGX_CONF_UNSET;

    return fcf;
}


static char *
ngx_foo_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_foo_conf_t *fcf = conf;

    ngx_conf_init_value(fcf->enable, 0);

    return NGX_CONF_OK;
}


static char *
ngx_foo_enable(ngx_conf_t *cf, void *post, void *data)
{
    ngx_flag_t  *fp = data;

    if (*fp == 0) {
        return NGX_CONF_OK;
    }

    ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Foo Module is enabled");

    return NGX_CONF_OK;
}

配置指令

ngx_command_t类型定义了单个配置指令。每个支持配置的模块都提供了一组这样的结构,用于描述如何处理参数以及要调用的处理程序:

typedef struct ngx_command_s  ngx_command_t;

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

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

  • NGX_CONF_NOARGS - 指令不需要任何参数。
  • NGX_CONF_1MORE - 指令采用一个或多个参数。
  • NGX_CONF_2MORE - 指令需要两个或更多参数。
  • NGX_CONF_TAKE1.. NGX_CONF_TAKE7- 指令完全采用指定数量的参数。
  • NGX_CONF_TAKE12NGX_CONF_TAKE13NGX_CONF_TAKE23NGX_CONF_TAKE123NGX_CONF_TAKE1234-指令可以采取不同数量的参数。选项仅限于给定的数字。例如,NGX_CONF_TAKE12意味着它需要一个或两个参数。

指令类型的标志是:

  • NGX_CONF_BLOCK - 指令是一个块,也就是说,它可以在其打开和关闭花括号中包含其他指令,甚至可以实现其自己的解析器来处理内部的内容。
  • NGX_CONF_FLAG- 指令采用布尔值,on或者off

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

  • NGX_MAIN_CONF - 在顶级环境中。
  • NGX_HTTP_MAIN_CONF- 在http块中。
  • NGX_HTTP_SRV_CONF- 在server块内的http块中。
  • NGX_HTTP_LOC_CONF- 在location块内的http块中。
  • NGX_HTTP_UPS_CONF- 在upstream块内的http块中。
  • NGX_HTTP_SIF_CONF- 在if块中块内的server块中http
  • NGX_HTTP_LIF_CONF- 在if块中块内的location块中http
  • NGX_HTTP_LMT_CONF- 在limit_except块内的http块中。
  • NGX_STREAM_MAIN_CONF- 在stream块中。
  • NGX_STREAM_SRV_CONF- 在server块内的stream块中。
  • NGX_STREAM_UPS_CONF- 在upstream块内的stream块中。
  • NGX_MAIL_MAIN_CONF- 在mail块中。
  • NGX_MAIL_SRV_CONF- 在server块内的mail块中。
  • NGX_EVENT_CONF- 在event块中。
  • NGX_DIRECT_CONF - 由不创建上下文层次结构并且只有一个全局配置的模块使用。该配置作为conf参数传递给处理程序。

配置解析器使用这些标记在错误指令的情况下引发错误,并调用指向处理程序的正确配置指针,以便不同位置的指令可以将它们的值存储在不同的位置。

set字段定义一个处理程序,该处理程序处理指令并将分析的值存储到相应的配置中。有一些执行常见转换的功能:

  • ngx_conf_set_flag_slot-在文字串转换onoffngx_flag_t;值分别为1或0,值。
  • ngx_conf_set_str_slot- 将字符串存储为ngx_str_t类型的值。
  • ngx_conf_set_str_array_slot- 将值附加到ngx_array_t字符串数组ngx_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- 将提供的参数转换为一个ngx_uint_t值。ngx_conf_enum_tpost字段中传递的以null结尾的数组定义可接受的字符串和相应的整数值。
  • ngx_conf_set_bitmask_slot- 将提供的参数转换为一个ngx_uint_t值。每个参数的掩码值都进行了或运算以产生结果。ngx_conf_bitmask_tpost字段中传递的以null结尾的数组定义可接受的字符串和相应的掩码值。
  • set_path_slot- 将提供的参数转换为一个ngx_path_t值并执行所有必需的初始化。有关详细信息,请参阅proxy_temp_path指令的文档。
  • set_access_slot - 将提供的参数转换为文件权限掩码。有关详细信息,请参阅proxy_store_access指令的文档。

conf字段定义将哪个配置结构传递给目录处理程序。核心模块只有全局配置和设置NGX_DIRECT_CONF标志才能访问它。像HTTP,Stream或Mail这样的模块可以创建配置层次结构。例如,一个模块的配置是为创建serverlocationif范围。

  • NGX_HTTP_MAIN_CONF_OFFSET- http块的配置。
  • NGX_HTTP_SRV_CONF_OFFSET- server块内http块的组态。
  • NGX_HTTP_LOC_CONF_OFFSET- 在一个location块内的配置http
  • NGX_STREAM_MAIN_CONF_OFFSET- stream块的配置。
  • NGX_STREAM_SRV_CONF_OFFSET- server块内stream块的组态。
  • NGX_MAIL_MAIN_CONF_OFFSET- mail块的配置。
  • NGX_MAIL_SRV_CONF_OFFSET- server块内mail块的组态。

offset定义了在保持值对于该特定指令的模块配置结构的字段的偏移量。典型的用法是使用offsetof()宏。

post字段有两个目的:它可以用来定义一个处理程序,在主处理程序完成后调用,或将附加数据传递给主处理程序。在第一种情况下,ngx_conf_post_t结构需要使用指向处理程序的指针进行初始化,例如:

static char *ngx_do_foo(ngx_conf_t *cf, void *post, void *data);
static ngx_conf_post_t  ngx_foo_post = { ngx_do_foo };

post参数是ngx_conf_post_t对象本身,并且data是一个指针值,由主处理机从参数转换与适当的类型。

HTTP

连接

每个HTTP客户端连接通过以下阶段运行:

  • ngx_event_accept()接受客户端TCP连接。响应于侦听套接字上的读取通知,调用此处理程序。ngx_connecton_t在此阶段创建一个新对象来包装新接受的客户端套接字。每个nginx监听器都提供一个处理程序来传递新的连接对象。对于HTTP连接而言ngx_http_init_connection(c)
  • ngx_http_init_connection()执行HTTP连接的早期初始化。在此阶段ngx_http_connection_t为连接创建一个对象,并将其引用存储在连接的data字段中。稍后它将被一个HTTP请求对象替换。PROXY协议解析器和SSL握手也在此阶段开始。
  • ngx_http_wait_request_handler()当数据在客户端套接字上可用时,将调用read事件处理程序。在这个阶段,HTTP请求对象ngx_http_request_t被创建并设置到连接的data字段。
  • ngx_http_process_request_line()读事件处理程序读取客户端请求行。处理程序由...设置ngx_http_wait_request_handler()。数据被读入连接buffer。缓冲区的大小最初由指令client_header_buffer_size设置。整个客户机头应该放在缓冲区中。如果初始大小不足,则分配一个更大的缓冲区,容量由large_client_header_buffers指令设置。
  • ngx_http_process_request_headers()读事件处理程序,在ngx_http_process_request_line()读取客户端请求头之后设置。
  • ngx_http_core_run_phases()在请求头被完全读取和解析时被调用。该函数从NGX_HTTP_POST_READ_PHASEto 运行请求阶段NGX_HTTP_CONTENT_PHASE。最后一个阶段是为了产生一个响应并将其沿着过滤器链传递。在这个阶段,回应不一定会发送给客户。它可能会保持缓冲并在最终阶段发送。
  • ngx_http_finalize_request()通常在请求产生所有输出或产生错误时调用。在后一种情况下,查找适当的错误页面并将其用作响应。如果响应没有完全发送到客户端,ngx_http_writer()则会激活HTTP编写器以完成发送未完成的数据。
  • ngx_http_finalize_connection()在完整响应已发送给客户端并且请求可能被销毁时调用。如果启用了客户端连接保持活动功能,ngx_http_set_keepalive()则会调用该功能,这会破坏当前请求并等待连接上的下一个请求。否则,ngx_http_close_request()会破坏请求和连接。

请求

对于每个客户端HTTP请求,ngx_http_request_t都会创建该对象。这个对象的一些字段是:

  • 连接 - 指向ngx_connection_t客户端连接对象的指针。 几个请求可以同时引用同一个连接对象 - 一个主要请求及其子请求。 删除请求后,可以在同一个连接上创建新的请求。请注意,对于HTTP连接,ngx_connection_t的数据字段指向请求。 这种请求被称为活动的,而不是与连接相关的其他请求。 活动请求用于处理客户端连接事件,并允许将其响应输出到客户端。 通常情况下,每个请求都会在某个时间点处于活动状态,以便可以发送其输出。
  • ctx - HTTP模块上下文数组。 每个NGX_HTTP_MODULE类型的模块都可以在请求中存储任何值(通常指向结构的指针)。 该值存储在模块的ctx_index位置的ctx数组中。 以下宏提供了一种获取和设置请求上下文的简便方法:
-  `ngx_http_get_module_ctx(r, module)` — Returns the `module`'s context 
-  `ngx_http_set_ctx(r, c, module)` — Sets `c` as the `module`'s context 
  • main_confsrv_confloc_conf-当前请求的配置的阵列。配置存储在模块的ctx_index位置。
  • read_event_handlerwrite_event_handler-读写事件处理程序的请求。通常,HTTP连接的读取和写入事件处理程序都设置为ngx_http_request_handler()。该函数调用当前活动请求的处理程序read_event_handlerwrite_event_handler处理程序。
  • cache - 请求缓存对象以缓存上游响应。
  • upstream - 请求上游对象进行代理。
  • pool - 请求池。请求对象本身被分配在这个池中,当请求被删除时它被销毁。对于需要在客户端连接的整个生命周期中可用的分配,请改为使用ngx_connection_t池。
  • header_in - 读取客户端HTTP请求标头的缓冲区。
  • headers_inheaders_out- 输入和输出HTTP标头对象。两个对象都包含用于保留标题原始列表的headers类型字段ngx_list_t。除此之外,特定的标题可用于获取和设置为单独的字段,例如content_length_nstatus等等。
  • request_body - 客户端请求正文对象。
  • start_secstart_msec-时间点创建请求时,用于跟踪请求的持续时间。
  • methodmethod_name-所述客户端的HTTP请求方法的数字和文本表示。对于方法的数值定义在src/http/ngx_http_request.h与宏NGX_HTTP_GETNGX_HTTP_HEADNGX_HTTP_POST等。
  • http_protocol - 原始文本格式的客户端HTTP协议版本(“HTTP / 1.0”,“HTTP / 1.1”等)。
  • http_version-以数字形式客户端HTTP协议版本(NGX_HTTP_VERSION_10NGX_HTTP_VERSION_11等)。
  • http_majorhttp_minor- 数字形式的客户端HTTP协议版本分为主要部分和次要部分。
  • request_lineunparsed_uri-请求行和URI在原始客户机请求。
  • uriargsexten- URI,参数和当前请求的文件扩展名。由于规范化,此处的URI值可能与客户端发送的原始URI不同。在整个请求处理过程中,这些值可能会随着内部重定向的执行而改变。
  • main - 指向主要请求对象的指针。创建此对象是为了处理客户端HTTP请求,而不是为了在主要请求中执行特定子任务而创建的子请求。
  • parent - 指向子请求的父请求的指针。
  • postponed - 输出缓冲区和子请求的列表,按它们的发送和创建顺序排列。该列表由推迟过滤器在部分子请求创建时提供一致的请求输出。
  • post_subrequest - 当一个子请求被完成时,指向一个处理器的上下文被调用。未用于主要请求。
  • posted_requests- 通过调用请求完成的要开始或恢复的请求列表write_event_handler。通常,这个处理程序持有请求主函数,它首先运行请求阶段,然后生成输出。通常通过ngx_http_post_request(r, NULL)电话发布请求。它始终发布到主要的请求posted_requests列表中。该函数ngx_http_run_posted_requests(c)运行传递的连接的活动请求的主要请求中发布的所有请求。所有事件处理程序都会调用ngx_http_run_posted_requests,这会导致发布新的请求。通常,在调用请求的读取或写入处理程序后调用它。
  • phase_handler - 当前请求阶段的索引。
  • ncapturescapturescaptures_data-正则表达式捕获由请求的最后一个正则表达式的匹配产生的。在请求处理过程中,可以在许多地方进行正则表达式匹配:映射查找,通过SNI或HTTP主机进行的服务器查找,重写,代理重定向等。查找产生的捕获存储在上述字段中。该字段ncaptures包含捕获次数,captures持有捕获边界并captures_data保存正则表达式匹配的字符串,并用于提取捕获。每次匹配新的正则表达式后,请求捕获将被重置为保存新值。
  • count - 请求参考计数器。该字段仅适用于主要请求。增加计数器是简单的r->main->count++。要减少计数器,请致电ngx_http_finalize_request(r, rc)。创建子请求并运行请求体读取过程都会增加计数器。
  • subrequests - 当前子请求嵌套级别。每个子请求继承父级的嵌套级别,减少一级。如果值达到零,则会生成错误。主请求的值由NGX_HTTP_MAX_SUBREQUESTS常量定义。
  • uri_changes - 请求剩余的URI更改数量。请求可以更改其URI的总次数受NGX_HTTP_MAX_URI_CHANGES常数的限制。随着每次更改,值都会递减,直到达到零,此时会产生错误。重写和内部重定向到正常或指定位置被视为URI更改。
  • blocked - 请求中保留的块的计数器。虽然此值不为零,但请求无法终止。目前,该值由于等待AIO操作(POSIX AIO和线程操作)和活动高速缓存锁定而增加。
  • buffered - 位掩码显示哪些模块缓冲了请求产生的输出。许多过滤器可以缓冲输出; 例如,由于部分字符串匹配,sub_filter可以缓冲数据,由于缺少空闲输出缓冲区等,复制过滤器可以缓冲数据。只要该值不为零,请求就不会在冲洗之前完成。
  • header_only - 表示输出不需要主体的标志。例如,此标志由HTTP HEAD请求使用。
  • keepalive - 指示是否支持客户端连接保持活动的标志。该值是从HTTP版本和“连接”标题的值中推断出来的。
  • header_sent - 指示输出头已被请求发送的标志。
  • internal - 指示当前请求是内部的标志。要进入内部状态,请求必须通过内部重定向或成为子请求。内部请求被允许进入内部位置。
  • allow_ranges - 指示可以按照HTTP范围标头的要求将部分响应发送到客户端的标志。
  • subrequest_ranges - 指示可以在处理子请求时发送部分响应的标志。
  • single_range - 表示只有单个连续范围的输出数据可以发送到客户端的标志。此标志通常在发送数据流(例如来自代理服务器)时设置,并且整个响应在一个缓冲区中不可用。
  • main_filter_need_in_memoryfilter_need_in_memory-标志,要求输出在内存缓冲区,而不是文件生成的。即使启用了sendfile,这也是复制过滤器从文件缓冲区读取数据的信号。两个标志之间的区别是设置它们的过滤器模块的位置。在过滤器链集中的推迟过滤器之前调用的过滤器filter_need_in_memory,要求只有当前请求输出进入内存缓冲区。在过滤器链集中稍后调用的过滤器main_filter_need_in_memory,要求主请求和所有子请求都在发送输出时读取内存中的文件。
  • filter_need_temporary - 标志请求在临时缓冲区中产生请求输出,但不在只读内存缓冲区或文件缓冲区中产生。这被过滤器使用,它可以直接在发送它的缓冲区中改变输出。

组态

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

  • 主配置 - 适用于整个http块。作为模块的全局设置。
  • 服务器配置 - 适用于单个server块。作为模块的服务器特定设置。
  • 位置配置-适用于一个单一的locationiflimit_except块。用作模块的位置特定设置。

配置结构是在nginx配置阶段通过调用函数来创建的,这些函数分配结构,初始化它们并合并它们。以下示例显示如何为模块创建简单的位置配置。该配置具有一个foo无符号整数类型的设置。

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


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

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

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

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


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

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

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


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

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

如示例中所示,该ngx_http_foo_create_loc_conf()函数创建一个新的配置结构,并将ngx_http_foo_merge_loc_conf()配置与较高级别的配置合并。实际上,服务器和位置配置不仅仅存在于服务器和位置级别,还会为其上的所有级别创建。具体来说,还会在主级别创建服务器配置,并在主级别,服务器级别和位置级别创建位置配置。这些配置使得可以在nginx配置文件的任何级别指定服务器和位置特定的设置。最终配置被合并。一些像宏NGX_CONF_UNSETNGX_CONF_UNSET_UINT提供了用于指示缺少的设置,而忽略它,而合并。标准nginx合并宏,如ngx_conf_merge_value()ngx_conf_merge_uint_value()提供一种合并设置并设置默认值的简便方法,如果没有配置提供明确的值。有关不同类型宏的完整列表,请参阅src/core/ngx_conf_file.h

以下宏可用。用于在配置时访问HTTP模块的配置。他们都参考 ngx_conf_t作为第一个参数。

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

以下示例获取指向标准nginx核心模块ngx_http_core_module的位置配置的指针,并替换保存在handler结构字段中的位置内容处理程序。

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);


static ngx_command_t  ngx_http_foo_commands[] = {

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

      ngx_null_command
};


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

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

    return NGX_CONF_OK;
}

以下宏可用于在运行时访问HTTP模块的配置。

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

这些宏接收对HTTP请求的引用ngx_http_request_t。请求的主要配置不会改变。在选择了请求的虚拟服务器后,服务器配置可以从缺省更改。选择用于处理请求的位置配置可以因重写操作或内部重定向而多次更改。以下示例显示如何在运行时访问模块的HTTP配置。

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

    flcf = ngx_http_get_module_loc_conf(r, ngx_http_foo_module);

    ...
}

Phases

每个HTTP请求都通过一系列阶段。在每个阶段,对请求执行不同类型的处理。特定于模块的处理程序可以在大多数阶段注册,并且许多标准nginx模块注册它们的阶段处理程序,作为在请求处理的特定阶段调用的方式。阶段被连续地处理,并且阶段处理程序在请求到达阶段时被调用。以下是nginx HTTP阶段列表。

  • NGX_HTTP_POST_READ_PHASE- 第一阶段。ngx_http_realip_module在此阶段注册其处理程序,以在调用任何其他模块之前启用对客户端地址的替换。
  • NGX_HTTP_SERVER_REWRITE_PHASE- 处理在server块中定义的重写指令(但在location块之外)的阶段。ngx_http_rewrite_module在这个阶段安装它的处理程序。
  • NGX_HTTP_FIND_CONFIG_PHASE - 根据请求URI选择位置的特殊阶段。在此阶段之前,将相关虚拟服务器的默认位置分配给该请求,并且任何请求位置配置的模块都将接收默认服务器位置的配置。此阶段为请求分配一个新位置。在这个阶段没有额外的处理程序可以注册。
  • NGX_HTTP_REWRITE_PHASE- 与NGX_HTTP_SERVER_REWRITE_PHASE之前阶段选择的相同,但是在位置中定义的重写规则。
  • NGX_HTTP_POST_REWRITE_PHASE - 如果在重写过程中URI的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_module和ngx_http_auth_basic_module)在此阶段注册其处理程序。默认情况下,客户端必须通过对此阶段注册的所有处理程序的授权检查,才能继续进行下一个阶段。如果任何阶段处理程序授权客户端,则可以使用该满足指令来允许处理继续。
  • NGX_HTTP_POST_ACCESS_PHASE - 处理满足任何指令的特殊阶段。如果某些访问阶段处理程序拒绝访问并且没有明确允许访问,则请求已完成。在这个阶段没有额外的处理程序可以注册。
  • NGX_HTTP_PRECONTENT_PHASE - 处理程序在生成内容之前被调用的阶段。标准模块(如ngx_http_try_files_module和ngx_http_mirror_module)在此阶段注册其处理程序。
  • NGX_HTTP_CONTENT_PHASE - 正常生成响应的阶段。多个nginx标准模块在此阶段注册它们的处理程序,包括ngx_http_index_module或ngx_http_static_module。它们被顺序调用,直到其中一个产生输出。也可以在每个位置设置内容处理程序。如果ngx_http_core_module的位置配置已handler设置,则将其作为内容处理程序调用,并且在此阶段安装的处理程序将被忽略。
  • NGX_HTTP_LOG_PHASE - 执行请求记录的阶段。目前,只有ngx_http_log_module在此阶段注册其处理程序以进行访问日志记录。在释放请求之前,在请求处理的最后调用日志阶段处理程序。

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

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

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

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

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


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

    ua = r->headers_in->user_agent;

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

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

    return NGX_DECLINED;
}


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

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

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

    *h = ngx_http_foo_handler;

    return NGX_OK;
}

预计阶段处理程序将返回特定的代码:

  • NGX_OK - 继续下一个阶段。
  • NGX_DECLINED - 继续执行当前阶段的下一个处理程序。如果当前处理程序是当前阶段的最后一个处理程序,则转到下一个阶段。
  • NGX_AGAINNGX_DONE- 暂停阶段处理,直到某些将来可能是异步I / O操作或仅延迟的事件为止。假定稍后通过调用来恢复阶段处理ngx_http_core_run_phases()
  • 阶段处理程序返回的任何其他值都被视为请求终止代码,特别是HTTP响应代码。该请求是使用提供的代码完成的。

对于某些阶段,返回代码的处理方式稍有不同。在内容阶段,任何NGX_DECLINED被认为是最终代码的返回代码。来自位置内容处理程序的任何返回代码都被视为终结代码。在接入阶段,在满足任何模式下,比其它任何返回代码NGX_OKNGX_DECLINEDNGX_AGAINNGX_DONE被认为是一个否定。如果后续访问处理程序不允许或拒绝使用其他代码访问,则拒绝代码将成为终止代码。

变量

访问现有变量

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

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

index = ngx_http_get_variable_index(cf, &name);

这里的cf是一个指向nginx配置的指针,并name指向一个包含变量名称的字符串。NGX_ERROR否则,该函数返回错误或有效索引,通常将其存储在模块配置的某处以备将来使用。

所有HTTP变量都在给定的HTTP请求的上下文中进行评估,并且结果是特定于该HTTP请求并缓存的。所有评估变量的函数都返回该ngx_http_variable_value_t类型,代表变量值:

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

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

    u_char     *data;
} ngx_variable_value_t;

如下:

  • len - 值的长度
  • data - 价值本身
  • valid - 该值有效
  • not_found- 变量没有找到,因此datalen字段无关; 这可能会发生,例如,像$arg_foo一个请求中没有传递相应参数时那样的变量
  • no_cacheable - 不要缓存结果
  • escape - 记录模块在内部使用,以标记需要在输出上转义的值。

ngx_http_get_flushed_variable()ngx_http_get_indexed_variable()功能被用于获得变量的值。它们具有相同的接口 - 接受HTTP请求r作为评估变量的上下文和index标识它的变量。典型用法的一个例子:

ngx_http_variable_value_t  *v;

v = ngx_http_get_flushed_variable(r, index);

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

/* some meaningful value is found */

函数之间的区别在于ngx_http_get_indexed_variable()返回一个缓存值而ngx_http_get_flushed_variable()为非缓存变量刷新缓存。

某些模块(如SSI和Perl)需要处理在配置时不知道名称的变量。因此索引不能用于访问它们,但该ngx_http_get_variable(r, name, key)功能可用。它搜索一个给定的变量,namekey从该名称派生哈希。

创建变量

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

  • NGX_HTTP_VAR_CHANGEABLE - 允许重新定义变量:如果另一个模块定义了一个名称相同的变量,则不会发生冲突。这允许set指令覆盖变量。
  • NGX_HTTP_VAR_NOCACHEABLE- 禁用缓存,这对变量如像$time_local
  • NGX_HTTP_VAR_NOHASH - 表示此变量只能通过索引访问,而不能通过名称访问。当知道在SSI或Perl等模块中不需要该变量时,这是一个小的优化。
  • NGX_HTTP_VAR_PREFIX - 变量的名称是一个前缀。在这种情况下,处理程序必须实现额外的逻辑来获取特定变量的值。例如,所有arg_变量都由相同的处理程序处理,该处理程序在请求参数中执行查找并返回特定参数的值。

该函数在发生错误时返回NULL,否则返回ngx_http_variable_t的指针:

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

getset处理程序调用以获取或设置变量值,data传递给可变处理程序,以及index保存指派用于引用变量变量索引。

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

static ngx_http_variable_t  ngx_http_foo_vars[] = {

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

      ngx_http_null_variable
};

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

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

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

    return NGX_OK;
}

该示例中的函数用于初始化preconfigurationHTTP模块上下文的字段,并在解析HTTP配置之前调用,以便解析器可以引用这些变量。

get处理程序负责在特定请求的上下文中评估的变量,例如:

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

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

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

    return NGX_OK;
}

如果发生内部错误(例如,内存分配失败),则返回NGX_ERROR,否则返回NGX_OK。 要了解变量评估的状态,请检查ngx_http_variable_value_t中的标志(请参阅上面的描述)。

set处理器可以通过设置变量引用的属性。例如,$limit_rate变量的集合处理程序修改请求的limit_rate字段:

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

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

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

    s = ngx_parse_size(&val);

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

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

    *sp = s;

    return;
}

复杂的值

一个复杂的值,尽管它的名字,提供了一个简单的方法来评估表达式,其中可以包含文本,变量和它们的组合。

复杂的值描述在ngx_http_compile_complex_value配置阶段进行编译ngx_http_complex_value_t,运行时用于获取表达式评估的结果。

ngx_str_t                         *value;
ngx_http_complex_value_t           cv;
ngx_http_compile_complex_value_t   ccv;

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

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

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

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

这里的ccv保存初始化复数值所需的所有参数cv

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

zero当结果传递给需要零终止字符串的库时,该标志非常有用,并且在处理文件名时前缀非常方便。

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

ngx_http_set_complex_value_slot()是一个方便的函数,用于在指令声明本身完全初始化一个复杂的值。

在运行时,可以使用以下ngx_http_complex_value()函数计算复杂的值:

ngx_str_t  res;

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

鉴于请求r和以前编译的值cv,函数将评估表达式并将结果写入res

请求重定向

一个HTTP请求总是通过ngx_http_request_t结构的loc_conf字段连接到一个位置。这意味着在任何时候都可以通过调用ngx_http_get_module_loc_conf(r,module)从请求中检索任何模块的位置配置。请求的位置可以在请求的生命周期中多次更改。最初,默认服务器的默认服务器位置被分配给请求。如果请求切换到不同的服务器(由HTTP“主机”头或SSL SNI扩展选择),请求也会切换到该服务器的默认位置。位置的下一次更改发生在NGX_HTTP_FIND_CONFIG_PHASE请求阶段。在此阶段,通过为服务器配置的所有非命名位置中的请求URI选择一个位置。由于rewrite指令,ngx_http_rewrite_module可以在NGX_HTTP_REWRITE_PHASE请求阶段更改请求URI,并将请求发送回NGX_HTTP_FIND_CONFIG_PHASE阶段,以根据新URI选择新位置。

也可以通过调用ngx_http_internal_redirect(r,uri,args)或ngx_http_named_location(r,name)中的一个将请求重定向到新位置。

ngx_http_internal_redirect(r,uri,args)函数更改请求URI并将请求返回到NGX_HTTP_SERVER_REWRITE_PHASE阶段。 该请求继续进行服务器默认位置。 稍后在NGX_HTTP_FIND_CONFIG_PHASE处根据新的请求URI选择一个新位置。

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

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

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

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

该功能ngx_http_named_location(r, name)将请求重定向到指定位置。该位置的名称作为参数传递。该位置在当前服务器的所有命名位置之间查找,之后请求切换到NGX_HTTP_REWRITE_PHASE阶段。

以下示例执行重定向到指定位置@foo。

ngx_int_t
ngx_http_foo_named_redirect(ngx_http_request_t *r)
{
    ngx_str_t  name;

    ngx_str_set(&name, "foo");

    return ngx_http_named_location(r, &name);
}

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

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

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

子请求

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

子请求从NGX_HTTP_SERVER_REWRITE_PHASE阶段开始。它作为普通请求通过相同的后续阶段,并根据其自己的URI分配一个位置。

子请求中的输出标题始终被忽略。的ngx_http_postpone_filter地方相对于由父请求产生的其他数据的正确位置中的子请求的输出机构。

子请求与主动请求的概念有关。 如果c-> data == r,则请求r被视为活动的,其中c是客户端连接对象。 在任何给定的点上,只有请求组中的活动请求才允许将其缓冲区输出到客户端。 不活动的请求仍然可以将其输出发送到过滤器链,但不会超出ngx_http_postpone_filter范围,并保持该过滤器的缓冲状态,直到请求变为活动状态。 以下是请求激活的一些规则:

  • 最初,主要要求是积极的。
  • 活动请求的第一个子请求在创建后立即生效。
  • ngx_http_postpone_filter激活主动请求的子请求列表,一旦请求之前,所有的数据都发送一个请求。
  • 当一个请求结束时,它的父代被激活。

通过调用函数ngx_http_subrequest(r,uri,args,psr,ps,flags)来创建子请求,其中r是父请求,uri和args是子请求的URI和参数,psr是输出参数,它接收 新创建的子请求引用,ps是一个回调对象,用于通知父请求子请求正在终结,标志是标志的位掩码。 以下标志可用:

  • NGX_HTTP_SUBREQUEST_IN_MEMORY - 输出不会发送到客户端,而是存储在内存中。该标志只影响由其中一个代理模块处理的子请求。子请求完成后,其输出可用于某种r->upstream->buffer类型ngx_buf_t
  • NGX_HTTP_SUBREQUEST_WAITED- done即使子请求在最终确定时处于非活动状态,子请求标志也会被设置。该子请求标志由SSI过滤器使用。
  • NGX_HTTP_SUBREQUEST_CLONE - 子请求被创建为父级的克隆。它在相同的位置开始,并从与父请求相同的阶段开始。

以下示例使用/ foo的URI创建子请求。

ngx_int_t            rc;
ngx_str_t            uri;
ngx_http_request_t  *sr;

...

ngx_str_set(&uri, "/foo");

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

本示例克隆当前请求并为子请求设置终结回调。

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

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

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

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


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

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

    return rc;
}

子请求通常在正文过滤器中创建,在这种情况下,它们的输出可以像来自任何明确请求的输出一样对待。这意味着最终子请求的输出会在创建子请求之前和创建之后传递的任何缓冲区之前传递的所有显式缓冲区之后发送到客户端。即使对于大量的子请求层次,也可以保留此顺序。以下示例在所有请求数据缓冲区之后但在带有last_buf标志的最终缓冲区之前插入子请求的输出。

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

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

    last = 0;

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

    /* Output explicit output buffers */

    rc = ngx_http_next_body_filter(r, in);

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

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

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

    ngx_http_set_ctx(r, NULL, ngx_http_foo_filter_module);

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

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

    b->last_buf = 1;

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

    return ngx_http_output_filter(r, &out);
}

子请求也可以创建用于数据输出之外的其他用途。例如,ngx_http_auth_request_module模块在该NGX_HTTP_ACCESS_PHASE阶段创建一个子请求。要在此时禁用输出,该header_only标志将设置在子请求上。这可以防止将子请求正文发送给客户端。请注意,子请求的标题永远不会发送到客户端。子请求的结果可以在回调处理程序中分析。

请求完成

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

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

  • NGX_DONE - 快速完成。递减请求count并在请求达到零时销毁请求。当前请求被销毁后,客户端连接可用于更多请求。
  • NGX_ERRORNGX_HTTP_REQUEST_TIME_OUT408),NGX_HTTP_CLIENT_CLOSED_REQUEST499) - 错误结束。尽快终止请求并关闭客户端连接。
  • NGX_HTTP_CREATED201),NGX_HTTP_NO_CONTENT204),代码大于或等于NGX_HTTP_SPECIAL_RESPONSE300) - 特殊响应终止。对于这些值,nginx或者向客户端发送代码的默认响应页面,或者执行内部重定向到error_page位置(如果为代码配置的话)。
  • 其他代码被认为是成功的终结代码,并可能激活请求书写器完成发送响应主体。一旦主体完全发送,请求count就会减少。如果它达到零,请求将被销毁,但客户端连接仍可用于其他请求。如果count是肯定的,请求中还有未完成的活动,这些活动将在晚些时候完成。

请求正文

用于处理客户机请求的主体中,nginx提供ngx_http_read_client_request_body(r, post_handler)ngx_http_discard_request_body(r)功能。第一个函数读取请求主体并通过request_body请求字段使其可用。第二个函数指示nginx放弃(读取和忽略)请求体。必须为每个请求调用其中一个函数。通常,内容处理程序发起呼叫。

不允许从子请求中读取或丢弃客户端请求主体。必须始终在主要要求中完成。创建request_body子请求时,如果主请求先前已读取请求主体,则它继承父请求的子对象可以使用的对象。

该函数ngx_http_read_client_request_body(r, post_handler)启动读取请求主体的过程。当主体被完全读取时,post_handler调用回调来继续处理请求。如果请求主体缺失或已被读取,则立即调用回调。该函数ngx_http_read_client_request_body(r, post_handler)分配request_body类型的请求字段ngx_http_request_body_t。该bufs对象的字段将结果保存为缓冲链。如果由client_body_buffer_size指令指定的容量不足以将整个主体放入内存中,则主体可以保存在内存缓冲区或文件缓冲区中。

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

ngx_int_t
ngx_http_foo_content_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    rc = ngx_http_read_client_request_body(r, ngx_http_foo_init);

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

    return NGX_DONE;
}


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

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

    len = 0;

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

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

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

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

    rc = ngx_http_send_header(r);

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

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

    rc = ngx_http_output_filter(r, &out);

    ngx_http_finalize_request(r, rc);
}

请求的以下字段确定如何读取请求正文:

  • request_body_in_single_buf - 将主体读取到单个内存缓冲区。
  • request_body_in_file_only - 始终将文本读取到文件中,即使放入内存缓冲区也是如此。
  • request_body_in_persistent_file - 创建后不要立即取消链接文件。具有此标志的文件可以移动到另一个目录。
  • request_body_in_clean_file - 请求完成时取消链接文件。当文件应该移动到另一个目录但由于某种原因未被移动时,这可能很有用。
  • request_body_file_group_access - 通过用0660替换默认的0600访问掩码来启用对该文件的组访问。
  • request_body_file_log_level - 记录文件错误的严重级别。
  • request_body_no_buffering - 无缓冲地读取请求主体。

request_body_no_buffering标志启用读取请求主体的非缓冲模式。 在这种模式下,在调用ngx_http_read_client_request_body()之后,bufs链可能仅保留正文的一部分。 要阅读下一部分,请调用ngx_http_read_unbuffered_request_body(r)函数。 返回值NGX_AGAIN和请求标志reading_body表示有更多数据可用。 如果在调用此函数后bufs为NULL,那么此刻没有任何可读的内容。 请求回调read_event_handler将在请求主体的下一部分可用时被调用。

响应

在nginx中,HTTP响应是通过发送响应头和随后的可选响应主体来产生的。标题和正文都通过一系列过滤器传递,最终被写入客户端套接字。nginx模块可以将其处理程序安装到标题或正文过滤器链中,并处理来自以前处理程序的输出。

响应头

ngx_http_send_header(r)函数发送输出标题。 在r-> headers_out包含生成HTTP响应头所需的全部数据之前,请不要调用此函数。 必须始终设置r-> headers_out中的状态字段。 如果响应状态指示响应主体在头部之后,则也可以设置content_length_n。 该字段的默认值是-1,这意味着主体大小未知。 在这种情况下,使用分块传输编码。 要输出任意标题,请追加标题列表。

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

    /* send header */

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

    /* X-Foo: foo */

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

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

    rc = ngx_http_send_header(r);

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

    /* send body */

    ...
}

标头过滤器

ngx_http_send_header(r)函数通过调用ngx_http_top_header_filter变量中存储的第一个头过滤器处理程序来调用头过滤器链。假定每个头处理程序调用链中的下一个处理程序,直到ngx_http_header_filter(r)调用最终处理程序。最终的头处理程序基于HTTP构建HTTP响应r->headers_out并将其传递给ngx_http_writer_filter输出。

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

以下示例的头过滤器模块将HTTP头“ X-Foo: foo”添加到每个具有状态的响应200

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


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


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

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

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

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


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


static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;


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

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

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

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

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

    return ngx_http_next_header_filter(r);
}


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

    return NGX_OK;
}

响应体

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

以下示例生成一个完整的HTTP响应,其中包含“foo”作为其正文。例如,作为子请求以及主要请求工作,该last_in_chain标志设置在输出的最后一个缓冲区中。该last_buf标志仅为主请求设置,因为子请求的最后一个缓冲区不会结束整个输出。

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

    /* send header */

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

    rc = ngx_http_send_header(r);

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

    /* send body */

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

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

    b->memory = 1;

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

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

    return ngx_http_output_filter(r, &out);
}

主体过滤器

该函数ngx_http_output_filter(r, cl)通过调用存储在ngx_http_top_body_filter变量中的第一个body过滤器处理函数来调用body过滤器链。假定每个主体处理程序调用链中的下一个处理程序,直到ngx_http_write_filter(r, cl)调用最终处理程序。

主体过滤器处理程序接收一系列缓冲区。处理程序应该处理缓冲区并将可能的新链传递给下一个处理程序。值得注意的是,ngx_chain_t传入链的链接属于呼叫者,不能重复使用或更改。处理程序完成后,调用程序可以使用其输出链接来跟踪它发送的缓冲区。为了保存缓冲区链或在传递到下一个过滤器之前替换一些缓冲区,处理程序需要分配自己的链式链接。

以下是一个简单的身体过滤器示例,用于计算正文中的字节数。结果$counter可用作访问日志中使用的变量。

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


typedef struct {
    off_t  count;
} ngx_http_counter_filter_ctx_t;


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


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

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

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

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


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


static ngx_http_output_body_filter_pt  ngx_http_next_body_filter;

static ngx_str_t  ngx_http_counter_name = ngx_string("counter");


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

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

        ngx_http_set_ctx(r, ctx, ngx_http_counter_filter_module);
    }

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

    return ngx_http_next_body_filter(r, in);
}


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

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

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

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

    return NGX_OK;
}


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

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

    var->get_handler = ngx_http_counter_variable;

    return NGX_OK;
}


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

    return NGX_OK;
}

建立过滤器模块

编写主体或标头过滤器时,请特别注意过滤器顺序中的过滤器位置。有许多由nginx标准模块注册的头文件和正文过滤器。nginx标准模块注册了许多头和主体过滤器,因此在正确的位置注册新的过滤器模块非常重要。通常情况下,模块在后配置处理程序中注册过滤器。处理过程中调用过滤器的顺序显然与它们的注册顺序相反。

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

以下示例显示了假设只有一个源文件的模块的过滤器模块配置文件ngx_http_foo_filter_module.c

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

. auto/module

缓冲区重用

发布或更改缓冲区流时,通常需要重新使用分配的缓冲区。在nginx代码中一个标准和广泛采用的方法是为此保留两个缓冲链:空闲和忙碌。自由链保留所有空闲缓冲区,可以重复使用。 busy链保留当前模块发送的所有缓冲区,这些缓冲区仍被其他一些过滤器处理程序使用。如果缓冲区的大小大于零,则认为正在使用缓冲区。通常,当过滤器消耗一个缓冲区时,其pos(或文件缓冲区的file_pos)将移至最后(文件缓冲区的file_last)。一旦缓冲区完全消耗完毕,就可以重新使用了。要将新释放的缓冲区添加到自由链中,它足以遍历繁忙的链,并将零大小的缓冲区移动到空闲位置。这个操作非常常见,以至于它有一个特殊的功能,ngx_chain_update_chains(free,busy,out,tag)。该函数将输出链附加到繁忙状态,并将忙闲状态下的空闲缓冲区移动到空闲状态。只有具有指定标签的缓冲区才会被重用。这使得模块只能重用它自己分配的缓冲区。

以下示例是在每个传入缓冲区之前插入字符串“foo”的正文过滤器。 如果可能的话,模块分配的新缓冲区将被重新使用。 请注意,为了使此示例正常工作,还需要设置标题筛选器并将content_length_n重置为-1,但此处不提供相关代码。

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


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

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

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

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

    ll = &out;

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

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

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

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

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

        /* append the next incoming buffer */

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

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

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

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

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

    return rc;
}

负载均衡

ngx_http_upstream_module提供将请求传递到远程服务器所需的基本功能。实现特定协议的模块(如HTTP或FastCGI)使用此功能。该模块还提供了一个用于创建自定义负载平衡模块的界面,并实现了默认的循环方法。

least_conn和hash模块实现了替代的负载均衡方法,但实际上被实现为上游循环模块的扩展,并与之共享许多代码,例如服务器组的表示。Keepalive模块是一个扩展上游功能的独立模块。

ngx_http_upstream_module可以通过将相应的上游块放入配置文件中明确配置,或者通过使用伪指令(如proxy_pass)来隐式配置,该指令接受在某个时间点被评估为服务器列表的URL。可选的负载平衡方法仅适用于明确的上游配置。上游模块配置有其自己的指令上下文NGX_HTTP_UPS_CONF。结构定义如下:

struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;

    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */

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

#if (NGX_HTTP_UPSTREAM_ZONE)
    ngx_shm_zone_t                  *shm_zone;
#endif
};
  • srv_conf - 上游模块的配置上下文。
  • servers- 数组ngx_http_upstream_server_t,解析upstream块中的一组服务器伪指令的结果。
  • flags - 主要标记负载平衡方法支持哪些功能的标志。这些功能被配置为服务器指令的参数:
    • NGX_HTTP_UPSTREAM_CREATE - 区分显式定义的上游和由proxy_pass指令自动创建的上游以及“friends”(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块所在的行。
  • portno_port- 不用于显式定义的上游组。
  • shm_zone - 此上游组使用的共享内存区域(如果有)。
  • peer - 保存用于初始化上游配置的通用方法的对象:typedef struct {ngx_http_upstream_init_pt init_upstream; ngx_http_upstream_init_peer_pt init; void * data; } ngx_http_upstream_peer_t; 实现负载均衡算法的模块必须设置这些方法并初始化私有data。如果init_upstream在配置解析期间未初始化,ngx_http_upstream_module将其设置为默认的ngx_http_upstream_init_round_robin算法。
    • init_upstream(cf, us)- 配置时间方法,负责初始化一组服务器并init()在成功时初始化该方法。典型的负载平衡模块使用upstream块中的服务器列表来创建它使用的高效数据结构,并将其自己的配置保存到data现场。
    • init(r, us)- 初始化ngx_http_upstream_peer_t.peer用于负载均衡的每个请求结构(不要与ngx_http_upstream_srv_conf_t.peer上面描述的每个上游相混淆)。它作为data参数传递给处理服务器选择的所有回调。

当nginx必须将请求传递给另一个主机进行处理时,它会使用配置的负载平衡方法获取要连接的地址。该方法从ngx_http_upstream_t.peer类型的对象获得ngx_peer_connection_t

struct ngx_peer_connection_s {
    ...

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

    ngx_uint_t                       tries;

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

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

    ...
};

该结构具有以下字段:

  • sockaddrsocklenname-上游服务器的地址来连接; 这是负载平衡方法的输出参数。
  • data - 负载均衡方法的每个请求数据; 保持选择算法的状态并且通常包括到上游配置的链接。它作为参数传递给处理服务器选择的所有方法(参见下文)。
  • tries - 允许尝试连接到上游服务器的次数。
  • getfreenotifyset_session,和save_session-所述负载平衡模块,如下所述方法。

所有方法至少接受两个参数:一个对等连接对象pcdata创建者ngx_http_upstream_srv_conf_t.peer.init()。请注意,它可能与pc.data负载平衡模块的“链接” 不同。

  • get(pc, data) - 上游模块准备好将请求传递给上游服务器并需要知道其地址时调用的方法。该方法具有以填充sockaddrsocklenname领域ngx_peer_connection_t结构。返回是以下之一:
    • NGX_OK - 服务器被选中。
    • NGX_ERROR - 发生内部错误。
    • NGX_BUSY - 目前没有服务器可用。发生这种情况的原因很多,其中包括:动态服务器组为空,组中的所有服务器都处于失败状态,或者组中的所有服务器都已经处理了最大连接数。
    • NGX_DONE - 底层连接被重用,并且不需要创建到上游服务器的新连接。该值由keepalive模块设置。
  • free(pc, data, state) - 上游模块完成与特定服务器一起工作时调用的方法。该state参数是上游连接,具有以下可能的值的位掩码的完成状态:
    • NGX_PEER_FAILED - 尝试失败
    • NGX_PEER_NEXT- 上游服务器返回代码403404不被视为故障的特殊情况。
    • NGX_PEER_KEEPALIVE - 目前未使用

这种方法也减少了tries计数器。

  • notify(pc, data, type) - 目前尚未在OSS版本中使用。
  • set_session(pc, data)以及save_session(pc, data)- 特定于SSL的方法,可将缓存会话启用到上游服务器。该实现由循环均衡方法提供。
Nginx

Nginx是一款轻量级的 Web 服务器/反向代理服务器及电子邮件代理服务器,可在 BSD-like 协议下发行。其特点是占有内存少,并发能力强。

主页 https://nginx.org/
源码 http://hg.nginx.org/nginx
发布版本 1.13.6

Nginx目录

1.指南 | Guides
2.核心 | Core
3.ngx_google_perftools_module
4.ngx_http_access_module
5.ngx_http_addition_module
6.ngx_http_api_module
7.ngx_http_auth_basic_module
8.ngx_http_auth_jwt_module
9.ngx_http_auth_request_module
10.ngx_http_autoindex_module
11.ngx_http_browser_module
12.ngx_http_charset_module
13.ngx_http_core_module
14.ngx_http_dav_module
15.ngx_http_empty_gif_module
16.ngx_http_f4f_module
17.ngx_http_fastcgi_module
18.ngx_http_flv_module
19.ngx_http_geoip_module
20.ngx_http_geo_module
21.ngx_http_gunzip_module
22.ngx_http_gzip_module
23.ngx_http_gzip_static_module
24.ngx_http_headers_module
25.ngx_http_hls_module
26.ngx_http_image_filter_module
27.ngx_http_index_module
28.ngx_http_js_module
29.ngx_http_keyval_module
30.ngx_http_limit_conn_module
31.ngx_http_limit_req_module
32.ngx_http_log_module
33.ngx_http_map_module
34.ngx_http_memcached_module
35.ngx_http_mirror_module
36.ngx_http_mp4_module
37.ngx_http_perl_module
38.ngx_http_proxy_module
39.ngx_http_random_index_module
40.ngx_http_realip_module
41.ngx_http_referer_module
42.ngx_http_rewrite_module
43.ngx_http_scgi_module
44.ngx_http_secure_link_module
45.ngx_http_session_log_module
46.ngx_http_slice_module
47.ngx_http_spdy_module
48.ngx_http_split_clients_module
49.ngx_http_ssi_module
50.ngx_http_ssl_module
51.ngx_http_status_module
52.ngx_http_stub_status_module
53.ngx_http_sub_module
54.ngx_http_upstream_conf_module
55.ngx_http_upstream_hc_module
56.ngx_http_upstream_module
57.ngx_http_userid_module
58.ngx_http_uwsgi_module
59.ngx_http_v2_module
60.ngx_http_xslt_module
61.ngx_mail_auth_http_module
62.ngx_mail_core_module
63.ngx_mail_imap_module
64.ngx_mail_pop3_module
65.ngx_mail_proxy_module
66.ngx_mail_smtp_module
67.ngx_mail_ssl_module
68.ngx_stream_access_module
69.ngx_stream_core_module
70.ngx_stream_geoip_module
71.ngx_stream_geo_module
72.ngx_stream_js_module
73.ngx_stream_limit_conn_module
74.ngx_stream_log_module
75.ngx_stream_map_module
76.ngx_stream_proxy_module
77.ngx_stream_realip_module
78.ngx_stream_return_module
79.ngx_stream_split_clients_module
80.ngx_stream_ssl_module
81.ngx_stream_ssl_preread_module
82.ngx_stream_upstream_hc_module
83.ngx_stream_upstream_module