【发布时间】:2020-12-23 11:14:45
【问题描述】:
我知道在NGINX中可能会多次调用输出(响应)体过滤器,所以我想使用累积缓冲区来保存整个响应体并通过修改第二个输入参数每次写入一个空缓冲区链直到收到最后一个缓冲区(参见下面的代码 sn-p)。
结果发现客户端可以收到reponse header(chunked response),但是收不到reponse body。
Server: nginx/1.19.3
Connection: keep-alive
Transfer-Encoding: chunked
为什么或如何做(保存它们并在以后发送它们,即使在调用ngx_http_output_body_filter_by_test 之后)?
// Use the placement new constructor to all a constructor on an already allocated memory
// buffer = malloc(/*...*/);
// object = new (buffer) MyClass(/*...*/);
// But later you have to call the destructor explicitly too :
// object->~MyClass();
// free(buffer);
// https://en.cppreference.com/w/cpp/language/new
typedef struct ngx_http_test_ctx_s {
std::string buffer;
} ngx_http_test_ctx_t;
std::string cumulative_buffer;
//@param chain nullable, e.g. rc = ngx_http_output_filter(r, NULL) in nginx\src\http\ngx_http_request.c#ngx_http_writer
ngx_int_t ngx_http_output_body_filter_by_test(ngx_http_request_t *req, ngx_chain_t *chain) {
// TODO Auto-generated method stub
logdf("%.*s|%d,%d", ARGS_NGX_STR(req->unparsed_uri), req->headers_out.content_length_n, req->chunked);
int last = 0;
for (ngx_chain_t *cl = chain; cl; cl = cl->next) {
ngx_buf_t *buf = cl->buf;
// 0/0 if ngx_http_send_special(req, NGX_HTTP_LAST);
logdf("chain@%p %d%d %" PRIdMAX " / %" PRIdMAX, chain, buf->last_buf, buf->last_in_chain, buf->last - buf->pos, buf->end - buf->start);
cumulative_buffer.append(buf->pos, buf->last);
// ngx_chain_update_chains ngx_event_pipe_write_to_downstream
buf->pos = buf->last; // avoid being write_to_downstream
buf->flush = 0; // avoid being write_to_downstream
if (buf->last_buf || buf->last_in_chain) {
buf->last_in_chain = 0; // avoid being considered as the last buffer by NGINX
buf->last_buf = 0; // avoid being considered as the last buffer by NGINX
last = 1;
}
}
if (last) {
logdf("cumulative_buffer=%s", cumulative_buffer.c_str());
// do something use the entire reponse body
// auto newbuf = reinterpret_cast<ngx_buf_t*>(ngx_alloc_buf(req->pool));
auto newbuf = ngx_create_temp_buf(req->pool, cumulative_buffer.length());
newbuf->last = ngx_cpymem(newbuf->last, cumulative_buffer.data(), cumulative_buffer.length());
newbuf->last_buf = 1;
newbuf->last_in_chain = 1;
#ifdef ENABLE_NEW_CHAIN_IN_REPONSE_BODY_FILTER
auto newchain = reinterpret_cast<ngx_chain_t*>(ngx_alloc_chain_link(req->pool));
newchain->buf = newbuf;
newchain->next = NULL;
ngx_int_t rc = ngx_http_next_output_body_filter(req, newchain);
req->write_event_handler(req); //req->write_event_handler is ngx_http_writer
return rc;
#else
// ngx_free_chain(req->pool, chain);
chain->buf = newbuf;
chain->next = NULL;
return ngx_http_next_output_body_filter(req, chain);
//req->buffered |= NGX_HTTP_SUB_BUFFERED;
#endif
}
return ngx_http_next_output_body_filter(req, chain);
//if (chain) {
// req->buffered &= ~NGX_HTTP_SUB_BUFFERED;
//}
// Delay the call to the ngx_http_next_output_body_filter function because response may need to be revised.
// return NGX_OK;
}
static void ngx_http_test_cleanup(void *data) {
auto ctx = reinterpret_cast<ngx_http_em_subrequest_enforcer_ctx_t*>(data);
ctx->~ngx_http_test_ctx_t();
// delete ctx->xx;
logdf("ctx@%p", ctx);
}
将第二个return ngx_http_next_output_body_filter(req, chain); 替换为return NGX_OK; 后,客户端可以收到响应正文。但是,有时,last 总是 0(即没有 buf 其 buf->last_buf || buf->last_in_chain 是 true)(内容长度与分块?)。
ngx_int_t ngx_http_output_body_filter_by_test(ngx_http_request_t *req, ngx_chain_t *chain) {
//// TODO Auto-generated method stub
logdf("%.*s|content_length_n=%" PRIdMAX ", chunked=%d", ARGS_NGX_STR(req->unparsed_uri), req->headers_out.content_length_n, req->chunked);
if (req->upstream) {
logdf("upstream: length = %" PRIdMAX ", buffer@%p.size=%" PRIdMAX ", bytes_received=%" PRIdMAX ", bytes_sent=%" PRIdMAX
", response_length=%" PRIdMAX , req->upstream->length // pending bytes to send to the downstream client
, req->upstream->buffer.pos, req->upstream->buffer.last - req->upstream->buffer.pos
, req->upstream->state->bytes_received, req->upstream->state->bytes_sent
, req->upstream->state->response_length); // req->upstream->pipe->preread_size
}
if (NGX_STR_STARTS_WITH(req->unparsed_uri, "/stream/")) {
#if 0
for (ngx_chain_t *cl = chain; cl; cl = cl->next) {
ngx_buf_t *buf = cl->buf;
logdf("chain@%p buf@%p %d%d %" PRIdMAX "/%" PRIdMAX, chain, buf->pos,
buf->last_buf, buf->last_in_chain, buf->last - buf->pos, buf->end - buf->start);
}
return ngx_http_next_output_body_filter(req, chain);
#endif
if (0 == (intptr_t)ngx_http_get_module_ctx(req, ngx_http_em_module)) {
ngx_http_set_ctx(req, (void*)1, ngx_http_em_module);
cumulative_buffer.clear();
}
int last = 0;
for (ngx_chain_t *cl = chain; cl; cl = cl->next) {
ngx_buf_t *buf = cl->buf;
// 0/0 if ngx_http_send_special(req, NGX_HTTP_LAST);
logdf("chain@%p buf@%p %d%d %" PRIdMAX "/%" PRIdMAX, chain, buf->pos,
buf->last_buf, buf->last_in_chain, buf->last - buf->pos, buf->end - buf->start);
cumulative_buffer.append(buf->pos, buf->last);
if (buf->last_buf || buf->last_in_chain) {
//buf->last_in_chain = 0; // avoid being considered as the last buffer by NGINX
//buf->last_buf = 0; // avoid being considered as the last buffer by NGINX
last = 1;
}
//// ngx_chain_update_chains ngx_event_pipe_write_to_downstream
//buf->pos = buf->last; // avoid being write_to_downstream
//buf->flush = 0; // avoid being write_to_downstream
}
if (last) {
logdf("cumulative_buffer(length=%" PRIdMAX ")=%.16s", cumulative_buffer.length(), cumulative_buffer.c_str());
// do something use the entire reponse body
// auto newbuf = reinterpret_cast<ngx_buf_t*>(ngx_alloc_buf(req->pool));
auto len = ngx_min(1024, cumulative_buffer.length());
auto newbuf = ngx_create_temp_buf(req->pool, len);
newbuf->last = ngx_cpymem(newbuf->last, cumulative_buffer.data(), len);
newbuf->last_buf = 1;
newbuf->last_in_chain = 1;
#ifdef ENABLE_NEW_CHAIN_IN_REPONSE_BODY_FILTER
auto newchain = reinterpret_cast<ngx_chain_t*>(ngx_alloc_chain_link(req->pool));
newchain->buf = newbuf;
newchain->next = NULL;
ngx_int_t rc = ngx_http_next_output_body_filter(req, newchain);
req->write_event_handler(req); //req->write_event_handler is ngx_http_writer
return rc;
#else
// ngx_free_chain(req->pool, chain);
chain->buf = newbuf;
chain->next = NULL;
return ngx_http_next_output_body_filter(req, chain);
//req->buffered |= NGX_HTTP_SUB_BUFFERED;
#endif
}
return NGX_OK; // ngx_http_next_output_body_filter(req, chain);
}
}
nginx.conf sn-p
# /stream/{n} Stream n JSON responses, max n is 100, 59.6 kB, 58690 bytes = 57.314453125 kB
location /stream/ {
proxy_buffer_size 4k;
proxy_buffers 4 8k;
proxy_busy_buffers_size 16k;
# proxy_pass http://httpbin.org;
proxy_pass http://127.0.0.1:8080;
}
我发现 NGINX 在SSL_ERROR_WANT_WRITE 之后没有重试(“SSL_write: -1”和“SSL_get_error: 3”)。
> NGINX.exe!ngx_ssl_write(ngx_connection_s * c, unsigned char * data, unsigned __int64 size) Line 2576 C
NGINX.exe!ngx_ssl_send_chain(ngx_connection_s * c, ngx_chain_s * in, __int64 limit) Line 2495 C
NGINX.exe!ngx_http_write_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 294 C
NGINX.exe!ngx_http_chunked_body_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 219 C
NGINX.exe!ngx_http_gzip_body_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 308 C
NGINX.exe!ngx_http_postpone_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 91 C
NGINX.exe!ngx_http_ssi_body_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 413 C
NGINX.exe!ngx_http_charset_body_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 557 C
NGINX.exe!ngx_http_trailers_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 264 C
NGINX.exe!ngx_output_chain(ngx_output_chain_ctx_s * ctx, ngx_chain_s * in) Line 74 C
NGINX.exe!ngx_http_copy_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 152 C
NGINX.exe!ngx_http_range_body_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 635 C
ngx_http_test_module.dll!ngx_http_output_body_filter_by_test(ngx_http_request_s * req, ngx_chain_s * chain) Line 1148 C++
NGINX.exe!ngx_http_output_filter(ngx_http_request_s * r, ngx_chain_s * in) Line 1846 C
NGINX.exe!ngx_http_send_special(ngx_http_request_s * r, unsigned __int64 flags) Line 3515 C
NGINX.exe!ngx_http_upstream_finalize_request(ngx_http_request_s * r, ngx_http_upstream_s * u, __int64 rc) Line 4487 C
NGINX.exe!ngx_http_upstream_process_request(ngx_http_request_s * r, ngx_http_upstream_s * u) Line 4051 C
NGINX.exe!ngx_http_upstream_process_upstream(ngx_http_request_s * r, ngx_http_upstream_s * u) Line 3963 C
NGINX.exe!ngx_http_upstream_handler(ngx_event_s * ev) Line 1286 C
NGINX.exe!ngx_event_process_posted(ngx_cycle_s * cycle, ngx_queue_s * posted) Line 35 C
NGINX.exe!ngx_process_events_and_timers(ngx_cycle_s * cycle) Line 265 C
NGINX.exe!ngx_worker_thread(void * data) Line 795 C
我预计(在断点的情况下来自error.log),
[debug] 10512#20076: *1 reusable connection: 0
[debug] 10512#20076: *1 http upstream temp fd: -1
[debug] 10512#20076: *1 http output filter "/stream/21?"
[debug] 10512#20076: *1 malloc: 0000024099030A50:307242
[debug] 10512#20076: *1 http copy filter: "/stream/21?"
[debug] 10512#20076: *1 http postpone filter "/stream/21?" 000000B5F25FEDE8
[debug] 10512#20076: *1 http chunk: 307242
[debug] 10512#20076: *1 write old buf t:1 f:0 000002409902B468, pos 000002409902B468, size: 156 file: 0, size: 0
[debug] 10512#20076: *1 write new buf t:1 f:0 000002409902CB78, pos 000002409902CB78, size: 7 file: 0, size: 0
[debug] 10512#20076: *1 write new buf t:1 f:0 0000024099030A50, pos 0000024099030A50, size: 307242 file: 0, size: 0
[debug] 10512#20076: *1 write new buf t:0 f:0 0000000000000000, pos 00007FF78CC708F8, size: 7 file: 0, size: 0
[debug] 10512#20076: *1 http write filter: l:1 f:0 s:307412
[debug] 10512#20076: *1 http write filter limit 0
[debug] 10512#20076: *1 malloc: 0000024098E6EB50:512
[debug] 10512#20076: *1 malloc: 0000024098E67EC0:16384
[debug] 10512#20076: *1 SSL buf copy: 156
[debug] 10512#20076: *1 SSL buf copy: 7
[debug] 10512#20076: *1 SSL buf copy: 16221
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 16384
[debug] 10512#20076: *1 SSL to write: 16384
[debug] 10512#20076: *1 SSL_write: 16384
[debug] 10512#20076: *1 SSL buf copy: 12493
[debug] 10512#20076: *1 SSL buf copy: 7
[debug] 10512#20076: *1 SSL to write: 12500
[debug] 10512#20076: *1 SSL_write: 12500
[debug] 10512#20076: *1 http write filter 0000000000000000
[debug] 10512#20076: *1 http copy filter: 0 "/stream/21?"
[debug] 10512#20076: *1 http finalize request: 0, "/stream/21?" a:1, c:1
[debug] 10512#20076: *1 set http keepalive handler
[debug] 10512#20076: *1 http close request
[debug] 10512#20076: *1 http log handler
[debug] 10512#20076: *1 free: 000002409902EA10
[debug] 10512#20076: *1 free: 0000024099030A50
[debug] 10512#20076: *1 free: 0000000000000000
[debug] 10512#20076: *1 free: 0000000000000000
[debug] 10512#20076: *1 free: 0000000000000000
[debug] 10512#20076: *1 free: 00000240990298B0, unused: 2
[debug] 10512#20076: *1 free: 000002409902A8F0, unused: 12
[debug] 10512#20076: *1 free: 000002409902C970, unused: 3279
[debug] 10512#20076: *1 free: 0000024098E6E710
[debug] 10512#20076: *1 hc free: 0000000000000000
[debug] 10512#20076: *1 hc busy: 0000000000000000 0
[debug] 10512#20076: *1 free: 0000024098E67EC0
[debug] 10512#20076: *1 reusable connection: 1
[debug] 10512#20076: *1 event timer add: 576: 65000:1608876752566
[debug] 10512#20076: worker cycle
而不是(真正运行时没有任何调试断点),
[debug] 10512#20076: *3 reusable connection: 0
[debug] 10512#20076: *3 http upstream temp fd: -1
[debug] 10512#20076: *3 http output filter "/stream/22?"
[debug] 10512#20076: *3 malloc: 0000024099030A50:307242
[debug] 10512#20076: *3 http copy filter: "/stream/22?"
[debug] 10512#20076: *3 http postpone filter "/stream/22?" 000000B5F25FEDE8
[debug] 10512#20076: *3 http chunk: 307242
[debug] 10512#20076: *3 write old buf t:1 f:0 0000024098E0C878, pos 0000024098E0C878, size: 156 file: 0, size: 0
[debug] 10512#20076: *3 write new buf t:1 f:0 0000024098E6E188, pos 0000024098E6E188, size: 7 file: 0, size: 0
[debug] 10512#20076: *3 write new buf t:1 f:0 0000024099030A50, pos 0000024099030A50, size: 307242 file: 0, size: 0
[debug] 10512#20076: *3 write new buf t:0 f:0 0000000000000000, pos 00007FF78CC708F8, size: 7 file: 0, size: 0
[debug] 10512#20076: *3 http write filter: l:1 f:0 s:307412
[debug] 10512#20076: *3 http write filter limit 0
[debug] 10512#20076: *3 malloc: 0000024098DD79C0:512
[debug] 10512#20076: *3 malloc: 0000024098E67EC0:16384
[debug] 10512#20076: *3 SSL buf copy: 156
[debug] 10512#20076: *3 SSL buf copy: 7
[debug] 10512#20076: *3 SSL buf copy: 16221
[debug] 10512#20076: *3 SSL to write: 16384
[debug] 10512#20076: *3 SSL_write: 16384
[debug] 10512#20076: *3 SSL buf copy: 16384
[debug] 10512#20076: *3 SSL to write: 16384
[debug] 10512#20076: *3 SSL_write: 16384
[debug] 10512#20076: *3 SSL buf copy: 16384
[debug] 10512#20076: *3 SSL to write: 16384
[debug] 10512#20076: *3 SSL_write: 16384
[debug] 10512#20076: *3 SSL buf copy: 16384
[debug] 10512#20076: *3 SSL to write: 16384
[debug] 10512#20076: *3 SSL_write: 16384
[debug] 10512#20076: *3 SSL buf copy: 16384
[debug] 10512#20076: *3 SSL to write: 16384
[debug] 10512#20076: *3 SSL_write: -1
[debug] 10512#20076: *3 SSL_get_error: 3
[debug] 10512#20076: *3 http write filter 0000024098E6E20C
[debug] 10512#20076: *3 http copy filter: -2 "/stream/22?"
[debug] 10512#20076: *3 http finalize request: -2, "/stream/22?" a:1, c:1
[debug] 10512#20076: *3 event timer add: 576: 60000:1608876912668
[debug] 10512#20076: *3 select add event fd:576 ev:1
[debug] 10512#20076: worker cycle
参考文献
- Netty User guide for 4.x - Dealing with a Stream-based Transport
-
ngx_http_sub_filter_module.c (ngx_http_sub_body_filter)。
ngx_http_sub_module模块是一个过滤器,它通过将一个指定的字符串替换为另一个来修改响应。 -
openresty / replace-filter-nginx-module
ngx_http_replace_body_filter- 响应正文中的流式正则表达式替换。 Replace Filter。 -
Writing an Nginx Response Body Filter Module (
ngx_http_html_head_body_filter) - Emiller’s Guide To Nginx Module Development - 4.2. Anatomy of a Body Filter
NGINX 参考资料
- Extending NGINX - Module API - HTTP API - Structures - ngx_http_request_t
- Extending NGINX - Module API - Memory Management API - ngx_buf_t & ngx_chain_t
- NGINX Development guide - HTTP - Request (ngx_http_request_t)
- NGINX Development guide - Buffer
- NGINX Development guide - HTTP - Header filters (response)
- NGINX Development guide - HTTP - Body filters (response)
- Extending NGINX - Module API - HTTP API - Callbacks
【问题讨论】:
-
你在构建什么,一个 nginx 模块?它的目标是什么?稍微介绍一下就好了。
-
nginx 模块。将链式缓冲区合并到一个连续的内存(std::string),然后找到两个关键字之间的内容并将其替换为新内容。