[PATCH 7 of 7] Added the "client_body_min_rate" configuration directive

Maxim Dounin mdounin at mdounin.ru
Wed Jun 18 12:37:39 UTC 2025


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1750204984 -10800
#      Wed Jun 18 03:03:04 2025 +0300
# Node ID 121d6685362076da8b261e92674b17c2a7ca145e
# Parent  422bc6906b928c937e053a44c9854815223ff609
Added the "client_body_min_rate" configuration directive.

Similarly to send_min_rate, this directive limits minimum allowed data rate
when reading the request body.

diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -167,6 +167,7 @@ void ngx_http_test_reading(ngx_http_requ
 
 
 void ngx_http_send_timeout(ngx_http_request_t *r, off_t sent);
+void ngx_http_request_body_timeout(ngx_http_request_t *r, off_t bytes);
 
 
 char *ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -373,6 +373,13 @@ static ngx_command_t  ngx_http_core_comm
       offsetof(ngx_http_core_loc_conf_t, client_body_timeout),
       NULL },
 
+    { ngx_string("client_body_min_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_body_min_rate),
+      NULL },
+
     { ngx_string("client_body_temp_path"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
       ngx_conf_set_path_slot,
@@ -3594,6 +3601,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t
     clcf->client_max_body_size = NGX_CONF_UNSET;
     clcf->client_body_buffer_size = NGX_CONF_UNSET_SIZE;
     clcf->client_body_timeout = NGX_CONF_UNSET_MSEC;
+    clcf->client_body_min_rate = NGX_CONF_UNSET_SIZE;
     clcf->satisfy = NGX_CONF_UNSET_UINT;
     clcf->auth_delay = NGX_CONF_UNSET_MSEC;
     clcf->if_modified_since = NGX_CONF_UNSET_UINT;
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -359,6 +359,7 @@ struct ngx_http_core_loc_conf_s {
     off_t         directio_alignment;      /* directio_alignment */
 
     size_t        client_body_buffer_size; /* client_body_buffer_size */
+    size_t        client_body_min_rate;    /* client_body_min_rate */
     size_t        send_min_rate;           /* send_min_rate */
     size_t        send_lowat;              /* send_lowat */
     size_t        postpone_output;         /* postpone_output */
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -303,6 +303,8 @@ typedef struct {
     ngx_buf_t                        *buf;
     off_t                             rest;
     off_t                             received;
+    ngx_msec_t                        rate_last;
+    off_t                             rate_excess;
     ngx_chain_t                      *free;
     ngx_chain_t                      *busy;
     ngx_http_chunked_t               *chunked;
diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c
--- a/src/http/ngx_http_request_body.c
+++ b/src/http/ngx_http_request_body.c
@@ -12,6 +12,8 @@
 
 static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r);
 static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r);
+static ngx_int_t ngx_http_request_body_min_rate(ngx_http_request_t *r,
+    off_t bytes);
 static ngx_int_t ngx_http_copy_pipelined_header(ngx_http_request_t *r,
     ngx_buf_t *buf);
 static ngx_int_t ngx_http_write_request_body(ngx_http_request_t *r);
@@ -84,6 +86,7 @@ ngx_http_read_client_request_body(ngx_ht
      *     rb->busy = NULL;
      *     rb->chunked = NULL;
      *     rb->received = 0;
+     *     rb->rate_last = 0;
      *     rb->no_buffering = 0;
      *     rb->filter_need_buffering = 0;
      *     rb->last_sent = 0;
@@ -334,19 +337,19 @@ ngx_http_read_client_request_body_handle
 static ngx_int_t
 ngx_http_do_read_client_request_body(ngx_http_request_t *r)
 {
-    off_t                      rest;
-    size_t                     size;
-    ssize_t                    n;
-    ngx_int_t                  rc;
-    ngx_uint_t                 flush;
-    ngx_chain_t                out;
-    ngx_connection_t          *c;
-    ngx_http_request_body_t   *rb;
-    ngx_http_core_loc_conf_t  *clcf;
+    off_t                     rest, bytes;
+    size_t                    size;
+    ssize_t                   n;
+    ngx_int_t                 rc;
+    ngx_uint_t                flush;
+    ngx_chain_t               out;
+    ngx_connection_t         *c;
+    ngx_http_request_body_t  *rb;
 
     c = r->connection;
     rb = r->request_body;
     flush = 1;
+    bytes = 0;
     n = NGX_AGAIN;
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -382,9 +385,7 @@ ngx_http_do_read_client_request_body(ngx
                     }
 
                     if (rb->filter_need_buffering) {
-                        clcf = ngx_http_get_module_loc_conf(r,
-                                                         ngx_http_core_module);
-                        ngx_add_timer(c->read, clcf->client_body_timeout);
+                        ngx_http_request_body_timeout(r, bytes);
 
                         if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                             return NGX_HTTP_INTERNAL_SERVER_ERROR;
@@ -436,6 +437,7 @@ ngx_http_do_read_client_request_body(ngx
 
             rb->buf->last += n;
             r->request_length += n;
+            bytes += n;
 
             /* pass buffer to request body filter chain */
 
@@ -475,8 +477,7 @@ ngx_http_do_read_client_request_body(ngx
 
         if (n == NGX_AGAIN || !c->read->ready || rb->rest == 0) {
 
-            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-            ngx_add_timer(c->read, clcf->client_body_timeout);
+            ngx_http_request_body_timeout(r, bytes);
 
             if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                 return NGX_HTTP_INTERNAL_SERVER_ERROR;
@@ -504,6 +505,67 @@ ngx_http_do_read_client_request_body(ngx
 }
 
 
+void
+ngx_http_request_body_timeout(ngx_http_request_t *r, off_t bytes)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (!ngx_http_request_body_min_rate(r, bytes)) {
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    ngx_add_timer(r->connection->read, clcf->client_body_timeout);
+}
+
+
+static ngx_int_t
+ngx_http_request_body_min_rate(ngx_http_request_t *r, off_t bytes)
+{
+    ngx_msec_t                 now;
+    ngx_msec_int_t             ms;
+    ngx_connection_t          *c;
+    ngx_http_request_body_t   *rb;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    rb = r->request_body;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->client_body_min_rate == 0) {
+        return (bytes > 0 || !c->read->timer_set);
+    }
+
+    now = ngx_current_msec;
+
+    if (rb->rate_last == 0 || !c->read->timer_set) {
+        rb->rate_last = now;
+        rb->rate_excess = 0;
+        return 1;
+    }
+
+    ms = (ngx_msec_int_t) (now - rb->rate_last);
+    ms = ngx_max(ms, 0);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http body min rate: %O, %O, %M",
+                   bytes, rb->rate_excess, ms);
+
+    if (rb->rate_excess + bytes
+        > (off_t) clcf->client_body_min_rate * ms / 1000)
+    {
+        rb->rate_last = now;
+        rb->rate_excess = 0;
+        return 1;
+    }
+
+    rb->rate_excess += bytes;
+
+    return 0;
+}
+
+
 static ngx_int_t
 ngx_http_copy_pipelined_header(ngx_http_request_t *r, ngx_buf_t *buf)
 {
diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c
+++ b/src/http/v2/ngx_http_v2.c
@@ -3988,7 +3988,7 @@ ngx_http_v2_read_request_body(ngx_http_r
     }
 
     if (!buf) {
-        ngx_add_timer(r->connection->read, clcf->client_body_timeout);
+        ngx_http_request_body_timeout(r, 0);
     }
 
     r->read_event_handler = ngx_http_v2_read_client_request_body_handler;
@@ -4002,11 +4002,11 @@ static ngx_int_t
 ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos,
     size_t size, ngx_uint_t last, ngx_uint_t flush)
 {
-    size_t                     n;
-    ngx_int_t                  rc;
-    ngx_connection_t          *fc;
-    ngx_http_request_body_t   *rb;
-    ngx_http_core_loc_conf_t  *clcf;
+    off_t                     bytes;
+    size_t                    n;
+    ngx_int_t                 rc;
+    ngx_connection_t         *fc;
+    ngx_http_request_body_t  *rb;
 
     fc = r->connection;
     rb = r->request_body;
@@ -4018,6 +4018,8 @@ ngx_http_v2_process_request_body(ngx_htt
         return NGX_AGAIN;
     }
 
+    bytes = 0;
+
     for ( ;; ) {
         for ( ;; ) {
             if (rb->buf->last == rb->buf->end && size) {
@@ -4070,6 +4072,7 @@ ngx_http_v2_process_request_body(ngx_htt
 
             pos += n;
             size -= n;
+            bytes += n;
 
             if (size == 0 && last) {
                 rb->rest = 0;
@@ -4096,8 +4099,7 @@ ngx_http_v2_process_request_body(ngx_htt
         }
 
         if (size == 0) {
-            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-            ngx_add_timer(fc->read, clcf->client_body_timeout);
+            ngx_http_request_body_timeout(r, bytes);
 
             if (!flush) {
                 ngx_post_event(fc->read, &ngx_posted_events);
diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c
--- a/src/http/v3/ngx_http_v3_request.c
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -1336,19 +1336,19 @@ ngx_http_v3_read_unbuffered_request_body
 static ngx_int_t
 ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r)
 {
-    off_t                      rest;
-    size_t                     size;
-    ssize_t                    n;
-    ngx_int_t                  rc;
-    ngx_uint_t                 flush;
-    ngx_chain_t                out;
-    ngx_connection_t          *c;
-    ngx_http_request_body_t   *rb;
-    ngx_http_core_loc_conf_t  *clcf;
+    off_t                     rest, bytes;
+    size_t                    size;
+    ssize_t                   n;
+    ngx_int_t                 rc;
+    ngx_uint_t                flush;
+    ngx_chain_t               out;
+    ngx_connection_t         *c;
+    ngx_http_request_body_t  *rb;
 
     c = r->connection;
     rb = r->request_body;
     flush = 1;
+    bytes = 0;
     n = NGX_AGAIN;
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -1384,9 +1384,7 @@ ngx_http_v3_do_read_client_request_body(
                     }
 
                     if (rb->filter_need_buffering) {
-                        clcf = ngx_http_get_module_loc_conf(r,
-                                                         ngx_http_core_module);
-                        ngx_add_timer(c->read, clcf->client_body_timeout);
+                        ngx_http_request_body_timeout(r, bytes);
 
                         if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                             return NGX_HTTP_INTERNAL_SERVER_ERROR;
@@ -1436,6 +1434,7 @@ ngx_http_v3_do_read_client_request_body(
             }
 
             rb->buf->last += n;
+            bytes += n;
 
             /* pass buffer to request body filter chain */
 
@@ -1475,8 +1474,7 @@ ngx_http_v3_do_read_client_request_body(
 
         if (n == NGX_AGAIN || !c->read->ready || rb->rest == 0) {
 
-            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-            ngx_add_timer(c->read, clcf->client_body_timeout);
+            ngx_http_request_body_timeout(r, bytes);
 
             if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                 return NGX_HTTP_INTERNAL_SERVER_ERROR;



More information about the nginx-devel mailing list