2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 《深入理解NGINX 模块开发与架构解析》之摘抄学习

《深入理解NGINX 模块开发与架构解析》之摘抄学习

时间:2018-08-14 04:18:27

相关推荐

《深入理解NGINX 模块开发与架构解析》之摘抄学习

1.基于Nginx框架开发程序有5个优势:

(1).Nginx将网络、磁盘及定时器等异步事件的驱动都做了非常好的封装,基于它开发将可以忽略这些事件处理的细节;

(2).Nginx封装了许多平台无关的接口、容器,适用于跨平台开发。

(3) 优秀的模块化设计,使得开发者可以轻易地复用各种已有的模块,其中既包括基本的读取配置、记录日志等模块,也包括处理请求的诸如HTTP.mail等高级功能模块;

(4)Nginx是作为服务器来设计其框架的,因此,它在服务器进程的管理上相当出色,基于它开发服务器程序可以轻松地实现程序的动态升级,子进程的监控、管理,配置项的动态修改生效等;

(5).Nginx充分考虑到各操作系统所擅长的“绝活”,能够使用特殊的系统调用更高效地完成任务时,绝不会去使用低效的通用接口。尤其对于Linux操作系统,Nginx不遗余力地做了大量优化。

2.由于默认的Linux内核参数考虑的是最通用的场景,这明显不符合用于支持高并发访问的Web服务器的定义,所以需要修改Linux内核参数,使得Nginx可以拥有更高的性能。

最常用的配置:

fs.file-max = 999999 //这个参数表示进程(比如一个worker进程)可以同时打开的最大句柄数net.ipv4.tcp_tw_reuse = 1 //这个参数设置为1,表示允许将TIME-WAIT状态的socket重新用于新的TCP连接,这对于服务器来说很有意义,因为服务器上总会有大量TIME-WAIT状态的连接。net.ipv4.tcp_keepalive_time = 600 //这个参数表示当前keepalive启用时,TCP发送keepalive消息的频度。默认是2小时,若将其设置得小一些,可以更快地清理无效的连接net.ipv4.tcp_fin_timeout = 30 //这个参数表示当前当服务器主动关闭连接时,socket保持在FIN-WAIT-2状态的最大时间net.ipv4.tcp_max_tw_buckets = 5000 //这个参数表示操作系统允许TIME_WAIT套接字数量的最大值,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。net.ipv4.ip_local_port_range = 1024 61000 //这个参数定义了在UDP和TCP连接中本地(不包括连接的远端)端口的取值范围。net.ipv4.tcp_rmem = 4096 32768 262142 //这个参数定义了TCP接收缓存(用于TCP接受滑动窗口)的最小值、默认值、最大值。net.ipv4.tcp_wmem = 4096 32768 262142 //这个参数定义了TCP发送缓存(用于TCP发送滑动窗口)的最小值、默认值、最大值。dev_max_backlog = 8096 //当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。这个参数表示该队列的最大值。net.core.rmem_default = 262144 //这个参数表示内核套接字接收缓存区默认的大小。net.core.wmem_default = 262144 //这个参数表示内核套接字发送缓存区默认的大小。net.core.rmem_max = 2097152 //这个参数表示内核套接字接收缓存区的最大大小。net.core.wmem_max = 2097152 //这个参数表示内核套接字发送缓存区的最大大小。net.ipv4.tcp_syncookies = 1 //该参数与性能无关,用于解决TCP的SYN攻击。net.ipv4.tcp_max_syn_backlog = 1024 //这个参数表示TCP三次握手建立阶段SYN请求队列的最大长度,默认为1024,将其设置得大一些可以使出现Nginx繁忙来不及accept新连接的情况时,Linux不至于丢失客户端发起的连接请求。

3.configure脚本的内容如下:

#!bin/sh# Copyright (C) Igor Sysoev# Copyright (C) Nginx, Inc.#auto/options脚本处理configure命令的参数。例如,如果参数是--help,那么显示支持的所有参数格式。options脚本会定义后续工作将要用到的变量,然后根据本次参数以及默认值设置这些变量. auto/options#auto/init脚本初始化后续将产生的文件路径。例如,Makefile、ngx_modules.c等文件默认情况下会在<nginx-source>/objs/. auto/init#auto/sources脚本将分析Nginx的源码结构,这样才能构造后续的Makefile文件. auto/sources# 编译过程中所有目录文件生成的路径由--builddir=DIR参数指定,默认情况下为<nginx-source>/objs,此时这个目录将会被创建test -d $NGX_OBJS || mkdir $NGX_OBJS# 开始准备建立ngx_auto_headers.h、autoconf.err等必要的编译文件echo > $NGX_AUTO_HEADERS_Hecho > $NGX_AUTOCONF_ERR# 向objs/ngx_auto_config.h写入命令行带的参数echo "#define NGX_CONFIGURE \"$NGX_CONFIGURE\"" > $NGX_AUTO_CONFIG_H# 判断DEBUG标志,如果有,那么在objs/ngx_auto_config.h文件中写入DEBUG宏if [ $NGX_DEBUG = YES ]; thenhave=NGX_DEBUG . auto/havefi# 现在开始检查操作系统参数是否支持后续编译if test -z "$NGX_PLATFORM"; thenecho "checking for OS"NGX_SYSTEM=`uname -s 2>/dev/null`NGX_RELEASE=`uname -r 2>/dev/null`NGX_MACHINE=`uname -m 2>/dev/null`#屏幕上输出OS名称、内核版本、32位/64位内核echo " + $NGX_SYSTEM $NGX_RELEASE $NGX_MACHINE"NGX_PLATFORM="$NGX_SYSTEM:$NGX_RELEASE:$NGX_MACHINE";case "$NGX_SYSTEM" inMINGW32_*)NGX_PLATFORM=win32;;esacelseecho "building for $NGX_PLATFORM"NGX_SYSTEM=$NGX_PLATFORMfi#检查并设置编译器,如GCC是否安装、GCC版本是否支持后续编译nginx. auto/cc/conf# 对非Windows操作系统定义一些必要的头文件,并检查其是否存在,一次决定configure后续步骤是否可以成功if [ "$NGX_PLATFORM" != win32 ]; then. auto/headersfi# 对于当前操作系统,定义一些特定的操作系统相关的方法并检查当前环境是否支持。例如,对于Linux,在这里使用sched_sestaffinity设置进程优先级,使用Linux特有的sendfile系统调用来加速向网络中发送文件块. auto/os/conf# 定义类UNIX操作系统中通用的头文件和系统调用等,并检查当前环境是否支持if [ "$NGX_PLATFORM" != win32 ]; then. auto/unixfi#最核心的构造运行期modules的脚本。它将会生成ngx_modules.c文件,这个文件会被编译进Nginx中,其中它所做的唯一的事情就是定义了ngx_modules数组。ngx_modules指明Nginx运行期间有哪些模块会参与到请求的处理中,包括HTTP请求可能会使用哪些模块处理,因此,它对数组元素的顺序非常敏感,也就是说,绝大部分模块在ngx_modules数组中的顺序其实是固定的。例如,一个请求必须先执行ngx_http_gzip_filter_module模块重新修改HTTP响应中的头部后,才能使用ngx_http_header_filter模块按照headers_in结构体里的成员构造出以TCP流形式发送给客户端的HTTP响应头部。注意,我们在--add-module=参数里加入的第三方模块也在此步骤写入到ngx_modules.c文件中了. auto/modules# conf脚本用来检查Nginx在链接期间需要链接的第三方静态库、动态库或者目标文件是否存在. auto/lib/conf# 处理Nginx安装后的路径case ".$NGX_PREFIX" in.)NGX_PREFIX=${NGX_PREFIX:-/usr/local/nginx}have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define;;.!)NGX_PREFIX=;;*)have=NGX_PREFIX value="\"$NGX_PREFIX/\"" . auto/define;;esac# 处理Nginx安装后conf文件的路径if [ ".$NGX_CONF_PREFIX" != "." ]; thenhave=NGX_CONF_PREFIX value="\"$NGX_CONF_PREFIX/\"" . auto/definefi# 处理Nginx安装后,二进制文件、pid、lock等其他文件的路径可参见configure参数中路径类选项的说明have=NGX_SBIN_PATH value="\"$NGX_SBIN_PATH\"" . auto/definehave=NGX_CONF_PATH value="\"$NGX_CONF_PATH\"" . auto/definehave=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/definehave=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/definehave=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/definehave=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/definehave=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\"" . auto/definehave=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\"" . auto/definehave=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\"" . auto/definehave=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\"" . auto/definehave=NGX_HTTP_SCGI_TEMP_PATH value="\"$NGX_HTTP_SCGI_TEMP_PATH\"" . auto/define# 创建编译时使用的objs/Makefile文件. auto/make# 为objs/Makefile加入需要连接的第三方静态库、动态库或者目标文件. auto/lib/make# 为objs/Makefile加入install功能,当执行make install时将编译生成的必要文件复制到安装路径,建立必要的目录. auto/install# 在ngx_auto_config.h文件中加入NGX_SUPPERSS_WARN宏、NGX_SMP宏. auto/stubs# 在ngx_auto_config.h文件中指定NGX_USER和NGX_GROUP宏,如果执行configure时没有参数指定,默认两者皆为nobody(也就是默认以nobody用户运行进程)have=NGX_USER value="\"$NGX_USER\"" . auto/definehave=NGX_GROUP value="\"$NGX_GROUP\"" . auto/define# 显示configure执行的结果,如果失败,则给出原因. auto/summary

4.ngx_moduels.c文件就是用来定义ngx_moduels数组的。它指明了每个模块在Nginx中的优先级,当一个请求同时符合多个模块的处理规则时,将按照它们在ngx_moduels数组中的顺序选择最靠前的模块优先处理。对于HTTP过滤模块而言,在ngx_modules数组中越是靠后的模块反而会首先处理HTTP响应。

5.日志文件回滚

使用-s reopen参数可以重新打开日志文件,这样可以先把当前日志文件改名或转移到其他目录中进行备份,再重新打开时就会生成新的日志文件。这个功能使得日志文件不至于过大。

6.平滑升级Nginx

当Nginx服务升级到新的版本时,必须要将旧的二进制文件Nginx替换掉,通常情况下这是需要重启服务的,但Nginx支持不重启服务来完成新版本的平滑升级。

升级时包括以下步骤:

1) 通知正在运行的旧版本Nginx准备升级。通过向master进程发送USR2信号可达到目的。例如:

kill -s SIGUSR2 <nginx master pid>

这时,运行中的Nginx会将pid文件重命名,如将/usr/local/nginx/los/nginx.pid重命名为/usr/local/nginx/logs/nginx.pid.oldbin,这样新的Nginx才有可能启动成功。

2) 启动新版本的Nginx,可以使用以上介绍过的任意一种启动方法,这时通过ps命令可以发现新旧版本的Nginx在同时运行。

3) 通过kill命令向旧版本的master进程发送SIGQUIT信号,以“优雅”的方式关闭旧版本的Nginx。随后将只有新版本的Nginx服务运行,此时平滑升级完毕。

7.部署后Nginx进程间的关系

8.系统调用gettimeofday的执行频率

默认情况下,每次内核的事件调用(如epoll、select、poll、kqueue等)返回时,都会执行一次gettimeofdata,实现用内核的时钟来更新Nginx中的缓存时钟。

9.server_name后可以跟多个主机名称,如server_name 、;。

在开始处理一个HTTP请求时,Nginx会取出header头中的Host,与每个server中的server_name进行匹配,以此决定到底由哪一个server块来处理这个请求。有可能一个Host与多个server块中的server_name都匹配,这是就会根据匹配优先级来选择实际处理的server块。server_name与Host的匹配优先级如下:

1) 首先选择所有字符串完全匹配的server_name,如.

2)其次选择通配符在前面的server_name,如 *.。

3)再次选择通配符在后面的server_name,如www.testweb.*。

4)最后选择使用正则表达式才匹配的server_name,如~^\.testweb\.com$。

10.作为静态Web服务器与反向代理服务器的Nginx

11.Nginx作为反向代理服务器时转发请求的流程

当客户端发来HTTP请求时,Nginx并不会立刻转发到上游服务器,而是先把用户的请求(包括HTTP包体)完整地接收到Nginx所在服务器的硬盘或者内存中,然后再向上游服务器发起连接,把缓存的客户端请求转发到上游服务器。而Squid等代理服务器则采用一边接收客户端请求,一边转发到上游服务器的方式。

Nginx的这种工作方式有什么优缺点呢?很明显,缺点是延长了一个请求的处理时间,并增加了用于缓存请求内容的内存内核磁盘空间。而优点则是降低了上游服务器的负载,尽量把压力放在Nginx服务器上。

12.Nginx HTTP模块调用的简化流程

13.在Linux平台下,Nginx对ngx_int_t和ngx_uint_t的定义如下:

typedef intptr_t ngx_int_t;typedef uintptr_t ngx_uint_t;

14.ngx_str_t的定义如下:

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

任何视图将ngx_str_t的data成员当做字符串来使用的情况,都可能导致内存越界!

Nginx使用ngx_str_t可以有效地降低内存使用量。例如,用户请求“GET /test?a=1 http/1.1\r\n”存储到内存地址0x1d0b0110上,这时只需要把r->method_name设置为{len = 3,data = 0x1d0b0110}就可以表示方法名"GET",而不需要单独为method_name再分配内存冗余的存储字符串。

15.ngx_list_t是Nginx封装的链表容器,它在Nginx中使用得很频繁,例如HTTP的头部就是用ngx_list_t来存储的。先看一下ngx_list_t相关成员的定义:

typedef struct ngx_list_part_s ngx_list_part_t;struct ngx_list_part_s {void *elts; //指向数组的起始地址。ngx_uint_t nelts; //表示数组中已经使用了多少个元素,当然,nelts必须小于ngx_list_t结构体中的nalloc.ngx_list_part_t *next; //下一个链表元素ngx_list_part_t的地址。};typedef struct {ngx_list_part_t *last; //指向链表的最后一个数组元素ngx_list_part_t part; //链表的首个数组元素size_t size; //链表中的每个ngx_list_part_t元素都是一个数组。因为数组存储的是某种类型的数据结构,且ngx_list_t是非常灵活的数据结构,所以它不会限制存储什么样的数据,只是通过size限制每一个数组元素的占用的空间大小,也就是用户要存储的一个数据所占用的字节数必须小于或等于size。ngx_uint_t nalloc; //链表的数组元素一旦分配后是不可更改的。nalloc表示每个ngx_list_part_t数组的容量,即最多可存储多少个数据。ngx_pool_t *pool; //链表中管理内存分配的内存池对象。用户要存放的数据占用的内存都是由pool分配的。} ngx_list_t;

ngx_list_t的内存分布情况如下:

上图中是由3个ngx_list_part_t数组元素组成的ngx_list_t链表中可能拥有的一种内存分布结构。这里,pool内存池为其分配了连续的内存,最前端内存存储的是ngx_list_t结构中的成员,紧接着是第一个ngx_list_part_t结构占用的内存,然后是ngx_list_part_t结构指向的数组,它们一共占用size*nalloc字节,表示数组中拥有nalloc个大小为size的元素。其后面是第2个ngx_list_part_t结构以及它所指向的数组,依次类推。

16.ngx_table_elt_t数据结构如下所示:

typedef struct {ngx_uint_t hash; //表明ngx_table_elt_t也可以是某个散列表数据结构(ngx_hash_t类型)中的成员ngx_str_t key;ngx_str_t value;u_char *lowcase_key;} ngx_table_elt_t;

显而易见,ngx_table_elt_t是为HTTP头部"量身定制"的。

17.缓冲区ngx_buf_t是Nginx处理大数据的关键数据结构,它既应用于内存数据也应用于磁盘数据。下面主要介绍ngx_buf_t结构体本身:

typedef struct ngx_buf_s ngx_buf_t;typedef void * ngx_buf_tag_t;struct ngx_buf_s {/* pos通常是用来告诉使用者本次应该从pos这个位置开始处理内存中的数据,这样设置是因为同一个ngx_buf_t可能被多次反复处理,当然,pos的含义是由使用它的模块定义的*/u_char *pos;/* last通常表示有效的内容到此为止,注意,pos与last之间的内存是希望nginx处理的内容*/u_char *last;/*处理文件时,file_pos与file_last的含义与处理内存时的pos与last相同,file_pos表示将要处理的文件位置,file_last表示截止的文件位置*/off_t file_pos;off_t file_last;//如果ngx_buf_t缓冲区用于内存,那么start指向这段内存的起始地址u_char *start;//与start成员对应,指向缓冲区内存的末尾u_char *end;/* 表示当前缓冲区的类型,例如由哪个模块使用就指向这个模块ngx_module_t变量的地址*/ngx_buf_tag_t tag;//引用的文件ngx_file_t *file;/* 当前缓冲区的影子缓冲区,该成员很少用到,仅仅在12.8节描述的使用缓冲区转发上游服务器的响应时才使用了shadow成员,这是因为Nginx太节约内存了,分配一块内存并使用ngx_buf_t表示接收到的上游服务器响应后,在向下游客户端转发时可能会把这块内存存储到文件中,也可能直接向下游发送,此时Nginx绝不会重新复制一份内存用于新的目的,而是再次建立一个ngx_buf_t结构体指向原内存,这样多个ngx_buf_t结构体指向了同一块内存,它们之间的关系就通过shadow成员来引用。这种设计过于复杂,通常不建议使用*/ngx_buf_t *shadow;//临时内存标志位,为1时表示数据在内存中且这段内存可以修改unsigned temporary:1;//标志位,为1时表示数据在内存中且这段内存不可以被修改unsigned memory:1;//标志位,为1时表示这段内存使用mmap系统调用映射过来的,不可以被修改unsigned mmap:1;//标志位,为1时表示可回收unsigned recycled:1;//标志位,为1时表示这段缓冲区处理的是文件而不是内存unsigned in_file:1;//标志位,为1时表示需要执行flush操作unsigned flush:1;/*标志位,对于操作这块缓冲区时是否使用同步方式,需谨慎考虑,这可能会阻塞Nginx进程,Nginx中所有操作几乎都是异步的,这是它支持高并发的关键。有些框架代码在sync为1时可能会有阻塞的方式进行I/O操作,它的意义视使用它的Nginx模块而定*/unsigned sync:1;/*标志位,表示是否是最后一块缓冲区,因为ngx_buf_t可以由ngx_chain_t链表串联起来,因为,当last_buf为1时,表示当前是最后一块待处理的缓冲区*/unsigned last_buf:1;//标志位,表示是否是ngx_chain-t中的最后一块缓冲区unsigned last_in_chain:1;/* 标志位,表示是否是最后一个影子缓冲区,与shadow域配合使用。通常不建议使用它*/unsigned last_shadow:1;//标志位,表示当前缓冲区是否属于临时文件unsigned temp_file:1;};

18.ngx_chain_t是与ngx_buf_t配合使用的链表数据结构,来看一下定义:

typedef struct ngx_chain_s ngx_chain_t;struct ngx_chain_s {ngx_buf_t *buf; //指向当前的ngx_buf_t缓冲区ngx_chain-t *next; //用来指向下一个ngx_chain_t,如果这是最后一个ngx_chain_t,则需要把next置为NULL。};

在向用户发送HTTP包体时,就要传入ngx_chain_t链表对象,注意,如果是最后一个ngx_chain_t,那么必须将next置为NULL,否则永远不会发送成功,而且这个请求将一直不会结束(Nginx框架的要求).

19.ngx_module_t是一个Nginx模块的数据结构,如下所示:

typedef struct ngx_module_s ngx_module_t;struct ngx_module_s {/* 下面的ctx_index、index、spare0、spare1、spare2、spare3、version变量不需要在定义时赋值,可以用Nginx准备好的宏NGX_MODULE_V1来定义,它已经定义好了这7个值 #define NGX_MODULE_V1 0,0,0,0,0,0,1对于一类模块(由下面的type成员决定类别)而言,ctx_index表示当前模块在这类模块中的序号。这个成员常常是由管理这类模块的一个Nginx核心模块设置的,对于所有的HTTP模块而言,ctx_index是由核心模块ngx_http_module设置的。ctx_index非常重要,Nginx的模块化设计非常依赖于各个模块的顺序,它们既用于表达优先级,也用于表明每个模块的位置,借以帮助Nginx框架快速获得某个模块的数据*/ngx_uint_t ctx_index;/*index表示当前模块在ngx_modules数组中的序号,注意,ctx_index表示的是当前模块在一类模块中的序号,而index表示当前模块在所有模块中的序号,它同样关键。Nginx启动时会根据ngx_modules数组设置各模块的index值,例如:ngx_max_module = 0;for (i=0; ngx_modules[i]; i++) {ngx_modules[i]->index = ngx_max_module++;}*/ngx_uint_t index;//spare系列的保留变量,暂未使用ngx_uint_t spare0;ngx_uint_t spare1;ngx_uint_t spare2;ngx_uint_t spare3;//模块的版本,便于将来的扩展。目前只有一种,默认为1ngx_uint_t version;/*ctx用于指向一类模块的上下文结构体,为什么需要ctx呢?因为前面说过,Nginx模块有许多种类,不同类模块之间的功能差别很大。例如,事件类型的模块主要处理I/O事件相关的功能,HTTP类型的模块主要处理HTTP应用层的功能。这样,每个模块都有了自己的特性,而ctx将会指向特定类型模块的公共接口。例如,在HTTP模块中,ctx需要指向ngx_http_module_t结构体*/void *ctx;//commands将处理nginx.conf中的配置项ngx_command_t *commands;/*type表示该模型的类型,它与ctx指针是紧密相关的。在官方Nginx中,它的取值范围是以下5种:NGX_HTTP_MODULE、NGX_CORE_MODULE、NGX_CONF_MODULE、NGX_EVENT_MODULE、NGX_MAIL_MODULE。*/ngx_uint_t type;/*在Nginx的启动、停止过程中,以下7个函数指针表示有7个执行点会分别调用者7种方法。对于任一个方法而言,如果不需要Nginx在某个时刻执行它,那么简单地把它设为NULL空指针即可*//*虽然从字面上理解应当在master进程启动时回调init_master,但到目前为止,框架代码从来不会调用它,因此,可将init_master设为NULL */ngx_int_t (*init_master)(ngx_lot_t *log);/* init_module回调方法在初始化所有模块时被调用。在master/worker模式下,这个阶段将在启动worker子进程前完成*/ngx_int_t (*init_module)(ngx_cycle_t *cycle);/*init_process回调方法在正常服务前被调用。在master/worker模式下,多个worker子进程已经产生,在每个worker进程的初始化过程会调用所有模块的init_process函数 */ngx_int_t (*init_process)(ngx_cycle_t *cycle);/*由于Nginx暂不支持多线程模式,所以init_thread在框架代码中没有被调用过,设为NULL*/ngx_int_t (*init_thread)(ngx_cycle_t *cycle);//同上,exit_thread也不支持,设为NULL.void (*exit_process)(ngx_cycle_t *cycle);//exit_master回调方法将在master进程退出前被调用void (*exit_master)(ngx_cycle_t *cycle);/*保留字段,目前没有使用*/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;};

20.HTTP框架在读取、重载配置文件时定义了由ngx_http_module_t接口描述的8个阶段,HTTP框架在启动过程中会在每一个阶段中调用ngx_http_module_t中相应的方法。

typedef struct {//解析配置文件前调用ngx_int_t (*preconfiguration)(ngx_conf_t *cf);//完成配置文件的解析后调用ngx_int_t (*postconfiguration)(ngx_conf_t *cf);/*当需要创建数据结构用于存储main级别(直属于http{...}块的配置项)的全局配置项时,可以通过create_main_conf回调方法创建存储全局配置项的结构体 */void *(*create_main_conf)(ngx_conf_t *cf);//常用于初始化main级别配置项char *(*init_main_conf)(ngx_conf_t *cf, void *conf);/* 当需要创建数据结构用于存储srv级别(直属于虚拟主机server{...}块的配置项)的配置项时,可以通过实现create_srv_conf回调方法创建存储srv级别配置项的结构体 */void *(*create_srv_conf)(ngx_conf_t *cf);//merge_srv_conf回调方法主要用于合并main级别和srv级别下的同名配置项char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);/*当需要创建数据结构用于存储loc级别(直属于location{...}块的配置项)的配置项时,可以实现create_loc_conf回调方法*/void *(*create_loc_conf)(ngx_conf_t *cf);//merge_loc_conf回调方法主要用于合并srv级别和loc级别下的同名配置项char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);} ngx_http_module_t;

21.每一个ngx_command_t结构体定义了自己感兴趣的一个配置项:

typedef struct ngx_command_s ngx_command_t;struct ngx_command_s {//配置项名称,如"gzip"ngx_str_t name;/*配置项类型,type将指定配置项可以出现的位置。例如,出现在server{}或location{}中,以及它可以携带的参数个数 */ngx_uint_t type;//出现了name中指定的配置项后,将会调用set方法处理配置项的参数char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//在配置文件中的偏移量ngx_uint_t conf;/*通常用于使用预设的解析方法解析配置项,这是配置模块的一个优秀设计。*/ngx_uint_t offset;//配置项读取后的处理方法,必须是ngx_conf_post_t结构的指针void *post;};

ngx_null_command只是一个空的ngx_command_t,如下所示:

#define ngx_null_command { ngx_null_string, 0, NULL, 0, 0, NULL }

22.

typedef enum {//在接收到完整的HTTP头部后处理的HTTP阶段NGX_HTTP_POST_READ_PHASE = 0,/*在还没有查询到URI匹配的location前,这时rewrite重写URL也作为一个独立的HTTP阶段*/NGX_HTTP_SERVER_REWRITE_PHASE,/*根据URI寻找匹配的location,这个阶段通常由ngx_http_core_module模块实现,不建议其他HTTP模块重新定义这一阶段的行为*/NGX_HTTP_FIND_CONFIG_PHASE,/*在NGX_HTTP_FIND_CONFIG_PHASE阶段之后重写URL的意义与NGX_HTTP_SERVER_REWRITE_PHASE阶段显然是不同的,因为这两者会导致查找到不同的location块(location是与URI进行匹配的) */NGX_HTTP_REWRITE_PHASE,/* 这一阶段是用于在rewrite重写URL后重新跳到NGX_HTTP_FIND_CONFIG_PHASE阶段,找到与心得URI匹配的location。所以,这一阶段是无法由第三方HTTP模块处理的,而仅由ngx_http_core_module模块使用*/NGX_HTTP_POST_REWRITE_PHASE,//处理NGX_HTTP_ACCESS_PHASE阶段前,HTTP模块可以介入的处理阶段NGX_HTTP_PREACCESS_PHASE,/*这个阶段用于让HTTP模块判断是否允许这个请求访问Nginx服务器*/NGX_HTTP_ACCESS_PHASE,/*当NGX_HTTP_ACCESS_PHASE阶段中HTTP模块的handler处理方法返回不允许访问的错误码时(实际是NGX_HTTP_FORBIDDEN或者NGX_HTTP_UNAUTHORIZED),这个阶段将负责构造拒绝服务的用户响应。所以,这个阶段实际上用于给NGX_HTTP_ACCESS_PHASE阶段收尾*/NGX_HTTP_POST_ACCESS_PHASE,/*这个阶段完全是为了try_files配置项而设计的。当HTTP请求访问静态文件资源时,try_files配置项可以使这个请求顺序地访问多个静态文件资源,如果某一次访问失败,则继续访问try_files中指定的下一个静态资源。另外,这个功能完全是在NGX_HTTP_TRY_FILES_PHASE阶段中实现的 */NGX_HTTP_TRY_FILES_PHASE,//用于处理HTTP请求内容的阶段,这是大部分HTTP模块最喜欢介入的阶段NGX_HTTP_CONTENT_PHASE,/* 处理完请求后记录日志的阶段,例如,ngx_http_log_module模块就在这个阶段中加入了一个handler处理方法,使得每个HTTP请求处理完毕后会记录access_log日志 */NGX_HTTP_LOG_PHASE} ngx_http_phases;

23.处理方法的返回值,其中包括了HTTP框架已经在/src/http/ngx_http_request.h文件中定义好的宏,如下所示:

#define NGX_HTTP_OK 200#define NGX_HTTP_CREATED 201#define NGX_HTTP_ACCEPTED 202#define NGX_HTTP_NO_CONTENT 204#define NGX_HTTP_PARTIAL_CONTENT 206#define NGX_HTTP_SPECIAL_RESPONSE 300#define NGX_HTTP_MOVED_PERMANENTLY 301#define NGX_HTTP_MOVED_TEMPORARILY 302#define NGX_HTTP_SEE_OTHER 303#define NGX_HTTP_NOT_MODIFIED 304#define NGX_HTTP_TEMPORARY_REDIRECT 307#define NGX_HTTP_BAD_REQUEST 400#define NGX_HTTP_UNAUTHORIZED 401#define NGX_HTTP_FORBIDDEN 403#define NGX_HTTP_NOT_FOUND 404#define NGX_HTTP_NOT_ALLOWED 405#define NGX_HTTP_REQUEST_TIME_OUT 408#define NGX_HTTP_CONFLICT 409#define NGX_HTTP_LENGTH_REQUIRED 411#define NGX_HTTP_PRECONDITION_FAILED 412#define NGX_HTTP_REQUEST_ENTITY_TOO_LARGE 414#define NGX_HTTP_REQUEST_URI_TOO_LARGE 414#define NGX_HTTP_UNSUPPORTED_MEDIA_TYPE 415#define NGX_HTTP_RANGE_NOT_SATISFIABLE 416/* The special code to close connection without any response */#define NGX_HTTP_CLOSE 444#define NGX_HTTP_NGINX_CODES 494#define NGX_HTTP_REQUEST_HEADER_TOO_LARGE 494#define NGX_HTTPS_CERT_ERROR 495#define NGX_HTTPS_NO_CERT 496#define NGX_HTTP_TO_HTTPS 497#define NGX_HTTP_CLIENT_CLOSED_REQUEST 499#define NGX_HTTP_INTERNAL_SERVER_ERROR 500#define NGX_HTTP_NOT_IMPLEMENTED 501#define NGX_HTTP_BAD_GATEWAY 502#define NGX_HTTP_SERVICE_UNAVAILABLE 503#define NGX_HTTP_GATEWAY_TIME_OUT 504#define NGX_HTTP_INSUFFICIENT_STORAGE 507

24.请求的所有信息(如方法、URI、协议版本号和头部等)都可以在传入的ngx_http_request_t类型参数r中取得。

typedef struct ngx_http_request_s ngx_http_request_t;struct ngx_http_request_s {...ngx_uint_t method;ngx_uint_t http_version;ngx_str_t request_line;ngx_str_t uri;ngx_str_t args;ngx_str_t exten;ngx_str_t unparsed_uri; //表示没有进行URL解码的原始请求。ngx_str_t method_name;ngx_str_t http_protocol;u_char *uri_start;u_char *uri_end;u_char *uri_ext;u_char *args_start;u_char *request_start;u_char *request_end;u_char *method_end;u_char *schema_start;u_char *schema_end;...};

25.ngx_http_headers_in_t类型的headers_in则存储已经解析过的HTTP头部。

typedef struct {/* 所有解析过的HTTP头部都在headers链表中,可以使用遍历链表的方法来获取所有的HTTP头部。注意:这里headers链表的每一个元素都是ngx_table_elt_t成员*/ngx_list_t headers;/*以下每个ngx_table_elt_t成员都是RFC1616规范中定义的HTTP头部,它们实际都指向headers链表中的响应成员。注意,当它们为NULL空指针时,表示没有解析到响应的HTTP头部*/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 *user_agent;ngx_table_elt_t *referer;ngx_table_elt_t *content_length;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 *expect;#if (NGX_HTTP_GZIP)ngx_table_elt_t *accept_encoding;ngx_table_elt_t *via;#endifngx_table_elt_t *authorization;ngx_table_elt_t *keep_alive;#if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO)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/* user和passwd是只有ngx_http_auth_basic_module才会用到的成员,这里可以忽略*/ngx_str_t user;ngx_str_t passwd;/* cookies是以ngx_array_t数组存储的。*/ngx_array_t cookies;//server名称ngx_str_t server;//根据ngx_table_elt_t *content_length计算出的HTTP包体代销off_t content_length_n;time_t keep_alive_n;/* HTTP连接类型,它的取值范围是0、NGX_http_CONNECTION_CLOSE或者NGX_HTTP_CONNECTION_KEEP_ALIVE */unsigned connection_type:2;/*以下7个标志位是HTTP框架根据浏览器传来的"useragent"头部,它们可用来判断浏览器的类型,值为1时表示是相应的浏览器发来的请求,值为0时则相反 */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;

26.HTTP包体的长度有可能非常大,如果视图一次性调用并读取完所有的包体,那么多半会阻塞Nginx进程,HTTP框架提供了一种方法来异步地接受包体:

ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler);

ngx_http_read_client_request_body是一个异步方法,调用它只是说明要求Nginx开始接收请求的包体,并不表示是否已经接收完,当接收完所有的包体内容后,post_handler指向的回调方法会被调用。因此,即使在调用了ngx_http-read_client_request_body方法后它已经返回,也无法确定这时是否已经调用过post_handler指向的方法。换句话说,ngx_http_read_client_request_body返回时既有可能已经接收完请求中所有的包体(假设包体的长度很小),也有可能还没开始接受包体。

27.HTTP框架提供的发送HTTP头部的方法如下所示:

ngx_int_t ngx_http_send_header(ngx_http_request_t *r);

28.headers_out的结构类型ngx_http_headers_out_t:

typedef struct {//待发送的HTTP头部链表,与headers_in中的headers成员类似ngx_list_t headers;/*响应中的状态值,如200表示成功。*/ngx_str_t status;//响应的状态行,如"HTTP/1.1 201 CREATED"ngx_str_t status_line;/*以下成员(包括ngx_table_elt_t)都是RFC1616规范中定义的HTTP头部,设置后,ngx_http_header_filter_module过滤模块可以把它们加到待发送的网络包中*/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_str_t *override_charset;/* 可以调用ngx_http_set_content_type(r)方法帮助我们设置Content-Type头部,这个方法会根据URI中的文件扩展名饼对应着mime.type来设置Content-Type值*/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;ngx_array_t cache_control;/*在这里指定过content_length_n后,不用再次到ngx_table_elt_t *content_length中设置响应长度*/off_t content_length_n;time_t date_time;time_t last_modified_time;} ngx_http_headers_out_t;

ngx_http_send_header方法会首先调用所有的HTTP过滤模块共同处理headers_out中定义的HTTP响应头部,全部处理完毕后才会序列化为TCP字符流发送到客户端。

28.注意:在向用户发送响应包体时,必须牢记Nginx是全异步的服务器,也就是说,不可以在进程的栈里分配内存并将其作为包体发送。当ngx_http_output_filter方法返回时,可能由于TCP连接上的缓冲区还不可写,所以导致ngx_buf_t缓冲区指向的内存还没有发送,可这时方法返回已把控制权交给Nginx了,又会导致栈里的内存被释放,最后就会造成内存越界错误。因此,在发送响应包体时,尽量将ngx_buf_t中的pos指针指向从内存池里分配的内存。

29.经典的"Hello World"示例

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r){//必须是GET或者HEAD方法,否则返回405 Not Allowedif (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {return NGX_HTTP_NOT_ALLOWED;}//丢弃请求中的包体ngx_int_t rc = ngx_http_discard_request_body(r);if (rc != NGX_OK) {return rc;}/* 设置返回的Content-Type.注意,ngx_str_t有一个很方便的初始化宏ngx_string,它可以把ngx_str_t的data和len成员都设置好 */ngx_str_t type = ngx_string("text/plain");//返回的包体内容ngx_str_t type = ngx_string("Hello World!");//设置返回状态码r->headers_out.status = NGX_HTTP_OK;//响应包是有包体内容的,需要设置Content-Lenght的长度r->headers_out.content_length_n = response.len;//设置Content-Typer->headers_out.content_type = type;//发送HTTP头部rc = ngx_http_send_header(r);if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {return rc;}//构造ngx_buf_t结构体准备发送包体ngx_buf_t *b;b = ngx_create_temp_buf(r->pool, response.len);if (b == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}//将Hello World复制到ngx_buf_t指向的内存中ngx_memcpy(b->pos, response.data, response.len);//注意,一定要设置好last指针b->last = b->pos + response.len;//声明这是最后一块缓冲区b->last_buf = 1;//构造发送时的ngx_chain_t结构体ngx_chain_t out;//赋值ngx_buf_tout.buf = b;//设置next为NULLout.next = NULL;/*最后一步为发送包体,发送结束后HTTP框架会调用ngx_http_finalize_request方法结束请求 */return ngx_http_output_filter(r, &out);}

30.ngx_file_t的结构如下:

typedef struct ngx_file_s ngx_file_t;struct ngx_file_s {//文件句柄描述符ngx_fd_t fd;//文件名称ngx_str_t name;//文件大小等资源信息,实际就是Linux系统定义的stat结构ngx_file_info_t info;/*该偏移量告诉Nginx现在处理到文件何处了,一般不用设置它,Nginx框架会根据当前发送状态设置它*/off_t offset;//当前文件系统偏移量,一般不用设置它off_t sys_offset;//日志对象,相关的日志会输出到log指定的日志文件中ngx_log_t *log;//目前未使用unsigned valid_info:1;//与配置文件中的directio配置项相对应,在发送大文件时可以设为1unsigned directio:1;};

Nginx不只对stat数据结构做了封装,对于由操作系统中获取文件信息的stat方法,Nginx也使用一个宏进行了简单的封装,如下:

#define ngx_file_info(file, sb) stat((const char *) file, sb)

之后必须要设置Content-Length头部:

r->headers_out.content_length_n = b->file->info.st_size;

还需要设置ngx_buf_t缓冲区的file_pod和file_last:

b->file_pos = 0;b->file_last = b->file->info.st_size;

这里告诉Nginx从文件的file_pos偏移量开始发送文件,一直到达file_last偏移量处截止。

31. HTTP框架定义了3个级别的配置main、srv、loc,分别表示直接出现在http{}、server{}、location{}块内的配置项。当nginx.conf中出现http{}时,HTTP框架会接管配置文件中http{}块内的配置项解析。当遇到http{...}配置块时,HTTP框架会调用所有HTTP模块可能实现的create_main_conf、create_srv_conf、create_loc_conf方法生成存储main级别配置参数的结构体;在遇到server{...}块时会再次调用所有HTTP模块的create_srv_conf、create_loc_conf回调方法生成存储srv级别配置参数的结构体;在遇到location{...}时则会再次调用create_loc_conf回调方法生成存储loc级别配置参数的结构体。因此,实现这3个回调方法的意义是不同的。

32.设定配置项的解析方式

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

(1) ngx_str_t name

其中,name是配置项名称。

(2) ngx_uint_t type

其中,type决定这个配置项可以在哪些块(如http、server、location、if、upstream块等)中出现,以及可以携带的参数类型和个数等。

ngx_command_t结构体中type成员的取值及其意义

解释:每个进程总都有一个唯一的ngx_cycle_t核心结构体,它有一个成员conf_ctx维护着所有模块的配置结构体,其类型是void ****。conf_ctx意义为首先指向一个成员皆为指针的数组,其中每个成员指针又指向另外一个成员皆为指针的数组,第2个子数组中的成员指针才会指向各模块生成的配置结构体。这正是为了事件模块、http模块、mail模块而设计的,这有利于不同于NGX_CORE_MODULE类型的特定模块解析配置项。然而,NGX_CORE_MODULE类型的核心模块解析配置项时,配置项一定是全局的,不会从属于任何{}配置块的,它不需要上述这种双数组设计。解析标识为NGX_DIRECT_CONF类型的配置项时,会把void ****类型的conf_ctx强制转换为void **,也就是说,此时,在conf_ctx指向的指针数组中,每个成员指针不再指向其他数组,直接指向核心模块生成的配置结构体。因此,NGX_DIRECT_CONF仅由NGX_CORE_MODULE类型的核心模块使用,而且配置项只应该出现在全局配置中。

c. char*(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

预设的14个配置项解析方法

d.ngx_uint_t conf

conf用于指示配置项所处内存的相对偏移位置,仅在type中没有设置NGX_DIRECT_CONF和NGX_MAIN_CONF时才会生效。对于HTTP模块,conf是必须要设置的,它的取值范围如下:

e.ngx_uint_t offset

offset表示当前配置项在整个存储配置项啊的结构体中的偏移位置(以字节(Byte)为单位)。

f.void *post

一般置为NULL。

33.解析HTTP配置的流程

(1) 主循环是指Nginx进程的主循环,主循环只有调用配置文件解析器才能解析nginx.conf文件(这里的"主循环"是指解析全部配置文件的循环代码)。

(2) 当发现配置文件中含有http{}关键字时,HTTP框架开始启动,这一过程见ngx_http_block方法。

(3) HTTP框架会初始化所有HTTP模块的序列号,并创建3个数组用于存储所有HTTP模块的create_main_conf、create_srv_conf、create_loc_conf方法返回的指针地址,并把这3个数组的地址保存到ngx_http_conf_ctx_t结构中。

(4) 调用每个HTTP模块(当然也包括例子中的mytest模块)的create_main_conf、create_srv_conf、create_loc_conf(如果实现的话)方法。

(5) 把各HTTP模块上述3个方法的地址依次保存到ngx_http_conf_ctx_t结构体的3个数组中。

(6) 调用每个HTTP模块的preconfiguration方法(如果实现的话).

(7) 注意,如果preconfiguration返回失败,那么Nginx进程将会停止。

(8) HTTP框架开始循环解析nginx.conf文件中http{...}里面的所有配置项,注意,这个过程到第19步才会返回。

(9) 配置文件解析器在检测到1个配置项后,会遍历所有的HTTP模块,检查它们的ngx_command_t数组中的name项是否与配置项名相同。

(10) 如果找到有1个HTTP模块对这个配置项感兴趣,就调用ngx_command_t结构中的set方法来处理。

(11) set方法返回是否处理成功。如果处理失败,那么Nginx进程会停止。

(12) 配置文件解析器继续监测配置项。如果发现server{...}配置项,就会调用ngx_http_core_module模块来处理。因为ngx_http_core_module模块明确表示希望处理server{}块下的配置项。注意,这次调用到第18步才会返回。

(13) ngx_http_core_module模块在解析server{...}之前,也会如第3步一样建立ngx_http_conf_ctx_t结构,并建立数组保存所有HTTP模块返回的指针地址。然后,它会调用每个HTTP模块的create_srv_conf、create_loc_conf方法(如果实现的话).

(14) 将上一步各HTTP模块返回的指针地址保存到ngx_http_conf_ctx_t对应的数组中。

(15) 开始调用配置文件解析器来处理server{...}里面的配置项,注意,这个过程在第17步返回。

(16) 继续重复第9步的过程,遍历nginx.conf中当前server{...}内的所有配置项。

(17)配置文件解析器继续解析配置项,发现当前server块已经遍历到尾部,说明server块内的配置项处理完毕,返回ngx_http_core_module模块。

(18) http core模块也处理完server配置项了,返回至配置文件解析器继续解析后面的配置项。

(19) 配置文件解析器继续解析配置项,这时发现处理到了http{...}的尾部,返回给HTTP框架继续处理。

(20) 在第3步和第13步,以及我们没有列出来的某些步骤中(如发现其他server块或者location块),都创建了ngx_http_conf_ctx_t结构体,这时将开始调用merge_srv_conf、merge_loc_conf等方法合并这些不同块(http、server、location)中每个HTTP模块分配的数据结构。

(21) HTTP框架处理完毕http配置项(也就是ngx_command_t结构中的set回调方法处理完毕),返回给配置文件解析器继续处理其他http{...}外的配置项。

(22) 配置文件解析器处理完所有配置项后会告诉Nginx主循环配置项解析完毕,这时Nginx才会启动Web服务器。

34.http块与server块下的ngx_http_conf_ctx_t所指向的内存间的关系

35.合并配置项过程的活动图,主要包含四大部分内容:

(1) 如果HTTP模块实现了merge_srv_conf方法,就将http{...}块下create_srv_conf生成的结构体与遍历每一个server{...}配置块下的结构体做merge_srv_conf操作;

(2) 如果HTTP模块实现了merge_loc_conf方法,就将http{...}块下create_loc_conf生成的结构体与嵌套的每一个server{...}配置块下生成的结构体做merge_loc_conf操作;

(3) 如果HTTP模块实现了merge_loc_conf方法,就将server{...}块下create_loc_conf生成的结构体与嵌套的每一个location{...}配置块下create_loc_conf生成的数据结构做merge_loc_conf操作;

(4) 如果HTTP模块实现了merge_loc_conf方法,就将location{...}块下create_loc_conf生成的结构体与继续嵌套的每一个location{...}配置块下create_loc_conf生成的数据结构做merge_loc_conf操作。

上图中包括4重循环,第1层(最外层)遍历所有的HTTP模块,第2层遍历所有的server{...}配置块,第3层是遍历某个server{}块中嵌套的所有location{...}块,第4层遍历某个location{}块中继续嵌套的所有location块(实际上,它会一直递归下去以解析可能被层层嵌套的location块).

36.请求的上下文

在Nginx中,上下文有很多种含义。HTTP框架定义的这个上下文是针对于HTTP请求的,而且一个HTTP请求对应于每一个HTTP模块都可以有一个独立的上下文结构体(并不是一个请求的上下文由所有HTTP模块共用)。

37.ngx_http_get_module_ctx和ngx_http_set_ctx这两个宏可以完成HTTP上下文的设置和使用。

#define ngx_http_get_module_ctx(r, module) (r)->ctx[module.ctx_index]#define ngx_http_set_ctx(r, c, module) r->ctx[module.ctx_index] = c;

当请求第1次进入mytest模块处理时,创建ngx_http_mytest_ctx_t结构体,并设置到这个请求的上下文中。

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r){//首先调用ngx_http_get_module_ctx宏来获取上下文结构体ngx_http_mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);//如果之前没有设置过上下文,那么应当返回NULL。if (myctx == NULL) {/*必须在当前请求的内存池r->pool中分配上下文结构体,这样请求结束时结构体占用的内存才会释放*/myctx = ngx_palloc(r->pool, sizeof(ngx_http_mytest_ctx_t));if (myctx == NULL) {return NGX_ERROR;}//将刚分配的结构体设置到当前请求的上下文中ngx_http_set_ctx(r, myctx, ngx_http_mytest_module);}//之后可以任意使用myctx这个上下文结构体...}

38.模块在处理任何一个请求时都有ngx_http_request_t结构的对象r,而请求r中又有一个ngx_http_upstream_t类型的成员upstream.

typedef struct ngx_http_request_s ngx_http_request_t;struct ngx_http_request_s {...ngx_http_upstream_t *upstream;...};

39.启动upstream的流程图

40.upstream执行的一般流程

41.ngx_http_upstream_t结构体

typedef struct ngx_http_upstream_s ngx_http_upstream_t;struct ngx_http_upstream_s {.../* request_bufs决定发送什么样的请求给上游服务器,在实现create_request方法是需要设置它 */ngx_chain_t *request_bufs;//upstream访问时的所有限制性参数ngx_http_upstream_conf_t *conf;//通过resolved可以直接指定上游服务器地址ngx_http_upstream_resolved_t *resolved;/* buffer成员存储接收自上游服务器发来的响应内容,由于它会被复用,所以具有下例多种意义:* a) 在使用process_header方法解析上游响应的包头时,buffer中将会保存完整的响应包头;* b) 当下面的buffering成员为1,而且此时upstream是向下游转发上游的包体时,buffer没有意义;* c) 当buffering标志位位0时,buffer缓冲区会被用于反复地接收上游的包体,进而向下游转发;* d) 当upstream并不用于转发上游包体时,buffer会被用于反复接收上游的包体,HTTP模块实现的input_filter方法需要关注它*/ngx_buf_t buffer;//构造发往上游服务器的请求内容ngx_int_t (*create_request)(ngx_http_request_t *r);/* 销毁upstream请求时调用 */void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc);//5个可选的回调方法ngx_int_t (*input_filter_init)(void *data);ngx_int_t (*input_filter)(void *data, ssize_t bytes);ngx_int_t (*reinit_request)(ngx_http_request_t *r);void (*abort_request)(ngx_http_request_t *r);ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix);//是否基于SSL协议访问上游服务器unsigned ssl:1;/* 在向客户端转发上游服务器的包体时才有用。当buffering为1时,表示使用多个缓冲区以及磁盘文件来转发上有的响应包体。当Nginx与上游间的网速远大于Nginx与下游客户端见的网速时,让Nginx开辟更多的内存甚至使用磁盘文件来缓存上游的响应包体,这是有意义的,它可以减轻上游服务器的并发压力。当buffering为0时,表示只使用上面的这一个buffer缓冲区来向下游转发响应包体*/unsigned buffering:1;...};

42.设置upstream的限制性参数

ngx_http_upstream_t中的conf成员,它用于设置upstream模块处理请求时的参数,包括连接、发送、接收的超时时间等。

typedef struct {...//连接上游服务器的超时时间,单位为毫秒ngx_msec_t connect_timeout;//发送TCP包到上游服务器的超时时间,单位为毫秒ngx_msec_t send_timeout;//接收TCP包到上游服务器的超时时间,单位为毫秒ngx_msec_t read_timeout;...} ngx_http_upstream_conf_t;

43.ngx_http_upstream_t结构中的resolved成员可以直接设置上游服务器的地址,首先介绍一下resolved的类型:

typedef struct {...//地址个数ngx_uint_t naddrs;//上游服务器的地址struct sockaddr *sockaddr;socklen_t socklen;...} ngx_http_upstream_resolved_t;

44.直接执行ngx_http_upstream_init方法即可启动upstream机制。

45.create_request回调方法

如上图,步骤分别如下:

1) 在Nginx主循环(之类的主循环是指ngx_worker_process_cycle方法)中,会定期地调用事件模块,以检查是否有网络事件发生。

2)事件模块在接收到HTTP请求后会调用HTTP框架来处理。假设接收、解析完HTTP头部后发现应该由mytest模块处理,这时会调用mytest模块的ngx_http_mytest_handler来处理。

3)设置回调函数和第三方地址;

4)调用ngx_http_upstream_init方法启动upstream;

5)upstream模块会去检查文件缓存,如果缓存中已经有合适的响应包,则会直接返回缓存(当然必须是在使用反向代理文件缓存的前提下)。

6)回调mytest模块已经实现的create_request回调方法;

7)mytest模块通过设置r->upstream->request_bufs已经决定好发送什么样的请求到上有服务器。

8)upstream模块会检查已经介绍过的resolved成员,如果有resolved成员的话,就根据它设置好上游服务器的地址r->upstream->peer成员。

9)用无阻塞的TCP套接字建立连接;

10)无论连接是否建立成功,负责建立连接的connect方法都会立刻返回。

11)ngx_http_upstream_init返回;

12)mytest模块的ngx_http_mytest_handler方法返回NGX_DONE。

13)当事件模块处理完这批网络事件后,将控制权交还给Nginx主循环.

46.reinit_request可能会被多次回调。它被调用的原因只有一个,就是在第一次试图向上游服务器建立连接时,如果连接由于各种异常原因失败,那么会根据upstream中conf参数的策略要求再次重连上游服务器,而这时就会调用reinit_request方法了。

上图中的流程的步骤描述如下:

1) Nginx主循环中会定期地调用事件模块,检查是否有网络事件发生。

2) 事件模块在确定与上游服务器的TCP连接建立成功后,会回调upstream模块的相关方法处理。

3) upstream模块这时会把r->upstream->request_sent标志位置为1,表示连接已经建立成功了,现在开始向上游服务器发送请求内容。

4) 发送请求到上游服务器。

5) 发送方法当然是无阻塞的(使用了无阻塞的套接字),会立刻返回。

6) upstream模块处理第2步中的TCP连接建立成功事件。

7) 事件模块处理完本轮网络事件后,将控制权交还给Nginx主循环。

8) Nginx主循环重复第1步,调用事件模块检查网络事件。

9) 这时,如果发现与上游服务器建立的TCP连接已经异常断开,那么事件模块会通知upstream模块处理它。

10) 在符合重试次数的前提下,upstream模块会毫不犹豫地再次用无阻塞的套接字试图建立连接。

11) 无论连接是否建立成功都立刻返回。

12) 这时检查r->upstream->request_sent标志位,会发现它已经被置为1了。

13) 如果mytest模块没有实现reinit_request方法,那么是不会调用它的。而如果reinit_request不为NULL空指针,就会回调它。

14) mytest模块在reinit_request中处理完自己的事情。

15) 处理完第9步中的TCP连接断开事件,将控制权交还给事件模块。

16) 事件模块处理完本轮网络事件后,交还控制权给Nginx主循环。

47.finalize_request回调方法

当调用ngx_http_upstream_init启动upstream机制后,在各种原因(无论成功还是失败)导致该请求被销毁前都会调用finalize_request方法。

在finalize_request方法中可以不做任何事情,但必须实现finalize_request方法,否则Nginx会出现空指针调用的严重错误。

48.process_header回调方法

process_header是用于解析上游服务器返回的基于TCP的响应头部的,因此,process_header可能会被多次调用,它的调用次数与process_header的返回值有关。如果process_header返回NGX_AGAIN,这意味着还没有接收到完整的响应头部,如果再次接收到上游服务器发来的TCP流,还会把它当做头部,仍然调用process_header处理。如果process_header返回NGX_OK(或者其他非NGX_AGAIN的值),那么在这次连接的后续处理中将不会再次调用process_header。

上图中的步骤解释:

1) Nginx主循环中会定期地调用事件模块,检查是否有网络事件发生。

2) 事件模块接收到上游服务器发来的响应时,会回调upstream模块处理。

3) upstream模块这时可以从套接字缓冲区中读取到来自上游的TCP流。

4) 读取的响应会存放到r->upstream->buffer指向的内存中。注意:在未解析完响应头部前,若多次接收到字符流,所有接收自上游的响应头回完整地存放到r->upstream->buffer缓冲区中。因此,在解析上游响应包头时,如果buffer缓冲区全满却还没有解析到完整的响应头部(也就是说,process_header一直在返回NGX_AGAIN),那么请求就会出错。

5) 调用mytest模块实现的process_header方法。

6) process_header方法实际上就是在解析r->upstream->buffer缓冲区,试图从中取到完整的响应头部(当然,如果上游服务器与Nginx通过HTTP通信,就是接收到完整的HTTP头部)。

7) 如果process_header返回NGX_AGAIN,那么表示还没有解析到完整的响应头部,下次还会调用process_header处理接收到的上游响应。

8) 调用无阻塞的读取套接字接口。

9) 这时有可能返回套接字缓冲区已经为空。

10) 当第2步中的读取上游响应时间处理完毕后,控制权交还给事件模块。

11) 事件模块处理完本轮网络事件后,交还控制权给Nginx主循环。

49.举例说明upstream机制。实现的功能很简单:"即以访问mytest模块的URL参数作为搜索引擎的关键字,用upstream方式访问google,查询URL里的参数,然后把google的结果返回给用户。"

每一个HTTP请求都会有独立的ngx_http_upstream_conf_t结构体,在mytest模块的例子中,所有的请求都将共享同一个ngx_http_upstream_conf_t结构体,因此,这里把它放到ngx_http_mytest_conf_t配置结构体中,如下所示:

typedef struct {ngx_http_upstream_conf_t upstream;} ngx_http_mytest_conf_t;

(1) 在启动upstream前,先将ngx_http_mytest_conf_t下的upstream成员赋给r->upstream->conf成员。

static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf){ngx_http_mytest_conf_t *mycf;mycf = (ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool, sizeof(ngx_http_mytest_conf_t));if (mycf == NULL) {return NULL;}/* 以下简单的硬编码ngx_http_upstream_conf_t结构中的各成员,如超时事件,都设为1分钟,这也是HTTP反向代理模块的默认值*/mycf->upstream.connect_timeout = 60000;mycf->upstream.send_timeout = 60000;mycf->upstream.read_timeout = 60000;mycf->upstream.store_access = 0600;/* 实际上,buffering已经决定了将以固定大小的内存作为缓冲区来转发上游的响应包体,这块固定缓冲区的大小就是buffer_size。如果buffering为1,就会使用更多的内存缓存来不及发往下游的响应。例如,最多使用bufs.num个缓冲区且每个缓冲区大小为bufs.size。另外,还会使用临时文件,临时文件的最大长度为max_temp_file_size */mycf->upstream.buffering = 0;mycf->upstream.bufs.num = 8;mycf->upstream.bufs.size = ngx_pagesize;mycf->upstream.buffer_size = ngx_pagesize;mycf->upstream.busy_buffers_size = 2*ngx_pagesize;mycf->upstream.temp_file_write_size = 2 * ngx_pagesize;mycf->upstream.max_temp_file_size = 1024 * 1024 * 1024;/* upstream模块要求hide_headers成员必须要初始化(upstream在解析完上游服务器返回的包头时,会调用ngx_http_upstream_process_headers方法按照hide_headers成员将本应转发给下游的一些HTTP头部隐藏),这里将它赋为NGX_CONF_UNSET_PTR,这是为了在merge合并配置项方法中使用upstream模块提供的ngx_http_upstream_hide_headers_hash方法初始化hide_headers成员 */mycf->upstream.hide_headers = NGX_CONF_UNSET_PTR;mycf->upstream.pass_headers = NGX_CONF_UNSET_PTR:retrurn mycf;}static char *ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child){ngx_http_mytest_conf_t *prev = (ngx_http_mytest_conf_t *)parent;ngx_http_mytest_conf_t *conf = (ngx_http_mytest_conf_t *)child;ngx_hash_init_t hash;hash.max_size = 100;hash.bucket_size = 1024;hash.name = "proxy_headers_hash";if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream, &prev->upstream, ngx_http_proxy_hide_headers, &hash) != NGX_OK) {return NGX_CONF_ERROR;}return NGX_CONF_OK;}

本例必须要使用上下文才能正确地解析upstream上游服务器的响应包,因为upstream模块每次接收到一段TCP流时都会回调mytest模块实现的process_header方法解析,这样就需要有一个上下文保存解析状态。

(2) 在create_request方法中构造请求

下面方法用于创建发送给上游服务器的HTTP请求,upstream模块将会回调它,实现如下:

static ngx_int_tmytest_upstream_create_request(ngx_http_request_t *r){/* 在发往google上游服务器的请求很简单,就是模仿正常的搜索请求,以/search?q=...的URL来发起搜索请求。backendQueryLine中的%V等转化格式的用法*/static ngx_str_t backendQueryLine = ngx_string("GET /search?q=%V HTTP/1.1\r\nHost: \r\nConnection: close\r\n\r\n");ngx_int_t queryLineLen = backendQueryLine.len + r->args.len - 2;/* 必须在内存池中申请内存,这有以下两点好处:一个好处是,在网络情况不佳的情况下,向上游服务器发送请求时,可能需要epoll多次调度send才能发送完成,这时必须保证这段内存不会被释放;另一个好处是,在请求结束时,这段内存会被自动释放,减低内存泄露的可能性*/ngx_buf_t *b = ngx_create_temp_buf(r->pool, queryLineLen);if (b == NULL) {return NGX_ERROR;}//last要指向请求的末尾b->last = b->pos + queryLineLen;//作用相当于snprintfngx_snprintf(b->pos, queryLineLen, (char *)backendQueryLine.data, &r->args);/* r->upstream->request_bufs是一个ngx_chain_t结构,它包含着要发送给上游服务器的请求 */r->upstream->request_bufs = ngx_alloc_chain_link(r->pool);if (r->upstream->request_bufs == NULL) return NGX_ERROR;//request_bufs在这里只包含1个ngx_buf_t缓冲区r->upstream->request_bufs->buf = b;r->upstream->request_bufs->next = NULL;r->upstream->request_sent = 0;r->upstream->header_sent = 0;// header_hash不可以为0r->header_hash = 1;return NGX_OK;}

(3) 在process_header方法中解析包头

process_header负责解析上游服务器发来的基于TCP的包头,在本例中,就是解析HTTP响应行和HTTP头部,因此,这里使用mytest_process_status_line方法解析HTTP响应行,使用mytest_upstream_process_header方法解析http响应头部。

static ngx_int_tmytest_process_status_line(ngx_http_request_t *r){size_t len;ngx_int_t rc;ngx_http_upstream_t *u;//上下文中才会保存多次解析HTTP响应行的状态,下面首先取出请求的上下文ngx_http_mytest_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);if (ctx == NULL) {return NGX_ERROR;}u = r->upstream;/* HTTP框架提供的ngx_http_parse_status_line方法可以解析HTTP响应行,它的输入就是收到的字符流和上下文中的ngx_http_status_t结构 */rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status);//返回NGX_AGAIN时,表示还没有解析出完整的HTTP响应行,需要接受更多的字符流再进行解析if (rc == NGX_AGAIN) {return rc;}//返回NGX_ERROR时,表示没有接收到合法的HTTP响应行if (rc == NGX_ERROR) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"upstream sent no valid HTTP/1.0 header");r->http_version = NGX_HTTP_VERSION_9;u->state->status = NGX_HTTP_OK;return NGX_OK;}/* 以下表示在解析到完整的HTTP响应行时,会做一些简单的赋值操作,将解析出的信息设置到r->upstream->headers_in结构体中。当upstream解析完所有的包头时,会把headers_in中的成员设置到将要向下游发送的r->headers_out结构体中,也就是说,现在用户向headers_in中设置的信息,最终都会发往下游客户端。为什么不直接设置r->headers_out而要多此一举呢?因为upstream希望能够按照ngx_http_upstream_conf_t配置结构体中的hide_headers等成员对发往下游的响应头部做统一处理 */if (u->state) {u->state->status = ctx->status.code;}u->headers_in.status_n = ctx->status.code;len = ctx->status.end - ctx->status.start;u->headers_in.status_line.len = len;u->headers_in.status_line.data = ngx_pnalloc(r->pool, len);if (u->headers_in.status_line.data == NULL) {return NGX_ERROR;}ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len);/* 下一步将开始解析HTTP头部。设置process_header回调方法为mytest_upstream_process_header,之后再收到的新字符流将由mytest_upstream_process_header解析 */u->process_header = mytest_upstream_process_header;/* 如果本次收到的字符流除了HTTP响应行外,还有多余的字符,那么将由mytest_upstream_process_header方式解析 */return mytest_upstream_process_header(r);}

mytest_upstream_process_header方法可以解析HTTP响应头部,而这里只是简单地把上游服务器发送的HTTP头部添加到了请求r->upstream->headers_in.headers链表中。如果有需要特殊处理的HTTP头部,那么应该在mytest_upstream_process_header方法中进行。

static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r){ngx_int_t rc;ngx_table_elt_t *h;ngx_http_upstream_header_t *hh;ngx_http_upstream_main_conf_t *umcf;/* 这里将upstream模块配置项ngx_http_upstream_main_conf_t取出来,目的只有一个,就是对将要转发给下游客户端的HTTP响应头部进行统一处理。该结构体中存储了需要进行统一处理的HTTP头部名称和回调方法 */umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);//循环地解析所有的HTTP头部for (;;) {/* HTTP框架提供了基础性的ngx_http_parse_haeder_line方法,它用于解析HTTP头部 */rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);//返回NGX_OK时,表示解析出一行HTTP头部if (rc == NGX_OK) {// 向headers_in.headers这个ngx_list_t链表中添加HTTP头部h = ngx_list_push(&r->upstream->headers_in.headers);if (h == NULL) {return NGX_ERROR;}//下面开始构造刚刚添加到headers链表中的HTTP头部h->hash = r->header_hash;h->key.len = r->header_name_end - r->header_name_start;h->value.len = r->header_end - r->header_start;//必须在内存池中分配存放HTTP头部的内存空间h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len);if (h->key.data == NULL) {return NGX_ERROR;}h->value.data = h->key.data + h->key.len + 1;h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1;ngx_memcpy(h->key.data, r->header_name_start, h->key.len);h->key.data[h->key.len] = '\0';ngx_memcpy(h->value.data, r->header_start, h->value.len);h->value.data[h->value.len] = '\0';if (h->key.len == r->lowcase_index) {ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len);} else {ngx_strlow(h->lowcase_key, h->key.data, h->key.len);}//upstream模块会对一些HTTP头部做特殊处理hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len);if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {return NGX_ERROR;}continue;}/* 返回NGX_HTTP_PARSE_HEADER_DONE时,表示响应中所有的HTTP头部解析完毕,接下来在接收到的都将是HTTP包体 */if (rc == NGX_HTTP_PARSE_HEADER_DONE) {/* 如果之前解析HTTP头部时没有发现server和date头部,那么下面会根据HTTP协议规范添加这两个头部 */if (r->upstream->headers_in.server == NULL) {h = ngx_list_push(&r->upstream->headers_in.headers);if (h == NULL) {return NGX_ERROR;}h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');ngx_str_set(&h->key, "Date");ngx_str_null(&h->value);h->lowcase_key = (u_char *) "date";}return NGX_OK;}/* 如果返回NGX_AGAIN, 则表示状态机还没有解析到完整的HTTP头部,此时要求upstream模块继续接收新的字符流,然后交由process_header回调方法解析 */if (rc == NGX_AGAIN) {return NGX_AGAIN;}//其他返回值都是非法的ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header");return NGX_HTTP_UPSTREAM_INVALID_HEADER;}}

当mytest_upstream_process_header返回NGX_OK后,upstream模块开始把上游的包体(如果有的话)直接转发到下游客户端。

(4) 在finalize_request方法中释放资源

当请求结束时,将会回调finalize_request方法,如果我们希望此时释放资源,如打开的句柄等,那么可以把这样的代码添加到finalize_request方法中。本例中定义了mytest_upstream_finalize_request方法,由于我们没有任何需要释放的资源,所以该方法没有完成任何实际工作,只是因为upstream模块要求必须实现finalize_request回调方法。

(5) 在ngx_http_mytest_handler方法中启动upstream

在开始介入处理客户端请求的ngx_http_mytest_handler方法中启动upstream机制,而何时会结束,则视Nginx与上游的google服务器间的通信而定。

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_r *r){//首先建立HTTP上下文结构体ngx_http_mytest_ctx_tngx_http_mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);if (myctx == NULL) {myctx = ngx_palloc(r->pool, sizeof(ngx_http_mytest_ctx_t));if (myctx == NULL) {return NGX_ERROR;}//得到配置结构体ngx_http_mytest_conf_tngx_http_mytest_conf_t *mycf = (ngx_http_mytest_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_mytest_module);ngx_http_upstream_t *u = r->upstream;//这里用配置文件中的结构体来赋给r->upstream->conf成员u->conf = &mycf->upstream;//决定转发包体时使用的缓冲区u->buffering = mycf->upstream.buffering;//以下代码开始初始化resolved结构体,用来保存上游服务器的地址u->resolved = (ngx_http_upstream_resolved_resolved_t *)ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));if (u->resolved == NULL) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pcalloc resolved error. %s.", strerror(errno));return NGX_ERROR;}//这里的上游服务器就是static struct sockaddr_in backendSockAddr;struct hostent *pHost = gethostbyname((char *) "");if (pHost == NULL) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gethostbyname fail. %s", strerror(errno));return NGX_ERROR;}//访问上游服务器的80端口backendSockAddr.sin_family = AF_INET;backendSockAddr.sin_port = htons((in_port_t) 80);char *pDmsIP = inet_ntoa(*(struct in_addr *) (pHost->h_addr_list[0]));backendSockAddr.sin_addr.s_addr = inet_addr(pDmsIP);myctx->backendServer.data = (u_char *)pDmsIP;myctx->backendServer.len = strlen(pDmsIP);//将地址设置到resolved成员中u->resolved->sockaddr = (struct sockaddr *)&backendSockAddr;u->resolved->socklen = sizeof(struct sockaddr_in);u->resolved->naddrs = 1;// 设置3个必须实现的回调方法u->create_request = mytest_upstream_create_request;u->process_header = mytest_process_status_line;u->finalize_request = mytest_upstream_finalize_request;//这里必须将count成员加1r->main->count++;//启动upstreamngx_http_upstream_init(r);//必须返回NGX_DONEreturn NGX_DONE;}

到此为止,高性能地访问第三方服务的upstream例子就介绍完了。在本例中,可以完全异步地访问第三方服务,并发访问数也只会受制于物理内存的大小,完全可以轻松达到几十万的并发TCP连接。

50.使用subrequest的方式只需要完成以下4步操作即可:

1) 在nginx.conf文件中配置好子请求的处理方式;

2) 启动subrequest子请求;

3) 实现子请求执行结束时的回调方法;

4) 实现父请求被激活时的回调方法。

51.ngx_http_subrequest的定义:

ngx_int_tngx_http_subrequest(ngx_http_request_t *r,ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,ngx_http_post_subrequest_t *ps, ngx_uint_t flags);

1) ngx_http_request_t *r

ngx_http_request_t *r 是当前的请求,也就是父请求。

2) ngx_str_t *uri

ngx_str_t *uri是子请求的URI,它对究竟选用nginx.conf配置文件中的哪个模块来处理子请求起决定性作用。

3) ngx_str_t *args

ngx_str_t *args是子请求的URI参数,如果没有参数,可以传送NULL空指针。

4) ngx_http_request_t **psr

psr是输出参数而不是输入参数,它将把ngx_http_subrequest生成的子请求传出来。

5) ngx_http_post_subrequest_t *ps

这里传入创建的ngx_http_post_subrequest_t结构体地址,它指出子请求结束时必须回调的处理方法。

6) ngx_uint_t flags

flag的取值范围包括: (1) 0.在没有特殊需求的情况下都应该填写它; (2) NGX_HTTP_SUBREQUEST_IN_MEMORY。这个宏会将子请求的subrequest_in_memory标志位置为1,这意味着如果子请求使用upstream访问上游服务器,那么上游服务器的响应都将会在内存中处理;(3) NGX_HTTP_SUBREQUEST_WAITED。这个宏会将子请求的waited标志位置为1,当子请求提前结束时,有个done标志位置为1,但目前HTTP框架并没有针对这两个标志位做任何实质性处理。注意,flag是按比特位操作的,这样可以同时包含上述3个值。

7) 返回值

返回NGX_OK表示成功建立子请求;返回NGX_ERROR表示建立子请求失败。

52.如何启动subrequest

处理父请求的过程中会创建子请求,在父请求的处理方法返回NGX_DONE后,HTTP框架会开始执行子请求

上图中的步骤如下:

1) Nginx主循环中会定期地调用事件模块,检查是否有网络事件发生;

2) 事件模块发现这个请求的回调方法属于HTTP框架,交由HTTP框架来处理请求。

3) 根据解析完的URI来决定使用哪个location下的模块来处理这个请求。

4) 调用mytest模块的ngx_http_mytest_handler方法处理这个请求。

5) 设置subrequest子请求的URI及回调方法。

6) 调用ngx_http_subrequest方法创建子请求。

7) 创建的子请求会添加到原始请求的posted_requests链表中,这样保证第10步时会在父请求返回NGX_DONE的情况下开始执行子请求。

8) ngx_http_subrequest方法执行完毕,子请求创建成功。

9) ngx_http_mytest_handler方法执行完毕,返回NGX_DONE,这样父请求不会被销毁,将等待以后的再次激活。

10) HTTP框架执行完当前请求(父请求)后,检查posted_requests链表中是否还有子请求,如果存在子请求,则调用子请求的write_event_handler方法。

11) 根据子请求的URI(第5步中建立),检查nginx.conf文件中所有的location配置,确定应由哪个模块来执行子请求。在本章的例子中,子请求是交由反向代理模块执行的。

12) 调用反向代理模块的入口方法ngx_http_proxy_handler来处理子请求。

13) 由于反向代理模块使用了upstream机制,所以它也要通过许多次的异步调用才能完整地处理完子请求,这时它的入口方法会返回NGX_DONE.

14) 再次检查是否还有子请求,这时会发现已经没有子请求需要执行了。当然,子请求可以继续建立新的子请求,只是这里的反向代理模块不会这样做。

15) 当第2步中的网络读取事件处理完毕后,交还控制权给事件模块。

16) 当本轮网络事件处理完毕后,交还控制权给Nginx主循环。

53.如何转发多个子请求的响应包体

每个请求的ngx_http_request_t结构体中都有一个postponed成员:

struct ngx_http_request_s {...ngx_http_postponed_request_t *postponed;...}它实际上是一个链表:typedef struct ngx_http_postponed_request_s ngx_http_postponed_request_t;struct ngx_http_postponed_request_s {ngx_http_request_t *request;ngx_chain_t *out;ngx_http_postponed_request_t *next;};

多个ngx_http_postponed_request_t之间使用next指针连接成一个单向链表。ngx_http_postponed_request_t中的out成员是ngx_chain_t结构,它指向的是来自上游的、将要转发给下游的响应包体。

每当使用ngx_http_output_filter方法(反向代理模块也使用该方法转发响应)向下游的客户端发送响应包体时,都会调用到ngx_http_postpone_filter_module过滤模块处理这段要发送的包体。

//这里的参数in就是将要发送给客户端的一段包体static ngx_int_tngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in){ngx_connection_t *c;ngx_http_postponed_request_t *pr;//c是Nginx与下游客户端间的连接,c->data保存的是原始请求c = r->connection;//如果当前请求r是一个子请求(因为c->data指向原始请求)if (r != c->data) {/* 如果待发送的in包体不为空,则把in加到postponed链表中属于当前请求的ngx_http_postponed_request_t结构体的out链表中,同时返回NGX_OK,这意味着本次不会把in包体发送客户端*/if (in) {ngx_http_postpone_filter_add(r, in);return NGX_OK;}//如果当前请求是子请求,而in包体又为空,那么直接返回即可return NGX_OK;}//如果postponed为空,表示请求r没有子请求产生的响应需要转发if (r->postponed == NULL) {/* 直接调用下一个HTTP过滤模块继续处理in包体即可。如果没有错误的话,就会向下游客户端发送响应 */if (in || c->buffered) {return ngx_http_next_filter(r->main, in);}return NGX_OK;}/* 至此,说明postponed链表中是有子请求产生的响应需要转发的,可以先把in包体加到待转发响应的末尾 */if (in) {ngx_http_postpone_filter_add(r, in);}//循环处理postponed链表中所有子请求待转发的包体do {pr = r->postponed;/* 如果pr->request是子请求,则加入到原始请求的posted_requests队列中,等待HTTP框架下次调用这个请求时再来处理 */if (pr->request) {r->postponed = pr->next;c->data = pr->request;return ngx_http_post_request(pr->request, NULL);}//调用下一个HTTP过滤模块转发out链表中保存的待转发的包体if (pr->out == NULL) {} else {if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) {return NGX_ERROR;}}//遍历完postponed链表r->postponed = pr->next;} while (r->postponed);return NGX_OK;}

54.子请求在结束前会回调在ngx_http_post_subrequest_t中实现的handler方法,在这个handler方法中,又设置了父请求被激活后的执行方法mytest_post_handler,流程如下:

上图中的步骤如下:

1) Nginx主循环中会定期地调用事件模块,检查是否有网络事件发生。

2) 如果事件模块检测到连接关闭事件,而这个请求的处理方法属于upstream模块,则交由upstream模块来处理请求。

3) upstream模块开始调用ngx_http_upstream_finalize_request方法来结束upstream机制下的请求。

4) 调用HTTP框架提供的ngx_http_finalize_request方法来结束子请求。

5) ngx_http_finalize_request方法会检查当前的请求是否是子请求,如果是子请求,则会回调post_subrequest成员中的handler方法,也就是会调用mytest_subrequest_post_handler方法。

6) 在实现的子请求回调方法中,解析子请求返回的响应包。注意,这时需要通过write_event_handler设置父请求被激活后的回调方法(因此此时父请求的回调方法已经被HTTP框架设置为什么事情也不做的ngx_http_request_empty_handler方法).

7) 子请求的回调方法执行完毕后,交由HTTP框架的ngx_http_finalize_request方法继续向下执行。

8) ngx_http_finalize_request方法执行完毕。

9) HTTP框架如果发现当前请求后还有父请求需要执行,则调用父请求的write_event_handler回调方法。

10) 这里可以根据第6步中解析子请求响应后的结果来构造响应包。

11) 调用无阻塞的ngx_http_send_header、ngx_http_output_filter发送方法,向客户端发送响应包。

12) 无阻塞发送方法会立刻返回,即使目前未发送完,Nginx之后也会异步地发送完所有的响应包,然后再结束请求。

13) 父请求的回调方法执行完毕。

14) 当第2步中的上游服务器连接关闭时间处理完毕后,交还控制权给事件模块。

15) 当本轮网络事件处理完毕后,交还控制权给Nginx主循环。

55.subrequest是分解复杂请求的设计方法,派生出的子请求使用某些HTTP模块基于upstream访问第三方服务是最常见的用法,通过subrequest可以使Nginx在保持高并发的前提下处理复杂的业务。

56.

57.默认即编译进Nginx的HTTP过滤模块

58.过滤模块例子中,HTTP头部处理方法的执行活动图

59.过滤模块例子中,HTTP包体处理方法的执行活动图

60.参考另一篇:/zhangge3663/article/details/83180659

61.ngx_module_t接口及其对核心、事件、HTTP、mail等4类模块ctx上下文成员的具体化

62.Nginx常用模块及其之间的关系

63.传统Web服务器和Nginx间的重要差别:前者是每个事件消费者独占一个进程资源,后者的事件消费者只是被事件分发者进程短期调用而已。

64.在阻塞代码段上按照下面4种方式来划分阶段:

(1) 将阻塞进程的方法按照相关的触发事件分解为两个阶段

(2) 将阻塞方法调用按照时间分解为多个阶段的方法调用

(3) 在“无所事事”且必须等待系统的响应,从而导致进程空转时,使用定时器划分阶段

(4) 如果阻塞方法完全无法继续划分,则必须使用独立的进程执行这个阻塞方法

65.内存池的设计

为了避免出现内存碎片、减少向操作系统申请内存的次数、降低各个模块的开发复杂度,Nginx设计了简单的内存池。这个内存池没有很复杂的功能:通常它不负责回收内存池中已经分配出的内存。

通常每一个请求都有一个这种简易的独立内存池(Nginx为每一个TCP连接都分配一个内存池,HTTP框架为每一个HTTP请求又分配了1个内存池),而在请求结束时则会销毁整个内存池,把曾经分配的内存一次性归还给操作系统。

66.Nginx核心的框架代码一直围绕着一个结构体展开,它就是ngx_cycle_t。无论是master管理进程、worker工作进程还是cache manager(loader)进程,每一个进程都毫无例外地拥有唯一一个ngx_cycle_t结构体。

作为一个Web服务器,Nginx首先需要监听端口并处理其中的网络事件。ngx_cycle_t对象中有一个动态数组成员叫做listening,它的每一个数组元素都是ngx_listening_t结构体,而每个ngx_listen_t结构体又代表着Nginx服务器监听的一个端口。

typedef struct ngx_listening_s ngx_listening_t;struct ngx_listening_s {//socket套接字句柄ngx_socket_t fd;//监听sockaddr地址struct sockaddr *sockaddr;//sockaddr地址长度socklen_t socklen;/* 存储IP地址的字符串addr_text最大长度,即它指定了addr_text所分配的内存大小 */size_t addr_text_max_len;//以字符串形式存储IP地址ngx_str_t addr_text;//套接字地址。例如,当type是SOCK_STREAM时,表示TCPint type;/* TCP实现监听时的backlog队列,它表示允许正在通过三次握手建立TCP连接但还没有任何进程开始处理的连接最大个数 */int backlog;//内核中对于这个套接字的接收缓冲区大小int rcvbuf;//内核中对于这个套接字的发送缓冲区大小int sndbuf;//当新的TCP连接成功建立后的处理方法ngx_connection_handler_pt handler;/* 实际上框架并不使用servers指针,它更多的是作为一个保留指针,目前主要用于HTTP或者mail等模块,用于保存当前监听端口对应着的所有主机名 */void *servers;//log和logp都是可用的日志对象的指针ngx_log_t log;ngx_lot_t *logp;//如果为新的TCP连接创建内存池,则内存池的初始大小应该是pool_sizesize_t pool_size;/* TCP_DEFER_ACCEPT选项将在建立TCP连接成功且接收到用户的请求数据后,才向对监听套接字感兴趣的进程发送事件通知,而连接建立成功后,如果post_accept_timeout秒后仍然没有收到的用户数据,则内核直接丢弃连接 */ngx_msec_t post_accept_timeout;/* 前一个ngx_listening_t 结构,多个ngx_listening_t结构体之间由previous指针组成单链表 */ngx_listening_t *previous;//当前监听句柄对应着的ngx_connection_t结构体ngx_connection_t *connection;/* 标志位,为1则表示在当前监听句柄有效,且执行ngx_init_cycle时不关闭监听端口,为0时则正常关闭。该标志位框架代码会自动设置 */unsigned open:1;/* 标志位,为1表示使用已有的ngx_cycle_t来初始化新的ngx_cycle_t结构体时,不关闭原来打开的监听端口,这对运行中升级程序很有用,remain为0时,表示正常关闭曾经打开的监听端口。该标志位框架代码会自动设置,参见ngx_init_cycle方法 */unsigned remain:1;/* 标志位,为1时表示跳过设置当前ngx_listening_t结构体中的套接字,为0时正常初始化套接字。该标志位框架代码会自动设置 */unsigned ignore:1;//表示是否已经绑定。实际上目前该标志位没有使用unsigned bound:1; /* 已经绑定 *//* 表示当前监听句柄是否来自前一个进程(如升级Nginx程序),如果为1,则表示来自前一个进程。一般会保留之前已经设置好的套接字,不做改变 */unsigned inherited:1; /* 来自前一个进程 *///目前未使用unsigned nonblocking_accept:1;//标志位,为1时表示当前结构体对应的套接字已经监听unsigned listen:1;//表示套接字是否阻塞,目前该标志位没有意义unsigned nonblocking:1;//目前该标志位没有意义unsigned shared:1;//标志位,为1时表示Nginx会将网络地址转变为字符串形式的地址unsigned addr_ntop:1;};

67.Nginx框架是围绕着ngx_cycle_t结构体来控制进程运行的。

typedef struct ngx_cycle_s ngx_cycle_t;struct ngx_cycle_s {/* 保存着所有模块存储配置项的结构体的指针,它首先是一个数组,每个数组成员又是一个指针,这个指针指向另一个存储着指针的数组,因此会看到void **** */void ****conf_ctx;//内存池ngx_pool_t *pool;/* 日志模块中提供了生成基本ngx_log_t日志对象的功能,这里的log实际上是在还没有执行ngx_init_cycle方法前,也就是还没有解析配置前,如果有信息需要输出到日志,就会暂时使用log对象,它会输出到屏幕。在ngx_init_cycle方法执行后,将会根据nginx.conf配置文件中的配置项,构造出正确的日志文件,此时会对log重新赋值 */ngx_lot_t *log;/*由nginx.conf配置文件读取到日志文件路径后,将开始初始化error_log日志文件,由于log对象还在用于输出日志到屏幕,这时会用new_log对象暂时性地替代log日志,待初始化成功后,会用new_log的地址覆盖上面的log指针 */ngx_lot_t new_log;//与下面的files成员配合使用,指出files数组里元素的总数ngx_uint_t files_n;/* 对于poll、rtsig这样的事件模块,会以有效文件句柄数来预先建立这些ngx_connection_t结构体,以加速事件的收集、分发。这时files就会保存所有ngx_connection_t的指针组成的数组,files_n就是指针的总数,而文件句柄的值用来访问files数组成员 */ngx_connection_t **files;//可用连接池,与free_connection_n配合使用ngx_connection_t *free_connections;//可用连接池中连接的总数ngx_uint_t free_connection_n;/* 双向链表容器,元素类型是ngx_connection_t结构体,表示可重复使用连接队列 */ngx_queue_t reusable_connections_queue;/*动态数组,每个数组元素存储着ngx_listening_t成员,表示监听端口及相关的参数 */ngx_array_t listening;/*动态数组容器,它保存着Nginx所有要操作的目录。如果有目录不存在,则会视图创建,而创建目录失败将会导致Nginx启动失败。例如,上传文件的临时目录也在pathes中,如果没有权限创建,则会导致Nginx无法启动 */ngx_array_t pathes;/* 单链表容器,元素类型是ngx_open_file_t结构体,它表示Nginx已经打开的所有文件。事实上,Nginx框架不会向open_files链表中添加文件,而是由对此感兴趣的模块向其中添加文件路径名,Nginx框架会有ngx_init_cycle方法中打开这些文件 */ngx_list_t open_files;/* 单链表容器,元素的类型是ngx_shm_zone_t结构体,每个元素表示一块共享内存*/ngx_list_t shared_memory;//当前进程中所有连接对象的总数,与下面的connections成员配合使用ngx_uint_t connection_n;//指向当前进程中的所有连接对象,与connection_n配合使用ngx_connection_t *connections;//指向当前进程中的所有读事件对象,connection_n同时表示所有读事件的总数ngx_event_t *read_events;//指向当前进程中的所有写事件对象,connection_n同时表示所有写事件的总数ngx_event_t *write_events;/* 旧的ngx_cycle_t对象用于引用上一个ngx_cycle_t对象中的成员。例如ngx_init_cycle方法,在启动初期,需要建立一个临时的ngx_cycle_t对象保存一些变量,再调用ngx_init_cycle方法时就可以把旧的ngx_cycle_t对象传进去,而这时old_cycle对象就会保存这个前期的ngx_cycle_t对象 */ngx_cycle_t *old_cycle;//配置文件相对于安装目录的路径名称ngx_str_t conf_file;/* Nginx处理配置文件时需要特殊处理的在命令行携带的参数,一般是-g选项携带的参数 */ngx_str_t conf_param;//Nginx配置文件所在目录的路径ngx_str_t conf_prefix;//Nginx安装目录的路径ngx_str_t prefix;//用于进程间同步的文件锁名称ngx_str_t lock_file;//使用gethostname系统调用得到的主机名ngx_str_t hostname;};

68.Nginx启动过程的流程图

69.worker进程正常工作、退出时的流程图

70.master进程不需要处理网络事件,它不负责业务的执行,只会通过管理worker等子进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。

71.epoll是目前Linux操作系统上最强大的事件管理机制。

72.首先,Nginx定义了一个核心模块ngx_events_module,这样在Nginx启动时会调用ngx_init_cycle方法解析配置项,一旦在nginx.conf配置文件中找到ngx_events_module感兴趣的“events{}”配置项,ngx_events_module模块就开始工作了。ngx_events_module模块定义了事件类型的模块,它的全部工作就是为所有的事件模块解析"events{}"中的配置项,同时管理这些事件模块存储配置项的结构体。

其次,Nginx定义了一个非常重要的事件模块ngx_event_core_module,这个模块会决定使用哪种事件驱动机制,以及如何管理事件。

最后,Nginx定义了一系列运行在不同操作系统、不同内核版本上的事件驱动模块,包括:ngx_epoll_module、ngx_kqueue_module、ngx_poll_module、ngx_select_module、ngx_devpoll_module、ngx_eventport_module、ngx_aio_module、ngx_rtsig_module和基于Windows的ngx_select_module模块。在ngx_event_core_module模块的初始化过程中,将会从以上模块中选取一个作为Nginx进程的事件驱动模块。

73.ngx_connection_t连接池示意图

74.所有事件模块配置项结构体的指针是如何管理的。

75.ngx_event_core_module事件模块启动时的工作流程

76.epoll在Linux内核中申请了一个简易的文件系统,把原来的一个select或者poll调用分成了3个部分:调用epoll_create建立1个epoll对象(在epoll文件系统中给这个句柄分配资源)、调用epoll_ctx向epoll对象中添加这100万个连接的套接字、调用epoll_wait收集发生事件的连接。

77.ngx_event_accept方法建立新连接的流程

78.ngx_process_events_and_times方法中的事件框架处理流程

79.解析server{}块内配置项的流程

80.解析location{}配置块的流程

81.HTTP框架的初始化流程

82.接收、解析HTTP请求行的流程图

83.ngx_http_process_request_headers方法接收HTTP头部的流程图

84.ngx_http_process_request处理HTTP请求的流程图

85.ngx_http_core_run_phases方法的执行流程

86.ngx_http_request_handler方法的执行流程

87.ngx_http_read_client_request_body方法的流程图

88.upstream机制的场景示意图

89.ngx_http_upstream_connect方法的流程图

90.ngx_http_upstream_send_request方法的流程图

91.邮件代理功能的示意序列图

从网络通信的角度来看,Nginx实现邮件代理功能时会把一个请求分为以下4个阶段。

1) 接收并解析客户端初始请求的阶段;

2) 向认证服务器验证请求合法性,并获取上游邮件服务器地址的阶段;

3) Nginx根据用户信息多次与上游邮件服务器交互验证合法性的阶段;

4) Nginx在客户端与上游邮件服务器间纯粹透传TCP流的阶段。

92.初始化邮件请求的流程

93.启动邮件认证、向认证服务器发起连接的流程

94.Nginx框架使用了3种传递消息传递方式:共享内存、套接字、信号。Nginx各进程间共享数据的主要方式就是使用共享内存.

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。