]> git.kaiwu.me - nginx.git/commitdiff
Added client_body_early_read directive
authorRoman Arutyunyan <arut@nginx.com>
Wed, 8 Apr 2026 17:07:15 +0000 (21:07 +0400)
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>
Fri, 21 Aug 2026 13:00:43 +0000 (17:00 +0400)
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
src/http/ngx_http_core_module.h
src/http/ngx_http_request.c

index fb4b227ac7c4f2816bf340e5f19868a24f7cbd1a..c6e20adcfb7f53c6d68552c77a353c618e2d7297 100644 (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);
index a13d7ade5680e207d00219ad32511959837c4674..d07754b5be9ffab6040d67a4dd7df553c6ce22b5 100644 (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;
index ed9a7e8ee77e900bfe6f2b342e24d29c8bee6703..92b92ff09257099db40ade369945eda09cb7c528 100644 (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)