Nginx Development guide

这篇文章来自nginx document /http://nginx.org/en/docs/dev/development_guide.html

nginx的开发指南

Introduction

Code layout

 

  • auto — 编译脚本
  • src
    • core — 基本类型基函数 — string, array, log, pool 等.
    • event — 事件处理
      • modules — 事件通知 modules: epollkqueueselect等.
    • http — HTTP核心代码
      • modules — HTTP相关模块
      • v2 — HTTP/2
    • mail — Mail 模块
    • os — 平台相关代码
      • unix
      • win32
    • stream — Stream模块

Include files

 

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

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

除此之外,HTTP代码应包括

#include <ngx_http.h>

Mail代码应包括

#include <ngx_mail.h>

Stream 代码应包括

#include <ngx_stream.h>

 

Integers

 

为了一般目的,NGINX代码使用两种整数类型 ngx_int_t 和 ngx_uint_t,它们分别是 intptr_t 和 uintptr_t的别名。

 

Common return codes

 

 

NGINX中的大多数功能返回以下代码:

  • NGX_OK — 处理成功。
  • NGX_ERROR — 处理失败。
  • NGX_AGAIN — 处理未完成;需要再次调用函数。
  • NGX_DECLINED — 拒绝操作,例如在配置中禁用,但这不是错误。
  • NGX_BUSY — 资源不可用。
  • NGX_DONE — 操作完成或者继续,是成功代码的一种。
  • NGX_ABORT — 处理中断,是错误代码的一种。

在调试keep alive时遇到过NGX_DONE场景,他替换其他轮算法getpeer(),由于是使用原有链接所以直接返回NGX_DONE,不在触发真实的到服务器的链接。

 

 

Error handling

 

ngx_errno 宏定义返回系统错误码,他对应POSIX平台的 errno 和Windows平台的 GetLastError()系统调用。 ngx_socket_errno 宏定义返回系统的socket错误码,类似 ngx_errno  他对应POSIX平台的 errno 对应Windows平台的 WSAGetLastError() . 连续访问ngx_errno 或 ngx_socket_errno 可能会引起性能问题,如果错误值被多次使用,可以存储他到一个局部变量类型 ngx_err_t 中,使用 ngx_set_errno(errno) 和 ngx_set_socket_errno(errno) 设置错误值。

ngx_errno 和 ngx_socket_errno 能够被传递到日志函数 ngx_log_error() 和 ngx_log_debugX()中,这样系统错误信息将能够添加到日志消息中。

例如 ngx_errno:

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

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

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

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

        return 1;
    }

    return 0;
}

 

Strings

 

Overview

 

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

ngx_str_t 定义如下:

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

len 字段保存string的长度 data 字段保存string的数据。ngx_str_t 中保存的字符串可能没有以null结尾,在多数情况下,不以null结尾,但是,在代码的某些部分中(比如解析配置) ngx_str_t 被认为是以null结尾,这简化了字符串的比较,使得传递给系统调用更加方便。

string 相关函数定义在 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() — 用0值进行填充
  • ngx_explicit_memzero() — 功能同 ngx_memzero(), 但是这个调用不会被编译器优化(dead store elimination optimization). 这个函数常用来清除敏感信息如密钥或者密码。

https://mailman.nginx.org/pipermail/nginx-devel/2018-November/011595.html 可以看到增加这个函数主要为了处理密码。

  • ngx_cpymem() — 类似ngx_memcpy(), 他可以返回最终结果字符串的地址,此地址很方便地连续附加多个字符串。
  • ngx_movemem() — 类似 ngx_memmove(), 他返回最终结果的字符串的地址.
  • ngx_strlchr() — 在两个指针间,寻找指定字符。

转换比较:

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

宏定义简化 string初始化:

  • ngx_string(text) — 使用C字符串初始化生成一个 ngx_str_t 类型变量
  • ngx_null_string — 初始化一个空的 ngx_str_t 类型变量
  • ngx_str_set(str, text) — 使用C字符串初始化一个 ngx_str_t * 类型变量
  • ngx_str_null(str) — 初始化一个空的 ngx_str_t * 类型变量

 

 

Formatting

 

nginx格式化输出的使用的函数:

  • ngx_sprintf(buf, fmt, ...)
  • ngx_snprintf(buf, max, fmt, ...)
  • ngx_slprintf(buf, last, fmt, ...)
  • ngx_vslprintf(buf, last, fmt, args)
  • ngx_vsnprintf(buf, max, fmt, args)

支持格式

src/core/ngx_string.c. 源码路径

  • %O — off_t
  • %T — time_t
  • %z — ssize_t
  • %i — ngx_int_t
  • %p — void *
  • %V — ngx_str_t *
  • %s — u_char * (null-terminated)
  • %*s — size_t + u_char *
  • %v — ngx_variable_value_t
  • %P — ngx_pid_t
  • %M — ngx_msec_t
  • %z — ssize_t
  • %d — int
  • %l — long
  • %D — int32_t
  • %L — int64_t
  • %A — ngx_atomic_int_t
  • %f — double
  • %r — rlimt (Windows )
  • %c — bytes
  • %Z — \0
  • %N — CRLF(Windows)LF(linux or other)
  • %% — %

增加 u 前缀使得他们为无符号类型,如果需要转为16进制编码,使用X或者x。

例如:

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

/* set n here */

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

Numeric conversion

 

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) — 将指定长度 n的浮点数转化为ngx_int_t类型的正整数。将其按照 point ,值进行左移(小数点右移动), 字符串保留小数点后points 位。例如 ngx_atofp("10.5", 4, 2) 返回 1050.
  • ngx_hextoi(line, n) — 将十六进制字符串转为为正整数类型ngx_int_t.

将给定长度的固定点浮点数转换为ngx_int_t类型的正整数。结果沿点小数点位置向左移动。该数字的字符串表示形式预计不超过点数数字。例如,ngx_atofp(“ 10.5”,4,2)返回1050。

 

Regular expressions

 

nginx使用的正则表式库使用 PCRE 封装的。对应的头文件在 src/core/ngx_regex.h.

要使用正则表达式进行字符串匹配,首先需要编译它,这通常是在配置阶段进行的。请注意,由于PCRE支持是可选的,因此使用该接口的所有代码使用预编译判断 NGX_PCRE 是否支持:

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

    u_char                errstr[NGX_MAX_CONF_ERRSTR];

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

    ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

    rc.pattern = value;
    rc.pool = cf->pool;
    rc.err.len = NGX_MAX_CONF_ERRSTR;
    rc.err.data = errstr;
    /* rc.options can be set to NGX_REGEX_CASELESS */

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

    re = rc.regex;
#endif

 

编译成功后,ngx_regex_compile_t结构 captures and named_captures 包含所有通过正则匹配的次数和设置了别名的匹配的次数 .

https://regex101.com/r/4rXoiw/1

https://stackoverflow.com/questions/48451280/how-do-named-and-unnamed-pcre-capturing-groups-interact

regular expression :^(?<object>[-\w]+)/([-\w]+)$

test string : Abc-def/edg

substitutiong : $2 - / - $1

edg - / - Abc-def

未设置别名,如果分组可以取对应的数字便可以获取 匹配值

$2 – / – ${object}

https://github.com/vislee/leevis.com/issues/65 写了非常详细的例子。

之后可以使用编译后的正则表达式匹配字符串

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
  • 可容纳任何匹配成功数组  captures 的以及数组的大小。

按照 PCRE API.的要求,匹配成功captures数组的大小必须是三的倍数。在示例中,大小是根据捕获的总数加上匹配的字符串本身的一个。

结果3的倍数pcre对应匹配成功后,有一个ovector多维数组记录匹配成功过的数据首尾偏移位置。

https://www.pcre.org/original/doc/html/pcre_exec.html

int pcre_exec(const pcre *code, const pcre_extra *extra, const char *subject, int length, int startoffset, int options, int *ovector, int ovecsize);

  ovector      Points to a vector of ints for result offsets
  ovecsize     Number of elements in the vector (a multiple of 3)

如果匹配成功, 可以访问匹配成功的数据向下面这样:

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 elements的数组(ngx_regex_elt_t包含正则表达式及其对应的名称),也就是要匹配的字符串和日志。该函数对待匹配字符串使用数组中的正则表达式,进行遍历匹配,直到找到匹配或无法匹配为止。如果有匹配返回NGX_OK,则否则为NGX_DECLINED ,或者在错误时 NGX_ERROR

相关函数原型 core/ngx_regex.h

ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log)

 

typedef struct {
    ngx_regex_t  *regex;
    u_char       *name;
} ngx_regex_elt_t;

 

Time

ngx_time_t 结构体有三个单独类型seconds, milliseconds, and the GMT offset:

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

ngx_tm_t 对应 Unix平台上的 struct tm ,Windows平台上的额 SYSTEMTIME 。

为了获得当前时间,通常足以缓存方式保存时间值,通过全局变量进行访问。

下面是一些常使用的值及应用场景:

  • ngx_cached_err_log_time —  error 日志格式: "1970/09/28 12:00:00"
  • ngx_cached_http_log_time —  HTTP access 日志格式: "28/Sep/1970:12:00:00 +0600"
  • ngx_cached_syslog_time —syslog日志格式: "Sep 28 12:00:00"
  • ngx_cached_http_time — HTTP Header格式: "Mon, 28 Sep 1970 06:00:00 GMT"
  • ngx_cached_http_log_iso8601 — HTTP 日志格式:ISO 8601 : "1970-09-28T12:00:00+06:00"

ngx_time() ,ngx_timeofday() 以秒为单位返回当前时间值,是访问缓存时间值的首选方法。他们是以宏定义方式实现。

获取更准确的时间, 使用 ngx_gettimeofday(), 会实时更新参数 struct timeval.当nginx从系统调用返回事件循环时,会完成时间更新。如果要立即更新时间,可以调用 ngx_time_update(), 也可以通过信号方式,通过信号处理函数 ngx_time_sigsafe_update() 更新时间。

以下函数将 time_t 转换为指定的分解时间表示。每对中的第一个函数将 time_t 转化为 ngx_tm_t ,第二个功能(带有 _libc_中缀)将 time_t 转换为 struct tm:

  • ngx_gmtime(), ngx_libc_gmtime() —  UTC时间
  • ngx_localtime(), ngx_libc_localtime() — 本地时区时间

https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html

broken-down time 一种时间格式方便阅读

struct tm {
    int tm_sec;    /* Seconds (0-60) */
    int tm_min;    /* Minutes (0-59) */
    int tm_hour;   /* Hours (0-23) */
    int tm_mday;   /* Day of the month (1-31) */
    int tm_mon;    /* Month (0-11) */
    int tm_year;   /* Year - 1900 */
    int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
    int tm_isdst;  /* Daylight saving time */
};

from https://man7.org/linux/man-pages/man3/ctime.3.html

ngx_gtime()在core/ngx_times.c 中

其他均在 os/unix/ngx_time.c中

 

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 array类型 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) 调用来在池中创建一个array,而 ngx_array_init(array, pool, n, size) 调用来初始化已经分配的array对象。

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

向array中增加元素的方法:

  • ngx_array_push(a) 在array末尾增加一个元素空间,并返回该位置元素指针;
  • ngx_array_push_n(a, n) 在array末尾增加n个元素空间,并返回新增的n个空间的第一个元素指针。

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

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

core/ngx_array.c

void *
‌ngx_array_push(ngx_array_t *a)
{
......

        } else {
            /* allocate a new array */

            new = ngx_palloc(p, 2 * size);
            if (new == NULL) {
                return NULL;
            }

            ngx_memcpy(new, a->elts, size);
            a->elts = new;
            a->nalloc *= 2;
        }

......
}

 

 

List

list 是一个序列化数组, 用于大量数据插入场景. 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;

实际的项存储在 ngx_list_part_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;
};

list使用前需要调用 ngx_list_init(list, pool, n, size) 或者调用 ngx_list_create(pool, n, size). 这两个函数参数都包含每个元素的大小以及存放元素的的个数。增加一个元素到list中,使用 ngx_list_push(list) . 遍历list,访问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]);
}

list主要用于 HTTP input and output headers

list不支持删除项目, 需要时可以将对应元素设置为失效,而不是适尽的从list中移除。比如 将HTTP output headers(存储在 ngx_table_elt_t 中)移除,可以将 ngx_table_elt_t 中的  hash 值设置为0。这样list在遍历时根据标记进行过滤。

static ngx_int_t
ngx_http_header_filter(ngx_http_request_t *r) 
{
    ngx_list_part_t    *part;
    ngx_table_elt_t    *header;


..... 
    part = &r->headers_out.headers.part;
    header = part->elts;

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

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

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

        if (header[i].hash == 0) {
            continue;
        }

        len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
               + sizeof(CRLF) - 1;
    }

....
}

 

 

Queue

在nginx中,Queue是一个双重链表,定义如下:

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) — 分割一个queue,返回分割后后面的queue
  • ngx_queue_add(h, n) — 合并queue h, n,n连接到h尾部
  • ngx_queue_head(h)ngx_queue_last(h) — 获取第一个或最后一个节点
  • ngx_queue_sentinel(h) – 哨兵节点, 返回h, 用于判断是否结束

应该是为了格式统一设置的宏

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

https://static.kancloud.cn/digest/understandingnginx/202592, 这个链接中通过详细的图例完成描述,非常棒。

 

 

Red-Black tree

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

 

创建一个rbtree需要, root和 sentiel节点,通常,你可以自定义结构,将你的数据放入rbtree中, 可以是具体数值或者是一个指针。

初始化rbtree:

my_tree_t  root;

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

 

遍历btree插入数据节点,使用 xxx_insert_value 插入数据其中xxx表示具体处理的类型, 比如 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() 比较典型的比较函数, 它返回一个比较结果值,封装复杂结构, 使用泛型方式抽象比较操作。

在tree上添加节点:可以通过:申请内存,初始化, 并调用 ngx_rbtree_insert()几个步骤完成

my_node_t          *my_node;
ngx_rbtree_node_t  *node;

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

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

ngx_rbtree_insert(&root->rbtree, node);

删除节点  ngx_rbtree_delete() :

ngx_rbtree_delete(&root->rbtree, node);

 

Hash

Hash table 相关定义在 src/core/ngx_hash.h文件中。支持精确的和通配符的匹配。后者需要额外的设置,并在下面的单独部分中进行描述。

typedef struct {
    ngx_hash_elt_t  **buckets;
    ngx_uint_t        size;
} ngx_hash_t;

 

hash初始化之前,nginx需要知道hash元素数量以便对其进行优化。需要配置的两个参数 max_size a和 bucket_size, 详细区别详见 document. 他们通常由用户配置。Hash初始化设置使用 ngx_hash_init_t 类型存储, hash本身是 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 标识来初始化hash表。

hash表的key存放在 ngx_hash_keys_arrays_t ,通过 ngx_hash_keys_array_init(arr, type),完成初始化,其中第二个参数type,可以控制重新申请资源的规模 NGX_HASH_SMALL , NGX_HASH_LARGE. 如果要存放大量量元素,后者更加合适。

ngx_hash_keys_arrays_t  foo_keys;

foo_keys.pool = cf->pool;
foo_keys.temp_pool = cf->temp_pool;

ngx_hash_keys_array_init(&foo_keys, NGX_HASH_SMALL);

使用 ngx_hash_add_key(keys_array, key, value, flags) 插入key:

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

构建hash表, 使用 ngx_hash_init(hinit, key_names, nelts)

ngx_hash_init(&hash, foo_keys.keys.elts, foo_keys.keys.nelts);

max_size  bucket_size 参数只过大函数也会失败。

if (hinit->bucket_size > 65536 - ngx_cacheline_size) {
    ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
                  "could not build %s, too large "
                  "%s_bucket_size: %i",
                  hinit->name, hinit->name, hinit->bucket_size);
    return NGX_ERROR;
}

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

 

Wildcard matching

 

ngx_hash_combined_t 用于创建与通配符配合使用的哈希,他包括上面提到的hash类型, 以及两个附加的key arrays: dns_wc_head 和 dns_wc_tail. 基本属性的初始化类似于常规哈希:

typedef struct {
    ngx_hash_t            hash;
    ngx_hash_wildcard_t  *wc_head;
    ngx_hash_wildcard_t  *wc_tail;
} ngx_hash_combined_t;

 

ngx_hash_init_t      hash
ngx_hash_combined_t  foo_hash;

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

通过 NGX_HASH_WILDCARD_KEY 标识位来增加通配符key。

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

该函数识别通配符并将键添加到相应的数组中。请参阅map模块文档,以获取通配符语法和匹配算法的描述。

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

if (foo_keys.dns_wc_head.nelts) {

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

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

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

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

keys数组组要排序,初始化后的结果必须添加到combined hash表中, dns_wc_tail array 初始化是类似的。

ngx_hash_find_combined(chash, key, name, len): 在combined hash 的查找可以通过下面方式调用。

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

 

 

Memory management

Heap

堆内存实现使用下面的函数

  • ngx_alloc(size, log) — 申请堆内存. 这个函数是对malloc() 封装,并支持日志功能. 申请错误后调试信息记录到log.
  • ngx_calloc(size, log) — 同ngx_alloc() 分配内存后空间初始化为0.
  • ngx_memalign(alignment, size, log) — 在一些平台上是对  posix_memalign() 封装,如果不支持 posix_memalign() 将调用ngx_alloc()

os/unix/ngx_alloc.h

/*
 * Linux has memalign() or posix_memalign()
 * Solaris has memalign()
 * FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()
 * aligns allocations bigger than page size at the page boundary
 */

#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)

void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);

#else

#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)

#endif

os/win32/ngx_alloc.h

‌#define ngx_free          free
‌#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)
  • ngx_free(p) — 释放内存. 是对 free() 封装

 

Pool

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

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

ngx_pool_t 是nginx pool类型, 下面是支持的操作:

  • ngx_create_pool(size, log) — 设置指定大小size,创建一个pool, 函数返回一个pool对象 The size 最少在 NGX_MIN_POOL_SIZE ,并且以 NGX_POOL_ALIGNMENT 进行对齐。
/*
 * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 */
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)

#define NGX_POOL_ALIGNMENT       16
#define NGX_MIN_POOL_SIZE                                                     \
    ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)),            \
              NGX_POOL_ALIGNMENT)
  • ngx_destroy_pool(pool) — 释放所有内存,包括pool 对象本身。
  • ngx_palloc(pool, size) — 从指定的pool申请内存。
  • ngx_pcalloc(pool, size) — 从指定的pool申请内存并且填充0。
  • ngx_pnalloc(pool, size) — 从指定pool分配未对齐的内存,大多数情况下是用来申请字符串。
  • ngx_pfree(pool, p) — 释放之前在指定pool申请的内存. 只有从转发到系统分配器的请求产生的分配才能释放。
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);

chain (ngx_chain_t) 常常出现在nginx实现代码中,是nginx pool 提供一种内存复用的方式。 chain 是ngx_pool_t 的一个字段,保存之前已经分配的内存方便复用. 使用 ngx_alloc_chain_link(pool) . 这个函数用于在 free chain 链表中查找一个适合的空间,或者在pool为空的时候申请一个新的chain链表。如果要释放一个链表,可以调用 ngx_free_chain(pool, cl)

清理处理函数可以注册在pool中, 一般以回调函数的方式调用。当pool被销毁时自动调用进行销毁。pool通常与特定的nginx对象绑定(Http request 或者 Stream的 session),并在对象生命周期结束被销毁。通过注册 pool 清理函数,是关闭文件描述符或对关联的共享数据的进行调整比较便捷的方法。

注册pool清理函数, 调用 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);
}

http_limit_conn_module模块设置了 clean_up,及操作参数,这里主要是模块使用的共享内存,红黑树结构。

static ngx_int_t
ngx_http_limit_conn_handler(ngx_http_request_t *r)
{
...
        cln = ngx_pool_cleanup_add(r->pool,
                                   sizeof(ngx_http_limit_conn_cleanup_t));
        if (cln == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        cln->handler = ngx_http_limit_conn_cleanup;
        lccln = cln->data;

        lccln->shm_zone = limits[i].shm_zone;
        lccln->node = node;
    }
}

在回调函数里进行清除释放操作。

static void
ngx_http_limit_conn_cleanup(void *data)
{
    ngx_http_limit_conn_cleanup_t  *lccln = data;

    ngx_rbtree_node_t           *node;
    ngx_http_limit_conn_ctx_t   *ctx;
    ngx_http_limit_conn_node_t  *lc;

    ctx = lccln->shm_zone->data;
    node = lccln->node;
    lc = (ngx_http_limit_conn_node_t *) &node->color;

    ngx_shmtx_lock(&ctx->shpool->mutex);

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lccln->shm_zone->shm.log, 0,
                   "limit conn cleanup: %08Xi %d", node->key, lc->conn);

    lc->conn--;

    if (lc->conn == 0) {
        ngx_rbtree_delete(&ctx->sh->rbtree, node);
        ngx_slab_free_locked(ctx->shpool, node);
    }

    ngx_shmtx_unlock(&ctx->shpool->mutex);
}

 

 

Shared memory

nginx共享内存结构 sharede zone 主要是用来完成进程间通讯, ngx_shared_memory_add(cf, name, size, tag) 用来增加新的共享内存,并增加到cycle的共享内存结构中。返回值 ngx_shm_zone_t . 函数两个参数 name 和 size :每一个  shared zone 的name参数必须是唯一的. 如果共享内存,则使用存在的共享内存, 根据tag判断是否同一模块使用的共享内存, 来决定是否复用现有的共享内存zone. 如果存在同样名称共享内存,但tag又不一致将报错。通常情况下tag传输为模块的地址信息这样是的,同一一个模块共享可以复用。

static char *
ngx_http_fastcgi_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
。。。
    flcf->upstream.cache_zone = ngx_shared_memory_add(cf, &value[1], 0,
                                                      &ngx_http_fastcgi_module);
    if (flcf->upstream.cache_zone == NULL) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}

 

共享内存结构 ngx_shm_zone_t 包含以下内容:

  • init —  初始化回调函数,当shared zone数据mmap到实际内存后调用
  • data — init回调函数参数
  • noreuse — 标识不能重新使用的shared zone。
  • tag — Shared zone tag
  • shm — 平台的一些特性, ngx_shm_t, 至少有以下域:
    • addr — 共享内存地址
    • size — 大小
    • name — 名称
    • log — 日志
    • exists — 标记共享内存的标志是从主进程(特定于Windows)继承的(仅windows生效)
typedef struct {
    u_char      *addr;
    size_t       size;
    ngx_str_t    name;
    HANDLE       handle;
    ngx_log_t   *log;
    ngx_uint_t   exists;   /* unsigned  exists:1;  */
} ngx_shm_t;

Shared zone 在 ngx_init_cycle() 中,完成配置文件解析后创建. 在 POSIX 系统, 通过 mmap() 系统调用来创建共享内存。 Windows系统, 通过 CreateFileMapping()MapViewOfFileEx() 两个函数完成相关创建。

ngx_shmem.c 可以看到三种申请共享内方式

  • mmap 使用MAP_ANON,不映射到具体文件
  • mmap 映射到文件 /dev/zero效果同上
  • 使用SYSV的shmget

分配共享内存nginx使用 ngx_slab_pool_t , slab pool能为nginx 提供共享内存分配管理功能 . pool 位于共享内存的开始位置,能够通过 (ngx_slab_pool_t *) shm_zone->shm.addr来访问. 在共享 Zone中申请内存可以 ngx_slab_alloc(pool, size) 或 ngx_slab_calloc(pool, size). 释放内存调用 ngx_slab_free(pool, p).

slab 有些类似linux 内存管理中的伙伴管理系统系统

slab pool 将所有zone切分成页. 每一个页分配同样大小,分配长度必须是2的次幂并且大于8字节. 不符合的长度按照2的次幂对齐。 A bitmask 跟踪每个页查看哪些页是空闲的, 对于大于半页尺寸(通常是2048字节),以此分配整页。

通过使用 在ngx_slab_pool_t 增加 mutex 引入锁机制,用于防止并发操作引起数据不一致. A mutex常常被用在内存申请和释放的场景,用来保护共享内存区分期其他用户数据结构. 通过调用ngx_shmtx_lock(&shpool->mutex) , ngx_shmtx_unlock(&shpool->mutex)锁定或解锁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 结构体处理日志信息,支持以下格式输出:

  • stderr — 标准错误输出
  • file — 文件方式
  • syslog — syslog系统日志
  • memory — 内存模式用于开发调试,内存可以通过debugger进行访问

A logger instance can be a chain of loggers, linked to each other with the next field. In this case, each message is written to all loggers in the chain.

logger结构体中包含一个next指针组成一个链表, 信息可以被写入到链表上所有的logger结构对应的日志对象上。

struct ngx_log_s {
    ngx_uint_t           log_level;
    ngx_open_file_t     *file;

    ngx_atomic_uint_t    connection;

    time_t               disk_full_time;

    ngx_log_handler_pt   handler;
    void                *data;

    ngx_log_writer_pt    writer;
    void                *wdata;

    /*
     * we declare "action" as "char *" because the actions are usually
     * the static strings and in the "u_char *" case we have to override
     * their types all the time
     */

    char                *action;

    ngx_log_t           *next;
};



static void
ngx_log_insert(ngx_log_t *log, ngx_log_t *new_log)
{
    ngx_log_t  tmp;

    if (new_log->log_level > log->log_level) {

        /*
         * list head address is permanent, insert new log after
         * head and swap its contents with head
         */

        tmp = *log;
        *log = *new_log;
        *new_log = tmp;

        log->next = new_log;
        return;
    }

    while (log->next) {
        if (new_log->log_level > log->next->log_level) {
            new_log->next = log->next;
            log->next = new_log;
            return;
        }

        log = log->next;
    }

    log->next = new_log;
}

支持以下日志级别,用于区分不同级别的日志,根据配置文件设置实现不同级别日志的输出:

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

对于调试日志,还设置了 The debug masks 如下:

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

通常,日志通过nginx error_log指令创建并在nginx生命周期每个阶段,配置,客户端链接或者其他结构体中生效。

nginx提供以下宏定义:

  • ngx_log_error(level, log, err, fmt, ...) — 错误日志
  • ngx_log_debug0(level, log, err, fmt)ngx_log_debug1(level, log, err, fmt, arg1) etc — 调试日志最多支持8个参数, ngx_log_debug8

日志信息被格式化到栈中一个大小为NGX_MAX_ERROR_STR 的缓存中,信息存放日志级别,PID,Connection ID和错误信息。对于非debug日志, log->handler 回调函数则用来打印相关专有信息,HTTP 模块通过设置ngx_http_log_error() 函数来记录客户和服务端的地址信息,以及正在进行的操作(存储在log->action),客户端请求信息url等,

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

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

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

上面例子输出结果如下:

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

 

 

Cycle

cycle对象存储从指定配置文件获取信息,创建的nginx运行时的环境信息。 类型是 ngx_cycle_t. 全局变量 ngx_cycle 保存当前生命周期,并被nginx工作进程继承。每次nginx配置文件重新加载时都会重新创建一个 cycle。旧的cycle通常在新的cycle加载完毕后被删除。

ngx_init_cycle() 函数创建一个周期,该函数将上一个之前的cycle做为参数,该函数载入上一个cycle使用的配置文件,尽可能多的继承资源,nginx启动会创建一个“init_cycle”的cycle,其做为old_cycle,然后被配置构建的实际cycle取代。

通过conf_ctx是否为空判断是否是首次启动

#define ngx_is_init_cycle(cycle)  (cycle->conf_ctx == NULL)

cycle 的成员包括:

  • pool — Cycle Pool.为没有个cycle结构生成一个pool。
  • log — Cycle log. 最初从旧周期继承,它将在读取配置后指向new_log。
  • new_log — Cycle log, 由配置创建。它受根指令error_log指令的影响。
  • connectionsconnection_n — 在初始化nginx worker时,由时间模型创建连接数组 类型为 ngx_connection_t, nginx可以通过指令 worker_connections 可以设置连接的数量 connection_n.
  • free_connectionsfree_connection_n — 当前可用连接的列表和数量,如果没有可用连接,nginx worker会拒绝接受客户端或者连接到上游服务器。
  • filesfiles_n — Array for mapping file descriptors to nginx connections. This mapping is used by the event modules, having the NGX_USE_FD_EVENT flag (currently, it’s poll and devpoll)。
  • filesfiles_n — 用于将文件描述符映射到nginx 连接数组connections.该映射由事件模块使用,如果是使用的是poll或devpoll,需要设置 NGX_USE_FD_EVENT 。
  • conf_ctx — core模块的配置数组,在读取nginx配置时,创建并进行设置.
  • modulesmodules_n — 静态模块和动态模块数组,根据当前配置加载 。
  • listening —  ngx_listening_t 类型的监听对象数组。 监听服务通常通过调用 ngx_create_listening() 创建,可以通过 listen 指令进行添加. 监听socket 基于监听对象生成.
  • paths —  ngx_path_t类型的路径数组, 通过调用模块提供的ngx_add_path() 函数来添加制定路径,这些目录是在读取配置后由nginx创建,此外,可以为每条路径添加两个处理程序:
    • path loader — 启动或重新加载后60秒执行一次,通常,加载程序读取目录并将数据存储在Nginx共享内存中。该处理程序是从专用的nginx进程“nginx cache loader”调用。
    • path manager — 定期执行, 从目录中删除旧文件,并更新 nginx内存,该处理程序是从专用的nginx进程“nginx cache manager”中调用。
typedef struct {
    ngx_str_t                  name;
    size_t                     len;
    size_t                     level[NGX_MAX_PATH_LEVEL];

    ngx_path_manager_pt        manager;
    ngx_path_purger_pt         purger;
    ngx_path_loader_pt         loader;
    void                      *data;

    u_char                    *conf_file;
    ngx_uint_t                 line;
} ngx_path_t;
static void
ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn)
{
    ngx_uint_t    i, manager, loader;
    ngx_path_t  **path;

    manager = 0;
    loader = 0;

    path = ngx_cycle->paths.elts;
    for (i = 0; i < ngx_cycle->paths.nelts; i++) {

        if (path[i]->manager) {
            manager = 1;
        }

        if (path[i]->loader) {
            loader = 1;
        }
    }

    if (manager == 0) {
        return;
    }

    ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
                      &ngx_cache_manager_ctx, "cache manager process",
                      respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN);

    ngx_pass_open_channel(cycle);

    if (loader == 0) {
        return;
    }

    ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
                      &ngx_cache_loader_ctx, "cache loader process",
                      respawn ? NGX_PROCESS_JUST_SPAWN : NGX_PROCESS_NORESPAWN);

    ngx_pass_open_channel(cycle);
}

static void
ngx_cache_manager_process_handler(ngx_event_t *ev)
{
    ngx_uint_t    i;
    ngx_msec_t    next, n;
    ngx_path_t  **path;

    next = 60 * 60 * 1000;

    path = ngx_cycle->paths.elts;
    for (i = 0; i < ngx_cycle->paths.nelts; i++) {

        if (path[i]->manager) {
            n = path[i]->manager(path[i]->data);

            next = (n <= next) ? n : next;

            ngx_time_update();
        }
    }

    if (next == 0) {
        next = 1;
    }

    ngx_add_timer(ev, next);
}


static void
ngx_cache_loader_process_handler(ngx_event_t *ev)
{
    ngx_uint_t     i;
    ngx_path_t   **path;
    ngx_cycle_t   *cycle;

    cycle = (ngx_cycle_t *) ngx_cycle;

    path = cycle->paths.elts;
    for (i = 0; i < cycle->paths.nelts; i++) {

        if (ngx_terminate || ngx_quit) {
            break;
        }

        if (path[i]->loader) {
            path[i]->loader(path[i]->data);
            ngx_time_update();
        }
    }

    exit(0);
}
  • open_files — 存放类型为 ngx_open_file_t对象列表, 这些对象通过 ngx_conf_open_file()创建。当前,nginx使用这种方式打开日志文件, 当读完配置后, nginx打开所有的文件并将其文件描述符号保存在 open_files 列表的fd字段中.这些文件已append和create模式打开,如果不能存在则进行创建。当nginx workers进程收到,收到重新打开信息号时(一般为USR1)时,文件将被重新打开, 这种情况下 fd 值将进行更新。
ngx_open_file_t *
ngx_conf_open_file(ngx_cycle_t *cycle, ngx_str_t *name)
{
。。。
    file = ngx_list_push(&cycle->open_files);
    if (file == NULL) {
        return NULL;
    }

。。。
}

调用ngx_conf_open_file 函数, 可以看到大多数是日志相关处理函数:

  • ngx_http_log_merge_loc_conf
  • ngx_http_log_set_log
  • ngx_log_open_default
  • ngx_log_set_log
  • ngx_stream_log_set_log
  • shared_memory — 共享内存区域列表,每个列表通过调用 ngx_shared_memory_add() 函数进行添加. 共享区域映射到所有nginx进程相同的地址范围,并用于共享常见数据,例如HTTP缓存。

Buffer

针对输入输出操作, nginx 提供了缓冲区类型 ngx_buf_t. 通常, 他用来缓存准备要写入的数据或读入后的数据,从技术上讲,缓冲区可以同时引用存放在内存或为文件中的数据,缓冲区的内存分别分配,与缓冲区结构ngx_buf_t无关。

ngx_buf_t 结构包含以下各个域:

  • startend — 为缓冲区分配内存块的边界。
  • poslast — 内存缓冲的边界,通常在startend的范围内。
  • file_posfile_last — 文件缓冲区的边界,从文件开头表示为偏移。
  • tag — 用于区分缓冲区,由不同的nginx模块创建,通常是为了缓冲区重用。
  • file — 文件对象。
  • struct ngx_file_s {
        ngx_fd_t                   fd;
        ngx_str_t                  name;
        ngx_file_info_t            info;
    
        off_t                      offset;
        off_t                      sys_offset;
    
        ngx_log_t                 *log;
    
    #if (NGX_THREADS || NGX_COMPAT)
        ngx_int_t                (*thread_handler)(ngx_thread_task_t *task,
                                                   ngx_file_t *file);
        void                      *thread_ctx;
        ngx_thread_task_t         *thread_task;
    #endif
    
    #if (NGX_HAVE_FILE_AIO || NGX_COMPAT)
        ngx_event_aio_t           *aio;
    #endif
    
        unsigned                   valid_info:1;
        unsigned                   directio:1;
    };
  • temporary — 标识表明缓冲区引用可写内存。
  • memory — 标识缓冲区可读。

temporary  在这个宏定义中标识是否存放在内存中,没有看到有涉及可写判断的地方 

在nginx-0.0.3-2004-05-28 版本中将ngx_hunk_t 结构体转化为ngx_buf_t

使用temporary, memory, mmap分别替换原有版本中的三个宏定义:

  • NGX_HUNK_TEMP
  • NGX_HUNK_MEMORY
  • NGX_HUNK_MMAP
#define ngx_buf_in_memory(b)       ((b)->temporary || (b)->memory || (b)->mmap)
  • in_file — 标识缓存数据在文件中。
  • flush — 标识缓冲去所有数据需要进行输出。
  • recycled — 标识可以重复使用缓冲区,并且需要尽快消耗。
  • sync — 标识缓冲无数据或者不带有 flush 或 last_buf标识, nginx默认此类缓冲为错误条件,此标识告诉nginx跳过检查
  • last_buf — 标识buffer是输出中最后一个缓冲节点。
  • last_in_chain — 标识表明请求request或子请求subrequest中没有更多的数据缓冲区

last_in_chain主要为http subrequest增加。后续还需要再分析??

static ngx_int_t
ngx_http_gunzip_filter_inflate_end(ngx_http_request_t *r,
    ngx_http_gunzip_ctx_t *ctx)
{
。。。
    b->last_buf = (r == r->main) ? 1 : 0;
    b->last_in_chain = 1;
。。。
}
  • shadow — 将现在的buffer关联到另外一个(“shadow”)buffer,通常数据使用“shadow”buffer 中的数据, 当buffer使用完毕,后“shadow”buffer 也一并释放。
  • last_shadow — 标识缓冲是最后shadow缓冲最后一个节点.
  • temp_file — 标识huan.

输入和输出buffer由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

Connection

ngx_connection_t 是对socket套接字的封装,他包括以下域:

  • fd — socket 描述符
  • data — 可变的链接信息,通常他指向一个应用层结构,比如http request或者 stream session.
  • readwrite — connection的 read, write事件.
  • recvsendrecv_chainsend_chain — connection对应的I/O操作.
  • pool — connection 内存池.
  • log — connection 日志.
  • sockaddrsocklenaddr_text — 对端socket 地址二进制格式及长度和文本格式.
  • local_sockaddrlocal_socklen — 本地socket地址二进制格式及长度。初始化为空, 使用 ngx_connection_local_sockaddr() 获取.
  • proxy_protocol_addrproxy_protocol_port – 如果连接使用了PROXY 协议, 使用这两个指令指定客户端地址和端口,如果为连接启用了代理协议.
  • ssl — 链接的 SSL 信息.
  • reusable — 标识连接是否是可重复使用链接.
  • close — 标识表明该连接已被重复使用并且需要关闭。

nginx 连接可以封装SSL层。在这种情况下, ssl 连接使用 ngx_ssl_connection_t 结构,并保留连接的所有与ssl相关的数据,包括SSL_CTX 和 SSLrecvsendrecv_chainsend_chain 处理程序设置为启用SSL的功能。

worker_connections 指令限制了每个nginx worker的连接数量。当worker进程重启后,保存在cycle 对象中的 connections 连接都将重建,要重新检索连接结构使用ngx_get_connection(s, log) ,他以s 为套接字描述符为参数,而 s 通常被封装到connection机构中。

由于每一个worker的连接数量的限制,nginx提供一种方法来获取当前正在使用的connections. 要启用或禁用重复使用连接使用 ngx_reusable_connection(c, reusable) 函数,调用 ngx_reusable_connection(c, 1) 设置reuse 标识在 connection结构体中,并且将连接插入 cycle对象的队列  reusable_connections_queue 中。 ngx_get_connection() 发现cycle 中 free_connections 队列中一些无效连接后,将调用  ngx_drain_connections() 释放指定数量的可重用链接。

static void
ngx_drain_connections(ngx_cycle_t *cycle)
{
    ngx_uint_t         i, n;
    ngx_queue_t       *q;
    ngx_connection_t  *c;

    if (cycle->free_connection_n > cycle->connection_n / 16
        || cycle->reusable_connections_n == 0)
    {
        return;
    }

 。。。
    c = NULL;
    n = ngx_max(ngx_min(32, cycle->reusable_connections_n / 8), 1);

    for (i = 0; i < n; i++) {
        if (ngx_queue_empty(&cycle->reusable_connections_queue)) {
            break;
        }

        q = ngx_queue_last(&cycle->reusable_connections_queue);
        c = ngx_queue_data(q, ngx_connection_t, queue);

        ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0,
                       "reusing connection");

        c->close = 1;
        c->read->handler(c->read);
    }

    if (cycle->free_connection_n == 0 && c && c->reusable) {

        /*
         * if no connections were freed, try to reuse the last
         * connection again: this should free it as long as
         * previous reuse moved it to lingering close
         */

        ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0,
                       "reusing connection again");

        c->close = 1;
        c->read->handler(c->read);
    }
}

可以看到 去了一个 两个数值 32和cycle->reusable_connections_n / 8 最小值, 进行可重用链接的回收。

对于每一个connection, close 标识将和read 处理函数将被设置最终将调用  ngx_close_connection(c) 释放链接并且使其可用。  ngx_reusable_connection(c, 0) 退出可重用状态,is called. HTTP客户端连接是nginx中可重复使用的连接的一个示例;它们被标记为可重复使用的,直到从客户端收到第一个请求字节为止。

 

Events

Event

nginx 提供了事件处理机制, 使用ngx_event_t 结构来处理指定事件.

ngx_event_t 各个域:

  • data — event句柄使用的数据, 通常指向一个连接。
  • handler — 事件发生时将被调用的回调函数。
  • write — 写事件标识,如果不存在标识为读事件。
  • active — 标识这个事件被注册用于接收I/O 通知事件,通常通知机制使用epollkqueuepoll
  • ready — 标识该事件已收到I/O通知
  • delayed — 标识I/O由于速率限制而被延迟。
  • timer — 红黑树节点用于将事件插入计时树中。
  • timer_set — 标识事件计时器已设置并且尚未过期。
  • timedout — 标识事件计时器已过期。
  • eof — 标识读取数据时收到EOF。
  • pending_eof — 标志表明EOF在socket已经关闭,当前未处理,通过的EPOLLRDHUP epoll 事件 和  kqueue 的EV_EOF标识传递该标识。
  • error — 标识在读取期间(读取事件)或写入期间(对于写入事件)发生错误。
  • cancelable — Timer事件标识在关闭workers时应忽略此事件。优雅的关闭worker被延迟,直到不存在不可取消计时器事件为止。
  • posted — 标识事件被推送到队列。
  • queue — 队列节点用于将事件发布到队列。
struct ngx_event_s {
    void            *data;

    unsigned         write:1;

    unsigned         accept:1;

    /* used to detect the stale events in kqueue and epoll */
    unsigned         instance:1;

    /*
     * the event was passed or would be passed to a kernel;
     * in aio mode - operation was posted.
     */
    unsigned         active:1;

    unsigned         disabled:1;

    /* the ready event; in aio mode 0 means that no operation can be posted */
    unsigned         ready:1;

    unsigned         oneshot:1;

    /* aio operation is complete */
    unsigned         complete:1;

    unsigned         eof:1;
    unsigned         error:1;

    unsigned         timedout:1;
    unsigned         timer_set:1;

    unsigned         delayed:1;

    unsigned         deferred_accept:1;

    /* the pending eof reported by kqueue, epoll or in aio chain operation */
    unsigned         pending_eof:1;

    unsigned         posted:1;

    unsigned         closed:1;

    /* to test on worker exit */
    unsigned         channel:1;
    unsigned         resolver:1;

    unsigned         cancelable:1;

#if (NGX_HAVE_KQUEUE)
    unsigned         kq_vnode:1;

    /* the pending errno reported by kqueue */
    int              kq_errno;
#endif

    /*
     * kqueue only:
     *   accept:     number of sockets that wait to be accepted
     *   read:       bytes to read when event is ready
     *               or lowat when event is set with NGX_LOWAT_EVENT flag
     *   write:      available space in buffer when event is ready
     *               or lowat when event is set with NGX_LOWAT_EVENT flag
     *
     * iocp: TODO
     *
     * otherwise:
     *   accept:     1 if accept many, 0 otherwise
     *   read:       bytes to read when event is ready, -1 if not known
     */

    int              available;

    ngx_event_handler_pt  handler;


#if (NGX_HAVE_IOCP)
    ngx_event_ovlp_t ovlp;
#endif

    ngx_uint_t       index;

    ngx_log_t       *log;

    ngx_rbtree_node_t   timer;

    /* the posted queue */
    ngx_queue_t      queue;


}
I/O events

 

通过调用 ngx_get_connection() 每个连接都有的两个相关事件,c->read 和 c->write, 用于接收进入读写状态的 socket。所有此类事件均以边缘触发模式运行, 这意味这他们仅在socket状态更改时触发通知。例如部分数据读取不会触发nginx通知,一直到更多数据到达。.即使I/O 通知机制使用的是的是水平触发 (pollselect etc), nginx 会将其转化为边缘触发模式。要使在不同平台上的所有通知系统中保持nginx事件通知 , 在进行完 I/O socket 通知和进行socket I/O 操作后必须调用 ngx_handle_read_event(rev, flags) 和 ngx_handle_write_event(wev, lowat) 通常,在每个读取或写入事件处理程序的末尾调用函数一次。

 

Http连接函数

void
ngx_http_init_connection(ngx_connection_t *c)
{
。。。
    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_http_close_connection(c);
        return;
    }
}

Stream模块的处理函数

static void
ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
    ngx_uint_t do_write)
{
。。。
    for ( ;; ) {

。。。


    if (ngx_handle_read_event(src->read, flags) != NGX_OK) {
        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
        return;
    }

    if (dst) {

。。。

        if (ngx_handle_write_event(dst->write, 0) != NGX_OK) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }

。。。
    }
}

 

Timer events

将活动设置为在超时到期时发送通知。事件使用的计时器计数毫秒数,从过去的一些未指定点开始计数,结果为转化为 ngx_msec_t 类型。它的当前值可以从ngx_current_msec变量获得。

这里的未指定值指:

CLOCK_MONOTONIC

Clock that cannot be set and represents monotonic time since some unspecified starting point. This clock is not affected by discontinuous jumps in the system time (e.g., if the system administrator manually changes the clock), but is affected by the incremental adjustments performed by adjtime(3) and NTP.

from https://linux.die.net/man/2/clock_gettime

void
ngx_time_update(void)
{
。。。
    ngx_current_msec = ngx_monotonic_time(sec, msec);
。。。
}

 

static ngx_msec_t
ngx_monotonic_time(time_t sec, ngx_uint_t msec)
{
#if (NGX_HAVE_CLOCK_MONOTONIC)
    struct timespec  ts;

#if defined(CLOCK_MONOTONIC_FAST)
    clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
#else
    clock_gettime(CLOCK_MONOTONIC, &ts);
#endif

    sec = ts.tv_sec;
    msec = ts.tv_nsec / 1000000;

#endif

    return (ngx_msec_t) sec * 1000 + msec;
}

CLOCK_MONOTONIC: 单调递增时间与系统时钟有关, 与CLOCK_BOOTTIME类似, 后者包含挂起时间, 由于依赖与系统时钟不受NTP影响, 可以较为准确的计算出时间差。这里 CLOCK_MONOTONIC_FAST没找到是否应该是CLOCK_MONOTONIC_COARSE

https://stackoverflow.com/questions/3523442/difference-between-clock-realtime-and-clock-monotonic

 

函数ngx_add_timer(ev, timer) 设置了事件的超时, ngx_del_timer(ev) 删除了先前设置的超时。当前设置的全局超时Red-Black树 ngx_event_timer_rbtree 存储所有超时。树中的key是类型ngx_msec_t,是事件发生的时间。树结构可实现快速插入和删除操作,并访问最近的超时,nginx使用它们来找出等待I/O事件的时间以及到期超时事件。

 

Posted events

 

事件推送这意味着在当前事件循环迭代中的某个时候将调用其处理程序。推送事件有利于简化代码和减少堆栈溢出。 推送事件被保存在推送队列中,ngx_post_event(ev, q) 宏将事件t ev 推送到队列 qngx_delete_posted_event(ev) 宏从当前推送队列中删除 ev 。通常,事件被推送到  ngx_posted_events 队列,已在事件循环中进行处理 – I/O和Timer事件已经完成。 ngx_event_process_posted() 被调用以处理事件队列。它调用事件处理程序,直到队列不为空为止。这意味着发布的事件处理程序可以在当前事件循环迭代中发布更多要处理的事件

 

事件处理, 循环取出后执行handler

void
ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted)
{
    ngx_queue_t  *q;
    ngx_event_t  *ev;

    while (!ngx_queue_empty(posted)) {

        q = ngx_queue_head(posted);
        ev = ngx_queue_data(q, ngx_event_t, queue);

        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                      "posted event %p", ev);

        ngx_delete_posted_event(ev);

        ev->handler(ev);
    }
}

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
。。。

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);
。。。

    ngx_event_process_posted(cycle, &ngx_posted_events);
}

可以看到ngx_event_process_posted 在 ngx_process_events_and_timers被调用, 后者是cycle 循环处理中一个步骤。

stream init connection时将session处理事件推送到 ngx_posted_events

void
ngx_stream_init_connection(ngx_connection_t *c)
{
。。。
    rev = c->read;
    rev->handler = ngx_stream_session_handler;
。。。
    if (ngx_use_accept_mutex) {
        ngx_post_event(rev, &ngx_posted_events);
        return;
    }

    rev->handler(rev);
}

 

事件处理的例子:

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

 

Event loop

 

除了nginx master进程外,所有nginx进程都具有I/O,因此具有事件循环。 (而Nginx Master进程大部分时间都花在 sigsuspend() 调用中等待信号到达的情况下。)NGINX事件循环已在 ngx_process_events_and_timers() 函数中实现,该函数被重复调用直至流程退出。

事件循环包含以下阶段:

  • 通过调用 ngx_event_find_timer()查找最接近到期​​的超时事件。此功能在计时器红黑树中找到最左边的节点,并返回毫秒的数量,直到节点到期为止。
  • 处理I/O事件通过调用处理程序(特定于事件通知机制),该事件由nginx配置选择。该处理程序至少等待一个I/O事件发生,但只有在下一次超时到期。当发生读取或写入事件时,将设置ready标志,并调用事件的处理程序。对于Linux,通常使用ngx_epoll_process_events() 处理程序,该处理程序称 epoll_wait() 等待I/O事件。
  • 通过调用 ngx_event_expire_timers()来调用到期计时器。从最左侧的元素到右侧,最左侧元素开始迭代,直到找到未过时的超时为止。对于每个过期的节点,设置了timedout事件标志,timer_set标志已重置,事件处理程序被调用。
  • 通过调用ngx_event_process_posted()来发布事件的过程。该功能反复从已发布的事件队列中删除第一个元素,并调用元素的处理程序,直到队列为空为止。

所有nginx流程也处理信号。在ngx_process_events_and_timers()之后, 信号处理程序设置的全局变量将被检查。

 

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    ngx_uint_t  flags;
    ngx_msec_t  timer, delta;

    if (ngx_timer_resolution) {
        timer = NGX_TIMER_INFINITE;
        flags = 0;

    } else {
        timer = ngx_event_find_timer();
        flags = NGX_UPDATE_TIME;

#if (NGX_WIN32)

        /* handle signals from master in case of network inactivity */

        if (timer == NGX_TIMER_INFINITE || timer > 500) {
            timer = 500;
        }

#endif
    }

    if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

            if (ngx_accept_mutex_held) {
                flags |= NGX_POST_EVENTS;

            } else {
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }

    if (!ngx_queue_empty(&ngx_posted_next_events)) {
        ngx_event_move_posted_next(cycle);
        timer = 0;
    }

    delta = ngx_current_msec;

    (void) ngx_process_events(cycle, timer, flags);

    delta = ngx_current_msec - delta;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "timer delta: %M", delta);

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

    ngx_event_expire_timers();

    ngx_event_process_posted(cycle, &ngx_posted_events);
}

 

其中ngx_process_events 是一个宏定义指向 ngx_event_actions.process_events, 这里由于支持多中引擎

#define ngx_process_events ngx_event_actions.process_events

 

以epoll为例子

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
。。。

    ngx_event_actions = ngx_epoll_module_ctx.actions;

。。。
}

最终调用

static ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
#if (NGX_HAVE_EVENTFD)
        ngx_epoll_notify,                /* trigger a notify */
#else
        NULL,                            /* trigger a notify */
#endif
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

 

Processes

 

nginx有几种类型的进程。过程的类型保留在ngx_process 全局变量中:

  • NGX_PROCESS_MASTER  – 读取nginx配置的主处理,创建周期,并开始并控制子进程。它不执行任何I/O,并且仅响应信号。它的处理函数是 ngx_master_process_cycle()

Listen的创建应该是master执行的, 只是后续I/O读写由子进程完成。

  • NGX_PROCESS_WORKER – 处理客户端连接的工作过程。它是由主过程启动的,并响应其信号和通道命令(channel commands)。它的处理函数是ngx_worker_process_cycle()。可以有多个worker进程,如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) – 优雅地关闭。收到此信号后,主过程将关闭信号发送给所有子进程。当没有孩子的过程时,主人会释放内存池并退出。当worker进程收到此信号时,它将关闭所有监听的套接字并等待,直到没有不可中断的的事件(non-cancelable events),然后释放内存池和退出。当Cache Manager或Cache Loader进程接收此信号时,它立即退出。当一个worker进程接收此信号时,ngx_quit变量设置为1,并且在处理后立即重置。同时工作进程处于关闭状态,ngx_exiting变量设置为1。
  • NGX_TERMINATE_SIGNAL(大多数系统上的SIGTERM) – 终止。收到此信号后,主过程向所有子进程发送终止信号。如果子进程在1秒内未退出,则主过程将发送SIGKILL 信号关闭它。当没有剩余的子进程时,主过程会释放内存池并退出。当worker进程,缓存管理器进程或缓存加载程序进程接收此信号时,它会释放内存池并退出。接收到此信号时,变量ngx_terminate 将设置为1。
  • NGX_NOACCEPT_SIGNAL(大多数系统上的SIGWINCH ) – 关闭所有worker进程和辅助过程。收到此信号后,主过程会关闭其子进程。如果先前启动的新nginx二进制退出,则旧master的子进程再次启动。当worker过程收到此信号时,它将 debug_points 指令设置的调试模式关闭。
  • NGX_RECONFIGURE_SIGNAL(大多数系统上的SIGHUP) – 重新配置。收到此信号后,master进程重新读取配置,并基于它创建了一个新的周期。如果成功创建了新的周期,则删除了旧周期并开始新的子进程。同时,旧worker进程接收NGX_SHUTDOWN_SIGNAL信号。在单过程模式下,nginx 创建了一个新的周期,但要保持旧的周期,直到不再有与之相关的主动连接的客户端为止。新的worker进程和辅助进程忽略了此信号(二进制更新方式)。
  • NGX_REOPEN_SIGNAL(大多数系统上的SIGUSR1 ) – 重新打开文件。master进程将此信号发送给worker进程,该信号重新打开了与周期相关的所有open_files
  • NGX_CHANGEBIN_SIGNAL(大多数系统上的SIGUSR2) – 更改nginx二进制。master进程启动了一个新的nginxX二进制文件,将所有监听套接字的列表中传递给新的二进制文件。在“NGINX”环境变量中传递的文本形式列表用分号分隔的描述符编号组成。新的nginx二进制程序读取“NGINX” 变量,并将sockets 添加到其初始化周期中。其他过程忽略了此信号。

尽管所有nginx工作流程都能接收并正确处理POSIX信号,但master进程过程并未使用标准kill()系统调用将信号传递给worker进程和辅助进程。相反,nginx使用过程间套接字对,允许在所有nginx进程之间发送消息。但是,目前,消息仅从主进程发送给子进程。消息带有标准信号。

 

Threads

可以通过单独线程来完成将可能会阻塞nginx进程的任务,比可以将nginx 配置为使用线程执行文件file I/O. 或者是一个没有异步接口的库,因此通常不能与Nginx一起使用。请记住,线程接口是现有的异步方法来处理客户端连接的助手,绝不是替换。

为了处理同步,可以使用以下针对pthreads的封装函数:

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

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

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

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

typedef struct ngx_thread_pool_s  ngx_thread_pool_t;

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

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

在配置时,模块使用线程的模块必须通过调用ngx_thread_pool_add(cf, name),来获得对线程池的引用,该模块要么创建带有给定name的新线程池,要么以该名称返回对池的引用,如果它已经存在。

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

typedef struct {
    int    foo;
} my_thread_ctx_t;


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

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


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

    /* executed in nginx event loop */
}


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

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

    ctx = task->ctx;

    ctx->foo = 42;

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

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

    return NGX_OK;
}

 

Modules

 

Adding new modules

 

每个独立的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, or MISC.
  • ngx_module_name — 模块名称.要从一组源文件中构建多个模块,请指定一个空格分隔的名称列表。模块名称是动态模块的输出二进制文件的名称。列表中的名称必须匹配源代码中使用的名称。

这里的名称要和源码文件中 ngx_module_t 定义的模块名称一致。

ngx_module_t  ngx_http_upstream_keepalive_module = {
    NGX_MODULE_V1,
    &ngx_http_upstream_keepalive_module_ctx, /* module context */
    ngx_http_upstream_keepalive_commands,    /* 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
};
  • ngx_addon_name — 该模块的名称从配置脚本中显示在控制台上的输出中。
  • ngx_module_srcs — 空格分隔用于编译模块的源文件的列表,  $ngx_addon_dir 变量表示模块目录的路径。.
  • ngx_module_incs — 头文件路径
  • ngx_module_deps — 模块的依赖性列表。通常,它是头文件列表。
  • ngx_module_libs — 空格分隔用于编译模块的库文件的列表,例如需要链接libpthread 使用ngx_module_libs=-lpthread . 下面一些宏定义用来链接相关的库: LIBXSLTLIBGDGEOIPPCREOPENSSLMD5SHA1ZLIB, and PERL.
  • ngx_module_link — 由构建系统设置  ADDON 为静态模块或者 DYNAMIC 动态模块,并用于确定根据链接类型的不同操作。
  • ngx_module_order — 该模块的加载顺序;对 HTTP_FILTER and HTTP_AUX_FILTER 模块类型有用。此选项的格式是一个空格分隔的模块列表。该列表设置了模块初始化的顺序,全局列表中当前模块列表之前的模块都将在该模块之后加载, 对于过滤器模块,越晚初始化意味着会更早的执行。   以下模块通常用作参考。ngx_http_copy_filter_module读取其他过滤器模块的数据,并放置在列表底部附近,以便它是第一个执行的数据。ngx_http_write_filter_module将数据写入客户端套接字,并放置在列表顶部附近,并且是最后执行的。默认情况下,将过滤器模块放置在模块列表中的ngx_http_copy_filter之前,以便在复制过滤器处理程序之后执行过滤器处理程序。对于其他模块类型,默认值是空字符串。

ngx_module_order

在动态模块时生效, 如果使用静态模块需要手工修改边变量内容

ngx_module_type=HTTP_FILTER
ngx_module_name=ngx_http_mytest_module
ngx_addon_name=$ngx_module_name
ngx_module_srcs="$ngx_addon_dir/ngx_http_mytest_module.c"

. auto/module

next=ngx_http_charset_filter_module
HTTP_FILTER_MODULES=`echo $HTTP_FILTER_MODULES \
                     | sed "s/$ngx_module_name//" \
                     | sed "s/$next/$next $ngx_module_name/"`

https://www.cnblogs.com/atskyline/p/7993414.html

auto/module 根据是否设置顺序以及模块ORDER

if [ -z "$ngx_module_order" -a \
     \( "$ngx_module_type" = "HTTP_FILTER" \
     -o "$ngx_module_type" = "HTTP_AUX_FILTER" \) ]
then
    eval ${ngx_module}_ORDER=\"$ngx_module_name \
                               ngx_http_copy_filter_module\"
else
    eval ${ngx_module}_ORDER=\"$ngx_module_order\"
fi

动态模块会生成一个模块名开头的.c文件, 生成以下结构

extern ngx_module_t  ngx_*_module;

ngx_module_t *ngx_modules[] = {
    &ngx_stream_*_module,
    NULL
};

char *ngx_module_names[] = {
    "ngx_stream_*_module",
    NULL
};

char *ngx_module_order[] = {
    "ngx_stream_*_module",
    "ngx_stream_set_module",
    NULL
};

 

再通过load_module加载动态库时完成模块排序

static char *
ngx_load_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
#if (NGX_HAVE_DLOPEN)
 。。。。

    order = ngx_dlsym(handle, "ngx_module_order");

    for (i = 0; modules[i]; i++) {
        module = modules[i];
        module->name = names[i];

        if (ngx_add_module(cf, &file, module, order) != NGX_OK) {
            return NGX_CONF_ERROR;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cf->log, 0, "module: %s i:%ui",
                       module->name, module->index);
    }

    return NGX_CONF_OK;

。。。

#endif
}

 

通过ngx_add_module 修改cycle->modules模块位置,完成模块排序。

 

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

 

 

Core Modules

 

模块是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填充。

私有部分包括以下这些:

struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;

    char                 *name;

    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    ngx_uint_t            version;
    const char           *signature;

 。。。。

    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

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

  • 配置指令处理, master进程的进行配置文件命令解析工作。
  • 成功解析配置后,在master进程调用init_module处理程序。每次加载配置时,init_module处理程序都会在master中调用。
  • master进程创建一个或多个worker进程,并在每个进程中调用init_process处理程序。
  • 当工作进程接收关闭或终止命令时,它将调用 exit_process 处理程序。
  • master进程退出之前调用  exit_master 处理程序。

由于线程仅在nginx中用作具有自己API的补充I/O设施,因此目前未调用init_threadexit_thread处理程序。也没有init_master处理程序,因为这是不必要的开销。

模块的type字段定义了ctx字段中存储的内容。它的值是以下类型之一:

  • NGX_CORE_MODULE
  • NGX_EVENT_MODULE
  • NGX_HTTP_MODULE
  • NGX_MAIL_MODULE
  • NGX_STREAM_MODULE

The NGX_CORE_MODULE is the most basic and thus the most generic and most low-level type of module. The other module types are implemented on top of it and provide a more convenient way to deal with corresponding domains, like handling events or HTTP requests.

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

核心模块的集合包括 ngx_core_modulengx_errlog_modulengx_regex_modulengx_thread_pool_module and ngx_openssl_module模块。Http 模块,Stream模块,mail模块和event模块也是核心模块。核心模块的上下文定义为:

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

 

name是模块名称的字符串,create_conf 和 init_conf 是分别创建和初始化模块配置的函数的函数指针。对于核心模块,nginx 在解析新配置之前调用create_conf,所有有配置成功地解析后调用init_conf。典型的create_conf函数分配了配置的内存并设置默认值。

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

/*
 * Copyright (C) Author.
 */


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


typedef struct {
    ngx_flag_t  enable;
} ngx_foo_conf_t;


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

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


static ngx_command_t  ngx_foo_commands[] = {

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

      ngx_null_command
};


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


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


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

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

    fcf->enable = NGX_CONF_UNSET;

    return fcf;
}


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

    ngx_conf_init_value(fcf->enable, 0);

    return NGX_CONF_OK;
}


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

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

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

    return NGX_CONF_OK;
}

 

 

Configuration Directives

 

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

typedef struct ngx_command_s  ngx_command_t;

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

用特殊值ngx_null_command做为数组结尾。name 是在配置文件中出现的指令的名称,例如”worker_processes”或” listen”。type是标志的位置,该标志指定了指令所采用的参数数量以及其出现的场景。目前支持以下标志:

  • NGX_CONF_NOARGS — 指令不带参数。
  • NGX_CONF_1MORE — 指令带一个或多个参数。
  • NGX_CONF_2MORE — 指令带两个或多个参数。
  • NGX_CONF_TAKE1..NGX_CONF_TAKE7 — 指令带指定数量的参数。
  • NGX_CONF_TAKE12NGX_CONF_TAKE13NGX_CONF_TAKE23NGX_CONF_TAKE123NGX_CONF_TAKE1234 — 指令带不同数量参数,数量限制为具体给的数值,比如NGX_CONF_TAKE12 只带1个或者2个参数。

指令类型标志:

  • NGX_CONF_BLOCK — 指令是一个区块,也就是说,它可以在其大括号{}中包含其他指令,甚至可以实施自己的解析器以处理内部的内容
  • NGX_CONF_FLAG — 指令参数为 boolean 类型值, on 或 off.

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

  • NGX_MAIN_CONF — 配置文件全局配置.
  • NGX_HTTP_MAIN_CONFhttp
  • NGX_HTTP_SRV_CONFhttp server
  • NGX_HTTP_LOC_CONFhttp  – location
  • NGX_HTTP_UPS_CONF http – upstream
  • NGX_HTTP_SIF_CONF — http – server – if
  • NGX_HTTP_LIF_CONF — http – server – location – if
  • NGX_HTTP_LMT_CONFhttplimit_except
  • NGX_STREAM_MAIN_CONF —  stream
  • NGX_STREAM_SRV_CONFstream server .
  • NGX_STREAM_UPS_CONF — streamupstream .
  • NGX_MAIL_MAIN_CONF —  mail .
  • NGX_MAIL_SRV_CONFmail – server .
  • NGX_EVENT_CONF —  event .
  • NGX_DIRECT_CONF — 由不创建上下文层次结构且仅具有一个全局配置的模块使用。此配置作为conf参数传递给处理程序。

http-server-location 属于嵌套包含: http-server, http 使用ngx_array_t 方式定义了server, server中locations 直接使用了http location类型。在server这一级别可以定义自己结构体。

/* set up the directive's configuration context */

conf = NULL;

if (cmd->type & NGX_DIRECT_CONF) {
    conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];

} else if (cmd->type & NGX_MAIN_CONF) {
    conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);

} else if (cf->ctx) {
    confp = *(void **) ((char *) cf->ctx + cmd->conf);

    if (confp) {
        conf = confp[cf->cycle->modules[i]->ctx_index];
    }
}

四级指针存放数据

  • NGX_DIRECT_CONF:直接存放配置。
    • NGX_MAIN_CONF:存放Main配置
      • NGX_HTTP_MAIN_CONF 级别配置 :存放http配置
        • NGX_HTTP_SRV_CONF 级别配置: 存放server, upstream配置

可以看到再往下没了, location做为一个指定数据结构存放到了, http的server下, 不再使用conf方式配置。也就是location这一个是不可以定制的, 而之上的这些配置文件都是可以自定义的。

配置解析器使用这些标志进行判断,指令位置错误时抛出错误,并调用带有适当配置指针的指令处理程序,以便在不同位置的相同指令可以将其值存储在不同的位置。

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

  • ngx_conf_set_flag_slot — Converts the literal strings on and off into an ngx_flag_t value with values 1 or 0, respectively.

由于ngx_flag_t  对应一个 intptr_t类型, 一般用来存放指针,这里用来存放了一个1,0两个常量值。

char *
ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char  *p = conf;

    ngx_str_t        *value;
    ngx_flag_t       *fp;
    ngx_conf_post_t  *post;

    fp = (ngx_flag_t *) (p + cmd->offset);

    if (*fp != NGX_CONF_UNSET) {
        return "is duplicate";
    }

    value = cf->args->elts;

    if (ngx_strcasecmp(value[1].data, (u_char *) "on") == 0) {
        *fp = 1;

    } else if (ngx_strcasecmp(value[1].data, (u_char *) "off") == 0) {
        *fp = 0;

    } else {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                     "invalid value \"%s\" in \"%s\" directive, "
                     "it must be \"on\" or \"off\"",
                     value[1].data, cmd->name.data);
        return NGX_CONF_ERROR;
    }

    if (cmd->post) {
        post = cmd->post;
        return post->post_handler(cf, post, fp);
    }

    return NGX_CONF_OK;
}

 

  • ngx_conf_set_str_slot — 存储字符串到 ngx_str_t 类型的值里。
  • ngx_conf_set_str_array_slot — 追加一个值到 类型为 ngx_str_t.值到 ngx_array_t 数组中。 数组不存在则创建。
  • ngx_conf_set_keyval_slot — 追加一个ngx_keyval_t 类型的 key-value 到 ngx_array_t 数组中。数组不存在则创建。
  • ngx_conf_set_num_slot — 转化命令参数为 ngx_int_t 类型的值。
  • ngx_conf_set_size_slot — 将 size 转化为 size_t 类型的字节数。

ngx_http_v2_module

如 http2_chunk_size size; size以 K,k,M,m 结尾标识对应的字节的单位结尾。

  • ngx_conf_set_off_slot — 转化 offset 为 off_t 类型的字节数。

ngx_http_proxy_module:

如:proxy_cache_max_range_offset

  • ngx_conf_set_msec_slot — 转化 time 为 ngx_msec_t 类型的毫秒数。
  • ngx_conf_set_sec_slot — 转化 time 为 time_t 类型的秒数。
  • ngx_conf_set_bufs_slot — 转化 两个参数数提供给 ngx_bufs_t 变量,一个保存数量,一个保存buffer的容量。

ngx_http_proxy_module:

proxy_buffers number size

  • ngx_conf_set_enum_slot — 将提供的参数转换为ngx_uint_t值。 通过 在ngx_command_s post 字段中传的ngx_conf_enum_t 数组,数组定义了参数可接受的字符串和相应的ngx_uint_t类型的值。
static ngx_conf_enum_t  ngx_http_core_satisfy[] = {
    { ngx_string("all"), NGX_HTTP_SATISFY_ALL },
    { ngx_string("any"), NGX_HTTP_SATISFY_ANY },
    { ngx_null_string, 0 }
};

static ngx_command_t  ngx_http_core_commands[] = {

    { ngx_string("satisfy"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_enum_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_core_loc_conf_t, satisfy),
      &ngx_http_core_satisfy },

}
  • ngx_conf_set_bitmask_slot — 将参数转化为 ngx_uint_t 类型值. 参数多个值或运算后产生的结果。通过 在ngx_command_s post 字段中传的ngx_conf_bitmask_t数组, 数组定义可接受的字符串和相应的整数值。

将位图封装到数组中,可以通过命令设置多个值

#define NGX_HTTP_GZIP_PROXIED_OFF       0x0002
#define NGX_HTTP_GZIP_PROXIED_EXPIRED   0x0004
#define NGX_HTTP_GZIP_PROXIED_NO_CACHE  0x0008
#define NGX_HTTP_GZIP_PROXIED_NO_STORE  0x0010
#define NGX_HTTP_GZIP_PROXIED_PRIVATE   0x0020
#define NGX_HTTP_GZIP_PROXIED_NO_LM     0x0040
#define NGX_HTTP_GZIP_PROXIED_NO_ETAG   0x0080
#define NGX_HTTP_GZIP_PROXIED_AUTH      0x0100
#define NGX_HTTP_GZIP_PROXIED_ANY       0x0200

static ngx_conf_bitmask_t  ngx_http_gzip_proxied_mask[] = {
    { ngx_string("off"), NGX_HTTP_GZIP_PROXIED_OFF },
    { ngx_string("expired"), NGX_HTTP_GZIP_PROXIED_EXPIRED },
    { ngx_string("no-cache"), NGX_HTTP_GZIP_PROXIED_NO_CACHE },
    { ngx_string("no-store"), NGX_HTTP_GZIP_PROXIED_NO_STORE },
    { ngx_string("private"), NGX_HTTP_GZIP_PROXIED_PRIVATE },
    { ngx_string("no_last_modified"), NGX_HTTP_GZIP_PROXIED_NO_LM },
    { ngx_string("no_etag"), NGX_HTTP_GZIP_PROXIED_NO_ETAG },
    { ngx_string("auth"), NGX_HTTP_GZIP_PROXIED_AUTH },
    { ngx_string("any"), NGX_HTTP_GZIP_PROXIED_ANY },
    { ngx_null_string, 0 }
};

static ngx_command_t  ngx_http_core_commands[] = {
    { ngx_string("gzip_proxied"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
      ngx_conf_set_bitmask_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_core_loc_conf_t, gzip_proxied),
      &ngx_http_gzip_proxied_mask },
}
  • ngx_conf_set_path_slot — 将提供的参数转换为ngx_path_t值,并执行初始化。有关详细信息,请参阅proxy_temp_path指令的文档。

proxy_temp_path path [level1 [level2 [level3]]];

proxy_temp_path /spool/nginx/proxy_temp 1 2;

/spool/nginx/proxy_temp/7/45/00000123457;

  • ngx_conf_set_access_slot — 将提供的参数转换为文件权限。有关详细信息,请参阅proxy_store_access 指令的文档。

proxy_store_access user:rw group:rw all:r;

 

conf字段定义了配置结构将传递给目录处理程序。核心模块仅具有全局配置,并设置NGX_DIRECT_CONF标志来访问它。HTTP, Stream , Mail之类的模块创建配置的层次结构。例如,为 serverlocationif 创建了模块的配置。

  • NGX_HTTP_MAIN_CONF_OFFSEThttp .
  • NGX_HTTP_SRV_CONF_OFFSEThttp server.
  • NGX_HTTP_LOC_CONF_OFFSET — http – location .
  • NGX_STREAM_MAIN_CONF_OFFSETstream .
  • NGX_STREAM_SRV_CONF_OFFSETstream – server .
  • NGX_MAIL_MAIN_CONF_OFFSETmail .
  • NGX_MAIL_SRV_CONF_OFFSETmailserver.

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

 

Connection

 

Each HTTP client connection runs through the following stages:

  • ngx_event_accept() 接受客户端TCP连接。该处理程序是为了响应在收听socket上的接入请求而调用。在此阶段创建一个新的ngx_http_connection_t对象,其包装新接收的客户端套接字。每个nginx侦听服务都提供了一个对新连接对象的处理程序。对于HTTP连接,它ngx_http_init_connection()
  • ngx_http_init_connection() 执行了HTTP connection的早期初始化工作。在这个阶段创建了ngx_http_connection_t对象,存储在 connection 的 data字段中 ,之后它将被HTTP request object 结构替代。 PROXY protocol 解析 和SSL握手也是在这个阶段开始。
  • ngx_http_wait_request_handler()  读取事件处理程序在客户端套接字上可用时调用。在此阶段,HTTP请求对象 ngx_http_request_t 将创建, 并保存在connection的data字段。
  • ngx_http_process_request_line()读取客户端请求,将数据读取到 connection的缓冲区中。缓冲区的大小最初是由指令client_header_buffer_size设置的。整个客户标头应该适合缓冲区。如果初始大小还不够,则分配较大的缓冲区,并由大large_client_header_buffers指令设置。
  • ngx_http_process_request_headers() 读取事件处理函数,在 ngx_http_process_request_line() 之后读取HTTP请求头。
  • ngx_http_core_run_phases() 在请求头被完全读取并解析后被调用。这个函数运行阶段从 NGX_HTTP_POST_READ_PHASE 到 NGX_HTTP_CONTENT_PHASE. 最后一个阶段旨在生成响应并沿过滤器链传递。响应不一定在此阶段发送给客户。它可能保持在缓冲中,并在最终阶段发送。

这个函数按照配置的阶段调用相关处理函数。不包含 NGX_HTTP_LOG_PHASE。

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,

    NGX_HTTP_SERVER_REWRITE_PHASE,

    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,

    NGX_HTTP_PREACCESS_PHASE,

    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,

    NGX_HTTP_PRECONTENT_PHASE,

    NGX_HTTP_CONTENT_PHASE,

    NGX_HTTP_LOG_PHASE
} ngx_http_phases;
  • ngx_http_finalize_request()当请求结束或产生错误时被调用。在后一种情况下,查找适当的错误页面并将其用作响应。如果此时有响应信息未发送,则激活HTTP Writer ngx_http_writer()完成数据发送。
  • ngx_http_finalize_connection() 当响应被完全发送到客户端请求结构可以被释放, 如果客户端使用keepalive,ngx_http_set_keepalive() 将被调用,释放当前请求结构,并在连接上等待下一个请求。否则,ngx_http_close_request() 会释放请求和连接相关结构。

 

 

Request

 

对于每一个客户端HTTP请求, 创建 ngx_http_request_t 对象。对象一些字段如下:

  • connection — 指向ngx_connection_t客户端连接对象的指针。几个请求可以同时引用相同的连接对象 – 一个主请求及其子要求。删除请求后,可以在相同的连接上创建新请求。注意HTTP Connections ngx_connection_t的 data字段将重点指向请求。此类请求称为主动请求,而不是与连接有关的其他请求。主动请求用于处理客户端连接事件,并允许输出其对客户端的响应。通常,每个请求在某个时候变得有效,以便它可以发送其输出。
  • ctx — HTTP模块上下文的数组。NGX_HTTP_MODULE类型的每个模块都可以在请求中存储任何值(通常是指向结构的指针)。该值存储在模块的ctx_index位置的ctx数组中。以下宏提供了获取和设置请求上下文的方便方法:
    • ngx_http_get_module_ctx(r, module) — 返回模块的上下文内容
    • ngx_http_set_ctx(r, c, module) — 将 c 保存为模块的上下文内容。
  • main_confsrv_confloc_conf — 当前请求配置的数组。配置存储在模块的ctx_index位置。
  • read_event_handlerwrite_event_handler – 读取和编写请求的事件处理程序。通常,HTTP连接的读取和写入事件处理程序都设置为ngx_http_request_handler()。此函数为当前活动请求调用read_event_handler and write_event_handler处理程序。
  • cache — 请求缓存缓存上游响应。
  • upstream — 请求上游对象。
  • pool — 请求池。请求对象本身分配在此池中该池被删除后将被销毁。对于需要在整个客户连接寿命中可用的分配,请改用ngx_connection_t的池。
  • header_in —读取客户端HTTP请求头的缓冲区。
  • headers_inheaders_out —输入和输出HTTP标头对象。这两个对象都包含ngx_list_t类型的headers字段,用于保留原始headers题列表。除此之外,还可以使用特定的headers作为单独的字段获取和设置,例如content_length_nstatus等。
typedef struct {
    ngx_list_t                        headers;

    ngx_table_elt_t                  *host;
    ngx_table_elt_t                  *connection;
    ngx_table_elt_t                  *if_modified_since;
    ngx_table_elt_t                  *if_unmodified_since;
    ngx_table_elt_t                  *if_match;
    ngx_table_elt_t                  *if_none_match;
    ngx_table_elt_t                  *user_agent;
    ngx_table_elt_t                  *referer;
    ngx_table_elt_t                  *content_length;
    ngx_table_elt_t                  *content_range;
    ngx_table_elt_t                  *content_type;

    ngx_table_elt_t                  *range;
    ngx_table_elt_t                  *if_range;

    ngx_table_elt_t                  *transfer_encoding;
    ngx_table_elt_t                  *te;
    ngx_table_elt_t                  *expect;
    ngx_table_elt_t                  *upgrade;

#if (NGX_HTTP_GZIP || NGX_HTTP_HEADERS)
    ngx_table_elt_t                  *accept_encoding;
    ngx_table_elt_t                  *via;
#endif

    ngx_table_elt_t                  *authorization;

    ngx_table_elt_t                  *keep_alive;

#if (NGX_HTTP_X_FORWARDED_FOR)
    ngx_table_elt_t                  *x_forwarded_for;
#endif

#if (NGX_HTTP_REALIP)
    ngx_table_elt_t                  *x_real_ip;
#endif

#if (NGX_HTTP_HEADERS)
    ngx_table_elt_t                  *accept;
    ngx_table_elt_t                  *accept_language;
#endif

#if (NGX_HTTP_DAV)
    ngx_table_elt_t                  *depth;
    ngx_table_elt_t                  *destination;
    ngx_table_elt_t                  *overwrite;
    ngx_table_elt_t                  *date;
#endif

    ngx_table_elt_t                  *cookie;

    ngx_str_t                         user;
    ngx_str_t                         passwd;

    ngx_str_t                         server;
    off_t                             content_length_n;
    time_t                            keep_alive_n;

    unsigned                          connection_type:2;
    unsigned                          chunked:1;
    unsigned                          multi:1;
    unsigned                          multi_linked:1;
    unsigned                          msie:1;
    unsigned                          msie6:1;
    unsigned                          opera:1;
    unsigned                          gecko:1;
    unsigned                          chrome:1;
    unsigned                          safari:1;
    unsigned                          konqueror:1;
} ngx_http_headers_in_t;


typedef struct {
    ngx_list_t                        headers;
    ngx_list_t                        trailers;

    ngx_uint_t                        status;
    ngx_str_t                         status_line;

    ngx_table_elt_t                  *server;
    ngx_table_elt_t                  *date;
    ngx_table_elt_t                  *content_length;
    ngx_table_elt_t                  *content_encoding;
    ngx_table_elt_t                  *location;
    ngx_table_elt_t                  *refresh;
    ngx_table_elt_t                  *last_modified;
    ngx_table_elt_t                  *content_range;
    ngx_table_elt_t                  *accept_ranges;
    ngx_table_elt_t                  *www_authenticate;
    ngx_table_elt_t                  *expires;
    ngx_table_elt_t                  *etag;

    ngx_table_elt_t                  *cache_control;
    ngx_table_elt_t                  *link;

    ngx_str_t                        *override_charset;

    size_t                            content_type_len;
    ngx_str_t                         content_type;
    ngx_str_t                         charset;
    u_char                           *content_type_lowcase;
    ngx_uint_t                        content_type_hash;

    off_t                             content_length_n;
    off_t                             content_offset;
    time_t                            date_time;
    time_t                            last_modified_time;
} ngx_http_headers_out_t;

结构都使用 ngx_table_elt_t 使用了一个链表方式,通过hash设置清理位

typedef struct ngx_table_elt_s  ngx_table_elt_t;

struct ngx_table_elt_s {
    ngx_uint_t        hash;
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;
    ngx_table_elt_t  *next;
};
#define ngx_http_clear_content_length(r)                                      \
                                                                              \
    r->headers_out.content_length_n = -1;                                     \
    if (r->headers_out.content_length) {                                      \
        r->headers_out.content_length->hash = 0;                              \
        r->headers_out.content_length = NULL;                                 \
    }

#define ngx_http_clear_accept_ranges(r)                                       \
                                                                              \
    r->allow_ranges = 0;                                                      \
    if (r->headers_out.accept_ranges) {                                       \
        r->headers_out.accept_ranges->hash = 0;                               \
        r->headers_out.accept_ranges = NULL;                                  \
    }

#define ngx_http_clear_last_modified(r)                                       \
                                                                              \
    r->headers_out.last_modified_time = -1;                                   \
    if (r->headers_out.last_modified) {                                       \
        r->headers_out.last_modified->hash = 0;                               \
        r->headers_out.last_modified = NULL;                                  \
    }

#define ngx_http_clear_location(r)                                            \
                                                                              \
    if (r->headers_out.location) {                                            \
        r->headers_out.location->hash = 0;                                    \
        r->headers_out.location = NULL;                                       \
    }

#define ngx_http_clear_etag(r)                                                \
                                                                              \
    if (r->headers_out.etag) {                                                \
        r->headers_out.etag->hash = 0;                                        \
        r->headers_out.etag = NULL;                                           \
    }

http://www.daileinote.com/computer/nginx/09

  • request_body — 客户端请求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, etc.).
  • http_majorhttp_minor — 数字形式的客户端HTTP协议版本分为major和minor部分。
  • request_lineunparsed_uri — 原始客户端请求中的请求行和URI。
  • uriargsexten — 当前请求的uri,参数(args)和文件扩展名(exten)。此处的uri值由于nginx处理可能与客户端发送的原始URI有所不同。在整个请求处理过程中,这些值可以随着内部重定向的执行而改变。
  • main — 指向主要请求对象的指针。创建此对象是为了处理客户端HTTP请求,而不是子请求,该请求是为了在主请求中执行特定子任务而创建的。
  • parent — 指向一个子请求的父请求。
  • postponed — 输出缓冲区和子请求的列表,按照它们发送和创建的顺序。当子请求创建时,该列表由postpone过滤器使用,以提供一致的请求输出。
  • 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,这可能触发发送所有请求。通常,在调用请求的读写处理程序后被调用。

 

ngx_http_run_posted_requests将请求发送出去了, 

void
ngx_http_run_posted_requests(ngx_connection_t *c)
{
    ngx_http_request_t         *r;
    ngx_http_posted_request_t  *pr;

    for ( ;; ) {

        if (c->destroyed) {
            return;
        }

        r = c->data;
        pr = r->main->posted_requests;

        if (pr == NULL) {
            return;
        }

        r->main->posted_requests = pr->next;

        r = pr->request;

        ngx_http_set_log_request(c->log, r);

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http posted request: \"%V?%V\"", &r->uri, &r->args);

        r->write_event_handler(r);
    }
}

ngx_http_post_request(r, NULL) 将请求r追加到主请求列表中

ngx_int_t
ngx_http_post_request(ngx_http_request_t *r, ngx_http_posted_request_t *pr)
{
    ngx_http_posted_request_t  **p;

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

    pr->request = r;
    pr->next = NULL;

    for (p = &r->main->posted_requests; *p; p = &(*p)->next) { /* void */ }

    *p = pr;

    return NGX_OK;
}

 

  • phase_handler — .当前请求阶段的索引。
  • ncapturescapturescaptures_data — Regex捕获了请求的最后一个正则匹配项产生的。在请求处理过程中,可以在许多地方进行正则匹配:MAP查找,SNI或HTTP主机的服务器查找,重写,Proxy_redirect等。捕获由查找产生的捕获存储在上述字段中。字段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的变化。

在下面三个函数中调用,用于内部跳转的限制 在request redirection 中可以看到这几个函数

ngx_int_t
ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
    ngx_http_core_srv_conf_t  *cscf;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "post rewrite phase: %ui", r->phase_handler);

    if (!r->uri_changed) {
        r->phase_handler++;
        return NGX_AGAIN;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "uri changes: %d", r->uri_changes);

    /*
     * gcc before 3.3 compiles the broken code for
     *     if (r->uri_changes-- == 0)
     * if the r->uri_changes is defined as
     *     unsigned  uri_changes:4
     */

    r->uri_changes--;

    if (r->uri_changes == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "rewrite or internal redirection cycle "
                      "while processing \"%V\"", &r->uri);

        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_OK;
    }

    r->phase_handler = ph->next;

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
    r->loc_conf = cscf->ctx->loc_conf;

    return NGX_AGAIN;
}




ngx_int_t
ngx_http_internal_redirect(ngx_http_request_t *r,
    ngx_str_t *uri, ngx_str_t *args)
{
    ngx_http_core_srv_conf_t  *cscf;

    r->uri_changes--;

    if (r->uri_changes == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "rewrite or internal redirection cycle "
                      "while internally redirecting to \"%V\"", uri);

        r->main->count++;
        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_DONE;
    }

    r->uri = *uri;

    if (args) {
        r->args = *args;

    } else {
        ngx_str_null(&r->args);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "internal redirect: \"%V?%V\"", uri, &r->args);

    ngx_http_set_exten(r);

    /* clear the modules contexts */
    ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
    r->loc_conf = cscf->ctx->loc_conf;

    ngx_http_update_location_config(r);

#if (NGX_HTTP_CACHE)
    r->cache = NULL;
#endif

    r->internal = 1;
    r->valid_unparsed_uri = 0;
    r->add_uri_to_alias = 0;
    r->main->count++;

    ngx_http_handler(r);

    return NGX_DONE;
}

ngx_int_t
ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name)
{
    ngx_http_core_srv_conf_t    *cscf;
    ngx_http_core_loc_conf_t   **clcfp;
    ngx_http_core_main_conf_t   *cmcf;

    r->main->count++;
    r->uri_changes--;

    if (r->uri_changes == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "rewrite or internal redirection cycle "
                      "while redirect to named location \"%V\"", name);

        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_DONE;
    }

    if (r->uri.len == 0) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "empty URI in redirect to named location \"%V\"", name);

        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return NGX_DONE;
    }

    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);

    if (cscf->named_locations) {

        for (clcfp = cscf->named_locations; *clcfp; clcfp++) {

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "test location: \"%V\"", &(*clcfp)->name);

            if (name->len != (*clcfp)->name.len
                || ngx_strncmp(name->data, (*clcfp)->name.data, name->len) != 0)
            {
                continue;
            }

            ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "using location: %V \"%V?%V\"",
                           name, &r->uri, &r->args);

            r->internal = 1;
            r->content_handler = NULL;
            r->uri_changed = 0;
            r->loc_conf = (*clcfp)->loc_conf;

            /* clear the modules contexts */
            ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);

            ngx_http_update_location_config(r);

            cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

            r->phase_handler = cmcf->phase_engine.location_rewrite_index;

            r->write_event_handler = ngx_http_core_run_phases;
            ngx_http_core_run_phases(r);

            return NGX_DONE;
        }
    }

    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                  "could not find named location \"%V\"", name);

    ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);

    return NGX_DONE;
}
  • blocked —根据请求持有的块。此值不为零,无法终止请求。有待处理AIO操作(POSIX AIO和线程操作)和激活的缓存锁都会增加此值。

一个标记记录当前状态是否为正在处理中。

static ngx_inline void
ngx_http_v2_handle_stream(ngx_http_v2_connection_t *h2c,
    ngx_http_v2_stream_t *stream)
{
    ngx_event_t       *wev;
    ngx_connection_t  *fc;

    if (stream->waiting || stream->blocked) {
        return;
    }

    fc = stream->request->connection;

    if (!fc->error && stream->exhausted) {
        return;
    }

    wev = fc->write;

    wev->active = 0;
    wev->ready = 1;

    if (!fc->error && wev->delayed) {
        return;
    }

    ngx_post_event(wev, &ngx_posted_events);
}

 

  • buffered —通过位图方式显示了哪些模块已缓冲了请求产生的输出。许多过滤器可以缓冲输出;例如,sub_filter可以由于部分字符串匹配而可以缓冲数据,因此copy filter 可以缓冲数据,因为缺乏自由输出缓冲区等。只要此值不为零,请求未完成等待将缓冲区中内容发送。
#define NGX_HTTP_LOWLEVEL_BUFFERED         0xf0
#define NGX_HTTP_WRITE_BUFFERED            0x10
#define NGX_HTTP_GZIP_BUFFERED             0x20
#define NGX_HTTP_SSI_BUFFERED              0x01
#define NGX_HTTP_SUB_BUFFERED              0x02
#define NGX_HTTP_COPY_BUFFERED             0x04
if (r->buffered || r->postponed) {

    if (ngx_http_set_write_handler(r) != NGX_OK) {
        ngx_http_terminate_request(r, 0);
    }

    return;
}

可以看到类似处理

  • header_only —  标志表示输出不需要http body。例如,HTTP HEAD请求使用此标志
  • keepalive — 标志指示客户连接是否保持。该值是从HTTP版本和“Connection”标头的值推断的出来的。
void
ngx_http_handler(ngx_http_request_t *r)
{
    ngx_http_core_main_conf_t  *cmcf;

    r->connection->log->action = NULL;

    if (!r->internal) {
        switch (r->headers_in.connection_type) {
        case 0:
            r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
            break;
  • header_sent — 标志表示输出标头已经通过请求发送。
  • internal — 标志表示当前请求是内部的。要输入内部状态,请求必须通过内部重定向或作为子要求。内部请求可以进入internal location。
  • Syntax: internal;
    Default:
    Context: location

    Specifies that a given location can only be used for internal requests. For external requests, the client error 404 (Not Found) is returned. Internal requests are the following:

    http://nginx.org/en/docs/http/ngx_http_core_module.html#internal

  • allow_ranges — Flag indicating that a partial response can be sent to the client, as requested by the HTTP Range header.标志表示可以按照HTTP范围标头的要求将部分响应发送给客户端。

HTTP 协议从 1.1 版本开始支持分段获取文件内容,这一功能为并行下载和断点续传提供了技术支持。

https://blog.jenisec.org/security/nginx-int-overflow.html

  • subrequest_ranges — 标志表明在处理子要求时可以发送部分响应。

在range_filter中可以看到

static ngx_int_t
ngx_http_range_header_filter(ngx_http_request_t *r)
{
    time_t                        if_range_time;
    ngx_str_t                    *if_range, *etag;
    ngx_uint_t                    ranges;
    ngx_http_core_loc_conf_t     *clcf;
    ngx_http_range_filter_ctx_t  *ctx;

    if (r->http_version < NGX_HTTP_VERSION_10
        || r->headers_out.status != NGX_HTTP_OK
        || (r != r->main && !r->subrequest_ranges)
        || r->headers_out.content_length_n == -1
        || !r->allow_ranges)
    {
        return ngx_http_next_header_filter(r);
    }

 

  • single_range — 标志表明只能将单个连续的输出数据范围发送到客户端。通常在发送数据流时设置此标志,例如从代理服务器发送,整个响应在不在一个缓冲区中。

还是在range_filter模块中

parse:

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

    ctx->offset = r->headers_out.content_offset;

    ranges = r->single_range ? 1 : clcf->max_ranges;

    switch (ngx_http_range_parse(r, ctx, ranges)) {

    case NGX_OK:
        ngx_http_set_ctx(r, ctx, ngx_http_range_body_filter_module);

        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
        r->headers_out.status_line.len = 0;

        if (ctx->ranges.nelts == 1) {
            return ngx_http_range_singlepart_header(r, ctx);
        }

        return ngx_http_range_multipart_header(r, ctx);

    case NGX_HTTP_RANGE_NOT_SATISFIABLE:
        return ngx_http_range_not_satisfiable(r);

    case NGX_ERROR:
        return NGX_ERROR;

    default: /* NGX_DECLINED */
        break;
    }

 

Syntax: max_ranges number;
Default:
Context: httpserverlocation

This directive appeared in version 1.1.2.

Limits the maximum allowed number of ranges in byte-range requests. Requests that exceed the limit are processed as if there were no byte ranges specified. By default, the number of ranges is not limited. The zero value disables the byte-range support completely.

  • main_filter_need_in_memoryfilter_need_in_memory — 标志要求在内存缓冲区而不是文件中产生的输出。这是对copy过滤器的信号,即使启用了sendfile,也可以从文件缓冲区读取数据。两个标志之间的区别是设置它们的滤波器模块的位置。过滤器链中的延迟过滤器调用之前设置 filter_need_in_memory,要求仅当前请求输出在内存缓冲区中。稍后在过滤器链中调用的过滤器设置main_filter_need_in_memory,要求在发送输出时,主请求和所有子请求都在内存中读取文件。

在copy过滤器中通过这两个选项设置need_in_memory

static ngx_int_t
ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                     rc;
    ngx_connection_t             *c;
    ngx_output_chain_ctx_t       *ctx;
    ngx_http_core_loc_conf_t     *clcf;
    ngx_http_copy_filter_conf_t  *conf;

    c = r->connection;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http copy filter: \"%V?%V\"", &r->uri, &r->args);

    ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);

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

        ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module);

        conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module);
        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        ctx->sendfile = c->sendfile;
        ctx->need_in_memory = r->main_filter_need_in_memory
                              || r->filter_need_in_memory;
        ctx->need_in_temp = r->filter_need_temporary;

仅仅在ngx_output_chain_as_is中做了一下判断

  • filter_need_temporary —标志请求请求输出以临时缓冲区的形式产生,但不会在可读的内存缓冲区或文件缓冲区中产生。这是由过滤器使用的,该过滤器可能会直接在发送的缓冲区中直接更改输出。

 

 

 

Configuration

 

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

  • Main configuration — 适用于整个 http 块。做为模块的全局设置。
  • Server configuration — 适用于单个server器块。作为模块的服务器特定设置的功能。
  • Location configuration — .适用于单个locationif or limit_except块。充当模块的location 的设置。

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

typedef struct {
    ngx_uint_t  foo;
} ngx_http_foo_loc_conf_t;


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

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

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

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


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

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

    conf->foo = NGX_CONF_UNSET_UINT;

    return conf;
}


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

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

如示例所示,ngx_http_foo_create_loc_conf()函数创建了新的配置结构,而ngx_http_foo_merge_loc_conf()将配置与配置合并,并从更高级别的级别合并。实际上,server和location配置不仅存在于服务器和位置级别,而且还针对它们上方的所有级别创建。具体而言,main级别上可以创建server配置,location的配置可以是在主,服务器和位置级别上创建的。这些配置使得在nginx配置文件的任何级别上指定服务器和location的设置。最终配置被合并。提供了许多宏,例如NGX_CONF_UNSET 和 NGX_CONF_UNSET_UINT,用于指示缺失的设置并在合并时忽略它。标准nginx合并宏(例如ngx_conf_merge_value()ngx_conf_merge_uint_value(), 如果配置没有都提供了显式值, 可以通过这些宏提供了一种合并设置并设置默认值的方便方法,。有关不同类型的宏的完整列表,请参见src/core/ngx_conf_file.h.。

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

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

以下示例获取指向标准nginx core module ngx_http_core_module 的location配置的指针, 替换了结构体内针对location内容处理程序。

static ngx_int_t ngx_http_foo_handler(ngx_http_request_t *r);


static ngx_command_t  ngx_http_foo_commands[] = {

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

      ngx_null_command
};


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

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

    return NGX_CONF_OK;
}

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

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

这些宏接收到HTTP请求 ngx_http_request_t的引用。请求的main配置永远不会更改。选择请求的虚拟服务器之后,server配置可以从默认值中更改。选择用于处理请求的location配置可能会因重写操作或内部重定向而导致多次更改。以下示例显示了如何在运行时访问模块的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选择位置的特殊阶段。在此阶段之前,将相关虚拟服务器的默认location分配给请求,任何请求location配置的模块都会收到默认服务器位置的配置。此阶段将新位置分配给请求。在此阶段无法注册其他处理程序。
  • NGX_HTTP_REWRITE_PHASE — 类似 NGX_HTTP_SERVER_REWRITE_PHASE, 为前一阶段选择的location进行定义rewrite规则。
  • NGX_HTTP_POST_REWRITE_PHASE — 如果在重写期间更改其URI,请求将请求重定向到新位置的并再次通过NGX_HTTP_FIND_CONFIG_PHASE 的请求来实现的。在此阶段无法注册其他处理程序。
  • NGX_HTTP_PREACCESS_PHASE — 不同类型的处理程序的常见阶段,与访问控制无关。标准nginx模块  ngx_http_limit_conn_module ngx_http_limit_req_module 在这个阶段注册他们的回调函数。
  • NGX_HTTP_ACCESS_PHASE — 可以验证客户端有权提出请求的阶段。标准nginx模块,例如 ngx_http_access_module 和 ngx_http_auth_basic_module 在此阶段注册其处理程序。默认情况下,客户必须通过此阶段注册的所有处理程序的授权检查,以便继续进入下一阶段。satisfy指令可用于允许处理,任意一个处理授权客户成功,则可以继续进行。

参数是any的时候标识如果有一个模块的检查通过则允许访问。

Syntax: satisfy all | any;
Default:
satisfy all;
Context: httpserverlocation

Allows access if all (all) or at least one (any) of the ngx_http_access_modulengx_http_auth_basic_modulengx_http_auth_request_module, or ngx_http_auth_jwt_module modules allow access.

  • NGX_HTTP_POST_ACCESS_PHASE —处理satisfy any的特殊阶段。如果存在访问控制模块没有明确允许访问,则该请求已完成。在此阶段无法注册其他处理程序。
  • NGX_HTTP_PRECONTENT_PHASE — 在生成内容之前要调用处理程序的阶段。标准模块,例如ngx_http_try_files_module and ngx_http_mirror_module 在此阶段注册其处理程序。.
  • NGX_HTTP_CONTENT_PHASE。在此阶段,多个nginx标准模块在此阶段注册其处理程序,包括ngx_http_index_module 和 ngx_http_static_module 它们被依次调用,直到其中一个产生输出为止。也可以per-location设置内容处理程序。如果在 ngx_http_core_module 的location配置设置了处理函数,则其将被调用内容处理程序,并且在此阶段其他模块设置处理程序被忽略。
  • 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 的返回代码都被视为最终化代码。来自location内容处理程序的任何返回代码都被视为最终化代码。在访问阶段,在satisfy any 模式下,NGX_OKNGX_DECLINEDNGX_AGAINNGX_DONE以外的任何返回代码都被认为是拒绝。如果没有随后的访问处理程序允许或拒绝使用不同的代码访问,则拒绝代码将成为最终返回代码。

 

nginx中 一些阶段可以嵌入一些定制化开发的模块, 下面是openresty最佳实践里的一幅图,介绍openresty中可以嵌入代码的阶段,相比nginx增加module方式更简单方便一些, nginx也有类似的功能njs, 不过社区目前没有openresty强大。

图片from https://moonbingbing.gitbooks.io/openresty-best-practices/content/

 

 

 

Variables

 

Accessing existing variables

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

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

index = ngx_http_get_variable_index(cf, &name);

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

所有HTTP变量均在给定的HTTP请求的上下文中生成,结果特定于该HTTP请求中并缓存。生成变量的所有函数返回ngx_http_variable_value_t类型,变量值结构:

typedef ngx_variable_value_t  ngx_http_variable_value_t;

typedef struct {
    unsigned    len:28;

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

    u_char     *data;
} ngx_variable_value_t;
  • len — 值的长度
  • data — 值本身
  • valid — 值是否有效
  • not_found — 找不到该变量,因此data 和 len 是无关紧要的。例如,当未在请求中传递相应的参数时,可能会发生这种变量,例如$arg_foo
  • no_cacheable — 没有缓存结果
  • escape — 日志记录模块内部使用以标记需要输出的值。

ngx_http_get_flushed_variable() 和 ngx_http_get_indexed_variable() 函数用于获得变量的值。它们具有相同的接口 – 接受HTTP请求结构体 r 存放生成变量和索引的上下文。典型用法的一个例子:

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)函数。它搜索具有给定名称的变量及其从名称中得出的哈希值。??

#define NGX_HTTP_VAR_CHANGEABLE   1
#define NGX_HTTP_VAR_NOCACHEABLE  2
#define NGX_HTTP_VAR_INDEXED      4
#define NGX_HTTP_VAR_NOHASH       8
#define NGX_HTTP_VAR_WEAK         16
#define NGX_HTTP_VAR_PREFIX       32

在定义变量的时候设置以上属性

struct ngx_http_variable_s {
    ngx_str_t                     name;   /* must be first to build the hash */
    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;
};

通过flags设置这些属性

Creating variables

要创建变量,请使用ngx_http_add_variable()函数。它参数包括以配置,变量名,控制函数行为的标志:

函数原型

ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name,
    ngx_uint_t flags);

行为标识:

  • NGX_HTTP_VAR_CHANGEABLE — 启用重新定义变量:如果另一个模块定义了具有相同名称的变量,则不会发生冲突。这允许set指令覆盖变量
  • NGX_HTTP_VAR_NOCACHEABLE — 不做缓存, 用于类似 $time_local这类实时要求高变量。
  • NGX_HTTP_VAR_NOHASH — 指示此变量仅可通过索引访问,而不是按名称访问,这是一个小的优化,但不适用SSI或Perl等模块变量。
  • NGX_HTTP_VAR_PREFIX — 变量的名称是前缀。在这种情况下,处理程序必须实现其他逻辑才能获得特定变量的值。例如,所有“ arg_”变量均由同一处理程序处理,该变量在请求参数中执行查找并返回特定参数的值。

函数在错误返回NULL或返回指针ngx_http_variable_t

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

其中 get 和 set 指向的函数句柄用于获取或者设置变量的值,data 用于传送变量给处理函数,  index 指向引用变量的索引。

通常,需要增加变量设置时,由模块创建ngx_http_variable_t结构是以null终止的静态数组,并在预配置阶段 preconfiguration 进行处理,以将变量添加到配置中,例如:

static ngx_http_variable_t  ngx_http_foo_vars[] = {

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

      ngx_http_null_variable
};

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

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

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

    return NGX_OK;
}

该示例中的此功能用于初始化HTTP模块上下文的 preconfiguration 阶段配置,并在HTTP配置解析之前调用,以便解析器可以参考这些变量。

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

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

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

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

    return NGX_OK;
}

如果内部错误(例如,内存分配失败),它将返回NGX_ERROR。要了解变量状态属性,请检查ngx_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;
}

 

 

 

Complex values

 

复杂变量使用起来简单灵活,他为生成包含文本,变量及其组合表达式提供了一种简便的方法。

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 — 启动\0结束标识
  • conf_prefix — 配置路径前置(nginx当前正在寻找配置的目录)
  • root_prefix — root路径前置(通常为nginx安装路径)

当要将结果传递给需要zero终止字符串的库时,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.

 

 

Request redirection

 

HTTP请求始终通过ngx_http_request_t 结构的loc_conf字段连接到location。这意味着,在任何时候,可以通过调用ngx_http_get_module_loc_conf(r, module).来从请求中检索任何模块的location配置。请求location可以在请求的一生中更改几次。最初,将默认服务器的默认服务器location分配给请求。如果请求切换到另一个服务器(由HTTP“Host”标头或SSL SNI扩展程序选择),请求也将切换到该服务器的默认location。locaiton的下一个更改发生在NGX_HTTP_FIND_CONFIG_PHASE请求阶段。在此阶段,在为服务器配置的所有non-named locations中,请求URI选择了一个location。  ngx_http_rewrite_module 可以根据重写指令而更改 NGX_HTTP_REWRITE_PHASE 请求阶段的请求URI,并将请求发送回 NGX_HTTP_FIND_CONFIG_PHASE 阶段,以根据新的URI选择新location。

name location与 non-named locations 可以通过@name为location 命名。

Syntax:   location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default:    —
Context:    server, location

 

也可以通过调用ngx_http_internal_redirect(r, uri, args) or ngx_http_named_location(r, name).的一个,将请求重定向到任何点的新location。

ngx_http_internal_redirect(r, uri, args)功能更改请求URI,并将请求返回到NGX_HTTP_SERVER_REWRITE_PHASE 阶段。请求选择服务器默认location。稍后在 NGX_HTTP_FIND_CONFIG_PHASE 阶段根据新请求URI选择新location。

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

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

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

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

函数ngx_http_named_location(r, name)将请求重定向到命名location。location的名称作为参数传递。该location在当前服务器的所有named location中查找,然后请求切换到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 locations。内部请求具有internal标志。

Syntax: internal;
Default:
Context: location

Specifies that a given location can only be used for internal requests. For external requests, the client error 404 (Not Found) is returned. Internal requests are the following:

struct ngx_http_core_loc_conf_s {
    ngx_str_t     name;          /* location name */
    ngx_str_t     escaped_name;
。。。
    ngx_flag_t    internal;                /* internal */
。。。
};

ngx_http_core_loc_conf_s中关于internal指令

struct ngx_http_request_s {
    uint32_t                          signature;         /* "HTTP" */

。。。
    unsigned                          internal:1;
。。。
};

ngx_http_request_s结构体中也有该属性的定义。

 

Subrequests

 

子请求(Subrequest)主要用于将一个请求的输出插入另一个请求,可能与其他数据混合。子请求看起来像是普通请求,但与父节点共享一些数据。特别是,共享与客户端输入相关的所有字段,因为子请求不会从客户端接收任何其他输入。request结构体请求字段包含指向其parent请求的指针,如果为NULL表示他是主请求。字段main包含一组请求中的主请求的指针。

子请求从NGX_HTTP_SERVER_REWRITE_PHASE阶段开始。它通过正常请求通过相同的后续阶段,并根据自己的URI分配location。

始终忽略子请求中的输出Header。 ngx_http_postpone_filter 将子请求的输出body放置在适当的位置,相对于父请求产生的其他数据。

子请求与父请求的概念有关。如果c->data == r,请求r被视为活动的,其中c是客户端连接对象。在任何给定的点,仅允许请求组中的活动请求将其缓冲区输出到客户端。不活动的请求仍然可以将其输出发送到过滤器链filter chain,但是它不会通过 ngx_http_postpone_filter,并且该过滤器一直缓冲直到请求变得活跃。以下是激活请求的一些规则:

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

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

  • NGX_HTTP_SUBREQUEST_IN_MEMORY – 输出不会发送到客户端,而是存储在内存中。该标志仅影响由代理模块之一处理的子请求。在子请求完成后,它的输出信息保存在类型为 ngx_buf_t的 r->out变量中 。
  • NGX_HTTP_SUBREQUEST_WAITED – 即使子请求在最终确定时未处于活动状态,也会设置子请求的完成标志。该子请求标志由 SSI 过滤器使用。
  • NGX_HTTP_SUBREQUEST_CLONE – 子请求是作为其父的克隆而创建的。它是在同一位置开始的,并从与父级请求相同的阶段开始。
  • NGX_HTTP_SUBREQUEST_BACKGROUND:创建后台子请求。此类子请求不参与主请求的响应 构造,也就不会占用主请求的响应时间,但它依然会保持对主请求的引用
  • 补充 NGX_HTTP_SUBREQUEST_BACKGROUND 说明, 这篇文章有相关说明 https://ialloc.org/blog/ngx-notes-http-subrequest/#section-3

以下示例与/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;
}

子请求通常在body过滤器中创建,在这种情况下,它们的输出可以被视为任何显式请求的输出。这意味着子请求的输出最终被发送到客户端,在创建子请求之前传递的所有显式缓冲区到下一个过滤器, 在子请求创建之后将输出放到缓冲区的末尾。 即使对于包含子请求或孙请求的层次结构,也会保留这种顺序。以下示例将子请求的输出插入到所有请求数据缓冲区之后,但在带有 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标志。这可以防止子请求正文被发送到客户端。请注意,子请求的标头永远不会发送给客户端。可以在回调处理程序中分析子请求的结果。

 

Request finalization

 

通过调用函数  ngx_http_finalize_request(r, rc). 来完成 HTTP 请求。它通常在所有输出缓冲区都发送到过滤器链之后由内容处理程序完成。此时,可能不会将所有输出发送到客户端,其中一些输出仍保留在过滤器链的某处缓冲。如果是ngx_http_finalize_request(r, rc) 函数会通过回调处理程序ngx_http_writer(r)来完成发送输出。如果发生错误或需要将标准 HTTP 响应代码返回给客户端,则请求也会最终确定。

ngx_http_finalize_request(r, rc) 中 rc 取值如下:

  • NGX_DONE – 快速结束。减少请求计数并在达到零时销毁请求。当前请求被销毁后,客户端连接复用于更多请求。

ngx_reusable_connection可以实现connection复用。相关结构体无需重新申请。

  • NGX_ERRORNGX_HTTP_REQUEST_TIME_OUT (408), NGX_HTTP_CLIENT_CLOSED_REQUEST (499) – 报错结束. 尽可能快中断请求关闭客户端链接。
  • NGX_HTTP_CREATED (201), NGX_HTTP_NO_CONTENT (204), 大于或等于NGX_HTTP_SPECIAL_RESPONSE (300) 的代码 – 特殊响应最终确定。对于这些值,nginx 要么向客户端发送代码的默认响应页面,要么执行内部重定向到 error_page location位置(如果使用了该指令)。
  • 其他代码被认为是成功的完成代码,并且可能会激活请求编写器以完成发送响应正文。一旦主体被完全发送,请求计数就会递减。如果它达到零,则请求被销毁,但客户端连接仍可用于其他请求。如果计数为正,则请求中有未完成的活动,这些活动将在稍后完成。

 

 

 

 

Request body

 

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

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

函数 ngx_http_read_client_request_body(r, post_handler) 启动读取请求正文的过程。当 body 读完后,调用  post_handler  回调继续处理请求。如果请求正文丢失或已被读取,则立即调用回调。函数  ngx_http_read_client_request_body(r, post_handler) 分配类型为  ngx_http_request_body_t的  request_body 请求字段。此对象的字段 bufs 将结果保存到buffer chain。如果 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);
}

The following fields of the request determine how the request body is read:

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

  • request_body_in_single_buf – 将正文读取到单个内存缓冲区。
  • request_body_in_file_only – 总是将正文存放到文件中, 即使适合内存缓冲区。

 

ngx_int_t
ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
   
。。。
    if (rb->temp_file || r->request_body_in_file_only) {

        if (rb->bufs && rb->bufs->buf->in_file) {
            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                          "body already in file");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (ngx_http_write_request_body(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (rb->temp_file->file.offset != 0) {

            cl = ngx_chain_get_free_buf(r->pool, &rb->free);
            if (cl == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            b = cl->buf;

            ngx_memzero(b, sizeof(ngx_buf_t));

            b->in_file = 1;
            b->file_last = rb->temp_file->file.offset;
            b->file = &rb->temp_file->file;

            rb->bufs = cl;
        }
    }

。。。

 

  • request_body_in_persistent_file – 创建后不要立即取消链接文件。带有此标志的文件可以移动到另一个目录。
  • request_body_in_clean_file – 完成请求后删除链接文件。当一个文件应该移动到另一个目录但由于某种原因没有移动时,这可能很有用。

这两个标识和临时文件有关。

typedef struct {
    ngx_file_t                 file;
    off_t                      offset;
    ngx_path_t                *path;
    ngx_pool_t                *pool;
    char                      *warn;

    ngx_uint_t                 access;

    unsigned                   log_level:8;
    unsigned                   persistent:1;
    unsigned                   clean:1;
    unsigned                   thread_write:1;
} ngx_temp_file_t;


static ngx_int_t
ngx_http_write_request_body(ngx_http_request_t *r)
{
    ssize_t                    n;
    ngx_chain_t               *cl, *ln;
    ngx_temp_file_t           *tf;
    ngx_http_request_body_t   *rb;
    ngx_http_core_loc_conf_t  *clcf;

    rb = r->request_body;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http write client request body, bufs %p", rb->bufs);

    if (rb->temp_file == NULL) {
        tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
        if (tf == NULL) {
            return NGX_ERROR;
        }

        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        tf->file.fd = NGX_INVALID_FILE;
        tf->file.log = r->connection->log;
        tf->path = clcf->client_body_temp_path;
        tf->pool = r->pool;
        tf->warn = "a client request body is buffered to a temporary file";
        tf->log_level = r->request_body_file_log_level;
        tf->persistent = r->request_body_in_persistent_file;
        tf->clean = r->request_body_in_clean_file;

 

  • request_body_file_group_access – 通过将默认的 0600 访问掩码替换为 0660 来启用对文件的组访问.
static ngx_int_t
ngx_http_write_request_body(ngx_http_request_t *r)
{  
。。。
        if (r->request_body_file_group_access) {
            tf->access = 0660;
        }
  • request_body_file_log_level – 记录文件错误的严重级别。与临时文件有关
  • request_body_no_buffering – 读取请求正文而不进行缓冲。

 

struct ngx_http_request_s {
。。。
    unsigned                          request_body_in_single_buf:1;
    unsigned                          request_body_in_file_only:1;
    unsigned                          request_body_in_persistent_file:1;
    unsigned                          request_body_in_clean_file:1;
    unsigned                          request_body_file_group_access:1;
    unsigned                          request_body_file_log_level:3;
    unsigned                          request_body_no_buffering:1;
。。。

 

request_body_no_buffering 标志启用读取请求正文的无缓冲模式。在这种模式下,调用 ngx_http_read_client_request_body() 后, bufs 链可能只保留 body 的一部分。要读取下一部分,要调用 ngx_http_read_unbuffered_request_body(r) 函数。返回值 NGX_AGAIN 和请求标志 reading_body 表示有更多数据可用。如果调用此函数后bufs 为 NULL,则此时没有可读取的内容。当请求正文的下一部分可用时,将调用请求回调 read_event_handler。

 

 

Request body filters

 

读取请求正文部分后,通过调用存储在 ngx_http_top_request_body_filter 变量中的第一个body过滤器处理程序将其传递到请求body过滤器链。假设每个 body 处理程序都会调用链中的下一个处理程序,直到最后一个处理程序  ngx_http_request_body_save_filter(r, cl) 被调用。此处理程序收集 r->request_body->bufs 中的缓冲区,并在必要时将它们写入文件。最后一个请求正文缓冲区具有非零 last_buf 标志。

 如果过滤器计划延迟数据缓冲区,它应该在第一次调用时将标志 r->request_body->filter_need_buffering 设置为 1 。
  以下是一个简单的请求主体过滤器示例,它将请求主体延迟一秒。
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


#define NGX_HTTP_DELAY_BODY  1000


typedef struct {
    ngx_event_t   event;
    ngx_chain_t  *out;
} ngx_http_delay_body_ctx_t;


static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r,
    ngx_chain_t *in);
static void ngx_http_delay_body_cleanup(void *data);
static void ngx_http_delay_body_event_handler(ngx_event_t *ev);
static ngx_int_t ngx_http_delay_body_init(ngx_conf_t *cf);


static ngx_http_module_t  ngx_http_delay_body_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_delay_body_init,      /* postconfiguration */

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

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

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


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


static ngx_http_request_body_filter_pt   ngx_http_next_request_body_filter;


static ngx_int_t
ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_chain_t                *cl, *ln;
    ngx_http_cleanup_t         *cln;
    ngx_http_delay_body_ctx_t  *ctx;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "delay request body filter");

    ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module);

    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t));
        if (ctx == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module);

        r->request_body->filter_need_buffering = 1;
    }

    if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (!ctx->event.timedout) {
        if (!ctx->event.timer_set) {

            /* cleanup to remove the timer in case of abnormal termination */

            cln = ngx_http_cleanup_add(r, 0);
            if (cln == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            cln->handler = ngx_http_delay_body_cleanup;
            cln->data = ctx;

            /* add timer */

            ctx->event.handler = ngx_http_delay_body_event_handler;
            ctx->event.data = r;
            ctx->event.log = r->connection->log;

            ngx_add_timer(&ctx->event, NGX_HTTP_DELAY_BODY);
        }

        return ngx_http_next_request_body_filter(r, NULL);
    }

    rc = ngx_http_next_request_body_filter(r, ctx->out);

    for (cl = ctx->out; cl; /* void */) {
        ln = cl;
        cl = cl->next;
        ngx_free_chain(r->pool, ln);
    }

    ctx->out = NULL;

    return rc;
}


static void
ngx_http_delay_body_cleanup(void *data)
{
    ngx_http_delay_body_ctx_t *ctx = data;

    if (ctx->event.timer_set) {
        ngx_del_timer(&ctx->event);
    }
}


static void
ngx_http_delay_body_event_handler(ngx_event_t *ev)
{
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    r = ev->data;
    c = r->connection;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "delay request body event");

    ngx_post_event(c->read, &ngx_posted_events);
}


static ngx_int_t
ngx_http_delay_body_init(ngx_conf_t *cf)
{
    ngx_http_next_request_body_filter = ngx_http_top_request_body_filter;
    ngx_http_top_request_body_filter = ngx_http_delay_body_filter;

    return NGX_OK;
}

 

 

Response

在 nginx 中,通过发送响应标头和可选的响应正文来生成 HTTP 响应。 header 和 body 都通过过滤器链并最终写入客户端套接字。 nginx 模块可以将其处理程序安装到header或body过滤器链中,并处理来自上一个处理程序的输出。

 

 

 

 

Response header

 

ngx_http_send_header(r) 函数发送输出header。在 r->headers_out包含生成 HTTP 响应标头所需的所有数据之前,请勿调用此函数。 r->headers_out中的status字段必须始终设置。如果响应状态表明响应体跟随header,也可以设置 content_length_n。该字段的默认值为-1,这意味着身体大小是未知的。在这种情况下,使用分块 chunked传输。要输出任意header,需要附加headers列表。

 

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

    /* send header */

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

    /* X-Foo: foo */

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

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

    rc = ngx_http_send_header(r);

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

    /* send body */

    ...
}

 

 

 

Header filters

 

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

要将处理程序添加到header过滤器链,请在配置时将其地址存储在全局变量 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;
}

 

 

Response body

 

要发送响应正文,请调用 ngx_http_output_filter(r, cl) 函数。该函数可以被多次调用。每次,它都会以缓冲链(buffer chain)的形式发送一部分响应体。在最后一个正文缓冲区中设置 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);
}

 

Response body filters

 

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

这一点和stream 模块类似

body 过滤器处理程序接收缓冲链(buffers chain)中  。处理程序应该处理缓冲链并将的新chain传递给下一个处理程序。值得注意的是,传入链的 ngx_chain_t对象 cl 属于调用者,不得重复使用或更改。处理程序完成后,调用者可以使用其输出chain link 来跟踪它已发送的缓冲区。为了保存缓冲链(buffer chain)或替换一些缓冲区,传递到下一个过滤器之前, 处理程序需要分配自己的 缓冲链(buffer chain)。

以下是一个简单的正文过滤器示例,它计算正文中的字节数。结果可用作可使用的 $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;
}

 

 

 

Building filter modules

 

在编写正文或标题过滤器时,请特别注意过滤器在过滤器顺序中的位置。 nginx 标准模块注册了许多 header 和 body 过滤器。 在正确的位置注册一个新的过滤器模块是很重要的。通常,模块在其postconfiguration 阶段处理程序中注册过滤器。在处理过程中调用过滤器的顺序显然与它们注册的顺序相反。

nginx给第三方模块提供了个特殊的槽口 HTTP_AUX_FILTER_MODULES。想在这个插槽注册一个filter模块,模块的配置里应该将 ngx_module_type 变量设置值为 HTTP_AUX_FILTER

下面是一个假设只有一个源文件的过滤器模块config文件的例子 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 config的说明可以看 商业版本wiki 中的详细说明https://www.nginx.com/resources/wiki/extending/new_config/

 

 

 

 

Buffer reuse

在生成或更改缓冲区时,通常希望重用分配的缓冲区。 nginx 代码中一个标准且被广泛采用的方法是为此目的保留两个缓冲区链:free and busyfree 链保留所有空闲缓冲区,可以重复使用。busy 链保留当前模块发送的所有缓冲区,这些缓冲区仍在被其他过滤器处理程序使用。如果缓冲区的大小大于零,则认为缓冲区正在使用。通常,当过滤器使用缓冲区时,其 pos (或文件缓冲区的 file_pos)移向last (文件缓冲区的file_last)。一旦缓冲区被完全消耗,它就可以被重用了。要将新释放的缓冲区添加到free链中,要释放的busy的链并将其pos, last指针设置为start(pos-start即为缓存数据字节数,  last与start相同表示buffer设置为大小为0),移动到free链。这个操作太常见了,所以有一个特殊的函数,ngx_chain_update_chains(free, busy, out, tag). 该函数将输出链outbusy 链, 并将空闲的buffer从busy移动到free。仅重用具有指定tag的缓冲区。这让模块只重用它自己分配的缓冲区。

 

void
ngx_chain_update_chains(ngx_pool_t *p, ngx_chain_t **free, ngx_chain_t **busy,
    ngx_chain_t **out, ngx_buf_tag_t tag)
{
    ngx_chain_t  *cl;

    if (*out) {
        if (*busy == NULL) {
            *busy = *out;

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

            cl->next = *out;
        }

        *out = NULL;
    }

    while (*busy) {
        cl = *busy;

        if (cl->buf->tag != tag) {
            *busy = cl->next;
            ngx_free_chain(p, cl);
            continue;
        }

        if (ngx_buf_size(cl->buf) != 0) {
            break;
        }

        cl->buf->pos = cl->buf->start;
        cl->buf->last = cl->buf->start;

        *busy = cl->next;
        cl->next = *free;
        *free = cl;
    }
}

可以看到这里pos就是缓存的位置, start,last为标识这缓冲开始和结束地址, 当pos, last在start位置的时候标识当前缓冲为空。可以看一下 buffer的计算 ngx_buf_size。

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

他用pos, start, last这些指针标识当前引用的缓存实际数据的大小。缓冲空间大小使用end和start标识。

struct ngx_buf_s {
    u_char          *pos;
    u_char          *last;
    off_t            file_pos;
    off_t            file_last;

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
    ngx_buf_tag_t    tag;
    ngx_file_t      *file;
    ngx_buf_t       *shadow;
。。。

 

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

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


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

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

        ngx_http_set_ctx(r, ctx, ngx_http_foo_filter_module);
    }

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

    ll = &out;

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

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

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

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

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

        /* append the next incoming buffer */

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

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

    *ll = NULL;

    /* send the new chain */

    rc = ngx_http_next_body_filter(r, out);

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

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

    return rc;
}

 

 

Load balancing

 

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

least_conn 和 hash 模块实现了替代的负载平衡方法,但实际上是作为轮询算法模块的扩展实现的,并与其共享大量代码,例如服务器组的表示。 keepalive 模块是一个扩展upstream功能的独立模块。

ngx_http_upstream_module 可以通过将相应的upstream 放入配置文件来显式配置,或者通过使用诸如  proxy_pass  之类的指令来隐式配置,该指令接受在某个时间点被评估为服务器列表的 URL。least_conn 和 hash负载平衡方法仅适用于显式上游配置。上游模块配置有自己的指令上下文 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 —上游模块的配置上下文。
  • serversngx_http_upstream_server_t的数组,存放一组 server 指令解析的配置。
  • flags — 主要标记负载平衡方法支持哪些功能的标志。这些功能被配置为 server 指令的参数:
    • NGX_HTTP_UPSTREAM_CREATE — 用来区分显式定义的upstream和通过proxy_pass类型指令隐式创建的upstream (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 — upstream名称
  • file_name, line — 配置文件名字以及upstream块所在行。
  • port and no_port — 用于标识隐式的定义upstream 如通过proxy_pass。
  • shm_zone — 此上游组使用的共享内存区域。
  • peer — 存放用来初始化upstream配置通用方法的对象。
typedef struct {
    ngx_http_upstream_init_pt        init_upstream;
    ngx_http_upstream_init_peer_pt   init;
    void                            *data;
} ngx_http_upstream_peer_t;

实现负载平衡算法的模块必须设置这些方法并初始化私有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_peer_connection_t 类型的 ngx_http_upstream_t.peer对象中获取

struct ngx_peer_connection_s {
    ...

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

    ngx_uint_t                       tries;

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

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

    ...
};

结构包含下面成员:

  • sockaddrsocklenname — upstream server 连接的地址, load-balancing 方法输出参数

轮询算法片段

ngx_int_t
ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
{
    ngx_http_upstream_rr_peer_data_t  *rrp = data;

    ngx_int_t                      rc;
    ngx_uint_t                     i, n;
    ngx_http_upstream_rr_peer_t   *peer;
    ngx_http_upstream_rr_peers_t  *peers;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                   "get rr peer, try: %ui", pc->tries);

    pc->cached = 0;
    pc->connection = NULL;

    peers = rrp->peers;
    ngx_http_upstream_rr_peers_wlock(peers);

    if (peers->single) {
        peer = peers->peer;

        if (peer->down) {
            goto failed;
        }

        if (peer->max_conns && peer->conns >= peer->max_conns) {
            goto failed;
        }

        rrp->current = peer;

    } else {

        /* there are several peers */

        peer = ngx_http_upstream_get_peer(rrp);

        if (peer == NULL) {
            goto failed;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "get rr peer, current: %p %i",
                       peer, peer->current_weight);
    }

    pc->sockaddr = peer->sockaddr;
    pc->socklen = peer->socklen;
    pc->name = &peer->name;

    peer->conns++;

    ngx_http_upstream_rr_peers_unlock(peers);

    return NGX_OK;
  • data — 负载均衡方法的每个请求所需数据;保持选择算法的状态,通常包括到上游配置的链接。它作为参数传递给所有处理服务器选择的方法(见下文)。
  • tries — 允许尝试链接upstream server的 次数
  • server.getfreenotifyset_session, and save_session – 负载均衡算法模块的方法.见下文

所有的方法至少接受两个参数:peer连接对象pc 以及由ngx_http_upstream_srv_conf_t.peer.init()创建的data参数。注意,一般来说,负载均衡算法的”chaining”和pc.data是不同的,

  • get(pc, data) — 当上游模块准备好向上游服务器传递请求并且需要知道其地址时调用的方法。该方法必须填充 ngx_peer_connection_t结构的sockaddrsocklen, 和 name 字段。返回是以下之一:T
    • NGX_OK — 真是服务器被选择.
    • NGX_ERROR — 内部错误.
    • NGX_BUSY — 当前没有可用服务器。有多种原因会导致这个情况的发生,例如:动态服务器组为空,全部服务器均为失败状态,全部服务器已经达到最大连接数或者其他类似情况
    • NGX_DONE — 底层连接被重用,无需创建与上游服务器的新连接。该值由 keepalive 模块设置。
  • free(pc, data, state) — 当上游模块完成与特定服务器的工作时调用的方法。state 参数是上游连接的完成状态,具有以下可能值的位掩码:
    • NGX_PEER_FAILED — 失败 unsuccessful
    • NGX_PEER_NEXT — 上游服务返回 403 or 404,这种情况不认为 failure.
    • NGX_PEER_KEEPALIVE — 目前未使用

    此方法还递减tries计数器。

  • notify(pc, data, type) — 开源版本未使用
  • set_session(pc, data) 和 save_session(pc, data) —  启用缓存会话到上游服务器,用于 SSL 的方法。该实现由轮询负载均衡方法提供。

 

 

Examples

nginx-dev-examples 提供了nginx模块的例子.

Code style

 

General rules

 

  • 最大文本宽度为80个字符
  • 缩进是4个空格
  • 没有tabs,行末无空格
  • 同一行上的列表元素与空间分开
  • 十六进制的文字是小写
  • 文件名,功能和类型名称以及全局变量具有ngx_ 或更具体的前缀,例如ngx_http_ 和ngx_mail_
size_t
ngx_utf8_length(u_char *p, size_t n)
{
    u_char  c, *last;
    size_t  len;

    last = p + n;

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

        c = *p;

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

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

    return len;
}
Files

典型的源文件可能包含以下部分,并由两个空行分隔:

  • 版权声明
  • includes文件
  • 预处理器定义
  • 类型定义
  • 函数声明
  • 变量定义
  • 函数定义

版权声明如下:

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

如果文件有重大修改,则应更新作者列表,将新作者添加到顶部。

ngx_config.hngx_core.h 文件总是首先包含,然后是ngx_http.hngx_stream.h, 和 ngx_mail.h之一。然后可选的外部头文件:

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

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

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

头文件应该应该使用宏定义方式保护,避免编译时提示重复引用:

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

 

Comments
  • //” 方式注释不被采用
  • 文本使用英文
  • 注释格式如下:
/*
 * The red-black tree code is based on the algorithm described in
 * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
 */

/* find the server configuration for the address:port */

 

 

Preprocessor

 

宏定义以 ngx_NGX_ 做为前缀. 常量的宏定义大写,初始化和参数宏定义小写, 名称和值之间用两个空格分隔:

#define NGX_CONF_BUFFER  4096

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

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

#define ngx_null_string  { 0, NULL }

条件在括号内, !否定操作在括号外:

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

 

 

Types

类型以“_t”做为后缀. 定义的类型与名称至少由两个空格分隔:

typedef ngx_uint_t  ngx_rbtree_key_t;

结构类型使用 typedef 定义。内部结构、成员类型和名称是对齐的:

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

保持文件中不同结构之间的对齐方式相同。指向结构体定义以“_s”结尾。相邻的结构定义用两个空行分隔:

typedef struct ngx_list_part_s  ngx_list_part_t;

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


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

结构体成员各占一行:

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

函数指针类型被定义为以“_pt”结尾:

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

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

枚举类型以“_e”结尾:

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

 

 

Variables

 

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

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

静态变量和全局变量可以在声明处初始化:

static ngx_str_t  ngx_http_memcached_key = ngx_string("memcached_key");
static ngx_uint_t  mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static uint32_t  ngx_crc32_table16[] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    ...
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

 

Functions

 

所有函数(甚至是静态函数)都应该有原型。原型包括参数名称。长原型在后续行上带有一个缩进:

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

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

定义中的函数名称以新行开头。函数体的左大括号和右大括号位于不同的行上。函数的主体是缩进的。函数之间有两个空行:

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


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

函数名和左括号后没有空格。长函数调用,如果可以续行从第一个函数参数的位置开始。如果不能满足的话,请格式化第一行续行,使其在位置 79 处结束, 使得最大长度80 字符。

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

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

ngx_inline替换inline:

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

 

 

Expressions

除“.”和“->”外的二元运算符应该与它们的操作数分开一个空格。一元运算符和下标与它们的操作数之间没有空格分隔:

width = width * 10 + (*fmt++ - '0');
ch = (u_char) ((decoded << 4) + (ch - '0'));
r->exten.data = &r->uri.data[i + 1];

类型转换与转换后的表达式之间用一个空格分隔。类型转换中的星号与类型名称用空格分隔:

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

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

if (status == NGX_HTTP_MOVED_PERMANENTLY
    || status == NGX_HTTP_MOVED_TEMPORARILY
    || status == NGX_HTTP_SEE_OTHER
    || status == NGX_HTTP_TEMPORARY_REDIRECT
    || status == NGX_HTTP_PERMANENT_REDIRECT)
{
    ...
}
p->temp_file->warn = "an upstream response is buffered "
                     "to a temporary file";

作为最后的手段,可以处理一个表达式,使续行在位置 79 处结束:

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

上述规则也适用于子表达式,其中每个子表达式都有自己的缩进级别:

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

有时,表达式放到在强制转换后。在这种情况下,续行是缩进的:

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

指针要强制与NULL比较而不是0:

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

 

 

Conditionals and Loops

 

if” 关键字与条件之间用一个空格分隔。左大括号位于同一行,如果条件需要多行,则位于专用行。右大括号位于专用行上,可选地后跟“else if / else”。通常,“else if / else”部分之前有一个空行:

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

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

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

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

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

格式化类似的格式化规则适用于“do” 和 “while”循环:

while (p < last && *p == ' ') {
    p++;
}
do {
    ctx->node = rn;
    ctx = ctx->next;
} while (ctx);

switch”关键字与条件之间用一个空格分隔。左大括号位于同一行。闭合支架位于专用线上。“case”关键字与“switch”对齐:

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

case '<':
    copy_end = p;
    break;

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

大多数 “for” 循环格式如下:

for (i = 0; i < ccf->env.nelts; i++) {
    ...
}
for (q = ngx_queue_head(locations);
     q != ngx_queue_sentinel(locations);
     q = ngx_queue_next(q))
{
    ...
}

如果省略了“for”语句的部分语句, 使用 “/* void */”注释:

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

如果是循环体为空, 使用“/* void */” 注释, 并放到同一行上:

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

无限循环的格式如下:

for ( ;; ) {
    ...
}

 

 

Labels

标签上各空一行,并跟随上一级缩进:

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

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

    u->naddrs = i;

    ...

    return NGX_OK;

failed:

    freeaddrinfo(res);
    return NGX_ERROR;

 

 

 

 

Debugging memory issues

 

要调试缓冲区溢出或释放后使用错误等内存问题,您可以使用一些现代编译器支持的 AddressSanitizer (ASan)。要使用gcc 和 clang 启用 ASan,请使用 -fsanitize=address 编译器和链接器选项。在构建 nginx 时,这可以通过在 configure 脚本的  --with-cc-opt--with-ld-opt 参数中添加选项来完成。

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

以下配置行总结了上面提供的信息。建议在开发第三方模块和在不同平台上测试 nginx 时使用。

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

 

Common Pitfalls

 

Writing a C module

 

最常见的陷阱是在可以避免的情况下尝试编写成熟的 C 模块。在大多数情况下,您的任务可以通过创建适当的配置来完成。如果编写一个模块是不可避免的,尽量让它尽可能小和简单。例如,一个模块只能导出一些variables

在开始一个模块之前,请考虑以下问题:

  • 是否可以使用已经available modules来实现所需的功能?
  • 是否可以使用内置脚本语言(Perl or njs)来解决问题?

尽量使用先用功能, 通过配置实现需求, 如果需要编写模块也要尽量简单, 复杂的业务逻辑最好通过 外部组件完成如openresty 还有作者提到的perl 或者njs ,虽然没怎么用过, 主要是就不要破坏现有简单的架构。

 

C Strings

nginx 中最常用的字符串类型,ngx_str_t 不是 C-Style 以零结尾的字符串。您不能将数据传递给标准 C 库函数,例如 strlen() 或 strstr()。相反,应该使用接受 ngx_str_t 或指向数据和长度的指针的 nginx 函数,参见 文档 string部分。但是,有一种情况是 ngx_str_t 持有一个指向零终止字符串的指针:配置文件解析的结果是零终止的字符串。

nginx ngx_str_t 是值+长度的方式, 与C字符串不同, 处理时要用ngx提供的函数更安全。

 

 

Global Variables

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

尽量避免使用全局变量, 如果使用,尤其是在重载时关注其释放和加载。

 

 

 

Manual Memory Management

学习如何使用 nginx pools,而不是处理容易出错的 malloc/free 方法。一个池被创建并绑定到一个对象——configurationcycleconnection, 或 HTTP request。当对象被销毁时,关联的池也被销毁。因此,在使用对象时,可以从相应的池中分配所需的数量,即使出现错误也不关心释放内存

使用nginx 提供的结构体,自身携带的内存池分配对象

 

 

Threads

建议避免在 nginx 中使用线程,因为它肯定会破坏:大多数 nginx 函数都不是线程安全的。预计线程将仅执行系统调用和线程安全库函数。如果您需要运行一些与客户端请求处理无关的代码,正确的方法是在 init_process 模块处理程序中安排一个计时器,并在计时器处理程序中执行所需的操作。 nginx 在内部使用threads 来提升与 IO 相关的操作,但这是一个特殊情况,有很多限制。

使用计数器而不是线程

 

 

 

Blocking Libraries

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

 

 

HTTP Requests to External Services

通常,模块需要对某些外部服务执行 HTTP 调用。一个常见的错误是使用一些外部库(例如 libcurl)来执行 HTTP 请求。绝对没有必要为可以由 nginx 本身完成的任务带来大量外部(可能是blocking)代码。

当需要外部请求时,有两种基本的使用场景:

  • 在处理客户端请求的上下文中(例如,在content阶段,添加处理模块)
  • 在工作进程的上下文中(例如,计时器处理程序)

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

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

 

 

参考及引用

 

https://static.kancloud.cn/digest/understandingnginx/202592

https://github.com/baishancloud/nginx-development-guide/blob/master/zh.md#%E6%96%87%E4%BB%B6

中文的图书不是很多, 下面两本可以参考:

https://book.douban.com/subject/23759678/

https://book.douban.com/subject/26745255/

nginx商业版本网站对文档也做了整理,  https://docs.nginx.com/nginx/

图片 from 陳丁光

文档是从5月10日开始整理,今日2022/11/15日结束, 相当与一个读书笔记, nginx 官网文档比较简洁, 相对上面书来说,更适合像我这样的初学者。

 

 

Comments are closed.