rxreq
expect req.bodylen == 257
txresp
+
+ accept
+
+ rxreq
+ expect req.bodylen == 2
+ txresp
} -start
syslog S -level info {
expect ~ "[^:\\[ ]*\\[[0-9]*\\]: .* .* fe1 fe1/<NOSRV> .* 408 .* - - cD-- .* .* \"GET /this-is-a-long-url-this-is-a-long-url-this-is-a-long-url-this-is-a-long-url-this-is-a-long-url-this-is-a-long-url-this-is-a-long-url HTTP/1\\.1\""
recv
expect ~ "[^:\\[ ]*\\[[0-9]*\\]: .* .* fe1 be1/srv1 [0-9]*/[0-9]*/[0-9]*/[0-9]*/[0-9]* 200 .* - - ---- .* .* \"GET / HTTP/1\\.1\""
+ recv
+ expect ~ "[^:\\[ ]*\\[[0-9]*\\]: .* .* fe1 be1/srv1 [0-9]*/[0-9]*/[0-9]*/[0-9]*/[0-9]* 200 .* - - ---- .* .* \"POST /1 HTTP/1\\.1\""
+ recv
+ expect ~ "[^:\\[ ]*\\[[0-9]*\\]: .* .* fe1 be1/<NOSRV> [0-9]*/-1/-1/-1/[0-9]* -1 .* - - CR-- .* .* \"POST /2 HTTP/1\\.1\""
} -start
haproxy h1 -conf {
use_backend be1
} -start
+# 1 byte of the payload is missing.
+# ==> The request must timed out with a 408 response
client c1 -connect ${h1_fe1_sock} {
send "GET"
send " "
expect resp.status == 408
} -run
+# Payload is fully sent
+# ==> Request must be sent to the server. A 200 must be received
client c2 -connect ${h1_fe1_sock} {
txreq -bodylen 257
rxresp
expect resp.status == 200
} -run
-syslog S -wait
+# Payload is fully sent in 2 steps (with a small delay, smaller than the client
+# timeout) and splitted on a chunk size.
+# ==> Request must be sent to the server. A 200 must be received
+client c3 -connect ${h1_fe1_sock} {
+ send "POST /1 HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1\r\n1\r\n1"
+ delay 0.01
+ send "\r\n1\r\n0\r\n\r\n"
+ rxresp
+ expect resp.status == 200
+} -run
+# Last CRLF of the request payload is missing but payload is sent in 2 steps
+# (with a small delay, smaller than the client timeout) and splitted on a chunk
+# size. The client aborts before sending the last CRLF.
+# ==> Request must be handled as an error with 'CR--' termination state.
+client c4 -connect ${h1_fe1_sock} {
+ send "POST /2 HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1\r\n1\r\n1"
+ delay 0.01
+ send "\r\n1\r\n0\r\n"
+} -run
+
+syslog S -wait
if (h1_eval_htx_size(meth, uri, vsn, hdrs) > max) {
if (htx_is_empty(htx))
goto error;
- h1m_init_req(h1m);
- h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
- return 0;
+ goto output_full;
}
/* By default, request have always a known length */
end:
return 1;
+ output_full:
+ h1m_init_req(h1m);
+ h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
+ return -2;
error:
h1m->err_pos = h1m->next;
h1m->err_state = h1m->state;
htx->flags |= HTX_FL_PARSING_ERROR;
- return 0;
+ return -1;
}
/* Postprocess the parsed headers for a response and convert them into an htx
if (h1_eval_htx_size(vsn, status, reason, hdrs) > max) {
if (htx_is_empty(htx))
goto error;
- h1m_init_res(h1m);
- h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
- return 0;
+ goto output_full;
}
if (((h1m->flags & H1_MF_METH_CONNECT) && code == 200) || code == 101) {
end:
return 1;
+ output_full:
+ h1m_init_res(h1m);
+ h1m->flags |= (H1_MF_NO_PHDR|H1_MF_CLEAN_CONN_HDR);
+ return -2;
error:
h1m->err_pos = h1m->next;
h1m->err_state = h1m->state;
htx->flags |= HTX_FL_PARSING_ERROR;
- return 0;
+ return -1;
}
-/* Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if
- * it couldn't proceed. Parsing errors are reported by setting the htx flag
- * HTX_FL_PARSING_ERROR and filling h1m->err_pos and h1m->err_state fields. This
- * functions is responsible to update the parser state <h1m> and the start-line
- * <h1sl> if not NULL.
- * For the requests, <h1sl> must always be provided. For responses, <h1sl> may
- * be NULL and <h1m> flags HTTP_METH_CONNECT of HTTP_METH_HEAD may be set.
+/* Parse HTTP/1 headers. It returns the number of bytes parsed on success, 0 if
+ * headers are incomplete, -1 if an error occurred or -2 if it needs more space
+ * to proceed while the output buffer is not empty. Parsing errors are reported
+ * by setting the htx flag HTX_FL_PARSING_ERROR and filling h1m->err_pos and
+ * h1m->err_state fields. This functions is responsible to update the parser
+ * state <h1m> and the start-line <h1sl> if not NULL. For the requests, <h1sl>
+ * must always be provided. For responses, <h1sl> may be NULL and <h1m> flags
+ * HTTP_METH_CONNECT of HTTP_METH_HEAD may be set.
*/
int h1_parse_msg_hdrs(struct h1m *h1m, union h1_sl *h1sl, struct htx *dsthtx,
- struct buffer *srcbuf, size_t ofs, size_t max)
+ struct buffer *srcbuf, size_t ofs, size_t max)
{
struct http_hdr hdrs[global.tune.max_http_hdr];
- int ret = 0;
+ int total = 0, ret = 0;
if (!max || !b_data(srcbuf))
goto end;
goto error;
goto end;
}
+ total = ret;
/* messages headers fully parsed, do some checks to prepare the body
* parsing.
h1m->err_state = h1m->state;
goto vsn_error;
}
- if (!h1_postparse_req_hdrs(h1m, h1sl, dsthtx, hdrs, max))
- ret = 0;
+ ret = h1_postparse_req_hdrs(h1m, h1sl, dsthtx, hdrs, max);
+ if (ret < 0)
+ return ret;
}
else {
if (h1sl && !h1_process_res_vsn(h1m, h1sl)) {
h1m->err_state = h1m->state;
goto vsn_error;
}
- if (!h1_postparse_res_hdrs(h1m, h1sl, dsthtx, hdrs, max))
- ret = 0;
+ ret = h1_postparse_res_hdrs(h1m, h1sl, dsthtx, hdrs, max);
+ if (ret < 0)
+ return ret;
}
/* Switch messages without any payload to DONE state */
h1m->state = H1_MSG_DONE;
end:
- return ret;
+ return total;
error:
h1m->err_pos = h1m->next;
h1m->err_state = h1m->state;
vsn_error:
dsthtx->flags |= HTX_FL_PARSING_ERROR;
- return 0;
+ return -1;
}
return total;
}
-/* Parse HTTP/1 trailers. It returns the number of bytes parsed if > 0, or 0 if
- * it couldn't proceed. Parsing errors are reported by setting the htx flags
- * HTX_FL_PARSING_ERROR and filling h1m->err_pos and h1m->err_state fields. This
- * functions is responsible to update the parser state <h1m>.
+/* Parse HTTP/1 trailers. It returns the number of bytes parsed on success, 0 if
+ * trailers are incomplete, -1 if an error occurred or -2 if it needs more space
+ * to proceed while the output buffer is not empty. Parsing errors are reported
+ * by setting the htx flags HTX_FL_PARSING_ERROR and filling h1m->err_pos and
+ * h1m->err_state fields. This functions is responsible to update the parser
+ * state <h1m>.
*/
int h1_parse_msg_tlrs(struct h1m *h1m, struct htx *dsthtx,
struct buffer *srcbuf, size_t ofs, size_t max)
if (h1_eval_htx_hdrs_size(hdrs) > max) {
if (htx_is_empty(dsthtx))
goto error;
- ret = 0;
- goto end;
+ goto output_full;
}
if (!htx_add_all_trailers(dsthtx, hdrs))
end:
return ret;
+ output_full:
+ return -2;
error:
h1m->err_state = h1m->state;
h1m->err_pos = h1m->next;
dsthtx->flags |= HTX_FL_PARSING_ERROR;
- return 0;
+ return -1;
}
/* Finish HTTP/1 parsing by adding the HTX EOM block. It returns 1 on success or
#define H1S_F_ERROR 0x00000001 /* An error occurred on the H1 stream */
#define H1S_F_REQ_ERROR 0x00000002 /* An error occurred during the request parsing/xfer */
#define H1S_F_RES_ERROR 0x00000004 /* An error occurred during the response parsing/xfer */
+
#define H1S_F_REOS 0x00000008 /* End of input stream seen even if not delivered yet */
#define H1S_F_WANT_KAL 0x00000010
#define H1S_F_WANT_TUN 0x00000020
#define H1S_F_BUF_FLUSH 0x00000100 /* Flush input buffer and don't read more data */
#define H1S_F_SPLICED_DATA 0x00000200 /* Set when the kernel splicing is in used */
#define H1S_F_PARSING_DONE 0x00000400 /* Set when incoming message parsing is finished (EOM added) */
-/* 0x00000800 .. 0x00001000 unused */
+/* 0x00000800 unused */
+#define H1S_F_RX_CONGESTED 0x00001000 /* Cannot process input data RX path is congested (waiting for more space in channel's buffer) */
#define H1S_F_HAVE_SRV_NAME 0x00002000 /* Set during output process if the server name header was added to the request */
#define H1S_F_HAVE_O_CONN 0x00004000 /* Set during output process to know connection mode was processed */
/*
* Parse HTTP/1 headers. It returns the number of bytes parsed if > 0, or 0 if
* it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
- * flag. If relies on the function http_parse_msg_hdrs() to do the parsing.
+ * flag. If more room is requested, H1S_F_RX_CONGESTED flag is set. If relies on
+ * the function http_parse_msg_hdrs() to do the parsing.
*/
static size_t h1_process_headers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max)
}
ret = h1_parse_msg_hdrs(h1m, &h1sl, htx, buf, *ofs, max);
- if (!ret) {
+ if (ret <= 0) {
TRACE_DEVEL("leaving on missing data or error", H1_EV_RX_DATA|H1_EV_RX_HDRS, h1s->h1c->conn, h1s);
- if (htx->flags & HTX_FL_PARSING_ERROR) {
+ if (ret == -1) {
if (!(h1m->flags & H1_MF_RESP)) {
h1s->flags |= H1S_F_REQ_ERROR;
TRACE_USER("rejected H1 request", H1_EV_RX_DATA|H1_EV_RX_HDRS|H1_EV_H1S_ERR, h1s->h1c->conn, h1s);
TRACE_STATE("parsing error", H1_EV_RX_DATA|H1_EV_RX_HDRS|H1_EV_H1S_ERR, h1s->h1c->conn, h1s);
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
}
+ else if (ret == -2) {
+ TRACE_STATE("RX path congested, waiting for more space", H1_EV_RX_DATA|H1_EV_RX_HDRS|H1_EV_H1S_BLK, h1s->h1c->conn, h1s);
+ h1s->flags |= H1S_F_RX_CONGESTED;
+ }
+ ret = 0;
goto end;
}
*ofs += ret;
end:
+ if (b_data(buf) != *ofs && (h1m->state == H1_MSG_DATA || h1m->state == H1_MSG_TUNNEL)) {
+ TRACE_STATE("RX path congested, waiting for more space", H1_EV_RX_DATA|H1_EV_RX_BODY|H1_EV_H1S_BLK, h1s->h1c->conn, h1s);
+ h1s->flags |= H1S_F_RX_CONGESTED;
+ }
+
TRACE_LEAVE(H1_EV_RX_DATA|H1_EV_RX_BODY, h1s->h1c->conn, h1s, 0, (size_t[]){ret});
return ret;
}
* Parse HTTP/1 trailers. It returns the number of bytes parsed if > 0, or 0 if
* it couldn't proceed. Parsing errors are reported by setting H1S_F_*_ERROR
* flag and filling h1s->err_pos and h1s->err_state fields. This functions is
- * responsible to update the parser state <h1m>.
+ * responsible to update the parser state <h1m>. If more room is requested,
+ * H1S_F_RX_CONGESTED flag is set.
*/
static size_t h1_process_trailers(struct h1s *h1s, struct h1m *h1m, struct htx *htx,
struct buffer *buf, size_t *ofs, size_t max)
TRACE_ENTER(H1_EV_RX_DATA|H1_EV_RX_TLRS, h1s->h1c->conn, h1s, 0, (size_t[]){max});
ret = h1_parse_msg_tlrs(h1m, htx, buf, *ofs, max);
- if (!ret) {
+ if (ret <= 0) {
TRACE_DEVEL("leaving on missing data or error", H1_EV_RX_DATA|H1_EV_RX_BODY, h1s->h1c->conn, h1s);
- if (htx->flags & HTX_FL_PARSING_ERROR) {
+ if (ret == -1) {
if (!(h1m->flags & H1_MF_RESP)) {
h1s->flags |= H1S_F_REQ_ERROR;
TRACE_USER("rejected H1 request", H1_EV_RX_DATA|H1_EV_RX_TLRS|H1_EV_H1S_ERR, h1s->h1c->conn, h1s);
TRACE_STATE("parsing error", H1_EV_RX_DATA|H1_EV_RX_TLRS|H1_EV_H1S_ERR, h1s->h1c->conn, h1s);
h1_capture_bad_message(h1s->h1c, h1s, h1m, buf);
}
+ else if (ret == -2) {
+ TRACE_STATE("RX path congested, waiting for more space", H1_EV_RX_DATA|H1_EV_RX_TLRS|H1_EV_H1S_BLK, h1s->h1c->conn, h1s);
+ h1s->flags |= H1S_F_RX_CONGESTED;
+ }
+ ret = 0;
goto end;
}
* Process incoming data. It parses data and transfer them from h1c->ibuf into
* <buf>. It returns the number of bytes parsed and transferred if > 0, or 0 if
* it couldn't proceed.
+ *
+ * WARNING: H1S_F_RX_CONGESTED flag must be removed before processing input data.
*/
static size_t h1_process_input(struct h1c *h1c, struct buffer *buf, size_t count)
{
if (h1c->flags & H1C_F_IN_BUSY)
goto end;
+ /* Always remove congestion flags and try to process more input data */
+ h1s->flags &= ~H1S_F_RX_CONGESTED;
+
do {
size_t used = htx_used_space(htx);
}
count -= htx_used_space(htx) - used;
- } while (!(h1s->flags & errflag));
+ } while (!(h1s->flags & (errflag|H1S_F_RX_CONGESTED)));
if (h1s->flags & errflag) {
TRACE_PROTO("parsing error", H1_EV_RX_DATA, h1c->conn, h1s);
if (!b_data(&h1c->ibuf))
h1_release_buf(h1c, &h1c->ibuf);
- if (h1s_data_pending(h1s) && !htx_is_empty(htx))
+
+ /* When Input data are pending for this message, notify upper layer that
+ * the mux need more space in the HTX buffer to continue if :
+ *
+ * - The parser is blocked in MSG_DATA or MSG_TUNNEL state
+ * - Headers or trailers are pending to be copied.
+ */
+ if (h1s->flags & (H1S_F_RX_CONGESTED)) {
h1s->cs->flags |= CS_FL_RCV_MORE | CS_FL_WANT_ROOM;
+ TRACE_STATE("waiting for more room", H1_EV_RX_DATA|H1_EV_H1S_BLK, h1c->conn, h1s);
+ }
else if (h1s->flags & H1S_F_REOS) {
h1s->cs->flags |= CS_FL_EOS;
if (h1m->state >= H1_MSG_DONE)