details: https://github.com/nginx/njs/commit/34b80511acfd44a5cbbbce835d7540081e5d7527 branches: master commit: 34b80511acfd44a5cbbbce835d7540081e5d7527 user: Dmitry Volyntsev <xei...@nginx.com> date: Mon, 16 Jun 2025 19:36:35 -0700 description: Fetch: fixed handling of Content-Length header when body is provided.
body value length takes precedence over Content-Length from header list. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch Let contentLength be httpRequest’s body’s length, if httpRequest’s body is non-null; otherwise null. Let contentLengthHeaderValue be null. If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`, then set contentLengthHeaderValue to `0`. If contentLength is non-null, then set contentLengthHeaderValue to contentLength, serialized and isomorphic encoded. If contentLengthHeaderValue is non-null, then append (`Content-Length`, contentLengthHeaderValue) to httpRequest’s header list. This fixes #930 issue in Github. --- nginx/ngx_js_fetch.c | 21 ++++++++++++++++++++- nginx/ngx_qjs_fetch.c | 21 ++++++++++++++++++++- nginx/t/js_fetch.t | 23 +++++++++++++++++++++-- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 45f2dc10..faa38aab 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -514,6 +514,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_url_t u; ngx_uint_t i; njs_bool_t has_host; + ngx_str_t method; ngx_pool_t *pool; njs_value_t *init, *value; ngx_js_http_t *http; @@ -674,6 +675,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, continue; } + if (h[i].key.len == 14 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); @@ -693,7 +701,18 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_chb_append(&http->chain, request.body.data, request.body.len); } else { - njs_chb_append_literal(&http->chain, CRLF); + method = request.method; + + if ((method.len == 4 + && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0)) + || (method.len == 3 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0)) + { + njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF); + + } else { + njs_chb_append_literal(&http->chain, CRLF); + } } if (u.addrs == NULL) { diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 084162ba..5ed8fc30 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -241,6 +241,7 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, JSValue init, value, promise; ngx_int_t rc; ngx_url_t u; + ngx_str_t method; ngx_uint_t i; ngx_pool_t *pool; ngx_js_ctx_t *ctx; @@ -410,6 +411,13 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, continue; } + if (h[i].key.len == 14 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); @@ -429,7 +437,18 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, njs_chb_append(&http->chain, request.body.data, request.body.len); } else { - njs_chb_append_literal(&http->chain, CRLF); + method = request.method; + + if ((method.len == 4 + && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0)) + || (method.len == 3 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0)) + { + njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF); + + } else { + njs_chb_append_literal(&http->chain, CRLF); + } } if (u.addrs == NULL) { diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t index 1c6fde77..76d9238d 100644 --- a/nginx/t/js_fetch.t +++ b/nginx/t/js_fetch.t @@ -64,6 +64,10 @@ http { js_content test.body; } + location /body_content_length { + js_content test.body_content_length; + } + location /body_special { js_content test.body_special; } @@ -156,6 +160,13 @@ $t->write_file('test.js', <<EOF); .catch(e => r.return(501, e.message)) } + async function body_content_length(r) { + let resp = await ngx.fetch(`http://127.0.0.1:$p0/loc`, + {headers: {'Content-Length': '100'}, + body: "CONTENT-BODY"}); + r.return(resp.status); + } + function property(r) { var opts = {headers:{}}; @@ -400,12 +411,12 @@ $t->write_file('test.js', <<EOF); export default {njs: test_njs, body, broken, broken_response, body_special, chain, chunked_ok, chunked_fail, header, header_iter, - host_header, multi, loc, property}; + host_header, multi, loc, property, body_content_length }; EOF $t->try_run('no njs.fetch'); -$t->plan(37); +$t->plan(38); $t->run_daemon(\&http_daemon, port(8082)); $t->waitforsocket('127.0.0.1:' . port(8082)); @@ -508,6 +519,14 @@ like(http_get('/body_special?loc=head/large&method=HEAD'), qr/200 OK.*<empty>$/s, 'fetch head method large content-length'); } +TODO: { +local $TODO = 'not yet' unless has_version('0.9.1'); + +like(http_get('/body_content_length'), qr/200 OK/s, + 'fetch body content-length'); + +} + ############################################################################### sub has_version { _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel