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;
    }
}
This commit is contained in:
Roman Arutyunyan
2026-08-21 17:00:43 +04:00
committed by Roman Arutyunyan
parent bc7c3424c3
commit 3f6f7824d4
3 changed files with 73 additions and 0 deletions
+11
View File
@@ -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);
+2
View File
@@ -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;
+60
View File
@@ -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)