MEDIUM: mux-h2: Remove support of the legacy HTTP mode

Now the H2 multiplexer only works in HTX. Code relying on the legacy HTTP mode
was removed.
This commit is contained in:
Christopher Faulet 2019-07-15 11:22:56 +02:00
parent 319303739a
commit 9b79a1025d

View File

@ -2745,8 +2745,8 @@ static int h2_recv(struct h2c *h2c)
do {
b_realign_if_empty(buf);
if (!b_data(buf) && (h2c->proxy->options2 & PR_O2_USE_HTX)) {
/* HTX in use : try to pre-align the buffer like the
if (!b_data(buf)) {
/* try to pre-align the buffer like the
* rxbufs will be to optimize memory copies. We'll make
* sure that the frame header lands at the end of the
* HTX block to alias it upon recv. We cannot use the
@ -3194,8 +3194,7 @@ static void h2_detach(struct conn_stream *cs)
h2s_destroy(h2s);
if (h2c->flags & H2_CF_IS_BACK &&
(h2c->proxy->options2 & PR_O2_USE_HTX)) {
if (h2c->flags & H2_CF_IS_BACK) {
if (!(h2c->conn->flags &
(CO_FL_ERROR | CO_FL_SOCK_RD_SH | CO_FL_SOCK_WR_SH))) {
if (!h2c->conn->owner) {
@ -3424,12 +3423,12 @@ static void h2_shutw(struct conn_stream *cs, enum cs_shw_mode mode)
h2_do_shutw(h2s);
}
/* Decode the payload of a HEADERS frame and produce the equivalent HTTP/1 or
* HTX request or response depending on the connection's side. Returns a
* positive value on success, a negative value on failure, or 0 if it couldn't
* proceed. May report connection errors in h2c->errcode if the frame is
* non-decodable and the connection unrecoverable. In absence of connection
* error when a failure is reported, the caller must assume a stream error.
/* Decode the payload of a HEADERS frame and produce the HTX request or response
* depending on the connection's side. Returns a positive value on success, a
* negative value on failure, or 0 if it couldn't proceed. May report connection
* errors in h2c->errcode if the frame is non-decodable and the connection
* unrecoverable. In absence of connection error when a failure is reported, the
* caller must assume a stream error.
*
* The function may fold CONTINUATION frames into the initial HEADERS frame
* by removing padding and next frame header, then moving the CONTINUATION
@ -3491,7 +3490,6 @@ static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f
int ret = 0;
int outlen;
int wrap;
int try = 0;
next_frame:
if (b_data(&h2c->dbuf) - hole < h2c->dfl)
@ -3595,22 +3593,11 @@ next_frame:
* always empty except maybe for trailers, in which case we simply have
* to wait for the upper layer to finish consuming what is available.
*/
if (h2c->proxy->options2 & PR_O2_USE_HTX) {
htx = htx_from_buf(rxbuf);
if (!htx_is_empty(htx)) {
h2c->flags |= H2_CF_DEM_SFULL;
goto leave;
}
} else {
if (b_data(rxbuf)) {
h2c->flags |= H2_CF_DEM_SFULL;
goto leave;
}
rxbuf->head = 0;
try = b_size(rxbuf);
}
/* past this point we cannot roll back in case of error */
outlen = hpack_decode_frame(h2c->ddht, hdrs, flen, list,
@ -3635,18 +3622,10 @@ next_frame:
goto trailers;
/* This is the first HEADERS frame so it's a headers block */
if (htx) {
/* HTX mode */
if (h2c->flags & H2_CF_IS_BACK)
outlen = h2_make_htx_response(list, htx, &msgf, body_len);
else
outlen = h2_make_htx_request(list, htx, &msgf, body_len);
} else {
/* HTTP/1 mode */
outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf, body_len);
if (outlen > 0)
b_add(rxbuf, outlen);
}
if (outlen < 0) {
/* too large headers? this is a stream error only */
@ -3657,7 +3636,6 @@ next_frame:
/* a payload is present */
if (msgf & H2_MSGF_BODY_CL) {
*flags |= H2_SF_DATA_CLEN;
if (htx)
htx->extra = *body_len;
}
else if (!(msgf & H2_MSGF_BODY_TUNNEL) && !htx)
@ -3673,22 +3651,10 @@ next_frame:
*flags |= H2_SF_HEADERS_RCVD;
if ((h2c->dff & H2_F_HEADERS_END_STREAM)) {
/* Mark the end of message, either using EOM in HTX or with the
* trailing CRLF after the end of trailers. Note that DATA_CHNK
* is not set during headers with END_STREAM. For HTX trailers,
* we must not leave an HTX trailers block not followed by an
* EOM block, the two must be atomic. Thus if we fail to emit
* the EOM block we must remove the TLR block we've just added.
*/
if (htx) {
/* Mark the end of message using EOM */
if (!htx_add_endof(htx, HTX_BLK_EOM))
goto fail;
}
else if (*flags & H2_SF_DATA_CHNK) {
if (!b_putblk(rxbuf, "\r\n", 2))
goto fail;
}
}
/* success */
ret = 1;
@ -3721,44 +3687,19 @@ next_frame:
trailers:
/* This is the last HEADERS frame hence a trailer */
if (!(h2c->dff & H2_F_HEADERS_END_STREAM)) {
/* It's a trailer but it's missing ES flag */
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
goto fail;
}
/* Trailers terminate a DATA sequence. In HTX we always handle them. In
* legacy, when using chunks, we have to emit the 0 CRLF marker first
* and then handle the trailers. For other modes, the trailers are
* silently dropped.
*/
if (htx) {
/* Trailers terminate a DATA sequence */
if (h2_make_htx_trailers(list, htx) <= 0)
goto fail;
}
else if (*flags & H2_SF_DATA_CHNK) {
/* Legacy mode with chunked encoding : we must finalize the
* data block message emit the trailing CRLF */
if (!b_putblk(rxbuf, "0\r\n", 3))
goto fail;
outlen = h2_make_h1_trailers(list, b_tail(rxbuf), try);
if (outlen > 0)
b_add(rxbuf, outlen);
else
goto fail;
}
goto done;
}
/* Transfer the payload of a DATA frame to the HTTP/1 side. When content-length
* or a tunnel is used, the contents are copied as-is. When chunked encoding is
* in use, a new chunk is emitted for each frame. This is supposed to fit
* because the smallest chunk takes 1 byte for the size, 2 for CRLF, X for the
* data, 2 for the extra CRLF, so that's 5+X, while on the H2 side the smallest
* frame will be 9+X bytes based on the same buffer size. The HTTP/2 frame
/* Transfer the payload of a DATA frame to the HTTP/1 side. The HTTP/2 frame
* parser state is automatically updated. Returns > 0 if it could completely
* send the current frame, 0 if it couldn't complete, in which case
* CS_FL_RCV_MORE must be checked to know if some data remain pending (an empty
@ -3770,11 +3711,11 @@ next_frame:
static int h2_frt_transfer_data(struct h2s *h2s)
{
struct h2c *h2c = h2s->h2c;
int block1, block2;
int block;
unsigned int flen = 0;
unsigned int chklen = 0;
struct htx *htx = NULL;
struct buffer *csbuf;
unsigned int sent;
h2c->flags &= ~H2_CF_DEM_SFULL;
@ -3783,11 +3724,10 @@ static int h2_frt_transfer_data(struct h2s *h2s)
h2c->flags |= H2_CF_DEM_SALLOC;
goto fail;
}
htx = htx_from_buf(csbuf);
try_again:
flen = h2c->dfl - h2c->dpl;
if (h2c->proxy->options2 & PR_O2_USE_HTX)
htx = htx_from_buf(csbuf);
if (!flen)
goto end_transfer;
@ -3797,21 +3737,18 @@ try_again:
goto fail;
}
if (htx) {
unsigned int sent;
block1 = htx_free_data_space(htx);
if (!block1) {
block = htx_free_data_space(htx);
if (!block) {
h2c->flags |= H2_CF_DEM_SFULL;
goto fail;
}
if (flen > block1)
flen = block1;
if (flen > block)
flen = block;
/* here, flen is the max we can copy into the output buffer */
block1 = b_contig_data(&h2c->dbuf, 0);
if (flen > block1)
flen = block1;
block = b_contig_data(&h2c->dbuf, 0);
if (flen > block)
flen = block;
sent = htx_add_data(htx, ist2(b_head(&h2c->dbuf), flen));
@ -3831,85 +3768,6 @@ try_again:
}
goto try_again;
}
else if (unlikely(b_space_wraps(csbuf) &&
flen + chklen <= b_room(csbuf) &&
b_data(csbuf) <= MAX_DATA_REALIGN)) {
/* it doesn't fit in a single block and the buffer is fragmented, if there are
* not too many data in the buffer, let's defragment it and try
* again.
*/
b_slow_realign(csbuf, trash.area, 0);
}
/* chunked-encoding requires more room */
if (h2s->flags & H2_SF_DATA_CHNK) {
chklen = MIN(flen, b_room(csbuf));
chklen = (chklen < 16) ? 1 : (chklen < 256) ? 2 :
(chklen < 4096) ? 3 : (chklen < 65536) ? 4 :
(chklen < 1048576) ? 4 : 8;
chklen += 4; // CRLF, CRLF
}
/* does it fit in output buffer or should we wait ? */
if (flen + chklen > b_room(csbuf)) {
if (chklen >= b_room(csbuf)) {
h2c->flags |= H2_CF_DEM_SFULL;
goto fail;
}
flen = b_room(csbuf) - chklen;
}
if (h2s->flags & H2_SF_DATA_CHNK) {
/* emit the chunk size */
unsigned int chksz = flen;
char str[10];
char *beg;
beg = str + sizeof(str);
*--beg = '\n';
*--beg = '\r';
do {
*--beg = hextab[chksz & 0xF];
} while (chksz >>= 4);
b_putblk(csbuf, beg, str + sizeof(str) - beg);
}
/* Block1 is the length of the first block before the buffer wraps,
* block2 is the optional second block to reach the end of the frame.
*/
block1 = b_contig_data(&h2c->dbuf, 0);
if (block1 > flen)
block1 = flen;
block2 = flen - block1;
if (block1)
b_putblk(csbuf, b_head(&h2c->dbuf), block1);
if (block2)
b_putblk(csbuf, b_peek(&h2c->dbuf, block1), block2);
if (h2s->flags & H2_SF_DATA_CHNK) {
/* emit the CRLF */
b_putblk(csbuf, "\r\n", 2);
}
/* now mark the input data as consumed (will be deleted from the buffer
* by the caller when seeing FRAME_A after sending the window update).
*/
b_del(&h2c->dbuf, flen);
h2c->dfl -= flen;
h2c->rcvd_c += flen;
h2c->rcvd_s += flen; // warning, this can also affect the closed streams!
if (h2s->flags & H2_SF_DATA_CLEN)
h2s->body_len -= flen;
if (h2c->dfl > h2c->dpl) {
/* more data available, transfer stalled on stream full */
h2c->flags |= H2_CF_DEM_SFULL;
goto fail;
}
end_transfer:
/* here we're done with the frame, all the payload (except padding) was
@ -3917,28 +3775,16 @@ try_again:
*/
if (h2c->dff & H2_F_DATA_END_STREAM) {
if (htx) {
if (!htx_add_endof(htx, HTX_BLK_EOM)) {
h2c->flags |= H2_CF_DEM_SFULL;
goto fail;
}
}
else if (h2s->flags & H2_SF_DATA_CHNK) {
/* emit the trailing 0 CRLF CRLF */
if (b_room(csbuf) < 5) {
h2c->flags |= H2_CF_DEM_SFULL;
goto fail;
}
chklen += 5;
b_putblk(csbuf, "0\r\n\r\n", 5);
}
}
h2c->rcvd_c += h2c->dpl;
h2c->rcvd_s += h2c->dpl;
h2c->dpl = 0;
h2c->st0 = H2_CS_FRAME_A; // send the corresponding window update
if (htx)
htx_to_buf(htx, csbuf);
return 1;
fail:
@ -3947,453 +3793,6 @@ try_again:
return 0;
}
/* Try to send a HEADERS frame matching HTTP/1 response present at offset <ofs>
* and for <max> bytes in buffer <buf> for the H2 stream <h2s>. Returns the
* number of bytes sent. The caller must check the stream's status to detect
* any error which might have happened subsequently to a successful send.
*/
static size_t h2s_frt_make_resp_headers(struct h2s *h2s, const struct buffer *buf, size_t ofs, size_t max)
{
struct http_hdr list[global.tune.max_http_hdr];
struct h2c *h2c = h2s->h2c;
struct h1m *h1m = &h2s->h1m;
struct buffer outbuf;
struct buffer *mbuf;
union h1_sl sl;
int es_now = 0;
int ret = 0;
int hdr;
if (h2c_mux_busy(h2c, h2s)) {
h2s->flags |= H2_SF_BLK_MBUSY;
return 0;
}
/* First, try to parse the H1 response and index it into <list>.
* NOTE! Since it comes from haproxy, we *know* that a response header
* block does not wrap and we can safely read it this way without
* having to realign the buffer.
*/
ret = h1_headers_to_hdr_list(b_peek(buf, ofs), b_peek(buf, ofs) + max,
list, sizeof(list)/sizeof(list[0]), h1m, &sl);
if (ret <= 0) {
/* incomplete or invalid response, this is abnormal coming from
* haproxy and may only result in a bad errorfile or bad Lua code
* so that won't be fixed, raise an error now.
*
* FIXME: we should instead add the ability to only return a
* 502 bad gateway. But in theory this is not supposed to
* happen.
*/
h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
ret = 0;
goto end;
}
h2s->status = sl.st.status;
/* certain statuses have no body or an empty one, regardless of
* what the headers say.
*/
if (sl.st.status >= 100 && sl.st.status < 200) {
h1m->flags &= ~(H1_MF_CLEN | H1_MF_CHNK);
h1m->curr_len = h1m->body_len = 0;
}
else if (sl.st.status == 204 || sl.st.status == 304) {
/* no contents, claim c-len is present and set to zero */
h1m->flags &= ~H1_MF_CHNK;
h1m->flags |= H1_MF_CLEN;
h1m->curr_len = h1m->body_len = 0;
}
mbuf = br_tail(h2c->mbuf);
retry:
if (!h2_get_buf(h2c, mbuf)) {
h2c->flags |= H2_CF_MUX_MALLOC;
h2s->flags |= H2_SF_BLK_MROOM;
return 0;
}
chunk_reset(&outbuf);
while (1) {
outbuf = b_make(b_tail(mbuf), b_contig_space(mbuf), 0, 0);
if (outbuf.size >= 9 || !b_space_wraps(mbuf))
break;
realign_again:
b_slow_realign(mbuf, trash.area, b_data(mbuf));
}
if (outbuf.size < 9)
goto full;
/* len: 0x000000 (fill later), type: 1(HEADERS), flags: ENDH=4 */
memcpy(outbuf.area, "\x00\x00\x00\x01\x04", 5);
write_n32(outbuf.area + 5, h2s->id); // 4 bytes
outbuf.data = 9;
/* encode status, which necessarily is the first one */
if (unlikely(list[0].v.len != 3)) {
/* this is an unparsable response */
h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
ret = 0;
goto end;
}
if (!hpack_encode_str_status(&outbuf, h2s->status, list[0].v)) {
if (b_space_wraps(mbuf))
goto realign_again;
goto full;
}
/* encode all headers, stop at empty name */
for (hdr = 1; hdr < sizeof(list)/sizeof(list[0]); hdr++) {
/* these ones do not exist in H2 and must be dropped. */
if (isteq(list[hdr].n, ist("connection")) ||
isteq(list[hdr].n, ist("proxy-connection")) ||
isteq(list[hdr].n, ist("keep-alive")) ||
isteq(list[hdr].n, ist("upgrade")) ||
isteq(list[hdr].n, ist("transfer-encoding")))
continue;
if (isteq(list[hdr].n, ist("")))
break; // end
if (!hpack_encode_header(&outbuf, list[hdr].n, list[hdr].v)) {
/* output full */
if (b_space_wraps(mbuf))
goto realign_again;
goto full;
}
}
/* we may need to add END_STREAM */
if (((h1m->flags & H1_MF_CLEN) && !h1m->body_len) || !h2s->cs || h2s->cs->flags & CS_FL_SHW)
es_now = 1;
/* update the frame's size */
h2_set_frame_size(outbuf.area, outbuf.data - 9);
if (es_now)
outbuf.area[4] |= H2_F_HEADERS_END_STREAM;
/* consume incoming H1 response */
max -= ret;
/* commit the H2 response */
b_add(mbuf, outbuf.data);
h2s->flags |= H2_SF_HEADERS_SENT;
if (es_now) {
// trim any possibly pending data (eg: inconsistent content-length)
ret += max;
h1m->state = H1_MSG_DONE;
h2s->flags |= H2_SF_ES_SENT;
if (h2s->st == H2_SS_OPEN)
h2s->st = H2_SS_HLOC;
else
h2s_close(h2s);
}
else if (h2s->status >= 100 && h2s->status < 200) {
/* we'll let the caller check if it has more headers to send */
h1m_init_res(h1m);
h1m->err_pos = -1; // don't care about errors on the response path
h2s->h1m.flags |= H1_MF_TOLOWER;
goto end;
}
/* now the h1m state is either H1_MSG_CHUNK_SIZE or H1_MSG_DATA */
end:
//fprintf(stderr, "[%d] sent simple H2 response (sid=%d) = %d bytes (%d in, ep=%u, es=%s)\n", h2c->st0, h2s->id, outbuf.len, ret, h1m->err_pos, h1m_state_str(h1m->err_state));
return ret;
full:
if ((mbuf = br_tail_add(h2c->mbuf)) != NULL)
goto retry;
h1m_init_res(h1m);
h1m->err_pos = -1; // don't care about errors on the response path
h2c->flags |= H2_CF_MUX_MFULL;
h2s->flags |= H2_SF_BLK_MROOM;
ret = 0;
goto end;
}
/* Try to send a DATA frame matching HTTP/1 response present at offset <ofs>
* for up to <max> bytes in response buffer <buf>, for stream <h2s>. Returns
* the number of bytes sent. The caller must check the stream's status to
* detect any error which might have happened subsequently to a successful send.
*/
static size_t h2s_frt_make_resp_data(struct h2s *h2s, const struct buffer *buf, size_t ofs, size_t max)
{
struct h2c *h2c = h2s->h2c;
struct h1m *h1m = &h2s->h1m;
struct buffer outbuf;
struct buffer *mbuf;
int ret = 0;
size_t total = 0;
int es_now = 0;
int size = 0;
const char *blk1, *blk2;
size_t len1, len2;
if (h2c_mux_busy(h2c, h2s)) {
h2s->flags |= H2_SF_BLK_MBUSY;
goto end;
}
mbuf = br_tail(h2c->mbuf);
retry:
if (!h2_get_buf(h2c, mbuf)) {
h2c->flags |= H2_CF_MUX_MALLOC;
h2s->flags |= H2_SF_BLK_MROOM;
goto end;
}
new_frame:
if (!max)
goto end;
chunk_reset(&outbuf);
while (1) {
outbuf = b_make(b_tail(mbuf), b_contig_space(mbuf), 0, 0);
if (outbuf.size >= 9 || !b_space_wraps(mbuf))
break;
realign_again:
/* If there are pending data in the output buffer, and we have
* less than 1/4 of the mbuf's size and everything fits, we'll
* still perform a copy anyway. Otherwise we'll pretend the mbuf
* is full and wait, to save some slow realign calls.
*/
if ((max + 9 > b_room(mbuf) || max >= b_size(mbuf) / 4)) {
if ((mbuf = br_tail_add(h2c->mbuf)) != NULL)
goto retry;
h2c->flags |= H2_CF_MUX_MFULL;
h2s->flags |= H2_SF_BLK_MROOM;
goto end;
}
b_slow_realign(mbuf, trash.area, b_data(mbuf));
}
if (outbuf.size < 9) {
if ((mbuf = br_tail_add(h2c->mbuf)) != NULL)
goto retry;
h2c->flags |= H2_CF_MUX_MFULL;
h2s->flags |= H2_SF_BLK_MROOM;
goto end;
}
/* len: 0x000000 (fill later), type: 0(DATA), flags: none=0 */
memcpy(outbuf.area, "\x00\x00\x00\x00\x00", 5);
write_n32(outbuf.area + 5, h2s->id); // 4 bytes
outbuf.data = 9;
switch (h1m->flags & (H1_MF_CLEN|H1_MF_CHNK)) {
case 0: /* no content length, read till SHUTW */
size = max;
h1m->curr_len = size;
break;
case H1_MF_CLEN: /* content-length: read only h2m->body_len */
size = max;
if ((long long)size > h1m->curr_len)
size = h1m->curr_len;
break;
default: /* te:chunked : parse chunks */
if (h1m->state == H1_MSG_CHUNK_CRLF) {
ret = h1_skip_chunk_crlf(buf, ofs, ofs + max);
if (!ret)
goto end;
if (ret < 0) {
/* FIXME: bad contents. how to proceed here when we're in H2 ? */
h1m->err_pos = ofs + max + ret;
h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
goto end;
}
max -= ret;
ofs += ret;
total += ret;
h1m->state = H1_MSG_CHUNK_SIZE;
}
if (h1m->state == H1_MSG_CHUNK_SIZE) {
unsigned int chunk;
ret = h1_parse_chunk_size(buf, ofs, ofs + max, &chunk);
if (!ret)
goto end;
if (ret < 0) {
/* FIXME: bad contents. how to proceed here when we're in H2 ? */
h1m->err_pos = ofs + max + ret;
h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
goto end;
}
size = chunk;
h1m->curr_len = chunk;
h1m->body_len += chunk;
max -= ret;
ofs += ret;
total += ret;
h1m->state = size ? H1_MSG_DATA : H1_MSG_TRAILERS;
if (!size)
goto send_empty;
}
/* in MSG_DATA state, continue below */
size = h1m->curr_len;
break;
}
/* we have in <size> the exact number of bytes we need to copy from
* the H1 buffer. We need to check this against the connection's and
* the stream's send windows, and to ensure that this fits in the max
* frame size and in the buffer's available space minus 9 bytes (for
* the frame header). The connection's flow control is applied last so
* that we can use a separate list of streams which are immediately
* unblocked on window opening. Note: we don't implement padding.
*/
if (size > max)
size = max;
if (size > h2s->mws)
size = h2s->mws;
if (size <= 0) {
h2s->flags |= H2_SF_BLK_SFCTL;
if (LIST_ADDED(&h2s->list))
LIST_DEL_INIT(&h2s->list);
goto end;
}
if (h2c->mfs && size > h2c->mfs)
size = h2c->mfs;
if (size + 9 > outbuf.size) {
/* It doesn't fit at once. If it at least fits once split and
* the amount of data to move is low, let's defragment the
* buffer now.
*/
if (b_space_wraps(mbuf) &&
(size + 9 <= b_room(mbuf)) &&
b_data(mbuf) <= MAX_DATA_REALIGN)
goto realign_again;
size = outbuf.size - 9;
}
if (size <= 0) {
if ((mbuf = br_tail_add(h2c->mbuf)) != NULL)
goto retry;
h2c->flags |= H2_CF_MUX_MFULL;
h2s->flags |= H2_SF_BLK_MROOM;
goto end;
}
if (size > h2c->mws)
size = h2c->mws;
if (size <= 0) {
h2s->flags |= H2_SF_BLK_MFCTL;
goto end;
}
/* copy whatever we can */
blk1 = blk2 = NULL; // silence a maybe-uninitialized warning
ret = b_getblk_nc(buf, &blk1, &len1, &blk2, &len2, ofs, max);
if (ret == 1)
len2 = 0;
if (!ret || len1 + len2 < size) {
/* FIXME: must normally never happen */
h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
goto end;
}
/* limit len1/len2 to size */
if (len1 + len2 > size) {
int sub = len1 + len2 - size;
if (len2 > sub)
len2 -= sub;
else {
sub -= len2;
len2 = 0;
len1 -= sub;
}
}
/* now let's copy this this into the output buffer */
memcpy(outbuf.area + 9, blk1, len1);
if (len2)
memcpy(outbuf.area + 9 + len1, blk2, len2);
send_empty:
/* we may need to add END_STREAM */
/* FIXME: we should also detect shutdown(w) below, but how ? Maybe we
* could rely on the MSG_MORE flag as a hint for this ?
*
* FIXME: what we do here is not correct because we send end_stream
* before knowing if we'll have to send a HEADERS frame for the
* trailers. More importantly we're not consuming the trailing CRLF
* after the end of trailers, so it will be left to the caller to
* eat it. The right way to do it would be to measure trailers here
* and to send ES only if there are no trailers.
*
*/
if (((h1m->flags & H1_MF_CLEN) && !(h1m->curr_len - size)) ||
!h1m->curr_len || h1m->state >= H1_MSG_DONE)
es_now = 1;
/* update the frame's size */
h2_set_frame_size(outbuf.area, size);
if (es_now)
outbuf.area[4] |= H2_F_DATA_END_STREAM;
/* commit the H2 response */
b_add(mbuf, size + 9);
/* consume incoming H1 response */
if (size > 0) {
max -= size;
ofs += size;
total += size;
h1m->curr_len -= size;
h2s->mws -= size;
h2c->mws -= size;
if (size && !h1m->curr_len && (h1m->flags & H1_MF_CHNK)) {
h1m->state = H1_MSG_CHUNK_CRLF;
goto new_frame;
}
}
if (es_now) {
if (h2s->st == H2_SS_OPEN)
h2s->st = H2_SS_HLOC;
else
h2s_close(h2s);
if (!(h1m->flags & H1_MF_CHNK)) {
// trim any possibly pending data (eg: inconsistent content-length)
total += max;
ofs += max;
max = 0;
h1m->state = H1_MSG_DONE;
}
h2s->flags |= H2_SF_ES_SENT;
}
end:
trace("[%d] sent simple H2 DATA response (sid=%d) = %d bytes out (%u in, st=%s, ep=%u, es=%s, h2cws=%d h2sws=%d) data=%u", h2c->st0, h2s->id, size+9, (unsigned int)total, h1m_state_str(h1m->state), h1m->err_pos, h1m_state_str(h1m->err_state), h2c->mws, h2s->mws, (unsigned int)b_data(buf));
return total;
}
/* Try to send a HEADERS frame matching HTX response present in HTX message
* <htx> for the H2 stream <h2s>. Returns the number of bytes sent. The caller
* must check the stream's status to detect any error which might have happened
@ -4403,7 +3802,7 @@ static size_t h2s_frt_make_resp_data(struct h2s *h2s, const struct buffer *buf,
* header blocks and an end of header, otherwise an invalid frame could be
* emitted and the resulting htx message could be left in an inconsistent state.
*/
static size_t h2s_htx_frt_make_resp_headers(struct h2s *h2s, struct htx *htx)
static size_t h2s_frt_make_resp_headers(struct h2s *h2s, struct htx *htx)
{
struct http_hdr list[global.tune.max_http_hdr];
struct h2c *h2c = h2s->h2c;
@ -4611,7 +4010,7 @@ static size_t h2s_htx_frt_make_resp_headers(struct h2s *h2s, struct htx *htx)
* header blocks and an end of header, otherwise an invalid frame could be
* emitted and the resulting htx message could be left in an inconsistent state.
*/
static size_t h2s_htx_bck_make_req_headers(struct h2s *h2s, struct htx *htx)
static size_t h2s_bck_make_req_headers(struct h2s *h2s, struct htx *htx)
{
struct http_hdr list[global.tune.max_http_hdr];
struct h2c *h2c = h2s->h2c;
@ -4862,7 +4261,7 @@ static size_t h2s_htx_bck_make_req_headers(struct h2s *h2s, struct htx *htx)
* happened subsequently to a successful send. Returns the number of data bytes
* consumed, or zero if nothing done. Note that EOM count for 1 byte.
*/
static size_t h2s_htx_frt_make_resp_data(struct h2s *h2s, struct buffer *buf, size_t count)
static size_t h2s_frt_make_resp_data(struct h2s *h2s, struct buffer *buf, size_t count)
{
struct h2c *h2c = h2s->h2c;
struct htx *htx;
@ -5127,7 +4526,7 @@ static size_t h2s_htx_frt_make_resp_data(struct h2s *h2s, struct buffer *buf, si
* the EOM, which is *not* removed. All trailers are processed at once and sent
* as a single frame. The ES flag is always set.
*/
static size_t h2s_htx_make_trailers(struct h2s *h2s, struct htx *htx)
static size_t h2s_make_trailers(struct h2s *h2s, struct htx *htx)
{
struct http_hdr list[global.tune.max_http_hdr];
struct h2c *h2c = h2s->h2c;
@ -5367,7 +4766,6 @@ static size_t h2_rcv_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
size_t ret = 0;
/* transfer possibly pending data to the upper layer */
if (h2c->proxy->options2 & PR_O2_USE_HTX) {
h2s_htx = htx_from_buf(&h2s->rxbuf);
if (htx_is_empty(h2s_htx)) {
/* Here htx_to_buf() will set buffer data to 0 because
@ -5401,10 +4799,6 @@ static size_t h2_rcv_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
htx_to_buf(buf_htx, buf);
htx_to_buf(h2s_htx, &h2s->rxbuf);
ret -= h2s_htx->data;
}
else {
ret = b_xfer(buf, &h2s->rxbuf, count);
}
end:
if (b_data(&h2s->rxbuf))
@ -5478,8 +4872,7 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
if (h2s->h2c->st0 < H2_CS_FRAME_H)
return 0;
/* htx will be enough to decide if we're using HTX or legacy */
htx = (h2s->h2c->proxy->options2 & PR_O2_USE_HTX) ? htx_from_buf(buf) : NULL;
htx = htx_from_buf(buf);
if (!(h2s->flags & H2_SF_OUTGOING_DATA) && count)
h2s->flags |= H2_SF_OUTGOING_DATA;
@ -5499,7 +4892,6 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
eb32_insert(&h2s->h2c->streams_by_id, &h2s->by_id);
}
if (htx) {
while (h2s->st < H2_SS_HLOC && !(h2s->flags & H2_SF_BLK_ANY) &&
count && !htx_is_empty(htx)) {
idx = htx_get_head(htx);
@ -5510,7 +4902,7 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
switch (btype) {
case HTX_BLK_REQ_SL:
/* start-line before headers */
ret = h2s_htx_bck_make_req_headers(h2s, htx);
ret = h2s_bck_make_req_headers(h2s, htx);
if (ret > 0) {
total += ret;
count -= ret;
@ -5521,7 +4913,7 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
case HTX_BLK_RES_SL:
/* start-line before headers */
ret = h2s_htx_frt_make_resp_headers(h2s, htx);
ret = h2s_frt_make_resp_headers(h2s, htx);
if (ret > 0) {
total += ret;
count -= ret;
@ -5536,7 +4928,7 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
* This EOM necessarily is one before trailers, as the EOM following
* trailers would have been consumed by the trailers parser.
*/
ret = h2s_htx_frt_make_resp_data(h2s, buf, count);
ret = h2s_frt_make_resp_data(h2s, buf, count);
if (ret > 0) {
htx = htx_from_buf(buf);
total += ret;
@ -5551,7 +4943,7 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
/* This is the first trailers block, all the subsequent ones AND
* the EOM will be swallowed by the parser.
*/
ret = h2s_htx_make_trailers(h2s, htx);
ret = h2s_make_trailers(h2s, htx);
if (ret > 0) {
total += ret;
count -= ret;
@ -5567,49 +4959,6 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
break;
}
}
goto done;
}
/* legacy transfer mode */
while (h2s->h1m.state < H1_MSG_DONE && count) {
if (h2s->h1m.state <= H1_MSG_LAST_LF) {
if (h2s->h2c->flags & H2_CF_IS_BACK)
ret = -1;
else
ret = h2s_frt_make_resp_headers(h2s, buf, total, count);
}
else if (h2s->h1m.state < H1_MSG_TRAILERS) {
ret = h2s_frt_make_resp_data(h2s, buf, total, count);
}
else if (h2s->h1m.state == H1_MSG_TRAILERS) {
/* consume the trailers if any (we don't forward them for now) */
ret = h1_measure_trailers(buf, total, count);
if (unlikely((int)ret <= 0)) {
if ((int)ret < 0)
h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
break;
}
// trim any possibly pending data (eg: extra CR-LF, ...)
total += count;
count = 0;
h2s->h1m.state = H1_MSG_DONE;
break;
}
else {
cs_set_error(cs);
break;
}
total += ret;
count -= ret;
if (h2s->st >= H2_SS_HLOC)
break;
if (h2s->flags & H2_SF_BLK_ANY)
break;
}
done:
if (h2s->st >= H2_SS_HLOC) {
@ -5627,11 +4976,7 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun
h2s_close(h2s);
}
if (htx) {
htx_to_buf(htx, buf);
} else {
b_del(buf, total);
}
/* The mux is full, cancel the pending tasks */
if ((h2s->h2c->flags & H2_CF_MUX_BLOCK_ANY) ||
@ -5798,34 +5143,6 @@ static int h2_parse_max_frame_size(char **args, int section_type, struct proxy *
/* The mux operations */
static const struct mux_ops h2_ops = {
.init = h2_init,
.wake = h2_wake,
.snd_buf = h2_snd_buf,
.rcv_buf = h2_rcv_buf,
.subscribe = h2_subscribe,
.unsubscribe = h2_unsubscribe,
.attach = h2_attach,
.get_first_cs = h2_get_first_cs,
.detach = h2_detach,
.destroy = h2_destroy,
.avail_streams = h2_avail_streams,
.used_streams = h2_used_streams,
.shutr = h2_shutr,
.shutw = h2_shutw,
.show_fd = h2_show_fd,
.flags = MX_FL_CLEAN_ABRT,
.name = "H2",
};
/* PROTO selection : this mux registers PROTO token "h2" */
static struct mux_proto_list mux_proto_h2 =
{ .token = IST("h2"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_FE, .mux = &h2_ops };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2);
/* The mux operations */
static const struct mux_ops h2_htx_ops = {
.init = h2_init,
.wake = h2_wake,
.snd_buf = h2_snd_buf,
@ -5845,10 +5162,10 @@ static const struct mux_ops h2_htx_ops = {
.name = "H2",
};
static struct mux_proto_list mux_proto_h2_htx =
{ .token = IST("h2"), .mode = PROTO_MODE_HTX, .side = PROTO_SIDE_BOTH, .mux = &h2_htx_ops };
static struct mux_proto_list mux_proto_h2 =
{ .token = IST("h2"), .mode = PROTO_MODE_HTX, .side = PROTO_SIDE_BOTH, .mux = &h2_ops };
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2_htx);
INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2);
/* config keyword parsers */
static struct cfg_kw_list cfg_kws = {ILH, {