diff options
author | Brad King <brad.king@kitware.com> | 2023-03-20 16:40:49 -0400 |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2023-03-20 16:40:49 -0400 |
commit | e3dc4df9b95272037d27be25ae5ef030ffd83fa4 (patch) | |
tree | 6132bc02d5204bd988b32ee8ab06053ec7c0e4c4 /Utilities/cmcurl/lib/http2.c | |
parent | 4e6c3cd93bc870f44442e6d7e59896bc6d86ce25 (diff) | |
parent | e8bff971d9d835a65bcb1711e7214afe37b238e0 (diff) | |
download | cmake-e3dc4df9b95272037d27be25ae5ef030ffd83fa4.tar.gz |
Merge branch 'upstream-curl' into update-curl
* upstream-curl:
curl 2023-03-20 (b16d1fa8)
Diffstat (limited to 'Utilities/cmcurl/lib/http2.c')
-rw-r--r-- | Utilities/cmcurl/lib/http2.c | 351 |
1 files changed, 192 insertions, 159 deletions
diff --git a/Utilities/cmcurl/lib/http2.c b/Utilities/cmcurl/lib/http2.c index bdb5e7378e..b0ce87d987 100644 --- a/Utilities/cmcurl/lib/http2.c +++ b/Utilities/cmcurl/lib/http2.c @@ -98,7 +98,6 @@ static size_t populate_binsettings(uint8_t *binsettings, struct cf_h2_ctx { nghttp2_session *h2; uint32_t max_concurrent_streams; - bool enable_push; /* The easy handle used in the current filter call, cleared at return */ struct cf_call_data call_data; @@ -116,6 +115,10 @@ struct cf_h2_ctx { int32_t pause_stream_id; /* stream ID which paused nghttp2_session_mem_recv */ size_t drain_total; /* sum of all stream's UrlState.drain */ + int32_t goaway_error; + int32_t last_stream_id; + BIT(goaway); + BIT(enable_push); }; /* How to access `call_data` from a cf_h2 filter */ @@ -364,41 +367,42 @@ static void http2_stream_free(struct HTTP *stream) } /* + * Returns nonzero if current HTTP/2 session should be closed. + */ +static int should_close_session(struct cf_h2_ctx *ctx) +{ + return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) && + !nghttp2_session_want_write(ctx->h2); +} + +/* * The server may send us data at any point (e.g. PING frames). Therefore, * we cannot assume that an HTTP/2 socket is dead just because it is readable. * * Check the lower filters first and, if successful, peek at the socket * and distinguish between closed and data. */ -static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data) +static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, + bool *input_pending) { struct cf_h2_ctx *ctx = cf->ctx; - int sval; - bool dead = TRUE; + bool alive = TRUE; - if(!cf->next || !cf->next->cft->is_alive(cf->next, data)) - return TRUE; + *input_pending = FALSE; + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + return FALSE; - sval = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0); - if(sval == 0) { - /* timeout */ - dead = FALSE; - } - else if(sval & CURL_CSELECT_ERR) { - /* socket is in an error state */ - dead = TRUE; - } - else if(sval & CURL_CSELECT_IN) { + 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, only "protocol frames" */ CURLcode result; ssize_t nread = -1; + *input_pending = FALSE; Curl_attach_connection(data, cf->conn); nread = Curl_conn_cf_recv(cf->next, data, ctx->inbuf, H2_BUFSIZE, &result); - dead = FALSE; if(nread != -1) { DEBUGF(LOG_CF(data, cf, "%d bytes stray data read before trying " "h2 connection", (int)nread)); @@ -406,15 +410,19 @@ static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data) ctx->inbuflen = nread; if(h2_process_pending_input(cf, data, &result) < 0) /* immediate error, considered dead */ - dead = TRUE; + alive = FALSE; + else { + alive = !should_close_session(ctx); + } } - else + else { /* the read failed so let's say this is dead anyway */ - dead = TRUE; + alive = FALSE; + } Curl_detach_connection(data); } - return dead; + return alive; } static CURLcode http2_send_ping(struct Curl_cfilter *cf, @@ -815,7 +823,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, ctx->max_concurrent_streams = nghttp2_session_get_remote_settings( session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); ctx->enable_push = nghttp2_session_get_remote_settings( - session, NGHTTP2_SETTINGS_ENABLE_PUSH); + session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0; DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS == %d", ctx->max_concurrent_streams)); DEBUGF(LOG_CF(data, cf, "ENABLE_PUSH == %s", @@ -829,9 +837,12 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, break; } case NGHTTP2_GOAWAY: + ctx->goaway = TRUE; + ctx->goaway_error = frame->goaway.error_code; + ctx->last_stream_id = frame->goaway.last_stream_id; if(data) { infof(data, "recveived GOAWAY, error=%d, last_stream=%u", - frame->goaway.error_code, frame->goaway.last_stream_id); + ctx->goaway_error, ctx->last_stream_id); multi_connchanged(data->multi); } break; @@ -858,7 +869,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, switch(frame->hd.type) { case NGHTTP2_DATA: - /* If body started on this stream, then receiving DATA is illegal. */ + /* If !body started on this stream, then receiving DATA is illegal. */ DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame DATA", stream_id)); if(!stream->bodystarted) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, @@ -940,7 +951,21 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, break; case NGHTTP2_RST_STREAM: DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv RST", stream_id)); + stream->closed = TRUE; stream->reset = TRUE; + drain_this(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + break; + case NGHTTP2_WINDOW_UPDATE: + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv WINDOW_UPDATE", stream_id)); + if((data_s->req.keepon & KEEP_SEND_HOLD) && + (data_s->req.keepon & KEEP_SEND)) { + data_s->req.keepon &= ~KEEP_SEND_HOLD; + drain_this(cf, data_s); + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] un-holding after win update", + stream_id)); + } break; default: DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame %x", @@ -1006,18 +1031,6 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, return NGHTTP2_ERR_PAUSE; } -#if 0 - /* pause execution of nghttp2 if we received data for another handle - in order to process them first. */ - if(CF_DATA_CURRENT(cf) != data_s) { - ctx->pause_stream_id = stream_id; - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] not call_data -> NGHTTP2_ERR_PAUSE", - stream_id)); - drain_this(cf, data_s); - return NGHTTP2_ERR_PAUSE; - } -#endif - return 0; } @@ -1030,44 +1043,43 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, struct HTTP *stream; int rv; (void)session; - (void)stream_id; - if(stream_id) { - /* get the stream from the hash based on Stream ID, stream ID zero is for - connection-oriented stuff */ - data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(!data_s) { - /* We could get stream ID not in the hash. For example, if we - decided to reject stream (e.g., PUSH_PROMISE). */ - return 0; - } - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] on_stream_close(), %s (err %d)", - stream_id, nghttp2_http2_strerror(error_code), error_code)); - stream = data_s->req.p.http; - if(!stream) - return NGHTTP2_ERR_CALLBACK_FAILURE; + /* get the stream from the hash based on Stream ID, stream ID zero is for + connection-oriented stuff */ + data_s = stream_id? + nghttp2_session_get_stream_user_data(session, stream_id) : NULL; + if(!data_s) { + return 0; + } + stream = data_s->req.p.http; + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] on_stream_close(), %s (err %d)", + stream_id, nghttp2_http2_strerror(error_code), error_code)); + if(!stream) + return NGHTTP2_ERR_CALLBACK_FAILURE; - stream->closed = TRUE; - if(CF_DATA_CURRENT(cf) != data_s) { - drain_this(cf, data_s); - Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - } - stream->error = error_code; + stream->closed = TRUE; + stream->error = error_code; + if(stream->error) + stream->reset = TRUE; - /* remove the entry from the hash as the stream is now gone */ - rv = nghttp2_session_set_stream_user_data(session, stream_id, 0); - if(rv) { - infof(data_s, "http/2: failed to clear user_data for stream %u", - stream_id); - DEBUGASSERT(0); - } - if(stream_id == ctx->pause_stream_id) { - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed the pause stream", - stream_id)); - ctx->pause_stream_id = 0; - } - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed, cleared", stream_id)); + if(CF_DATA_CURRENT(cf) != data_s) { + drain_this(cf, data_s); + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); } + + /* remove `data_s` from the nghttp2 stream */ + rv = nghttp2_session_set_stream_user_data(session, stream_id, 0); + if(rv) { + infof(data_s, "http/2: failed to clear user_data for stream %u", + stream_id); + DEBUGASSERT(0); + } + if(stream_id == ctx->pause_stream_id) { + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed the pause stream", + stream_id)); + ctx->pause_stream_id = 0; + } + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed now", stream_id)); return 0; } @@ -1383,7 +1395,8 @@ static void http2_data_done(struct Curl_cfilter *cf, ctx->pause_stream_id = 0; } - if(premature || (!stream->closed && stream->stream_id)) { + (void)premature; + if(!stream->closed && stream->stream_id) { /* RST_STREAM */ DEBUGF(LOG_CF(data, cf, "[h2sid=%u] RST", stream->stream_id)); if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, @@ -1446,15 +1459,6 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, } /* - * Returns nonzero if current HTTP/2 session should be closed. - */ -static int should_close_session(struct cf_h2_ctx *ctx) -{ - return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) && - !nghttp2_session_want_write(ctx->h2); -} - -/* * h2_process_pending_input() processes pending input left in * httpc->inbuf. Then, call h2_session_send() to send pending data. * This function returns 0 if it succeeds, or -1 and error code will @@ -1586,8 +1590,6 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, } } - /* Reset to FALSE to prevent infinite loop in readwrite_data function. */ - stream->closed = FALSE; if(stream->error == NGHTTP2_REFUSED_STREAM) { DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new " "connection", stream->stream_id)); @@ -1603,6 +1605,11 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, *err = CURLE_HTTP2_STREAM; return -1; } + else if(stream->reset) { + failf(data, "HTTP/2 stream %u was reset", stream->stream_id); + *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + return -1; + } if(!stream->bodystarted) { failf(data, "HTTP/2 stream %u was closed cleanly, but before getting " @@ -1638,7 +1645,7 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, stream->close_handled = TRUE; - DEBUGF(LOG_CF(data, cf, "http2_recv returns 0, http2_handle_stream_close")); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] closed cleanly", stream->stream_id)); return 0; } @@ -1720,9 +1727,29 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, struct HTTP *stream = data->req.p.http; ssize_t nread = -1; struct cf_call_data save; + bool conn_is_closed = FALSE; CF_DATA_SAVE(save, cf, data); + /* If the h2 session has told us to GOAWAY with an error AND + * indicated the highest stream id it has processes AND + * the stream we are trying to read has a higher id, this + * means we will most likely not receive any more for it. + * Treat this as if the server explicitly had RST the stream */ + if((ctx->goaway && ctx->goaway_error && + ctx->last_stream_id > 0 && + ctx->last_stream_id < stream->stream_id)) { + stream->reset = TRUE; + } + + /* If a stream is RST, it does not matter what state the h2 session + * is in, our answer to receiving data is always the same. */ + if(stream->reset) { + *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; + nread = -1; + goto out; + } + if(should_close_session(ctx)) { DEBUGF(LOG_CF(data, cf, "http2_recv: nothing to do in this session")); if(cf->conn->bits.close) { @@ -1763,7 +1790,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: win %u/%u", + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv: win %u/%u", stream->stream_id, nghttp2_session_get_local_window_size(ctx->h2), nghttp2_session_get_stream_local_window_size(ctx->h2, @@ -1846,57 +1873,40 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, stream->memlen = 0; if(ctx->inbuflen > 0) { - DEBUGF(LOG_CF(data, cf, "Use data left in connection buffer, nread=%zd", - ctx->inbuflen - ctx->nread_inbuf)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] %zd bytes in inbuf", + stream->stream_id, ctx->inbuflen - ctx->nread_inbuf)); if(h2_process_pending_input(cf, data, err)) return -1; } - while(stream->memlen == 0 /* have no data for this stream */ - && !ctx->pause_stream_id /* we are not paused either */ - && ctx->inbuflen == 0) { /* and out input buffer is empty */ + while(stream->memlen == 0 && /* have no data for this stream */ + !stream->closed && /* and it is not closed/reset */ + !ctx->pause_stream_id && /* we are not paused either */ + ctx->inbuflen == 0 && /* and out input buffer is empty */ + !conn_is_closed) { /* and connection is not closed */ /* Receive data from the "lower" filters */ nread = Curl_conn_cf_recv(cf->next, data, ctx->inbuf, H2_BUFSIZE, err); if(nread < 0) { - if(*err != CURLE_AGAIN) - failf(data, "Failed receiving HTTP2 data"); - else if(stream->closed) { - /* received when the stream was already closed! */ - nread = http2_handle_stream_close(cf, data, stream, err); - goto out; + DEBUGASSERT(*err); + if(*err == CURLE_AGAIN) { + break; } - - /* nothing to read from the lower layers, clear drain */ - drained_transfer(cf, data); - nread = -1; - goto out; + failf(data, "Failed receiving HTTP2 data"); + conn_is_closed = TRUE; } else if(nread == 0) { - if(!stream->closed) { - /* This will happen when the server or proxy server is SIGKILLed - during data transfer. We should emit an error since our data - received may be incomplete. */ - failf(data, "HTTP/2 stream %u was not closed cleanly before" - " end of the underlying stream", - stream->stream_id); - drained_transfer(cf, data); - *err = CURLE_PARTIAL_FILE; - nread = -1; - goto out; - } - - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] end of stream", + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] underlying connection is closed", stream->stream_id)); - *err = CURLE_OK; - nread = 0; - goto out; + conn_is_closed = TRUE; + } + else { + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] read %zd from connection", + stream->stream_id, nread)); + ctx->inbuflen = nread; + DEBUGASSERT(ctx->nread_inbuf == 0); + if(h2_process_pending_input(cf, data, err)) + return -1; } - - DEBUGF(LOG_CF(data, cf, "read %zd from connection", nread)); - ctx->inbuflen = nread; - DEBUGASSERT(ctx->nread_inbuf == 0); - if(h2_process_pending_input(cf, data, err)) - return -1; } } @@ -1933,11 +1943,18 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, *err = CURLE_OK; nread = retlen; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_h2_recv -> %zd", - stream->stream_id, nread)); goto out; } + if(conn_is_closed && !stream->closed) { + /* underlying connection is closed and we have nothing for the stream. + * Treat this as a RST */ + stream->closed = stream->reset = TRUE; + failf(data, "HTTP/2 stream %u was not closed cleanly before" + " end of the underlying connection", + stream->stream_id); + } + if(stream->closed) { nread = http2_handle_stream_close(cf, data, stream, err); goto out; @@ -1950,9 +1967,9 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } *err = CURLE_AGAIN; nread = -1; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv -> AGAIN", - stream->stream_id)); out: + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv -> %zd, %d", + stream->stream_id, nread, *err)); CF_DATA_RESTORE(cf, save); return nread; } @@ -1976,19 +1993,20 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, CURLcode result; struct h2h3req *hreq; struct cf_call_data save; + ssize_t nwritten; CF_DATA_SAVE(save, cf, data); - DEBUGF(LOG_CF(data, cf, "send len=%zu", len)); + DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) start", len)); if(stream->stream_id != -1) { if(stream->close_handled) { infof(data, "stream %u closed", stream->stream_id); *err = CURLE_HTTP2_STREAM; - len = -1; + nwritten = -1; goto out; } else if(stream->closed) { - len = http2_handle_stream_close(cf, data, stream, err); + nwritten = http2_handle_stream_close(cf, data, stream, err); goto out; } /* If stream_id != -1, we have dispatched request HEADERS, and now @@ -1998,26 +2016,24 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, rv = nghttp2_session_resume_data(ctx->h2, stream->stream_id); if(nghttp2_is_fatal(rv)) { *err = CURLE_SEND_ERROR; - len = -1; + nwritten = -1; goto out; } result = h2_session_send(cf, data); if(result) { *err = result; - len = -1; + nwritten = -1; goto out; } - len -= stream->upload_len; - /* Nullify here because we call nghttp2_session_send() and they - might refer to the old buffer. */ + nwritten = (ssize_t)len - (ssize_t)stream->upload_len; stream->upload_mem = NULL; stream->upload_len = 0; if(should_close_session(ctx)) { DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session")); *err = CURLE_HTTP2; - len = -1; + nwritten = -1; goto out; } @@ -2029,26 +2045,36 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, nghttp2_session_resume_data(ctx->h2, stream->stream_id); } -#ifdef DEBUG_HTTP2 - if(!len) { - infof(data, "http2_send: easy %p (stream %u) win %u/%u", - data, stream->stream_id, - nghttp2_session_get_remote_window_size(ctx->h2), - nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->stream_id) - ); - + if(!nwritten) { + size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2, + stream->stream_id); + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send: win %u/%zu", + stream->stream_id, + nghttp2_session_get_remote_window_size(ctx->h2), rwin)); + if(rwin == 0) { + /* We cannot upload more as the stream's remote window size + * is 0. We need to receive WIN_UPDATEs before we can continue. + */ + data->req.keepon |= KEEP_SEND_HOLD; + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] holding send as remote flow " + "window is exhausted", stream->stream_id)); + } } - infof(data, "http2_send returns %zu for stream %u", len, - stream->stream_id); -#endif + DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send returns %zd ", + stream->stream_id, nwritten)); + + /* handled writing BODY for open stream. */ goto out; } - + /* Stream has not been opened yet. `buf` is expected to contain + * request headers. */ + /* TODO: this assumes that the `buf` and `len` we are called with + * is *all* HEADERs and no body. We have no way to determine here + * if that is indeed the case. */ result = Curl_pseudo_headers(data, buf, len, NULL, &hreq); if(result) { *err = result; - len = -1; + nwritten = -1; goto out; } nheader = hreq->entries; @@ -2057,7 +2083,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, if(!nva) { Curl_pseudo_free(hreq); *err = CURLE_OUT_OF_MEMORY; - len = -1; + nwritten = -1; goto out; } else { @@ -2104,25 +2130,28 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u", nghttp2_strerror(stream_id), stream_id)); *err = CURLE_SEND_ERROR; - len = -1; + nwritten = -1; goto out; } infof(data, "Using Stream ID: %u (easy handle %p)", stream_id, (void *)data); stream->stream_id = stream_id; + /* See TODO above. We assume that the whole buf was consumed by + * generating the request headers. */ + nwritten = len; result = h2_session_send(cf, data); if(result) { *err = result; - len = -1; + nwritten = -1; goto out; } if(should_close_session(ctx)) { DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session")); *err = CURLE_HTTP2; - len = -1; + nwritten = -1; goto out; } @@ -2137,7 +2166,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, out: CF_DATA_RESTORE(cf, save); - return len; + return nwritten; } static int cf_h2_get_select_socks(struct Curl_cfilter *cf, @@ -2160,7 +2189,7 @@ static int cf_h2_get_select_socks(struct Curl_cfilter *cf, /* we're (still uploading OR the HTTP/2 layer wants to send data) AND there's a window to send data in */ - if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) || + if((((k->keepon & KEEP_SENDBITS) == KEEP_SEND) || nghttp2_session_want_write(ctx->h2)) && (nghttp2_session_get_remote_window_size(ctx->h2) && nghttp2_session_get_stream_remote_window_size(ctx->h2, @@ -2329,14 +2358,17 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf, } static bool cf_h2_is_alive(struct Curl_cfilter *cf, - struct Curl_easy *data) + struct Curl_easy *data, + bool *input_pending) { struct cf_h2_ctx *ctx = cf->ctx; CURLcode result; struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - result = (ctx && ctx->h2 && !http2_connisdead(cf, data)); + result = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending)); + DEBUGF(LOG_CF(data, cf, "conn alive -> %d, input_pending=%d", + result, *input_pending)); CF_DATA_RESTORE(cf, save); return result; } @@ -2479,7 +2511,8 @@ bool Curl_http2_may_switch(struct Curl_easy *data, int sockindex) { (void)sockindex; - if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { + if(!Curl_conn_is_http2(data, conn, sockindex) && + 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 |