From 3f6f7824d4e2eb1ac37dec76683d525ac0ff521c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 8 Apr 2026 21:07:15 +0400 Subject: [PATCH] Added client_body_early_read directive The directive accepts predicates. When enabled, request body is read immediately after the headers and before choosing a location. Body read settings from the server block are used in this case. Example: http { map $http_content_type $is_json { application/json 1; } server { listen 8000; client_body_early_read $is_json; client_max_body_size 256; client_body_buffer_size 256; return 200 $request_body; } } --- src/http/ngx_http_core_module.c | 11 ++++++ src/http/ngx_http_core_module.h | 2 ++ src/http/ngx_http_request.c | 60 +++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index fb4b227ac..c6e20adcf 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -394,6 +394,13 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_loc_conf_t, client_body_in_single_buffer), NULL }, + { ngx_string("client_body_early_read"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_1MORE, + ngx_http_set_predicate_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_core_srv_conf_t, client_body_early_read), + NULL }, + { ngx_string("sendfile"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_FLAG, @@ -3550,6 +3557,7 @@ ngx_http_core_create_srv_conf(ngx_conf_t *cf) cscf->ignore_invalid_headers = NGX_CONF_UNSET; cscf->merge_slashes = NGX_CONF_UNSET; cscf->underscores_in_headers = NGX_CONF_UNSET; + cscf->client_body_early_read = NGX_CONF_UNSET_PTR; cscf->file_name = cf->conf_file->file.name.data; cscf->line = cf->conf_file->line; @@ -3598,6 +3606,9 @@ ngx_http_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->underscores_in_headers, prev->underscores_in_headers, 0); + ngx_conf_merge_ptr_value(conf->client_body_early_read, + prev->client_body_early_read, NULL); + if (conf->server_names.nelts == 0) { /* the array has 4 empty preallocated elements, so push cannot fail */ sn = ngx_array_push(&conf->server_names); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index a13d7ade5..d07754b5b 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -205,6 +205,8 @@ typedef struct { ngx_flag_t merge_slashes; ngx_flag_t underscores_in_headers; + ngx_array_t *client_body_early_read; + unsigned listen:1; #if (NGX_PCRE) unsigned captures:1; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index ed9a7e8ee..92b92ff09 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -32,6 +32,7 @@ static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_read_early_body(ngx_http_request_t *r); static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c, ngx_http_virtual_names_t *virtual_names, ngx_str_t *host, ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp); @@ -2209,10 +2210,69 @@ ngx_http_process_request(ngx_http_request_t *r) c->write->handler = ngx_http_request_handler; r->read_event_handler = ngx_http_block_reading; + if (ngx_http_read_early_body(r) != NGX_OK) { + return; + } + ngx_http_handler(r); } +static ngx_int_t +ngx_http_read_early_body(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rc = ngx_http_test_predicates(r, cscf->client_body_early_read); + + if (rc == NGX_ERROR) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_DONE; + } + + if (rc == NGX_OK) { + return NGX_OK; + } + + /* rc == NGX_DECLINED */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http client request body early read"); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_in.content_length_n != -1 + && !r->discard_body + && clcf->client_max_body_size + && clcf->client_max_body_size < r->headers_in.content_length_n) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large body: %O bytes", + r->headers_in.content_length_n); + + r->expect_tested = 1; + (void) ngx_http_discard_request_body(r); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE); + return NGX_DONE; + } + + rc = ngx_http_read_client_request_body(r, ngx_http_handler); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_finalize_request(r, rc); + return NGX_DONE; + } + + ngx_http_finalize_request(r, NGX_DONE); + + return NGX_DONE; +} + + ngx_int_t ngx_http_validate_host(ngx_str_t *host, in_port_t *portp, ngx_pool_t *pool, ngx_uint_t alloc) -- 2.47.3