]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: lua: Fully consume large requests when an HTTP applet ends
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 27 Feb 2019 21:06:23 +0000 (22:06 +0100)
committerWilly Tarreau <w@1wt.eu>
Tue, 19 Mar 2019 08:49:50 +0000 (09:49 +0100)
In Lua, when an HTTP applet ends (in HTX and legacy HTTP), we must flush
remaining outgoing data on the request. But only outgoing data at time the
applet is called are consumed. If a request with a huge body is sent, an error
is triggerred because a SHUTW is catched for an unfinisehd request.

Now, we consume request data until the end. In fact, we don't try to shutdown
the request's channel for write anymore.

This patch must be backported to 1.9 after some observation period. It should
probably be backported in prior versions too. But honnestly, with refactoring
on the connection layer and the stream interface in 1.9, it is probably safer
to not do so.

src/hlua.c

index c95eb374e3ad3d951d7d2f5d2996b794965e8498..1667b4ab2f97f28f29d87231ed42529bf0b793e6 100644 (file)
@@ -159,6 +159,7 @@ static int hlua_panic_ljmp(lua_State *L) { longjmp(safe_ljmp_env, 1); }
 #define APPLET_CHUNKED  0x08 /* Use transfer encoding chunked. */
 #define APPLET_LAST_CHK 0x10 /* Last chunk sent. */
 #define APPLET_HTTP11   0x20 /* Last chunk sent. */
+#define APPLET_RSP_SENT 0x40 /* The response was fully sent */
 
 /* The main Lua execution context. */
 struct hlua gL;
@@ -7299,7 +7300,7 @@ static void hlua_applet_htx_fct(struct appctx *ctx)
                goto out;
        }
        /* check that the output is not closed */
-       if (res->flags & (CF_SHUTW|CF_SHUTW_NOW))
+       if (res->flags & (CF_SHUTW|CF_SHUTW_NOW|CF_SHUTR))
                ctx->ctx.hlua_apphttp.flags |= APPLET_DONE;
 
        /* Set the currently running flag. */
@@ -7352,7 +7353,7 @@ static void hlua_applet_htx_fct(struct appctx *ctx)
                case HLUA_E_AGAIN:
                        if (hlua->wake_time != TICK_ETERNITY)
                                task_schedule(ctx->ctx.hlua_apphttp.task, hlua->wake_time);
-                       return;
+                       goto out;
 
                /* finished with error. */
                case HLUA_E_ERRMSG:
@@ -7389,6 +7390,9 @@ static void hlua_applet_htx_fct(struct appctx *ctx)
        }
 
        if (ctx->ctx.hlua_apphttp.flags & APPLET_DONE) {
+               if (ctx->ctx.hlua_apphttp.flags & APPLET_RSP_SENT)
+                       goto done;
+
                if (!(ctx->ctx.hlua_apphttp.flags & APPLET_HDR_SENT))
                        goto error;
 
@@ -7398,40 +7402,27 @@ static void hlua_applet_htx_fct(struct appctx *ctx)
                        goto out;
                }
                channel_add_input(res, 1);
+               strm->txn->status = ctx->ctx.hlua_apphttp.status;
+               ctx->ctx.hlua_apphttp.flags |= APPLET_RSP_SENT;
        }
 
   done:
        if (ctx->ctx.hlua_apphttp.flags & APPLET_DONE) {
-               /* eat the whole request */
-               req_htx = htxbuf(&req->buf);
-               htx_reset(req_htx);
-               htx_to_buf(req_htx, &req->buf);
-               co_set_data(req, 0);
-               res->flags |= CF_READ_NULL;
-               si_shutr(si);
-       }
-
-       if ((res->flags & CF_SHUTR) && (si->state == SI_ST_EST))
-               si_shutw(si);
-
-       if (ctx->ctx.hlua_apphttp.flags & APPLET_DONE) {
-               if ((req->flags & CF_SHUTW) && (si->state == SI_ST_EST)) {
-                       si_shutr(si);
+               if (!(res->flags & CF_SHUTR)) {
                        res->flags |= CF_READ_NULL;
+                       si_shutr(si);
+               }
+
+               /* eat the whole request */
+               if (co_data(req)) {
+                       req_htx = htx_from_buf(&req->buf);
+                       co_htx_skip(req, req_htx, co_data(req));
+                       htx_to_buf(req_htx, &req->buf);
                }
        }
 
   out:
-       /* we have left the request in the buffer for the case where we
-        * process a POST, and this automatically re-enables activity on
-        * read. It's better to indicate that we want to stop reading when
-        * we're sending, so that we know there's at most one direction
-        * deciding to wake the applet up. It saves it from looping when
-        * emitting large blocks into small TCP windows.
-        */
        htx_to_buf(res_htx, &res->buf);
-       if (!channel_is_empty(res))
-               si_stop_get(si);
        return;
 
   error:
@@ -7460,6 +7451,7 @@ static void hlua_applet_http_fct(struct appctx *ctx)
 {
        struct stream_interface *si = ctx->owner;
        struct stream *strm = si_strm(si);
+       struct channel *req = si_oc(si);
        struct channel *res = si_ic(si);
        struct act_rule *rule = ctx->rule;
        struct proxy *px = strm->be;
@@ -7475,7 +7467,16 @@ static void hlua_applet_http_fct(struct appctx *ctx)
 
        /* If the stream is disconnect or closed, ldo nothing. */
        if (unlikely(si->state == SI_ST_DIS || si->state == SI_ST_CLO))
-               return;
+               goto out;
+
+       /* Check if the input buffer is avalaible. */
+       if (!b_size(&res->buf)) {
+               si_rx_room_blk(si);
+               goto out;
+       }
+       /* check that the output is not closed */
+       if (res->flags & (CF_SHUTW|CF_SHUTW_NOW|CF_SHUTR))
+               ctx->ctx.hlua_apphttp.flags |= APPLET_DONE;
 
        /* Set the currently running flag. */
        if (!HLUA_IS_RUNNING(hlua) &&
@@ -7488,9 +7489,9 @@ static void hlua_applet_http_fct(struct appctx *ctx)
                 */
 
                /* Read the maximum amount of data available. */
-               ret = co_getblk_nc(si_oc(si), &blk1, &len1, &blk2, &len2);
+               ret = co_getblk_nc(req, &blk1, &len1, &blk2, &len2);
                if (ret == -1)
-                       return;
+                       goto out;
 
                /* No data available, ask for more data. */
                if (ret == 1)
@@ -7499,11 +7500,11 @@ static void hlua_applet_http_fct(struct appctx *ctx)
                        len1 = 0;
                if (len1 + len2 < strm->txn->req.eoh + strm->txn->req.eol) {
                        si_cant_get(si);
-                       return;
+                       goto out;
                }
 
                /* skip the requests bytes. */
-               co_skip(si_oc(si), strm->txn->req.eoh + strm->txn->req.eol);
+               co_skip(req, strm->txn->req.eoh + strm->txn->req.eol);
        }
 
        /* Executes The applet if it is not done. */
@@ -7520,7 +7521,7 @@ static void hlua_applet_http_fct(struct appctx *ctx)
                case HLUA_E_AGAIN:
                        if (hlua->wake_time != TICK_ETERNITY)
                                task_schedule(ctx->ctx.hlua_apphttp.task, hlua->wake_time);
-                       return;
+                       goto out;
 
                /* finished with error. */
                case HLUA_E_ERRMSG:
@@ -7557,6 +7558,9 @@ static void hlua_applet_http_fct(struct appctx *ctx)
        }
 
        if (ctx->ctx.hlua_apphttp.flags & APPLET_DONE) {
+               if (ctx->ctx.hlua_apphttp.flags & APPLET_RSP_SENT)
+                       goto done;
+
                if (!(ctx->ctx.hlua_apphttp.flags & APPLET_HDR_SENT))
                        goto error;
 
@@ -7577,39 +7581,44 @@ static void hlua_applet_http_fct(struct appctx *ctx)
                        /* no enough space error. */
                        if (ret == -1) {
                                si_rx_room_blk(si);
-                               return;
+                               goto out;
                        }
 
-                       /* set the last chunk sent. */
-                       ctx->ctx.hlua_apphttp.flags |= APPLET_LAST_CHK;
+                       strm->txn->status = ctx->ctx.hlua_apphttp.status;
+                       ctx->ctx.hlua_apphttp.flags |= (APPLET_LAST_CHK|APPLET_RSP_SENT);
                }
+       }
 
-               /* close the connection. */
-
-               /* status */
-               strm->txn->status = ctx->ctx.hlua_apphttp.status;
+  done:
+       if (ctx->ctx.hlua_apphttp.flags & APPLET_DONE) {
+               if (!(res->flags & CF_SHUTR)) {
+                       res->flags |= CF_READ_NULL;
+                       si_shutr(si);
+               }
 
                /* eat the whole request */
-               co_skip(si_oc(si), co_data(si_oc(si)));
-               res->flags |= CF_READ_NULL;
-               si_shutr(si);
-
-               return;
+               if (co_data(req))
+                       co_skip(req, co_data(req));
        }
 
-error:
+  out:
+       return;
+
+  error:
 
        /* If we are in HTTP mode, and we are not send any
         * data, return a 500 server error in best effort:
         * if there is no room available in the buffer,
         * just close the connection.
         */
-       ci_putblk(res, error_500, strlen(error_500));
+       if (!(ctx->ctx.hlua_apphttp.flags & APPLET_HDR_SENT)) {
+               channel_erase(res);
+               ci_putblk(res, error_500, strlen(error_500));
+       }
        if (!(strm->flags & SF_ERR_MASK))
                strm->flags |= SF_ERR_RESOURCE;
-       si_shutw(si);
-       si_shutr(si);
        ctx->ctx.hlua_apphttp.flags |= APPLET_DONE;
+       goto done;
 }
 
 static void hlua_applet_http_release(struct appctx *ctx)