diff options
Diffstat (limited to 'Utilities/cmcurl/lib/http2.c')
-rw-r--r-- | Utilities/cmcurl/lib/http2.c | 172 |
1 files changed, 119 insertions, 53 deletions
diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c index efc082d..cfdb327 100644 --- a/Utilities/cmcurl/lib/http2.c +++ b/Utilities/cmcurl/lib/http2.c @@ -29,7 +29,7 @@ #include "http.h" #include "sendf.h" #include "curl_base64.h" -#include "rawstr.h" +#include "strcase.h" #include "multiif.h" #include "conncache.h" #include "url.h" @@ -92,8 +92,9 @@ static int http2_perform_getsock(const struct connectdata *conn, because of renegotiation. */ sock[0] = conn->sock[FIRSTSOCKET]; - if(nghttp2_session_want_read(c->h2)) - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + /* in a HTTP/2 connection we can basically always get a frame so we should + always be ready for one */ + bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); if(nghttp2_session_want_write(c->h2)) bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); @@ -150,6 +151,7 @@ void Curl_http2_setup_req(struct Curl_easy *data) http->pauselen = 0; http->error_code = NGHTTP2_NO_ERROR; http->closed = FALSE; + http->close_handled = FALSE; http->mem = data->state.buffer; http->len = BUFSIZE; http->memlen = 0; @@ -184,7 +186,7 @@ const struct Curl_handler Curl_handler_http2 = { ZERO_NULL, /* readwrite */ PORT_HTTP, /* defport */ CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ + PROTOPT_STREAM /* flags */ }; const struct Curl_handler Curl_handler_http2_ssl = { @@ -204,7 +206,7 @@ const struct Curl_handler Curl_handler_http2_ssl = { ZERO_NULL, /* readwrite */ PORT_HTTP, /* defport */ CURLPROTO_HTTPS, /* protocol */ - PROTOPT_SSL /* flags */ + PROTOPT_SSL | PROTOPT_STREAM /* flags */ }; /* @@ -317,7 +319,7 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) the middle of header, it could be matched in middle of the value, this is because we do prefix match.*/ if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] || - Curl_raw_equal(header, ":") || strchr(header + 1, ':')) + !strcmp(header, ":") || strchr(header + 1, ':')) return NULL; else { struct HTTP *stream = h->data->req.protop; @@ -488,8 +490,11 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, } stream = data_s->req.protop; - if(!stream) + if(!stream) { + DEBUGF(infof(conn->data, "No proto pointer for stream: %x\n", + stream_id)); return NGHTTP2_ERR_CALLBACK_FAILURE; + } DEBUGF(infof(data_s, "on_frame_recv() header %x stream %x\n", frame->hd.type, stream_id)); @@ -547,7 +552,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, /* if we receive data for another handle, wake that up */ if(conn_s->data != data_s) - Curl_expire(data_s, 1); + Curl_expire(data_s, 0); } break; case NGHTTP2_PUSH_PROMISE: @@ -621,8 +626,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, /* if we receive data for another handle, wake that up */ if(conn->data != data_s) - Curl_expire(data_s, 1); /* TODO: fix so that this can be set to 0 for - immediately? */ + Curl_expire(data_s, 0); DEBUGF(infof(data_s, "%zu data received for stream %u " "(%zu left in buffer %p, total %zu)\n", @@ -883,7 +887,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, Curl_add_buffer(stream->header_recvbuf, " \r\n", 3); /* if we receive data for another handle, wake that up */ if(conn->data != data_s) - Curl_expire(data_s, 1); + Curl_expire(data_s, 0); DEBUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)\n", stream->status_code, data_s)); @@ -899,7 +903,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); /* if we receive data for another handle, wake that up */ if(conn->data != data_s) - Curl_expire(data_s, 1); + Curl_expire(data_s, 0); DEBUGF(infof(data_s, "h2 header: %.*s: %.*s\n", namelen, name, valuelen, value)); @@ -941,11 +945,12 @@ static ssize_t data_source_read_callback(nghttp2_session *session, memcpy(buf, stream->upload_mem, nread); stream->upload_mem += nread; stream->upload_len -= nread; - stream->upload_left -= nread; + if(data_s->state.infilesize != -1) + stream->upload_left -= nread; } if(stream->upload_left == 0) - *data_flags = 1; + *data_flags = NGHTTP2_DATA_FLAG_EOF; else if(nread == 0) return NGHTTP2_ERR_DEFERRED; @@ -979,6 +984,43 @@ static int error_callback(nghttp2_session *session, } #endif +void Curl_http2_done(struct connectdata *conn, bool premature) +{ + struct Curl_easy *data = conn->data; + struct HTTP *http = data->req.protop; + struct http_conn *httpc = &conn->proto.httpc; + + if(http->header_recvbuf) { + DEBUGF(infof(data, "free header_recvbuf!!\n")); + Curl_add_buffer_free(http->header_recvbuf); + http->header_recvbuf = NULL; /* clear the pointer */ + Curl_add_buffer_free(http->trailer_recvbuf); + http->trailer_recvbuf = NULL; /* clear the pointer */ + if(http->push_headers) { + /* if they weren't used and then freed before */ + for(; http->push_headers_used > 0; --http->push_headers_used) { + free(http->push_headers[http->push_headers_used - 1]); + } + free(http->push_headers); + http->push_headers = NULL; + } + } + + if(premature) { + /* RST_STREAM */ + nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE, http->stream_id, + NGHTTP2_STREAM_CLOSED); + if(http->stream_id == httpc->pause_stream_id) { + infof(data, "stopped the pause stream!\n"); + httpc->pause_stream_id = 0; + } + } + if(http->stream_id) { + nghttp2_session_set_stream_user_data(httpc->h2, http->stream_id, 0); + http->stream_id = 0; + } +} + /* * Initialize nghttp2 for a Curl connection */ @@ -1091,9 +1133,10 @@ CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req, /* * Returns nonzero if current HTTP/2 session should be closed. */ -static int should_close_session(struct http_conn *httpc) { +static int should_close_session(struct http_conn *httpc) +{ return httpc->drain_total == 0 && !nghttp2_session_want_read(httpc->h2) && - !nghttp2_session_want_write(httpc->h2); + !nghttp2_session_want_write(httpc->h2); } static int h2_session_send(struct Curl_easy *data, @@ -1107,7 +1150,8 @@ static int h2_session_send(struct Curl_easy *data, */ static int h2_process_pending_input(struct Curl_easy *data, struct http_conn *httpc, - CURLcode *err) { + CURLcode *err) +{ ssize_t nread; char *inbuf; ssize_t rv; @@ -1155,9 +1199,41 @@ static int h2_process_pending_input(struct Curl_easy *data, return 0; } +/* + * Called from transfer.c:done_sending when we stop uploading. + */ +CURLcode Curl_http2_done_sending(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + if((conn->handler == &Curl_handler_http2_ssl) || + (conn->handler == &Curl_handler_http2)) { + /* make sure this is only attempted for HTTP/2 transfers */ + + struct HTTP *stream = conn->data->req.protop; + + if(stream->upload_left) { + /* If the stream still thinks there's data left to upload. */ + struct http_conn *httpc = &conn->proto.httpc; + nghttp2_session *h2 = httpc->h2; + + stream->upload_left = 0; /* DONE! */ + + /* resume sending here to trigger the callback to get called again so + that it can signal EOF to nghttp2 */ + (void)nghttp2_session_resume_data(h2, stream->stream_id); + + (void)h2_process_pending_input(conn->data, httpc, &result); + } + } + return result; +} + + static ssize_t http2_handle_stream_close(struct connectdata *conn, struct Curl_easy *data, - struct HTTP *stream, CURLcode *err) { + struct HTTP *stream, CURLcode *err) +{ char *trailer_pos, *trailer_end; CURLcode result; struct http_conn *httpc = &conn->proto.httpc; @@ -1178,8 +1254,7 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn, DEBUGASSERT(data->state.drain == 0); - /* Reset to FALSE to prevent infinite loop in readwrite_data - function. */ + /* Reset to FALSE to prevent infinite loop in readwrite_data function. */ stream->closed = FALSE; if(stream->error_code != NGHTTP2_NO_ERROR) { failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %d)", @@ -1216,6 +1291,8 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn, } } + stream->close_handled = TRUE; + DEBUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close\n")); return 0; } @@ -1268,10 +1345,6 @@ static int h2_session_send(struct Curl_easy *data, return nghttp2_session_send(h2); } -/* - * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return - * a regular CURLcode value. - */ static ssize_t http2_recv(struct connectdata *conn, int sockindex, char *mem, size_t len, CURLcode *err) { @@ -1382,6 +1455,8 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex, socket is not read. But it seems that usually streams are notified with its drain property, and socket is read again quickly. */ + DEBUGF(infof(data, "stream %x is paused, pause id: %x\n", + stream->stream_id, httpc->pause_stream_id)); *err = CURLE_AGAIN; return -1; } @@ -1497,7 +1572,6 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex, #define HEADER_OVERFLOW(x) \ (x.namelen > (uint16_t)-1 || x.valuelen > (uint16_t)-1 - x.namelen) -/* return number of received (decrypted) bytes */ static ssize_t http2_send(struct connectdata *conn, int sockindex, const void *mem, size_t len, CURLcode *err) { @@ -1525,6 +1599,14 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, DEBUGF(infof(conn->data, "http2_send len=%zu\n", len)); if(stream->stream_id != -1) { + if(stream->close_handled) { + infof(conn->data, "stream %d closed\n", stream->stream_id); + *err = CURLE_HTTP2_STREAM; + return -1; + } + else if(stream->closed) { + return http2_handle_stream_close(conn, conn->data, stream, err); + } /* If stream_id != -1, we have dispatched request HEADERS, and now are going to send or sending request body in DATA frame */ stream->upload_mem = mem; @@ -1661,12 +1743,12 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, goto fail; hlen = end - hdbuf; - if(hlen == 10 && Curl_raw_nequal("connection", hdbuf, 10)) { + if(hlen == 10 && strncasecompare("connection", hdbuf, 10)) { /* skip Connection: headers! */ skip = 1; --nheader; } - else if(hlen == 4 && Curl_raw_nequal("host", hdbuf, 4)) { + else if(hlen == 4 && strncasecompare("host", hdbuf, 4)) { authority_idx = i; nva[i].name = (unsigned char *)":authority"; nva[i].namelen = strlen((char *)nva[i].name); @@ -1687,28 +1769,6 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, failf(conn->data, "Failed sending HTTP request: Header overflow"); goto fail; } - /* Inspect Content-Length header field and retrieve the request - entity length so that we can set END_STREAM to the last DATA - frame. */ - if(nva[i].namelen == 14 && - Curl_raw_nequal("content-length", (char*)nva[i].name, 14)) { - size_t j; - stream->upload_left = 0; - if(!nva[i].valuelen) - goto fail; - for(j = 0; j < nva[i].valuelen; ++j) { - if(nva[i].value[j] < '0' || nva[i].value[j] > '9') - goto fail; - if(stream->upload_left >= CURL_OFF_T_MAX / 10) - goto fail; - stream->upload_left *= 10; - stream->upload_left += nva[i].value[j] - '0'; - } - DEBUGF(infof(conn->data, - "request content-length=%" - CURL_FORMAT_CURL_OFF_T - "\n", stream->upload_left)); - } ++i; } } @@ -1736,6 +1796,10 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, if(nva[i].valuelen > max_acc - acc) break; acc += nva[i].valuelen; + + DEBUGF(infof(conn->data, "h2 header: %.*s:%.*s\n", + nva[i].namelen, nva[i].name, + nva[i].valuelen, nva[i].value)); } if(i != nheader) { @@ -1751,6 +1815,12 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex, case HTTPREQ_POST: case HTTPREQ_POST_FORM: case HTTPREQ_PUT: + if(conn->data->state.infilesize != -1) + stream->upload_left = conn->data->state.infilesize; + else + /* data sending without specifying the data amount up front */ + stream->upload_left = -1; /* unknown, but not zero */ + data_prd.read_callback = data_source_read_callback; data_prd.source.ptr = NULL; stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, @@ -1850,10 +1920,6 @@ CURLcode Curl_http2_setup(struct connectdata *conn) infof(conn->data, "Connection state changed (HTTP/2 confirmed)\n"); Curl_multi_connchanged(conn->data->multi); - /* switch on TCP_NODELAY as we need to send off packets without delay for - maximum throughput */ - Curl_tcpnodelay(conn, conn->sock[FIRSTSOCKET]); - return CURLE_OK; } |