上一篇分析了 echo
模块的整体结构和大致执行流程,本篇分别对此模块用到的技术细节
进行详细的分析。
echo
模块各个函数识别的错误码及处理方式
echo_location[_async]
echo_location
和 echo_location_async
的区别:
Just like the
echo_location_async
directive, butecho_location
issues 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()ed
and must not be changed-
bit in_file
- data represented by thisngx_buf_t
resides in disk file -
bit flush
- send data out immediately, even it is not thelast_buf
or is currently being postponed. -
bit sync
- 找 agentzh 请教一下;agentzh 解答如下:-
The
sync
flag inngx_buf_t
is 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
sync
bufs generated inngx_echo
are 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 filter
will send data before it if they were postponed.gunzip filter
knows when to addZ_FINISH
because of it.image filter
knows where the last byte of image is according to it.chunked filter
appends the last chunk ofchunked encoding
when 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
不要轻轻地离开我,请留下点什么...