details: http://freenginx.org/hg/nginx/rev/cfe8cf095ff0 branches: changeset: 9418:cfe8cf095ff0 user: Maxim Dounin <[email protected]> date: Sun Aug 24 20:14:28 2025 +0300 description: Proxy: added the "proxy_allow_duplicate_chunked" directive.
This directive allows to accept duplicate "Transfer-Encoding: chunked" header lines. These are invalid, and rejected since 8033:2bf7792c262e (1.23.0), yet it turns out there are quite a few homegrown proxies, notably Java-based ones, which emit such duplicate headers. The "proxy_allow_duplicate_chunked" directive makes it possible to enable compatibility with such proxies by ignoring duplicate "Transfer-Encoding: chunked" headers instead of rejecting them. Prodded by Gennady Bekasov, https://github.com/freenginx/nginx/issues/11 diffstat: src/http/modules/ngx_http_fastcgi_module.c | 4 ++-- src/http/modules/ngx_http_grpc_module.c | 1 + src/http/modules/ngx_http_memcached_module.c | 1 + src/http/modules/ngx_http_proxy_module.c | 14 ++++++++++++-- src/http/modules/ngx_http_scgi_module.c | 4 ++-- src/http/modules/ngx_http_uwsgi_module.c | 4 ++-- src/http/ngx_http_upstream.c | 2 +- src/http/ngx_http_upstream.h | 1 + 8 files changed, 22 insertions(+), 9 deletions(-) diffs (139 lines): diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -2948,10 +2948,10 @@ ngx_http_fastcgi_create_loc_conf(ngx_con conf->upstream.intercept_errors = NGX_CONF_UNSET; - /* "fastcgi_cyclic_temp_file" is disabled */ + /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; - conf->upstream.change_buffering = 1; + conf->upstream.duplicate_chunked = 0; conf->catch_stderr = NGX_CONF_UNSET_PTR; diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -4431,6 +4431,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t conf->upstream.pass_request_headers = 1; conf->upstream.pass_request_body = 1; conf->upstream.force_ranges = 0; + conf->upstream.duplicate_chunked = 0; conf->upstream.pass_trailers = 1; conf->upstream.preserve_output = 1; diff --git a/src/http/modules/ngx_http_memcached_module.c b/src/http/modules/ngx_http_memcached_module.c --- a/src/http/modules/ngx_http_memcached_module.c +++ b/src/http/modules/ngx_http_memcached_module.c @@ -627,6 +627,7 @@ ngx_http_memcached_create_loc_conf(ngx_c conf->upstream.pass_request_headers = 0; conf->upstream.pass_request_body = 0; conf->upstream.force_ranges = 1; + conf->upstream.duplicate_chunked = 0; conf->index = NGX_CONF_UNSET; conf->gzip_flag = NGX_CONF_UNSET_UINT; diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -695,6 +695,13 @@ static ngx_command_t ngx_http_proxy_com offsetof(ngx_http_proxy_loc_conf_t, http09), NULL }, + { ngx_string("proxy_allow_duplicate_chunked"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, upstream.duplicate_chunked), + NULL }, + #if (NGX_HTTP_SSL) { ngx_string("proxy_ssl_session_reuse"), @@ -3392,6 +3399,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_ conf->upstream.request_buffering = NGX_CONF_UNSET; conf->upstream.ignore_client_abort = NGX_CONF_UNSET; conf->upstream.force_ranges = NGX_CONF_UNSET; + conf->upstream.duplicate_chunked = NGX_CONF_UNSET; conf->upstream.local = NGX_CONF_UNSET_PTR; conf->upstream.socket_keepalive = NGX_CONF_UNSET; @@ -3444,9 +3452,8 @@ ngx_http_proxy_create_loc_conf(ngx_conf_ conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif - /* "proxy_cyclic_temp_file" is disabled */ + /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; - conf->upstream.change_buffering = 1; conf->headers_source = NGX_CONF_UNSET_PTR; @@ -3523,6 +3530,9 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t ngx_conf_merge_value(conf->upstream.force_ranges, prev->upstream.force_ranges, 0); + ngx_conf_merge_value(conf->upstream.duplicate_chunked, + prev->upstream.duplicate_chunked, 0); + ngx_conf_merge_ptr_value(conf->upstream.local, prev->upstream.local, NULL); diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -1334,10 +1334,10 @@ ngx_http_scgi_create_loc_conf(ngx_conf_t conf->upstream.intercept_errors = NGX_CONF_UNSET; - /* "scgi_cyclic_temp_file" is disabled */ + /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; - conf->upstream.change_buffering = 1; + conf->upstream.duplicate_chunked = 0; ngx_str_set(&conf->upstream.module, "scgi"); diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -1578,10 +1578,10 @@ ngx_http_uwsgi_create_loc_conf(ngx_conf_ conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif - /* "uwsgi_cyclic_temp_file" is disabled */ + /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; - conf->upstream.change_buffering = 1; + conf->upstream.duplicate_chunked = 0; ngx_str_set(&conf->upstream.module, "uwsgi"); diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -5292,7 +5292,7 @@ ngx_http_upstream_process_transfer_encod u = r->upstream; - if (u->headers_in.transfer_encoding) { + if (u->headers_in.transfer_encoding && !u->conf->duplicate_chunked) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent duplicate header line: \"%V: %V\", " "previous value: \"%V: %V\"", diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -182,6 +182,7 @@ typedef struct { ngx_flag_t intercept_errors; ngx_flag_t cyclic_temp_file; ngx_flag_t force_ranges; + ngx_flag_t duplicate_chunked; ngx_path_t *temp_path;
