上一篇分析了 echo 模块的整体结构和大致执行流程,本篇分别对此模块用到的技术细节
进行详细的分析。
echo模块各个函数识别的错误码及处理方式
echo_location[_async]
echo_location 和 echo_location_async 的区别:
Just like the
echo_location_asyncdirective, butecho_locationissues subrequests in series rather than in parallel. That is, the content handler directives following this directive won't be executed until the subrequest issued by this directive completes.
另外,内部实现原理上,echo_subrequest[_async] 和这两个指令除了提供更多的选项
外,处理流程上大同小异。
在实现上,echo_location 和 echo_location_async 的差异是执行这两条命令的请求
在创建了子请求后,是否等待子请求完成后才继续下面的操作:
-
echo_location命令在子请求创建成功后 “挂起” 当前请求,直到其子请求完成后,再 通过post_subrequest回调函数即ngx_http_echo_post_subrequest继续该请求的执 行。case echo_opcode_echo_location: ... return ngx_http_echo_exec_echo_location(r, ctx, computed_args); -
echo_location_async命令在子请求创建成功后继续处理当前请求。Nginx 同时处理子 请求逻辑,然后依靠postpone filter组织和调整最终的响应内容。case echo_opcode_echo_location_async: ... rc = ngx_http_echo_exec_echo_location_async(r, ctx, computed_args); break;
patch for issue #26
ngx_int_t
ngx_http_echo_exec_echo_request_body(ngx_http_request_t *r,
ngx_http_echo_ctx_t *ctx)
{
ngx_buf_t *b;
ngx_chain_t *cl, *out, *chain;
ngx_chain_t **ll;
if (!r->request_body || !r->request_body->bufs) {
return NGX_OK;
}
out = NULL;
ll = &out;
for (cl = r->request_body->bufs; cl; cl = cl->next) {
chain = ngx_alloc_chain_link(r->pool);
if (chain == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (cl->buf->in_file) {
b->in_file = 1;
b->tag = (ngx_buf_tag_t) &ngx_http_echo_exec_echo_request_body;
b->file_last = cl->buf->file_last;
b->file = cl->buf->file;
} else {
b->temporary = 1;
b->tag = (ngx_buf_tag_t) &ngx_http_echo_exec_echo_request_body;
b->start = cl->buf->pos;
b->pos = cl->buf->pos;
b->last = cl->buf->last;
b->end = cl->buf->end;
}
chain->buf = b;
chain->next = NULL;
*ll = chain;
ll = &chain->next;
}
return ngx_http_echo_send_chain_link(r, ctx, out);
}
ngx_http_request_t::count
From <深入理解 Nginx>: 使用引用计数是为了让 Nginx 知道 HTTP 模对于该请求有独立
的异步处理机制 (流程)。每个操作在 “认为” 自身的动作结束时,都得最终调用
ngx_http_close_request 方法。
-
流程就是 顺畅执行 下来的一系列动作 (不满意这个总结)
-
count- 引用计数,用于防止ngx_http_request_t在处理某种事件的过程中被销 毁。请求创建时,此值为 1。任何独立于主流程之外的异步流程都需要将其加 1,每个流 程结束后,都要调用ngx_http_finalize_request正式结束此流程。-
例 1:
dav模块对PUT方法的处理在ngx_http_dav_handler中完成。-
此法 函数调用
ngx_http_read_client_request_body,增加count数, 相当于开启了新的流程,此流程需要新事件的触发。 -
ngx_http_dav_handler主流程正常返回NGX_DONE后,其调用者ngx_http_core_content_phase对当前请求调用ngx_http_finalize_request将主流程引用计数从count中减去。 -
ngx_http_read_client_request_body流程处理完足够的事件后,调用post_handler也就是ngx_http_dav_put_handler,此handler完成后 会再次调用ngx_http_finalize_request将此流程的引用计数从count中 减去。 -
此时引用计数为 0,Nginx 真正销毁此请求,释放资源。
-
-
例 2:在
ngx_http_upstream_create中创建上游连接时,需对老连接进行清理。-
主流程是为新上游连接结构体
ngx_http_upstream_t分配内存空间。 -
当发现上次遗留的上游连接未被清理时,调用
ngx_http_upstream_cleanup开始新的清理流程。清理函数本身不需要等待新的事件,但是它会在清理结束后 调用ngx_http_finalize_request减少引用计数并试图销毁此请求。 -
为了清理过程后请求的有效性。在清理开始前,增加引用计数。
-
-
例 3:
proxy模块的后端连接开启函数ngx_http_proxy_handler。和 例 1 大同小异。
-
-
一句话总结:有逻辑开启流程,引用数就会加 1,此流程结束时,必然有逻辑将引用数 减1。拿
ngx_http_read_client_request_body为例,函数开始就增加了引用计数,在 碰到异常结果时,可认为新流程并未正常开始,在此函数返回前就会将引用计数减一;如 果流程正常结束,引用计数就需要post_handler负责维护;如果一次函数调用未完成 处理,这时它会启用读事件处理函数ngx_http_read_client_request_body_handler来 继续这个流程的处理,对引用计数的处理方式和前面两种情况类似。 -
代码摘录 (1.4.2):
ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler) { ... r->main->count++; ... rc = ngx_http_do_read_client_request_body(r); ... if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { r->main->count--; } return rc; } static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r) { ... rc = ngx_http_do_read_client_request_body(r); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { ngx_http_finalize_request(r, rc); } } static ngx_int_t ngx_http_dav_handler(ngx_http_request_t *r) { ... switch (r->method) { case NGX_HTTP_PUT: ... rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; ... } static void ngx_http_dav_put_handler(ngx_http_request_t *r) { ... ngx_http_finalize_request(r, XXXX); return; }
request_body
-
在执行读取命令时,
r->request_body->bufs要么有了全部数据,要么还没有任何数 据。所以,不会出现发送它的内容过程中,它被修改的情况。upstream都是直接使用r->request_body.bufs作用发送chain。 -
请求包体使用专用的结构体
ngx_http_request_body_t表示,在每个请求中有些类型 的字段ngx_http_request_body_t *request_body,并且默认值为NULL。此字段内存 在ngx_http_read_client_request_body中分配。struct ngx_http_chunked_s { ngx_uint_t state; off_t size; /* 当前 chunk 大小 */ off_t length; }; typedef struct { ngx_temp_file_t *temp_file; /* 存储包体的临时文件 */ ngx_chain_t *bufs; /* 等处理 buf 链,buf 可能指 向内存空间,可能只是文件的 描述 */ ngx_buf_t *buf; /* 当前可用 ngx_buf_t */ off_t rest; /* 请求包体还剩余的字节数 */ ngx_chain_t *free; ngx_chain_t *busy; /* busy 和 free 用来跟踪 buf 的处理情况 */ ngx_http_chunked_t *chunked; /* ngx_http_parse_chunked 用,保存 chunk 解码状态 */ ngx_http_client_body_handler_pt post_handler; /* 包体接收操作完成后 的回调函数 */ } ngx_http_request_body_t; -
HTTP 请求包体相关的概念:
-
Expect - request-header. It is used to indicate that particular server behavior are required by the client.
-
100 Continue - response-status
This means that the server has received the request headers, and that the client should proceed to send the request body (in the case of a request for which a body needs to be sent; for example, a POST request). If the request body is large, sending it to a server when a request has already been rejected based upon inappropriate headers is inefficient. To have a server check if the request could be accepted based on the request's headers alone, a client must send `Expect: 100-continue` as a header in its initial request and check if a 100 Continue status code is received in response before continuing (or receive 417 Expectation Failed and not continue). -
ngx_http_test_expect- 用于检测客户端是否有Expect: 100-continue包头, 如果此包头出现,Nginx 就向客户端发送HTTP/1.1 100 Continue的响应,告诉客 户端可以开始发送请求包体数据了。 -
POST- HTTP method.Content-Lenght: xxx,Tranport-Encoding: chunked. -
请求的
Content-Length包头和Transport-Encoding包头在ngx_http_process_request_header函数中处理。另外,在较老的 Nginx 版本中 是不支持chunked传输编码格式的。if (r->headers_in.content_length) { r->headers_in.content_length_n = ngx_atoof(r->headers_in.content_length->value.data, r->headers_in.content_length->value.len); ... } ... if (r->headers_in.transfer_encoding) { if (r->headers_in.transfer_encoding->value.len == 7 && ngx_strncasecmp(r->headers_in.transfer_encoding->value.data, (u_char *) "chunked", 7) == 0) { r->headers_in.content_length = NULL; r->headers_in.content_length_n = -1; r->headers_in.chunked = 1; } ... }
-
-
相关函数用途
-
ngx_http_request_body_filter- 解析并存储请求包体数据 -
ngx_http_request_body_chunked_filter- 解析并存储chunked编码的请求 包体数据,并在解析过程中判断包体是否超过了client_max_body_size。一个chunk被成功识别后,从r->request_body->free中申请chain-buf对其进 行描述,并将chain-buf追加到r->request_body->busy和r->request_body->bufs中 (实际上chain被拷贝) -
ngx_http_request_body_length_filter- 解析并存储长度己由Content-Length标明的请求包体数据。它从r->request_body->free链中申请chain-buf,描述 可用数据信息,并将chain-buf追加到r->request_body->busy和r->request_body->bufs中 (实际上chain被拷贝) -
ngx_http_request_body_save_filter- 将存有数据的chain进行拷贝 (只有chain被拷贝) 并追加到r->request_body->bufs中 -
ngx_chain_update_chains- 使用r->request_body->free和r->request_body->busy跟踪数据的使用情况。free和busy的chain并 不会由其它函数保存,而chain指向的buf,也会被其它函数自己申请的chain引用 (这样,ngx_chain_update_chains才能知道buf的使用情况)。
-
-
Nginx 使用
ngx_http_read_client_request_body读取请求包体,用于数据缓存的内 存块大小由client_body_buffer_size指定,超出此内存缓存的部分被写入临时文件。 如果打开了client_body_in_file_only,也会将包体全部写入临时文件。-
如果客户端使用了
Expect: 100-continue包头,发送100 Continue响应给 客户端,告知它可以开始发送包体数据 -
如何
r->header_in缓存中己经读入了部分包体数据,将这部分数据追加到r->request_body->bufs中 -
如果包体已经接收完成,检查
client_body_in_file_only配置项是否打开。 如果配置项打开,将r->request_body->bufs中的包体数据写入临时文件,并用它 保存用于描述临时文件的ngx_buf_t,并执行步骤 8;如果配置项未打开,直执 行步骤 8 -
申请
client_body_buffer_size大小的 (如果此时能够判断剩余包体小于此配置 值,可以减小此值) 用于缓存包体数据的内存块,使用r->request_body->buf引用 -
从连接读取包体数据 (
c->recv) -
如果
r->request_body->buf已满,将其中数据写入临时文件,并清空r->request_body->bufs。重置r->request_body->buf -
请求包体读取完成后,检查
client_body_in_file_only。如果此配置项打开或 者在读取过程中已经创建了临时文件,将r->bufs中缓存的数据全部写入临时文件。 结果就是,请求包体要么都在内存缓存区中,要么存令在临时文件中 -
请求包体接收处理完成,调用
post_handler
-
ngx_buf_t
-
ngx_buf_t *shadow- -
bit temporary- the buf's content could be changed bit memory- the buf's content is in a memory cache or in a read-only memory and must not be changed.bit mmap- the buf's content ismmap()edand must not be changed-
bit in_file- data represented by thisngx_buf_tresides in disk file -
bit flush- send data out immediately, even it is not thelast_bufor is currently being postponed. -
bit sync- 找 agentzh 请教一下;agentzh 解答如下:-
The
syncflag inngx_buf_tis just for intentional empty bufs. Many nginx output filters could create "empty bufs" intentional to simplify the implementation of streaming filtering algorithm. Nginx will complain on pure "empty bufs" (zero sized and with no flags set). -
Forgot to mention that the
syncbufs generated inngx_echoare just for working with output filter modules likengx_xss: https://github.com/agentzh/xss-nginx-module
-
-
bit last_buf- the very last part of responsewrite filterwill send data before it if they were postponed.gunzip filterknows when to addZ_FINISHbecause of it.image filterknows where the last byte of image is according to it.chunked filterappends the last chunk ofchunked encodingwhen meets it.
-
bit last_in_chain- last buf in this chain, make foreach chain easier. -
bit last_shadow -
bit temp_file -
ngx_http_send_special(r, NGX_HTTP_FLUSH) ngx_http_send_special(r, NGX_HTTP_LAST);ngx_int_t ngx_http_send_special(ngx_http_request_t *r, ngx_uint_t flags) { ... b = ngx_calloc_buf(r->pool); ... if (flags & NGX_HTTP_LAST) { if (r == r->main && !r->post_action) { b->last_buf = 1; } else { b->sync = 1; b->last_in_chain = 1; } } if (flags & NGX_HTTP_FLUSH) { b->flush = 1; } out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out); }
ngx_chain_t
-
相关操作函数
-
ngx_alloc_chain_link- 从内存池申请一个ngx_chain_t -
ngx_free_chain- 释放ngx_chain_t到内存池free list,使其可复用 -
ngx_create_chain_of_bufs- 一次性从内存池申请多个chain-buf-memory。
使用
ngx_bufs_t描述ngx_buf_t的个数和其维护的内存块大小-
ngx_chain_add_copy(p, **chain, *in)- 拷贝in到*chain中,复用ngx_buf_t -
ngx_chain_get_free_buf(p, **free)- 从free list或内存池中申请chain-buf -
ngx_chain_update_chains(p, **free, **busy, **out, tag)- 维护free list和busy free,以复用chain-buf
-
-
从
in追加到out(拷贝chain-buf,不拷贝实际数据)ngx_buf_t *b; ngx_chain_t *cl, *chain, *out, **ll; for (cl = out; cl; cl = cl->next) { ll = &cl->next; } for (cl = in; cl; cl = cl->next) { chain = ngx_chain_get_free_buf(r->pool, &free); b = chain->buf; /* operations on b */ *ll = chain; ll = &chain->next; } *ll = NULL;
Comments
不要轻轻地离开我,请留下点什么...