diff --git a/src/mux_h2.c b/src/mux_h2.c index ce734476e..7b9c0bc5c 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -2075,6 +2075,242 @@ static int h2s_frt_make_resp_headers(struct h2s *h2s, struct buffer *buf) return ret; } +/* Try to send a DATA frame matching HTTP/1 response present in the response + * buffer , for stream . Returns 0 if not possible yet, <0 on error + * (one of the H2_ERR* or h2_status codes), >0 on success in which case it + * corresponds to the number of buffer bytes consumed. + */ +static int h2s_frt_make_resp_data(struct h2s *h2s, struct buffer *buf) +{ + struct h2c *h2c = h2s->h2c; + struct h1m *h1m = &h2s->res; + struct chunk outbuf; + int ret = 0; + int total = 0; + int es_now = 0; + int size = 0; + char *blk1, *blk2; + int len1, len2; + + if (h2c_mux_busy(h2c, h2s)) { + h2s->flags |= H2_SF_BLK_MBUSY; + goto end; + } + + if (!h2_get_mbuf(h2c)) { + h2c->flags |= H2_CF_MUX_MALLOC; + h2s->flags |= H2_SF_BLK_MROOM; + goto end; + } + + new_frame: + if (!buf->o) + goto end; + + chunk_reset(&outbuf); + + while (1) { + outbuf.str = bo_end(h2c->mbuf); + outbuf.size = bo_contig_space(h2c->mbuf); + outbuf.len = 0; + + if (outbuf.size >= 9 || !buffer_space_wraps(h2c->mbuf)) + break; + realign_again: + buffer_slow_realign(h2c->mbuf); + } + + if (outbuf.size < 9) { + 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.str, "\x00\x00\x00\x00\x00", 5); + write_n32(outbuf.str + 5, h2s->id); // 4 bytes + outbuf.len = 9; + + switch (h1m->flags & (H1_MF_CLEN|H1_MF_CHNK)) { + case 0: /* no content length, read till SHUTW */ + size = buf->o; + break; + case H1_MF_CLEN: /* content-length: read only h2m->body_len */ + size = buf->o; + if ((long long)size > h1m->curr_len) + size = h1m->curr_len; + break; + default: /* te:chunked : parse chunks */ + if (h1m->state == HTTP_MSG_CHUNK_CRLF) { + ret = h1_skip_chunk_crlf(buf, -buf->o, 0); + if (!ret) + goto end; + + if (ret < 0) { + /* FIXME: bad contents. how to proceed here when we're in H2 ? */ + h1m->err_pos = ret; + h2s_error(h2s, H2_ERR_INTERNAL_ERROR); + goto end; + } + bo_del(buf, ret); + total += ret; + h1m->state = HTTP_MSG_CHUNK_SIZE; + } + + if (h1m->state == HTTP_MSG_CHUNK_SIZE) { + unsigned int chunk; + + ret = h1_parse_chunk_size(buf, -buf->o, 0, &chunk); + if (!ret) + goto end; + + if (ret < 0) { + /* FIXME: bad contents. how to proceed here when we're in H2 ? */ + h1m->err_pos = ret; + h2s_error(h2s, H2_ERR_INTERNAL_ERROR); + goto end; + } + + size = chunk; + h1m->curr_len = chunk; + h1m->body_len += chunk; + bo_del(buf, ret); + total += ret; + h1m->state = size ? HTTP_MSG_DATA : HTTP_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 > buf->o) + size = buf->o; + + if (size > h2s->mws) + size = h2s->mws; + + if (size <= 0) { + h2s->flags |= H2_SF_BLK_SFCTL; + goto end; + } + + if (h2c->mfs && size > h2c->mfs) + size = h2c->mfs; + + if (size + 9 > outbuf.size) { + /* we have an opportunity for enlarging the too small + * available space, let's try. + */ + if (buffer_space_wraps(h2c->mbuf)) + goto realign_again; + size = outbuf.size - 9; + } + + if (size <= 0) { + 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 = bo_getblk_nc(buf, &blk1, &len1, &blk2, &len2); + 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.str + 9, blk1, len1); + if (len2) + memcpy(outbuf.str + 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 ? + */ + if (((h1m->flags & H1_MF_CLEN) && !(h1m->curr_len - size)) || + !h1m->curr_len || h1m->state >= HTTP_MSG_DONE) + es_now = 1; + + /* update the frame's size */ + h2_set_frame_size(outbuf.str, size); + + if (es_now) + outbuf.str[4] |= H2_F_DATA_END_STREAM; + + /* commit the H2 response */ + h2c->mbuf->o += size + 9; + h2c->mbuf->p = b_ptr(h2c->mbuf, size + 9); + + /* consume incoming H1 response */ + if (size > 0) { + bo_del(buf, 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 = HTTP_MSG_CHUNK_CRLF; + goto new_frame; + } + } + + if (es_now) { + if (h2s->st == H2_SS_OPEN) + h2s->st = H2_SS_HLOC; + else + h2s->st = H2_SS_CLOSED; + /* no trailers for now, we must consume them (whatever remains in the buffer) */ + bo_del(buf, buf->o); + h1m->state = HTTP_MSG_DONE; + h2s->flags |= H2_SF_ES_SENT; + } + + end: + trace("[%d] sent simple H2 DATA response (sid=%d) = %d bytes out (%d in, st=%s, ep=%u, es=%s, h2cws=%d h2sws=%d) buf->o=%d", h2c->st0, h2s->id, size+9, total, h1_msg_state_str(h1m->state), h1m->err_pos, h1_msg_state_str(h1m->err_state), h2c->mws, h2s->mws, buf->o); + return total; +} + /* Called from the upper layer, to send data */ static int h2_snd_buf(struct conn_stream *cs, struct buffer *buf, int flags) { @@ -2092,13 +2328,27 @@ static int h2_snd_buf(struct conn_stream *cs, struct buffer *buf, int flags) if (h2s->flags & H2_SF_BLK_ANY) break; } + else if (h2s->res.state < HTTP_MSG_TRAILERS) { + total += h2s_frt_make_resp_data(h2s, buf); + + if (h2s->st == H2_SS_ERROR) + break; + + if (h2s->flags & H2_SF_BLK_ANY) + break; + } else { - /* FIXME: DATA not handled for now */ cs->flags |= CS_FL_ERROR; break; } } + if (h2s->flags & H2_SF_BLK_SFCTL) { + /* stream flow control, quit the list */ + LIST_DEL(&h2s->list); + LIST_INIT(&h2s->list); + } + if (h2s->st == H2_SS_ERROR) cs->flags |= CS_FL_ERROR;