summaryrefslogtreecommitdiff
path: root/libs/libcurl/src/http2.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libcurl/src/http2.c')
-rw-r--r--libs/libcurl/src/http2.c160
1 files changed, 102 insertions, 58 deletions
diff --git a/libs/libcurl/src/http2.c b/libs/libcurl/src/http2.c
index 261c9fbb8a..108678a2b8 100644
--- a/libs/libcurl/src/http2.c
+++ b/libs/libcurl/src/http2.c
@@ -83,18 +83,17 @@
/* spare chunks we keep for a full window */
#define H2_STREAM_POOL_SPARES (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE)
-/* We need to accommodate the max number of streams with their window
- * sizes on the overall connection. Streams might become PAUSED which
- * will block their received QUOTA in the connection window. And if we
- * run out of space, the server is blocked from sending us any data.
- * See #10988 for an issue with this. */
+/* We need to accommodate the max number of streams with their window sizes on
+ * the overall connection. Streams might become PAUSED which will block their
+ * received QUOTA in the connection window. If we run out of space, the server
+ * is blocked from sending us any data. See #10988 for an issue with this. */
#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE)
#define H2_SETTINGS_IV_LEN 3
#define H2_BINSETTINGS_LEN 80
-static int populate_settings(nghttp2_settings_entry *iv,
- struct Curl_easy *data)
+static size_t populate_settings(nghttp2_settings_entry *iv,
+ struct Curl_easy *data)
{
iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
@@ -112,7 +111,7 @@ static ssize_t populate_binsettings(uint8_t *binsettings,
struct Curl_easy *data)
{
nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
- int ivlen;
+ size_t ivlen;
ivlen = populate_settings(iv, data);
/* this returns number of bytes it wrote or a negative number on error. */
@@ -133,10 +132,12 @@ struct cf_h2_ctx {
struct Curl_hash streams; /* hash of `data->id` to `h2_stream_ctx` */
size_t drain_total; /* sum of all stream's UrlState drain */
uint32_t max_concurrent_streams;
- int32_t goaway_error;
- int32_t last_stream_id;
+ uint32_t goaway_error; /* goaway error code from server */
+ int32_t remote_max_sid; /* max id processed by server */
+ int32_t local_max_sid; /* max id processed by us */
BIT(conn_closed);
- BIT(goaway);
+ BIT(rcvd_goaway);
+ BIT(sent_goaway);
BIT(enable_push);
BIT(nw_out_blocked);
};
@@ -289,10 +290,6 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf,
(void)cf;
DEBUGASSERT(data);
- if(!data->req.p.http) {
- failf(data, "initialization failure, transfer not http initialized");
- return CURLE_FAILED_INIT;
- }
stream = H2_STREAM_CTX(ctx, data);
if(stream) {
*pstream = stream;
@@ -437,7 +434,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0);
Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free);
- ctx->last_stream_id = 2147483647;
+ ctx->remote_max_sid = 2147483647;
rc = nghttp2_session_callbacks_new(&cbs);
if(rc) {
@@ -486,7 +483,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
DEBUGASSERT(stream);
stream->id = 1;
/* queue SETTINGS frame (again) */
- rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen,
+ rc = nghttp2_session_upgrade2(ctx->h2, binsettings, (size_t)binlen,
data->state.httpreq == HTTPREQ_HEAD,
NULL);
if(rc) {
@@ -507,7 +504,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
}
else {
nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
- int ivlen;
+ size_t ivlen;
ivlen = populate_settings(iv, data);
rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
@@ -612,8 +609,8 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data,
return FALSE;
if(*input_pending) {
- /* This happens before we've sent off a request and the connection is
- not in use by any other transfer, there shouldn't be any data here,
+ /* This happens before we have sent off a request and the connection is
+ not in use by any other transfer, there should not be any data here,
only "protocol frames" */
CURLcode result;
ssize_t nread = -1;
@@ -794,18 +791,9 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf,
{
struct Curl_easy *second = curl_easy_duphandle(data);
if(second) {
- /* setup the request struct */
- struct HTTP *http = calloc(1, sizeof(struct HTTP));
- if(!http) {
- (void)Curl_close(&second);
- }
- else {
- struct h2_stream_ctx *second_stream;
-
- second->req.p.http = http;
- http2_data_setup(cf, second, &second_stream);
- second->state.priority.weight = data->state.priority.weight;
- }
+ struct h2_stream_ctx *second_stream;
+ http2_data_setup(cf, second, &second_stream);
+ second->state.priority.weight = data->state.priority.weight;
}
return second;
}
@@ -867,9 +855,7 @@ fail:
static void discard_newhandle(struct Curl_cfilter *cf,
struct Curl_easy *newhandle)
{
- if(newhandle->req.p.http) {
- http2_data_done(cf, newhandle);
- }
+ http2_data_done(cf, newhandle);
(void)Curl_close(&newhandle);
}
@@ -967,6 +953,10 @@ static int push_promise(struct Curl_cfilter *cf,
rv = CURL_PUSH_DENY;
goto fail;
}
+
+ /* success, remember max stream id processed */
+ if(newstream->id > ctx->local_max_sid)
+ ctx->local_max_sid = newstream->id;
}
else {
CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it");
@@ -1007,7 +997,7 @@ static void h2_xfer_write_resp(struct Curl_cfilter *cf,
"RST-ing stream",
stream->id, stream->xfer_result, blen);
nghttp2_submit_rst_stream(ctx->h2, 0, stream->id,
- NGHTTP2_ERR_CALLBACK_FAILURE);
+ (uint32_t)NGHTTP2_ERR_CALLBACK_FAILURE);
}
}
@@ -1048,7 +1038,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
break;
case NGHTTP2_HEADERS:
if(stream->bodystarted) {
- /* Only valid HEADERS after body started is trailer HEADERS. We
+ /* Only valid HEADERS after body started is trailer HEADERS. We
buffer them in on_header callback. */
break;
}
@@ -1252,12 +1242,12 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
break;
}
case NGHTTP2_GOAWAY:
- ctx->goaway = TRUE;
+ ctx->rcvd_goaway = TRUE;
ctx->goaway_error = frame->goaway.error_code;
- ctx->last_stream_id = frame->goaway.last_stream_id;
+ ctx->remote_max_sid = frame->goaway.last_stream_id;
if(data) {
- infof(data, "received GOAWAY, error=%d, last_stream=%u",
- ctx->goaway_error, ctx->last_stream_id);
+ infof(data, "received GOAWAY, error=%u, last_stream=%u",
+ ctx->goaway_error, ctx->remote_max_sid);
Curl_multi_connchanged(data->multi);
}
break;
@@ -1654,7 +1644,7 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
return CURLE_FAILED_INIT;
}
- result = Curl_base64url_encode((const char *)binsettings, binlen,
+ result = Curl_base64url_encode((const char *)binsettings, (size_t)binlen,
&base64, &blen);
if(result) {
Curl_dyn_free(req);
@@ -1710,7 +1700,7 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
if(stream->error == NGHTTP2_REFUSED_STREAM) {
CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
"connection", stream->id);
- connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
+ connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */
data->state.refused_stream = TRUE;
*err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
return -1;
@@ -1817,7 +1807,7 @@ static void h2_pri_spec(struct cf_h2_ctx *ctx,
}
/*
- * Check if there's been an update in the priority /
+ * Check if there is been an update in the priority /
* dependency settings and if so it submits a PRIORITY frame with the updated
* info.
* Flush any out data pending in the network buffer.
@@ -1878,7 +1868,7 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
}
else if(stream->reset ||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
- (ctx->goaway && ctx->last_stream_id < stream->id)) {
+ (ctx->rcvd_goaway && ctx->remote_max_sid < stream->id)) {
CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id);
*err = data->req.bytecount? CURLE_PARTIAL_FILE : CURLE_HTTP2;
nread = -1;
@@ -2015,7 +2005,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
out:
result = h2_progress_egress(cf, data);
if(result == CURLE_AGAIN) {
- /* pending data to send, need to be called again. Ideally, we'd
+ /* pending data to send, need to be called again. Ideally, we would
* monitor the socket for POLLOUT, but we might not be in SENDING
* transfer state any longer and are unable to make this happen.
*/
@@ -2292,8 +2282,8 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
/* Unable to send all data, due to connection blocked or H2 window
* exhaustion. Data is left in our stream buffer, or nghttp2's internal
* frame buffer or our network out buffer. */
- size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2,
- stream->id);
+ size_t rwin = (size_t)nghttp2_session_get_stream_remote_window_size(
+ ctx->h2, stream->id);
/* At the start of a stream, we are called with request headers
* and, possibly, parts of the body. Later, only body data.
* If we cannot send pure body data, we EAGAIN. If there had been
@@ -2358,6 +2348,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
struct easy_pollset *ps)
{
struct cf_h2_ctx *ctx = cf->ctx;
+ struct cf_call_data save;
curl_socket_t sock;
bool want_recv, want_send;
@@ -2368,7 +2359,6 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
if(want_recv || want_send) {
struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
- struct cf_call_data save;
bool c_exhaust, s_exhaust;
CF_DATA_SAVE(save, cf, data);
@@ -2383,6 +2373,14 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
Curl_pollset_set(data, ps, sock, want_recv, want_send);
CF_DATA_RESTORE(cf, save);
}
+ else if(ctx->sent_goaway && !cf->shutdown) {
+ /* shutdown in progress */
+ CF_DATA_SAVE(save, cf, data);
+ want_send = nghttp2_session_want_write(ctx->h2);
+ want_recv = nghttp2_session_want_read(ctx->h2);
+ Curl_pollset_set(data, ps, sock, want_recv, want_send);
+ CF_DATA_RESTORE(cf, save);
+ }
}
static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
@@ -2446,6 +2444,7 @@ static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
CF_DATA_SAVE(save, cf, data);
cf_h2_ctx_clear(ctx);
CF_DATA_RESTORE(cf, save);
+ cf->connected = FALSE;
}
if(cf->next)
cf->next->cft->do_close(cf->next, data);
@@ -2462,6 +2461,50 @@ static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
}
}
+static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data, bool *done)
+{
+ struct cf_h2_ctx *ctx = cf->ctx;
+ struct cf_call_data save;
+ CURLcode result;
+ int rv;
+
+ if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ CF_DATA_SAVE(save, cf, data);
+
+ if(!ctx->sent_goaway) {
+ rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE,
+ ctx->local_max_sid, 0,
+ (const uint8_t *)"shutown", sizeof("shutown"));
+ if(rv) {
+ failf(data, "nghttp2_submit_goaway() failed: %s(%d)",
+ nghttp2_strerror(rv), rv);
+ result = CURLE_SEND_ERROR;
+ goto out;
+ }
+ ctx->sent_goaway = TRUE;
+ }
+ /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */
+ result = CURLE_OK;
+ if(nghttp2_session_want_write(ctx->h2))
+ result = h2_progress_egress(cf, data);
+ if(!result && nghttp2_session_want_read(ctx->h2))
+ result = h2_progress_ingress(cf, data, 0);
+
+ *done = (ctx->conn_closed ||
+ (!result && !nghttp2_session_want_write(ctx->h2) &&
+ !nghttp2_session_want_read(ctx->h2)));
+
+out:
+ CF_DATA_RESTORE(cf, save);
+ cf->shutdown = (result || *done);
+ return result;
+}
+
static CURLcode http2_data_pause(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool pause)
@@ -2474,10 +2517,10 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
if(ctx && ctx->h2 && stream) {
uint32_t window = pause? 0 : stream->local_window_size;
- int rv = nghttp2_session_set_local_window_size(ctx->h2,
- NGHTTP2_FLAG_NONE,
- stream->id,
- window);
+ int rv = (int)nghttp2_session_set_local_window_size(ctx->h2,
+ NGHTTP2_FLAG_NONE,
+ stream->id,
+ (int32_t)window);
if(rv) {
failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
nghttp2_strerror(rv), rv);
@@ -2505,7 +2548,7 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
#ifdef DEBUGBUILD
{
/* read out the stream local window again */
- uint32_t window2 =
+ uint32_t window2 = (uint32_t)
nghttp2_session_get_stream_local_window_size(ctx->h2,
stream->id);
DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u",
@@ -2632,6 +2675,7 @@ struct Curl_cftype Curl_cft_nghttp2 = {
cf_h2_destroy,
cf_h2_connect,
cf_h2_close,
+ cf_h2_shutdown,
Curl_cf_def_get_host,
cf_h2_adjust_pollset,
cf_h2_data_pending,
@@ -2729,7 +2773,7 @@ bool Curl_http2_may_switch(struct Curl_easy *data,
data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
- /* We don't support HTTP/2 proxies yet. Also it's debatable
+ /* We do not support HTTP/2 proxies yet. Also it is debatable
whether or not this setting should apply to HTTP/2 proxies. */
infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
return FALSE;
@@ -2753,7 +2797,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data,
if(result)
return result;
- conn->httpversion = 20; /* we know we're on HTTP/2 now */
+ conn->httpversion = 20; /* we know we are on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
conn->bundle->multiuse = BUNDLE_MULTIPLEX;
Curl_multi_connchanged(data->multi);
@@ -2777,7 +2821,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
return result;
cf_h2 = cf->next;
- cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */
+ cf->conn->httpversion = 20; /* we know we are on HTTP/2 now */
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
Curl_multi_connchanged(data->multi);
@@ -2830,7 +2874,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
" after upgrade: len=%zu", nread);
}
- conn->httpversion = 20; /* we know we're on HTTP/2 now */
+ conn->httpversion = 20; /* we know we are on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
conn->bundle->multiuse = BUNDLE_MULTIPLEX;
Curl_multi_connchanged(data->multi);