From: Christopher Faulet Date: Mon, 15 Jul 2019 09:22:56 +0000 (+0200) Subject: MEDIUM: mux-h2: Remove support of the legacy HTTP mode X-Git-Tag: v2.1-dev2~375 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9b79a1025d1b7c7b7eee8a44885f79011b935879;p=thirdparty%2Fhaproxy.git 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. --- diff --git a/src/mux_h2.c b/src/mux_h2.c index 7866c7883e..271d19a551 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -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,21 +3593,10 @@ 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); + htx = htx_from_buf(rxbuf); + if (!htx_is_empty(htx)) { + h2c->flags |= H2_CF_DEM_SFULL; + goto leave; } /* past this point we cannot roll back in case of error */ @@ -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 (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); if (outlen < 0) { /* too large headers? this is a stream error only */ @@ -3657,8 +3636,7 @@ next_frame: /* a payload is present */ if (msgf & H2_MSGF_BODY_CL) { *flags |= H2_SF_DATA_CLEN; - if (htx) - htx->extra = *body_len; + htx->extra = *body_len; } else if (!(msgf & H2_MSGF_BODY_TUNNEL) && !htx) *flags |= H2_SF_DATA_CHNK; @@ -3673,21 +3651,9 @@ 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) { - 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; - } + /* Mark the end of message using EOM */ + if (!htx_add_endof(htx, HTX_BLK_EOM)) + goto fail; } /* success */ @@ -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) { - 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; - } - + /* Trailers terminate a DATA sequence */ + if (h2_make_htx_trailers(list, htx) <= 0) + 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,140 +3737,47 @@ try_again: goto fail; } - if (htx) { - unsigned int sent; - - block1 = htx_free_data_space(htx); - if (!block1) { - h2c->flags |= H2_CF_DEM_SFULL; - goto fail; - } - if (flen > block1) - flen = block1; - - /* here, flen is the max we can copy into the output buffer */ - block1 = b_contig_data(&h2c->dbuf, 0); - if (flen > block1) - flen = block1; - - sent = htx_add_data(htx, ist2(b_head(&h2c->dbuf), flen)); - - b_del(&h2c->dbuf, sent); - h2c->dfl -= sent; - h2c->rcvd_c += sent; - h2c->rcvd_s += sent; // warning, this can also affect the closed streams! - - if (h2s->flags & H2_SF_DATA_CLEN) { - h2s->body_len -= sent; - htx->extra = h2s->body_len; - } - - if (sent < flen) { - h2c->flags |= H2_CF_DEM_SFULL; - goto fail; - } - - 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); + block = htx_free_data_space(htx); + if (!block) { + h2c->flags |= H2_CF_DEM_SFULL; + goto fail; } + if (flen > block) + flen = block; - /* 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; + /* here, flen is the max we can copy into the output buffer */ + block = b_contig_data(&h2c->dbuf, 0); + if (flen > block) + flen = block; - if (block1) - b_putblk(csbuf, b_head(&h2c->dbuf), block1); + sent = htx_add_data(htx, ist2(b_head(&h2c->dbuf), flen)); - if (block2) - b_putblk(csbuf, b_peek(&h2c->dbuf, block1), block2); + b_del(&h2c->dbuf, sent); + h2c->dfl -= sent; + h2c->rcvd_c += sent; + h2c->rcvd_s += sent; // warning, this can also affect the closed streams! - if (h2s->flags & H2_SF_DATA_CHNK) { - /* emit the CRLF */ - b_putblk(csbuf, "\r\n", 2); + if (h2s->flags & H2_SF_DATA_CLEN) { + h2s->body_len -= sent; + htx->extra = h2s->body_len; } - /* 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 */ + if (sent < flen) { h2c->flags |= H2_CF_DEM_SFULL; goto fail; } + goto try_again; + end_transfer: /* here we're done with the frame, all the payload (except padding) was * transferred. */ 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); + if (!htx_add_endof(htx, HTX_BLK_EOM)) { + h2c->flags |= H2_CF_DEM_SFULL; + goto fail; } } @@ -3938,8 +3785,7 @@ try_again: 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); + htx_to_buf(htx, csbuf); return 1; fail: if (htx) @@ -3947,453 +3793,6 @@ try_again: return 0; } -/* Try to send a HEADERS frame matching HTTP/1 response present at offset - * and for bytes in buffer for the H2 stream . 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 . - * 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 - * for up to bytes in response buffer , for stream . 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 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 * for the H2 stream . 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,45 +4766,40 @@ 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 - * the HTX is empty. - */ - htx_to_buf(h2s_htx, &h2s->rxbuf); - goto end; - } - - ret = h2s_htx->data; - buf_htx = htx_from_buf(buf); - - /* is empty and the message is small enough, swap the - * buffers. */ - if (htx_is_empty(buf_htx) && htx_used_space(h2s_htx) <= count) { - htx_to_buf(buf_htx, buf); - htx_to_buf(h2s_htx, &h2s->rxbuf); - b_xfer(buf, &h2s->rxbuf, b_data(&h2s->rxbuf)); - goto end; - } - - htx_xfer_blks(buf_htx, h2s_htx, count, HTX_BLK_EOM); + h2s_htx = htx_from_buf(&h2s->rxbuf); + if (htx_is_empty(h2s_htx)) { + /* Here htx_to_buf() will set buffer data to 0 because + * the HTX is empty. + */ + htx_to_buf(h2s_htx, &h2s->rxbuf); + goto end; + } - if (h2s_htx->flags & HTX_FL_PARSING_ERROR) { - buf_htx->flags |= HTX_FL_PARSING_ERROR; - if (htx_is_empty(buf_htx)) - cs->flags |= CS_FL_EOI; - } + ret = h2s_htx->data; + buf_htx = htx_from_buf(buf); - buf_htx->extra = (h2s_htx->extra ? (h2s_htx->data + h2s_htx->extra) : 0); + /* is empty and the message is small enough, swap the + * buffers. */ + if (htx_is_empty(buf_htx) && htx_used_space(h2s_htx) <= count) { htx_to_buf(buf_htx, buf); htx_to_buf(h2s_htx, &h2s->rxbuf); - ret -= h2s_htx->data; + b_xfer(buf, &h2s->rxbuf, b_data(&h2s->rxbuf)); + goto end; } - else { - ret = b_xfer(buf, &h2s->rxbuf, count); + + htx_xfer_blks(buf_htx, h2s_htx, count, HTX_BLK_EOM); + + if (h2s_htx->flags & HTX_FL_PARSING_ERROR) { + buf_htx->flags |= HTX_FL_PARSING_ERROR; + if (htx_is_empty(buf_htx)) + cs->flags |= CS_FL_EOI; } + buf_htx->extra = (h2s_htx->extra ? (h2s_htx->data + h2s_htx->extra) : 0); + htx_to_buf(buf_htx, buf); + htx_to_buf(h2s_htx, &h2s->rxbuf); + ret -= h2s_htx->data; + end: if (b_data(&h2s->rxbuf)) cs->flags |= (CS_FL_RCV_MORE | CS_FL_WANT_ROOM); @@ -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,18 +4892,17 @@ 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); - blk = htx_get_blk(htx, idx); - btype = htx_get_blk_type(blk); - bsize = htx_get_blksz(blk); + while (h2s->st < H2_SS_HLOC && !(h2s->flags & H2_SF_BLK_ANY) && + count && !htx_is_empty(htx)) { + idx = htx_get_head(htx); + blk = htx_get_blk(htx, idx); + btype = htx_get_blk_type(blk); + bsize = htx_get_blksz(blk); - switch (btype) { + 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; @@ -5565,53 +4957,10 @@ static size_t h2_snd_buf(struct conn_stream *cs, struct buffer *buf, size_t coun total += bsize; count -= bsize; 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: + done: if (h2s->st >= H2_SS_HLOC) { /* trim any possibly pending data after we close (extra CR-LF, * unprocessed trailers, abnormal extra data, ...) @@ -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); - } + htx_to_buf(htx, buf); /* The mux is full, cancel the pending tasks */ if ((h2s->h2c->flags & H2_CF_MUX_BLOCK_ANY) || @@ -5813,43 +5158,15 @@ static const struct mux_ops h2_ops = { .shutr = h2_shutr, .shutw = h2_shutw, .show_fd = h2_show_fd, - .flags = MX_FL_CLEAN_ABRT, + .flags = MX_FL_CLEAN_ABRT|MX_FL_HTX, .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 }; + { .token = IST("h2"), .mode = PROTO_MODE_HTX, .side = PROTO_SIDE_BOTH, .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, - .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|MX_FL_HTX, - .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 }; - -INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2_htx); - /* config keyword parsers */ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_GLOBAL, "tune.h2.header-table-size", h2_parse_header_table_size },