typedef struct { /* * 字符串的有效长度 */ size_t len; /* * 有效字符串的起始地址,该字符串一般并不以'\0'结尾. */ u_char *data; } ngx_str_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 的地址 */ ngx_list_part_t *next; }; /* 描述整个链表 */ typedef struct { /* * 指向链表的最后一个数组元素 */ ngx_list_part_t *last; /* * 链表的首个数组元素 */ ngx_list_part_t part; /* * 数组中每一个元素的大小 */ size_t size; /* * 表示每一个 ngx_list_part_t 数组的容量,即最能够存储多少个数据 */ ngx_uint_t nalloc; /* * 链表中管理内存分配的内存池对象 */ ngx_pool_t *pool; } ngx_list_t;
typedef struct { /* * 代表 ngx_table_elt_t 能够是某个散列表数据结构 * (ngx_hash_t 类型)中的成员. ngx_uint_t 类型的 hash * 成员能够在 ngx_hash_t 中更快地找到相同 key 的 * ngx_table_elt_t 数据 */ ngx_uint_t hash; ngx_str_t key; ngx_str_t value; /* * 指向全小写的 key 字符串 */ u_char *lowcase_key; } ngx_table_elt_t;
typedef void * ngx_buf_tag_t; typedef struct ngx_buf_s ngx_buf_t; struct ngx_buf_s { /* * 该缓存中有效待处理数据的起始地址 */ u_char *pos; /* * 该缓存中有效待处理数据的末尾,即 pos 到 last * 之间的内存是但愿 Nginx 处理的内容. */ u_char *last; /* * 处理文件时,file_pos 与 file_last 的含义与处理内存时的 pos * 与 last 相同,file_pos 表示将要处理的文件位置,file_last * 表示截止的文件位置. */ off_t file_pos; off_t file_last; u_char *start; /* start of buffer */ u_char *end; /* end of buffer */ /* * 表示当前缓冲区的类型,如由哪一个模块使用就指向这个模块的 * ngx_module_t 变量的地址 */ ngx_buf_tag_t tag; /* * 引用的文件 */ ngx_file_t *file; /* * 当前缓冲区的影子缓存区,该成员不多使用,仅在描述的使用缓冲区 * 转发上游服务器的响应时才使用了 shadow 成员,这是由于 Nginx 太 * 节约内存了,分配一块内存并使用 ngx_buf_t 表示接收到的上游服务器 * 响应后,在向下游客户端转发时可能会把这块内存存储到文件中,也可能 * 直接向下游发送,此时 Nginx 毫不会从新复制一分内存用于新的目的, * 而是再次创建一个 ngx_buf_t 结构体指向原内存,这样多个 ngx_buf_t * 结构体指向同一块内存,它们之间的关系就经过 shadow 成员来引用. */ ngx_buf_t *shadow; /* the buf's content could be changed */ unsigned temporary:1; /* * the buf's content is in a memory cache or in a read only memory * and must not be changed */ unsigned memory:1; /* the buf's content is mmap()ed and must not be changed */ unsigned mmap:1; /* * 标志位,为 1 表示可回收 */ unsigned recycled:1; /* * 标志位,为 1 表示这段缓冲区处理的是文件而不是内存 */ unsigned in_file:1; /* * 标志位,为 1 时表示须要执行 flush 操做 */ unsigned flush:1; /* * 标志位,对于操做这块内存是否使用同步方式,需谨慎,可能会阻塞 Nginx 进程, * 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; /* STUB */ int num; };
typedef struct ngx_chain_s ngx_chain_t; struct ngx_chain_s { /* * 指向当前的 ngx_buf_t 缓冲区 */ ngx_buf_t *buf; /* * 指向下一个ngx_chain_t,若这是最后一个 ngx_chain_t,则置为 NULL */ ngx_chain_t *next; };
在向用户发送 HTTP 包体时,就要传入 ngx_chain_t 链表对象,注意,若是这是最后一个 ngx_chain_t,则必须将 next 设置为 NULL,不然永远不会发送成功,且这个请求将一直不会结束.nginx
在 configure 脚本执行时加入参数:--add-module=<PATH>
。设计模式
开发一个 HTTP 模块,则 config 文件中需定义如下 3 个变量:数组
"$HTTP_MODULES ngx_http_mytest_module"
。所以,对于一个第三方 mytest 模块,能够这样编写 config 文件:浏览器
ngx_addon_name=ngx_http_mytest_module HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
typedef struct ngx_module_s ngx_module_t; struct ngx_module_s { /* * 表示当前模块在这类模块中的序号. */ ngx_uint_t ctx_index; /* * 表示当前模块在 ngx_modules 数组中的序号. ctx_index 表示的是当前模块在 * 一类模块中的序号,而 index 表示当前模块在全部模块中的序号。 */ ngx_uint_t index; /* * 模块的名称 */ char *name; ngx_uint_t spare0; ngx_uint_t spare1; ngx_uint_t version; const char *signature; /* * ctx 用于指向一类模块的上下文结构体。 */ 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); 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; };
typedef struct { /* * 解析配置文件前调用 */ ngx_int_t (*preconfiguration)(ngx_conf_t *cf); /* * 完成配置文件的解析后调用 */ ngx_int_t (*postconfiguration)(ngx_conf_t *cf); /* * 当须要建立数据结构用于存储 main 级别(直属于 http{...} 块的配置项) * 的全局配置项时,能够经过该回调方法建立存储全局配置项的结构体 */ void *(*create_main_conf)(ngx_conf_t *cf); /* * 初始化 main 级别的配置项 */ char *(*init_main_conf)(ngx_conf_t *cf, void *conf); /* * 当须要建立数据结构用于存储 srv 级别(直属于 server{...} 块的配置项) * 的全局配置项时,能够经过该回调方法建立存储 srv 级别配置项的结构体 */ void *(*create_srv_conf)(ngx_conf_t *cf); /* * 用于合并 main 级别和 srv 级别下的同名配置项 */ char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); /* * 当须要建立数据结构用于存储 loc 级别(直属于 location{...} 块的配置项) * 的全局配置项时,能够经过该回调方法建立存储 loc 级别配置项的结构体 */ void *(*create_loc_conf)(ngx_conf_t *cf); /* * 用于合并 srv 级别和 loc 级别下的同名配置项 */ char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); } ngx_http_module_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; };
自定义的 HTTP 模块介入 Nginx 的方式:缓存
在这种方式下,mytest 处理请求是固定在 NGX_HTTP_CONTENT_PAHSE 阶段开始处理请求。服务器
static ngx_command_t ngx_http_mytest_commands[] = { { ngx_string("mytest"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF| NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_http_mytest, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command };
当某个配置块出现 mytest 配置项时,Nginx 将会调用 ngx_http_mytest 方法:cookie
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_loc_conf_t *clcf; /* * 首先找到 mytest 配置项所属的配置块. */ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); /* HTTP 框架在处理用户请求进行到 NGX_HTTP_CONTENT_PAHSE 阶段时, * 若是请求的域名、URI 与 mytest 配置项所在配置块相匹配,则会调用 * ngx_http_mytest_handler 处理该请求. */ clcf->handler = ngx_http_mytest_handler; return NGX_CONF_OK; }
static ngx_http_module_t ngx_http_mytest_module_ctx = { NULL, /* preconfiguration */ NULL, /* 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 */ };
若没有什么工做须要在 HTTP 框架初始化时完成,则可如上定义 ngx_http_module_t 接口。网络
ngx_module_t ngx_http_mytest_module = { NGX_MODULE_V1, &ngx_http_mytest_module_ctx, ngx_http_mytest_commands, NGX_HTTP_MODULE, 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 };
当出现 mytest 配置项时,ngx_http_mytest 方法会被调用,该方法中将 ngx_http_core_loc_conf_t 结构的 handler 成员指定为 ngx_http_mytest_handler。这样,当 HTTP 框架在接收完 HTTP 请求的头部后,在 NGX_HTTP_CONTENT_PAHSE 阶段会执行 handler 指向的方法。该方法原型以下:数据结构
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
HTTP 框架在 NGX_HTTP_CONTENT_PAHSE 阶段调用 ngx_http_mytest_handler 后,会将 ngx_http_mytest_handler 的返回值做为参数传给 ngx_http_finalize_request 方法。并发
if (r->content_handler) { r->write_event_handler = ngx_http_request_empty_handler; ngx_http_finalize_request(r, r->content_handler(r)); return NGX_OK; }
所以,ngx_http_finalize_request 决定了 ngx_http_mytest_handler 如何起做用。
四个通用的返回码:
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t* r) { if (!(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 type = ngx_string("text/plain"); ngx_str_t response = ngx_string("hello world"); r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = response.len; r->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; out.buf = b; out.next = NULL; /* 发送包体,发送结束后 HTTP 框架会调用 ngx_http_finalize_request * 方法结束请求 */ return ngx_http_output_filter(r, &out); }
处理 HTTP 配置项能够分为下面 4 个步骤:
typedef struct { ngx_str_t my_str; ngx_int_t my_num; ngx_flag_t my_flag; size_t my_size; ngx_array_t* my_str_array; ngx_array_t* my_keyval; off_t my_off; ngx_msec_t my_msec; time_t my_sec; ngx_bufs_t my_bufs; ngx_uint_t my_enum_seq; ngx_uint_t my_bitmask; ngx_uint_t my_access; ngx_uint_t my_path; }ngx_http_mytest_conf_t;
在 Nginx 中,多个 location 块(或者 http 块、server 块)中的相同配置项是容许同时生效的,也就是说,ngx_http_mytest_conf_t 结构必须在 Nginx 的内存中保存许多份。事实上,HTTP 框架在解析 nginx.conf 文件时只要遇到 http{}、server{} 或者 location{} 配置块就会马上分配一个新的 ngx_http_mytext_conf_t 结构体。所以,HTTP 模块感兴趣的配置项须要统一地使用一个 struct 结构体来保存,若是 nginx.conf 文件中在 http{} 下有多个 server{} 或者 location{},那么这个 struct 结构体在 Nginx 进程中就会存在多份实例。
普通的 HTTP 模块每每只实现 create_loc_conf 回调方法,由于它们只关注匹配某种 URL 的请求。mytest 模块一样如此,只实现 create_loc_conf 方法,所以,此时 ngx_http_module_t 接口的定义以下:
static ngx_http_module_t ngx_http_mytest_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_mytest_create_loc_conf, /* create location configuration */ NULL /* merge location configuration */ };
ngx_http_mytest_create_loc_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; } mycf->test_flag = NGX_CONF_UNSET; }
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); } if (!(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; } ngx_str_t type = ngx_string("text/plain"); r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_type = type; rc = ngx_http_send_header(r); if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { return rc; } ngx_buf_t *b; b = ngx_palloc(r->pool, sizeof(ngx_buf_t)); u_char* filename = (u_char*)"/home/rong/samba/nginx-1.13.2/tmp/sbin/test.txt"; b->in_file = 1; b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); b->file->fd = ngx_open_file(filename, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, NGX_FILE_OPEN, 0); b->file->log = r->connection->log; b->file->name.data = filename; b->file->name.len = sizeof(filename) - 1; if (b->file->fd <= 0) { return NGX_HTTP_NOT_FOUND; } if (ngx_file_info(filename, &b->file->info) == NGX_FILE_ERROR) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } r->headers_out.content_length_n = b->file->info.st_size; /* 从文件的 file_pos 位置开始发送文件,一直到file_last偏移量处 */ b->file_pos = 0; b->file_last = b->file->info.st_size; ngx_chain_t out; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); } static char *ngx_http_mytest(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); /* HTTP 框架在处理用户请求进行到 NGX_HTTP_CONTENT_PAHSE 阶段时, * 若是请求的域名、URI 与 mytest 配置项所在配置块相匹配,则会调用 * ngx_http_mytest_handler 处理该请求. */ clcf->handler = ngx_http_mytest_handler; return NGX_CONF_OK; }
Nginx 提供了两种全异步方式来与第三方服务器通讯:upstream 和 subrequest。
upstreasm 能够保证与第三方服务器交互时(包括三次握手创建 TCP 链接、发送请求、接收响应、四次握手关闭 TCP 链接等)不会阻塞 Nginx 进程处理其余请求。
subrequest 是分解复杂请求的一种设计模式,它本质上与访问第三方服务没有任何关系,subrequest 访问第三方服务最终也是基于 upstream 实现的。
若但愿把第三方服务的内容几乎原封不动地返回给用户时,通常使用 upstream 方式,它能够很是高效地透传 HTTP。若是访问第三方服务只是为了获取某些信息,再依据这些信息来构造响应并发送给用户,这时应该用 subrequest 方式。
HTTP 模块启用 upstream 机制流程:
typedef struct ngx_http_upstream_s ngx_http_upstream_t; struct ngx_http_upstream_s { /* * 处理读事件的回调方法,每个阶段都有不一样的 read_event_handler */ ngx_http_upstream_handler_pt read_event_handler; /* * 处理写事件的回调方法,每个阶段都有不一样的 write_event_handler */ ngx_http_upstream_handler_pt write_event_handler; /* * 表示主动向上游服务器发起的链接 */ ngx_peer_connection_t peer; /* * 当向下游客户端转发响应时(ngx_http_request_t 结构体中的 subrequest_in_memory * 标志位为 0),若是打开了缓存且认为上游网速更快(conf 配置中的 buffering 标志 * 位为 1),这时会使用 pipe 成员来转发响应。在使用这种方式转发响应时,必须由 * HTTP 模块在使用 upstream 机制前构造 pipe 结构体,不然会出现严重的 coredump * 错误. */ ngx_event_pipe_t *pipe; /* * request_bufs 以链表的方式把 ngx_buf_t 缓存区连接起来,它表示全部须要发送到 * 上游服务器的请求内容。因此,HTTP 模块实现的 create_request 回调方法就在于 * 构造 request_bufs 链表 */ ngx_chain_t *request_bufs; /* * 定义了向下游发送响应的方式 */ ngx_output_chain_ctx_t output; ngx_chain_writer_ctx_t writer; /* * upstream 访问时的全部限制性参数 */ ngx_http_upstream_conf_t *conf; ngx_http_upstream_srv_conf_t *upstream; #if (NGX_HTTP_CACHE) ngx_array_t *caches; #endif /* * HTTP 模块在实现 process_header 方法时,若是但愿 upstream 直接转发响应, * 就须要把解析出的响应头部适配为 HTTP 的响应头部,同时须要把包头中的信息 * 设置到 headers_in 结构体,这样,会把 headers_in 中设置的头部添加到要发 * 送到下游客户端的响应头部 headers_out 中 */ ngx_http_upstream_headers_in_t headers_in; /* * 经过 resolved 能够直接指定上游服务器地址 */ ngx_http_upstream_resolved_t *resolved; ngx_buf_t from_client; /* * 存储接收自上游服务器发来的响应内容,因为它会被复用,因此具备下列多种意义: * 1. 在使用 process_header 方法解析上游响应的包头时,buffer 中将会保存完整的 * 响应包头; * 2. 当下面的 buffering 成员为 1,并且此时 upstream 是向下游转发上游的包体时, * buffer 没有意义; * 3. 当 buffering 标志位为 0 时,buffer 缓冲区会被用于反复地接收上游服务器的 * 包体,进而向下游转发; * 4. 当 upstream 并不用于转发上游包体时,buffer 会被用于反复接收上游的包体, * HTTP 模块实现的 input_filter 方法须要关注它. */ ngx_buf_t buffer; /* * 表示来自上游服务器的响应包体的长度 */ off_t length; /* * out_bufs 在两种场景下有不一样的意义:1. 当不须要转发包体,且使用默认 * 的 input_filter 方法(也就是 ngx_http_upstream_non_buffered_filter * 方法)处理包体时,out_bufs 将会指向响应包体,事实上,out_bufs 链表 * 中会产生多个 ngx_buf_t 缓冲区,每一个缓冲区都指向 buffer 缓存中的一部 * 分,而这里的一部分就是每次调用 recv 方法接收到的一段 TCP 流。2. 当 * 须要转发响应包体到下游时(buffering 标志位为 0,即如下游网速优先), * 这个链表指向上一次向下游转发响应到如今这段时间内接收自上游的缓存响应 */ ngx_chain_t *out_bufs; /* * 当须要转发响应包体到下游时(buffering 标志位为 0,即如下游网速优先), * 它表示上一次向下游转发响应时没有发送完的内容 */ ngx_chain_t *busy_bufs; /* * 这个链表将用于回收 out_bufs 中已经发送给下游的 ngx_buf_t 结构体,这 * 一样应用在 buffering 标志位为 0 即如下游网速优先的场景 */ ngx_chain_t *free_bufs; /* * 处理包体前的初始化方法,其中 data 参数用于传递用户数据结构,它实际上 * 就是下面的 input_filter_ctx 指针 */ ngx_int_t (*input_filter_init)(void *data); /* * 处理包体的方法,其中 data 参数用于传递用户数据结构,它实际上就是下面的 * input_filter_ctx 指针,而 bytes 表示本次接收到的包体长度。返回 NGX_ERROR * 时表示处理包体错误,请求须要结束,不然都将继续 upstream 流程 */ ngx_int_t (*input_filter)(void *data, ssize_t bytes); /* * 用于传递 HTTP 模块自定义的数据结构,在 input_filter_init 和 input_filter * 方法被回调时会做为参数传递过去 */ void *input_filter_ctx; #if (NGX_HTTP_CACHE) ngx_int_t (*create_key)(ngx_http_request_t *r); #endif /* * 用于构造发往上游服务器的请求内容 */ ngx_int_t (*create_request)(ngx_http_request_t *r); /* * 与上游服务器的通讯失败后,若是按照重试规则还须要再次向上游服务器发起 * 链接,则会调用 reinit_request 方法 */ ngx_int_t (*reinit_request)(ngx_http_request_t *r); /* * 解析上游服务器返回响应的包头,返回 NGX_AGAIN 表示包头尚未接收完整, * 返回 NGX_HTTP_UPSTREAM_INVALID_HEADER 表示包头不合法,返回 NGX_ERROR * 表示出现错误,返回 NGX_OK 表示解析到完整的包头. */ ngx_int_t (*process_header)(ngx_http_request_t *r); void (*abort_request)(ngx_http_request_t *r); /* * 请求结束时会调用 */ void (*finalize_request)(ngx_http_request_t *r, ngx_int_t rc); /* * 在上游返回的响应出现 Location 或者 Refresh 头部时表示重定向时,会经过 * ngx_http_upstream_process_headers 方法调用到可由 HTTP 模块实现的 * rewrite_redirect 方法 */ ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix); ngx_int_t (*rewrite_cookie)(ngx_http_request_t *r, ngx_table_elt_t *h); ngx_msec_t timeout; /* * 用于表示上游响应的错误码、包体长度等信息 */ ngx_http_upstream_state_t *state; ngx_str_t method; /* * schema 和 uri 成员仅在记录日志时会用到,除此以外没有意义 */ ngx_str_t schema; ngx_str_t uri; #if (NGX_HTTP_SSL || NGX_COMPAT) ngx_str_t ssl_name; #endif ngx_http_cleanup_pt *cleanup; /* * 是否指定文件缓存路径的标志位 */ unsigned store:1; /* * 是否启用文件缓存 */ unsigned cacheable:1; unsigned accel:1; /* * 是否基于 SSL 协议访问上游服务器 */ unsigned ssl:1; #if (NGX_HTTP_CACHE) unsigned cache_status:3; #endif /* * 在向客户端转发上游服务器的包体时才有用。当 buffering 为 1 时,表示使用多个 * 缓冲区以及磁盘文件来转发上游的响应包体。当 Nginx 与上游间的网速远大于 Nginx * 与下游客户端间的网速时,让 Nginx 开辟更多的内存甚至使用磁盘文件来缓存上游的 * 响应包体,这能够减轻上游服务器的压力。当 buffering 为 0 时,表示只使用上面 * 的这一个 buffer 缓冲区来向下游转发响应包体. */ unsigned buffering:1; unsigned keepalive:1; unsigned upgrade:1; /* * request_sent 表示是否已经向上游服务器发送了请求,当 request_sent 为 * 1 时,表示 upstream 机制已经向上游服务器发送了所有或者部分的请求。 * 事实上,这个标志位更多的是为了使用 ngx_output_chain 方法发送请求, * 由于该方法发送请求时会自动把未发送完的 request_bufs 链表记录下来, * 为了防止反复发送重复请求,必须有 request_sent 标志位记录是否调用过 * ngx_output_chain 方法 */ unsigned request_sent:1; unsigned request_body_sent:1; /* * 将上游服务器的响应划分为包头和包尾,若是把响应直接转发给客户端, * header_sent 标志位表示包头是否发送,header_sent 为 1 时表示已经 * 把包头转发给客户端了。若是不转发响应到客户端,则 header_sent * 没有意义. */ unsigned header_sent:1; };
typedef struct { ... /* 链接上游服务器的超时时间,单位毫秒 */ ngx_msec_t connect_timeout; /* 发送 TCP 包到上游服务器的超时时间,单位毫秒 */ ngx_msec_t send_timeout; /* 接收 TCP 包到上游服务的超时时间,单位毫秒 */ ngx_msec_t read_timeout; ... }ngx_http_upstream_conf_t;
该结构体中的三个超时时间必须设置,由于它们默认为 0,若不设置将永远没法与上游服务器创建链接。
下面是设置 conn_timeout 链接超时时间的示例:
static ngx_command_t ngx_http_mytest_commands[] = { { ngx_string("mytest"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF| NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_http_mytest, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("upstream_conn_timeout"), NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, /* 给出 conn_timeout 成员在ngx_http_mytest_conf_t结构体中的偏移量*/ offsetof(ngx_http_mytest_conf_t, upstream.conn_timeout), NULL }, ngx_null_command };
解析到 upstream_conn_timeout 后,在 ngx_http_mytest_handler 中可以下设置:
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); } /* 将解析自配置文件中的upstream的限制参数结构体赋给conf */ ngx_http_mytest_conf_t *mycf = (ngx_http_mytest_conf_t *) ngx_http_get_module_loc_conf(r, ngx_http_mytest_module); r->upstream->conf = &mycf->upstream; ... }
ngx_http_upstream_t 结构体中的 resolved 成员能够直接设置上游服务器的地址。
typedef struct { ngx_str_t host; in_port_t port; ngx_uint_t no_port; /* unsigned no_port:1 */ /* 地址个数 */ ngx_uint_t naddrs; ngx_resolver_addr_t *addrs; /* 上游服务器的地址 */ struct sockaddr *sockaddr; socklen_t socklen; ngx_str_t name; ngx_resolver_ctx_t *ctx; } ngx_http_upstream_resolved_t;
三个必须实现的回调方法为 create_request、process_header、finalize_request。
该示例 nginx.conf 中的相关配置:
location /test { mytest; }
客户端浏览器则输入: http://xxx:xxx/test?lumia
。
upstream 的完整示例以下:
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #include <ngx_http_upstream.h> typedef struct { /* 正常每一个HTTP请求都会有一个独立的ngx_http_upstrem_conf_t结构体 * 这里为了简便,全部的请求都共享同一个ngx_http_upstream_conf_t */ ngx_http_upstream_conf_t upstream; }ngx_http_mytest_conf_t; typedef struct { ngx_str_t backendServer; /* 保存接收的响应行 */ ngx_http_status_t status; }ngx_http_mytest_ctx_t; static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void *ngx_http_mytest_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r); static ngx_int_t mytest_process_status_line(ngx_http_request_t *r); static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r); static void mytest_upstream_finalize_request(ngx_http_request_t *r, ngx_int_t rc); static ngx_command_t ngx_http_mytest_commands[] = { { ngx_string("mytest"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF| NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, ngx_http_mytest, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("upstream_conn_timeout"), NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_HTTP_LOC_CONF_OFFSET, /* 给出 conn_timeout 成员在ngx_http_mytest_conf_t结构体中的偏移量*/ offsetof(ngx_http_mytest_conf_t, upstream.connect_timeout), NULL }, ngx_null_command }; static ngx_http_module_t ngx_http_mytest_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_mytest_create_loc_conf, /* create location configuration */ ngx_http_mytest_merge_loc_conf /* merge location configuration */ }; ngx_module_t ngx_http_mytest_module = { NGX_MODULE_V1, &ngx_http_mytest_module_ctx, ngx_http_mytest_commands, NGX_HTTP_MODULE, 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_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); } /* 将新建的上下文与请求关联起来 */ if (ngx_http_upstream_create(r) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_upstream_create() failed"); return NGX_ERROR; } /* 获得配置结构体ngx_http_mytest_conf_t */ ngx_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; u->resolved = (ngx_http_upstream_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 *) "www.google.com"); 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成员加1 */ r->main->count++; /* 启动upstream */ ngx_http_upstream_init(r); /* 必须返回NGX_DONE */ return NGX_DONE; } static char *ngx_http_mytest(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); /* HTTP 框架在处理用户请求进行到 NGX_HTTP_CONTENT_PAHSE 阶段时, * 若是请求的域名、URI 与 mytest 配置项所在配置块相匹配,则会调用 * ngx_http_mytest_handler 处理该请求. */ clcf->handler = ngx_http_mytest_handler; return NGX_CONF_OK; } 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分钟 */ 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,就会使用更多的内存缓冲来不及发往下游的 * 响应 */ 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_proces_header方法按照 * 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; return 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"; extern ngx_str_t ngx_http_proxy_hide_headers[]; 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; } /* 建立用于发送给上游服务器的HTTP请求 */ static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r) { /* 发往google上游服务器的请求就是模仿正常的搜索请求,以 * /search?q=...的URL来发起搜索请求 */ static ngx_str_t backendQueryLine = ngx_string("GET /search?q=%V HTTP/1.1\r\n" "Host: www.google.com\r\n" "Connection: close\r\n\r\n"); ngx_int_t queryLineLen = backendQueryLine.len + r->args.len - 2; /* * 必须在内存池中申请内存,好处以下: * 1. 在网络很差的状况下,向上游服务器发送请求时,可能须要epoll屡次调度 * send才能发送完成,这时必须保证这段内存不被释放; * 2. 在请求结束时,这段内存会被自动释放,下降内存泄露的可能 */ ngx_buf_t *b = ngx_create_temp_buf(r->pool, queryLineLen); if (b == NULL) { return NGX_ERROR; } /* b->last 指向请求的末尾 */ b->last = b->pos + queryLineLen; /* 访问的URL是"/test?lumia",则args即为"lumia" */ ngx_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在这里只包含一个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不能够为0 */ r->header_hash = 1; return NGX_OK; } /* 解析响应行 */ static ngx_int_t mytest_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; 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; } /* 如下表示解析到完整的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); } /* process_header 负责解析上游服务器发来的基于TCP的包头 */ 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_header_line方法,用于解析HTTP头部 */ rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1); /* 返回NGX_OK时,表示解析出一行HTTP头部 */ if (rc == NGX_OK) { /* 向headers_in.headers链表中添加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, "Server"); ngx_str_null(&h->value); h->lowcase_key = (u_char*) "server"; } if (r->upstream->headers_in.date == NULL) { h = ngx_list_push(&r->upstream->headers_in.headers); if (h == NULL) { return NGX_ERROR; } h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e'); 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; } } static void mytest_upstream_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "mytest_upstream_finalize_request"); }
subrequest 是由 HTTP 框架提供的一种分解复杂请求的设计模式,它能够把原始请求分解为许多子请求,使得诸多请求协同完成一个用户请求,而且每一个请求只关注于一个功能。
subrequest 与访问第三方服务及 upstream 机制关系:
subrequest 设计的基础是生成一个子请求的代价要很是小,消耗的内存也要不多,而且不会一直占用进程资源。
使用 subrequest 的步骤:
子请求的处理过程与普通请求彻底相同,须要在 nginx.conf 中配置相应的模块处理。子请求与普通请求的不一样之处在于,子请求是由父请求生成的,不是接收客户端发来的网络包再由HTTP框架解析出的。
假设生成子请求是以 URI 为 /list 开头的请求,使用 ngx_http_proxy_module 模块让子请求访问新浪的 hq.sinajs.cn 股票服务器,在 nginx.conf 中可以下设置:
location /list { proxy_pass http://hq.sinajs.cn; /* 不但愿第三方服务发来的HTTP包体作过gzip压缩,由于不想在子请求结束时再对 * 响应作gzip解压缩操做 */ proxy_set_header Accept-Encoding ""; }
Nginx 在子请求正常结束或异常结束时,都会调用 ngx_http_post_subrequest_pt 回调方法:
typedef ngx_int_t (*ngx_http_post_subrequest_pt) (ngx_http_request_t *r, void *data, ngx_int_t rc);
经过创建 ngx_http_post_subrequest_t 结构体将这个回调方法传递给 subrequest 子请求:
typedef struct { ngx_http_post_subrequest_pt handler; /* handler 函数中 data 参数就是该 data */ void *data; }ngx_http_post_subrequest_t;
ngx_http_post_subrequest_pt 回调方法中的 rc 参数是子请求在结束时的状态,它的取值则是执行 ngx_http_finalize_request 销毁请求时所传递的 rc 参数,相应源码以下
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ... /* 若是当前请求属于某个原始请求的子请求 */ if (r != r->main && r->post_subrequest) { rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc); } ... }
在 ngx_http_post_subrequest_pt 回调方法内必须设置父请求激活后的处理方法,首先找出父请求:
ngx_http_request_t *pr = r->parent;
而后将实现好的 ngx_http_post_subrequest_pt 回调方法赋给父请求的 write_event_handler 指针(由于父请求正处于等待发送响应的阶段):
pr->write_event_handler = mytest_post_handler;
mytest_post_handler 是父请求从新激活后的回调方法,以下:
typedef void (*ngx_http_event_handler_pt)(ngx_http_request_t *r); struct ngx_http_request_s { ... ngx_http_event_handler_pt write_event_handler; ... };
这个方法负责发送响应包给用户.
在 ngx_http_mytest_handler 处理方法中,能够启动 subrequest 子请求。首先调用 ngx_http_subrequest 方法创建 subreuest 子请求,在 ngx_http_mytest_handler 返回后,HTTP 框架会自动执行子请求。以下为 ngx_http_subrequest 的定义:
ngx_int_t ngx_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);
当使用浏览器方位 /query?s_sh000001 时(s_sh000001 是新浪服务器上的A股上证指数),Nginx 由 mytest 模块处理,它会生成一个子请求,由反向代理处理这个子请求,访问新浪的 http://hq.sinajs.cn 服务器,这时子请求获得的响应包是上证指数的当天价格交易量等信息,而 mytest 模块会解析这个响应,从新构造发往客户端浏览器的 HTTP 响应。浏览器获得的返回值格式为: stock[上证指数], Today current price: 2373.436, volumn: 770.
若访问新浪服务器的 URL 为 /list=s_sh000001, 则能够这样设置:
location /list { /* 决定访问的上游服务器地址是hq.sinajs.cn */ proxy_pass http://hq.sinajs.cn; /* 不但愿第三方服务发来的HTTP包体进行过gzip压缩 */ proxy_set_header Accept-Encoding ""; }
此外,处理以 /query 开头的 URI 用户请求还需选用 mytest 模块:
location /query { mytest; }
这里的上下文仅用于保存子请求回调方法中解析出来的股票数据:
typedef struct { ngx_str_t stock[6]; }ngx_http_mytest_ctx_t;
新浪服务器的返回大体以下:
var hq_str_s_sh000009="上证 380,3356.356,-5.725,-0.17,266505,251997";
以下定义 mytest_subrequest_post_handler 做为子请求结束时的回调方法:
static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r, void *data, ngx_int_t rc) { /* 当前请求 r 是子请求,它的parent成员指向父请求 */ ngx_http_request_t *pr = r->parent; /* 因为上下文是保存在父请求中的,全部要由pr取上下文。其实参数data就是上下文,初始化subrequest * 时就对其进行设置。这里仅是为了说明如何获取到父请求的上下文 */ ngx_http_mytest_ctx_t * myctx = ngx_http_get_module_ctx(pr, ngx_http_mytest_module); pr->headers_out.status = r->headers_out.status; /* 若是返回NGX_HTTP_OK(即200),则意味着访问新浪服务器成功,接着将开始解析 * HTTP包体 */ if (r->headers_out.status == NGX_HTTP_OK) { int flag = 0; /* 在不转发响应时,buffer中会保存上游服务器的响应。特别是在使用反向代理模块访问上游 * 服务器时,若是它使用upstream机制时没有重定义input_filter方法,upstream机制默认 * 的input_filter方法会试图把全部的上游响应所有保存到buffer缓冲区中 */ ngx_buf_t *pRecvBuf = &r->upstream->buffer; /* 如下开始解析上游服务器的响应,并将解析出的值赋到上下文结构体myctx->stock数组中 */ for (; pRecvBuf->pos != pRecvBuf->last; pRecvBuf->pos++) { if (*pRecvBuf->pos == ',' || *pRecvBuf->pos == '\"') { if (flag > 0) { myctx->stock[flag - 1].len = pRecvBuf->pos - myctx->stock[flag - 1].data; } flag++; myctx->stock[flag - 1].data = pRecvBuf->pos + 1; } if (flag > 6) { break; } } } /* 设置接下来父请求的回调方法 */ pr->write_event_handler = mytest_post_handler; return NGX_OK; }
将父请求的回调方法定义为 mytest_post_handler:
static void mytest_post_handler(ngx_http_request_t *r) { /* 若是没有返回200,则直接把错误码发回用户 */ if (r->headers_out.status != NGX_HTTP_OK) { ngx_http_finalize_request(r, r->headers_out.status); return; } /* 当前请求是父请求,直接取其上下文 */ ngx_http_mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module); /* 定义发给用户的HTTP包体内存,格式为:stock[...],Today current price: ..., volumn:... */ ngx_str_t output_format = ngx_string("stock[%V],Today current price: %V, volumn: %V"); /* 计算待发送包体的长度 */ int bodylen = output_format.len + myctx->stock[0].len + myctx->stock[1].len + myctx->stock[4].len - 6; r->headers_out.content_length_n = bodylen; /* 在内存池上分配内存以保存将要发送的包体 */ ngx_buf_t *b = ngx_create_temp_buf(r->pool, bodylen); ngx_snprintf(b->pos, bodylen, (char *)output_format.data, &myctx->stock[0], &myctx->stock[1], &myctx->stock[4]); b->last = b->pos + bodylen; b->last_buf = 1; ngx_chain_t out; out.buf = b; out.next = NULL; /* 设置Content-Type,注意,在汉字编码方面,新浪服务器使用了GBK */ static ngx_str_t type = ngx_string("text/plain; charset=GBK"); r->headers_out.content_type = type; r->headers_out.status = NGX_HTTP_OK; r->connection->buffered |= NGX_HTTP_WRITE_BUFFERED; ngx_int_t ret = ngx_http_send_header(r); ret = ngx_http_output_filter(r, &out); /* 注意,这里发送完响应后必须手动调用ngx_http_finalize_request结束请求, * 由于这时HTTP框架不会再帮忙调用它 */ ngx_http_finalize_request(r, ret); }
在处理用户请求的 ngx_http_mytest_handler 方法中,开始建立 subrequest 子请求。
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r) { /* 建立HTTP上下文 */ ngx_http_mytest_ctx *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; } /* 将上下文设置到原始请求r中 */ ngx_http_set_ctx(r, myctx, ngx_http_mytest_module); } /* ngx_http_post_subrequest_t 结构体会决定子请求的回调方法 */ ngx_http_post_subrequest_t *pst = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t)); if (psr == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } /* 设置子请求回调方法为mytest_subrequest_post_handler */ psr->handler = mytest_subrequest_post_handler; /* 将data设为myctx上下文,这样回调mytest_subrequest_post_handler时传入的 * data参数就是myctx */ psr->data = myctx; /* 子请求的URI前缀是/list,这是由于访问新浪服务器的请求必须时相似/list=s_sh000001的 * URI,这与在nginx.conf中配置的子请求location的URI是一致的 */ ngx_str_t sub_prefix = ngx_string("/list="); ngx_str_t sub_location; sub_location.len = sub_prefix.len + r->args.len; sub_location.data = ngx_palloc(r->pool, sub_location.len); ngx_snprintf(sub_location.data, sub_location.len, "%V%V", &sub_prefix, &r->args); /* sr就是子请求 */ ngx_http_request_t *sr; /* * 调用ngx_http_subrequest建立子请求,它只会返回NGX_OK或者NGX_ERROR。返回 * NGX_OK时,sr已是合法的子请求。注意,这里的NGX_HTTP_SUBREQUEST_IN_MEMORY * 参数将告诉upstream模块把上游服务器的响应所有保存在子请求的sr->upstream->buffer * 内存缓冲区中 */ ngx_int_t rc = ngx_http_subrequest(r, &sub_location, NULL, &r, psr, NGX_HTTP_SUBREQUEST_IN_MEMORY); if (rc != NGX_OK) { return NGX_ERROR; } /* 必须返回NGX_DONE */ return NGX_DONE; }