summaryrefslogtreecommitdiff
path: root/Utilities/cmcurl/lib/http2.c
diff options
context:
space:
mode:
Diffstat (limited to 'Utilities/cmcurl/lib/http2.c')
-rw-r--r--Utilities/cmcurl/lib/http2.c351
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