BUG/MEDIUM: lua: Fully consume large requests when an HTTP applet ends
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.
diff --git a/src/hlua.c b/src/hlua.c
index c95eb37..1667b4a 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -159,6 +159,7 @@
#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 @@
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 @@
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 @@
}
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 @@
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 @@
{
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 @@
/* 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 @@
*/
/* 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 @@
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 @@
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 @@
}
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 @@
/* 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)