changeset 1:7c2d64d9c656

Working delay body and tests. Depends on rb->filter_need_buffering and rb->buffered, as well as corresponding changes in the request body reading code to work. Might not be the best solution, as current body reading code relies on rb->buffered to be properly set at various stages. Notably, rb->buffered must be cleared when calling the next filter, since the save body filter relies on it. Possible future improvements: implement last buffer checking in the save body filter instead of checking for (rb->rest == 0 && !rb->buffered). The code uses its own event to implement delay timer. To remove the timer in case of abnormal request termination a cleanup handler is added. While in theory it is possible to use a timer on c->read instead, this implies additional changes to the request body reading code. Custom event was chosen to reduce complexity of changes needed.
author Maxim Dounin <mdounin@mdounin.ru>
date Fri, 20 Aug 2021 22:35:05 +0300
parents a386f95c5ae9
children b049c3a0543e
files ngx_http_delay_body_filter_module.c t/delay_body.t
diffstat 2 files changed, 113 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/ngx_http_delay_body_filter_module.c
+++ b/ngx_http_delay_body_filter_module.c
@@ -9,10 +9,21 @@
 
 
 typedef struct {
-    ngx_msec_t  delay;
+    ngx_msec_t    delay;
 } ngx_http_delay_body_conf_t;
 
 
+typedef struct {
+    ngx_event_t   event;
+    ngx_chain_t  *out;
+    ngx_uint_t    buffered;
+} ngx_http_delay_body_ctx_t;
+
+
+static ngx_int_t ngx_http_delay_body_filter(ngx_http_request_t *r,
+    ngx_chain_t *in);
+static void ngx_http_delay_body_cleanup(void *data);
+static void ngx_http_delay_body_event_handler(ngx_event_t *ev);
 static void *ngx_http_delay_body_create_conf(ngx_conf_t *cf);
 static char *ngx_http_delay_body_merge_conf(ngx_conf_t *cf, void *parent,
     void *child);
@@ -69,7 +80,10 @@ static ngx_http_request_body_filter_pt  
 static ngx_int_t
 ngx_http_delay_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
 {
-    ngx_chain_t                 *cl;
+    ngx_int_t                    rc;
+    ngx_chain_t                 *cl, *ln;
+    ngx_http_cleanup_t          *cln;
+    ngx_http_delay_body_ctx_t   *ctx;
     ngx_http_delay_body_conf_t  *conf;
 
     conf = ngx_http_get_module_loc_conf(r, ngx_http_delay_body_filter_module);
@@ -81,18 +95,100 @@ ngx_http_delay_body_filter(ngx_http_requ
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "delay request body filter");
 
-    /* TODO: delay */
+    ctx = ngx_http_get_module_ctx(r, ngx_http_delay_body_filter_module);
+
+    if (ctx == NULL) {
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_delay_body_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_delay_body_filter_module);
 
-    for (cl = in; cl; cl = cl->next) {
+#if 1 /* XXX */
+        r->request_body->filter_need_buffering = 1;
+#endif
+    }
+
+    if (ngx_chain_add_copy(r->pool, &ctx->out, in) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (!ctx->event.timedout) {
+        if (!ctx->event.timer_set) {
 
-        if (cl->buf->last_buf) {
-            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                           "delay body: last buf");
+            /* cleanup to remove the timer in case of abnormal termination */
+
+            cln = ngx_http_cleanup_add(r, 0);
+            if (cln == NULL) {
+                return NGX_ERROR;
+            }
+
+            cln->handler = ngx_http_delay_body_cleanup;
+            cln->data = ctx;
+
+            /* add timer */
+
+            ctx->event.handler = ngx_http_delay_body_event_handler;
+            ctx->event.data = r;
+            ctx->event.log = r->connection->log;
+
+            ngx_add_timer(&ctx->event, conf->delay);
+
+            ctx->buffered = 1;
+#if 1 /* XXX */
+            r->request_body->buffered++;
+#endif
         }
 
+        return ngx_http_next_request_body_filter(r, NULL);
+    }
+
+    if (ctx->buffered) {
+        ctx->buffered = 0;
+#if 1 /* XXX */
+        r->request_body->buffered--;
+#endif
+    }
+
+    rc = ngx_http_next_request_body_filter(r, ctx->out);
+
+    for (cl = ctx->out; cl; /* void */) {
+        ln = cl;
+        cl = cl->next;
+        ngx_free_chain(r->pool, ln);
     }
 
-    return ngx_http_next_request_body_filter(r, in);
+    ctx->out = NULL;
+
+    return rc;
+}
+
+
+static void
+ngx_http_delay_body_cleanup(void *data)
+{
+    ngx_http_delay_body_ctx_t *ctx = data;
+
+    if (ctx->event.timer_set) {
+        ngx_del_timer(&ctx->event);
+    }
+}
+
+
+static void
+ngx_http_delay_body_event_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    r = ev->data;
+    c = r->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "delay request body event");
+
+    ngx_post_event(c->read, &ngx_posted_events);
 }
 
 
--- a/t/delay_body.t
+++ b/t/delay_body.t
@@ -35,11 +35,19 @@ http {
     server {
         listen       127.0.0.1:8080;
         server_name  localhost;
+
         location / {
             delay_body 1s;
             add_header X-Time $request_time;
             proxy_pass http://127.0.0.1:8080/empty;
         }
+
+        location /no {
+            delay_body 0s;
+            add_header X-Time $request_time;
+            proxy_pass http://127.0.0.1:8080/empty;
+        }
+
         location /empty {
             return 200 "test response body\n";
         }
@@ -53,7 +61,7 @@ EOF
 ###############################################################################
 
 like(get_body('/', '123456'), qr/200 OK.*X-Time: 1/ms, 'delay');
-like(get_body('/empty', '123456'), qr/200 OK.*X-Time: 0/ms, 'no delay');
+like(get_body('/no', '123456'), qr/200 OK.*X-Time: 0/ms, 'no delay');
 
 # pipelining