aboutsummaryrefslogtreecommitdiff
path: root/lib/http2.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/http2.c')
-rw-r--r--lib/http2.c194
1 files changed, 95 insertions, 99 deletions
diff --git a/lib/http2.c b/lib/http2.c
index c8b059498..c3157d1ef 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -107,14 +107,14 @@ static int populate_settings(nghttp2_settings_entry *iv,
return 3;
}
-static size_t populate_binsettings(uint8_t *binsettings,
- struct Curl_easy *data)
+static ssize_t populate_binsettings(uint8_t *binsettings,
+ struct Curl_easy *data)
{
nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
int ivlen;
ivlen = populate_settings(iv, data);
- /* this returns number of bytes it wrote */
+ /* this returns number of bytes it wrote or a negative number on error. */
return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
iv, ivlen);
}
@@ -219,10 +219,10 @@ static void drain_stream(struct Curl_cfilter *cf,
if(!stream->send_closed &&
(stream->upload_left || stream->upload_blocked_len))
bits |= CURL_CSELECT_OUT;
- if(data->state.dselect_bits != bits) {
- CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x",
+ if(data->state.select_bits != bits) {
+ CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x",
stream->id, bits);
- data->state.dselect_bits = bits;
+ data->state.select_bits = bits;
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
}
@@ -283,13 +283,20 @@ static void http2_data_done(struct Curl_cfilter *cf,
return;
if(ctx->h2) {
+ bool flush_egress = FALSE;
+ /* returns error if stream not known, which is fine here */
+ (void)nghttp2_session_set_stream_user_data(ctx->h2, stream->id, NULL);
+
if(!stream->closed && stream->id > 0) {
/* RST_STREAM */
CURL_TRC_CF(data, cf, "[%d] premature DATA_DONE, RST stream",
stream->id);
- if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
- stream->id, NGHTTP2_STREAM_CLOSED))
- (void)nghttp2_session_send(ctx->h2);
+ stream->closed = TRUE;
+ stream->reset = TRUE;
+ stream->send_closed = TRUE;
+ nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
+ stream->id, NGHTTP2_STREAM_CLOSED);
+ flush_egress = TRUE;
}
if(!Curl_bufq_is_empty(&stream->recvbuf)) {
/* Anything in the recvbuf is still being counted
@@ -299,19 +306,11 @@ static void http2_data_done(struct Curl_cfilter *cf,
nghttp2_session_consume(ctx->h2, stream->id,
Curl_bufq_len(&stream->recvbuf));
/* give WINDOW_UPATE a chance to be sent, but ignore any error */
- (void)h2_progress_egress(cf, data);
+ flush_egress = TRUE;
}
- /* -1 means unassigned and 0 means cleared */
- if(nghttp2_session_get_stream_user_data(ctx->h2, stream->id)) {
- int rv = nghttp2_session_set_stream_user_data(ctx->h2,
- stream->id, 0);
- if(rv) {
- infof(data, "http/2: failed to clear user_data for stream %u",
- stream->id);
- DEBUGASSERT(0);
- }
- }
+ if(flush_egress)
+ nghttp2_session_send(ctx->h2);
}
Curl_bufq_free(&stream->sendbuf);
@@ -369,12 +368,15 @@ static ssize_t nw_out_writer(void *writer_ctx,
{
struct Curl_cfilter *cf = writer_ctx;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
- ssize_t nwritten;
- nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, err);
- if(nwritten > 0)
- CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten);
- return nwritten;
+ if(data) {
+ ssize_t nwritten = Curl_conn_cf_send(cf->next, data,
+ (const char *)buf, buflen, err);
+ if(nwritten > 0)
+ CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten);
+ return nwritten;
+ }
+ return 0;
}
static ssize_t send_callback(nghttp2_session *h2,
@@ -452,9 +454,14 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
* in the H1 request and we upgrade from there. This stream
* is opened implicitly as #1. */
uint8_t binsettings[H2_BINSETTINGS_LEN];
- size_t binlen; /* length of the binsettings data */
+ ssize_t binlen; /* length of the binsettings data */
binlen = populate_binsettings(binsettings, data);
+ if(binlen <= 0) {
+ failf(data, "nghttp2 unexpectedly failed on pack_settings_payload");
+ result = CURLE_FAILED_INIT;
+ goto out;
+ }
result = http2_data_setup(cf, data, &stream);
if(result)
@@ -1076,16 +1083,11 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
stream->reset = TRUE;
}
stream->send_closed = TRUE;
- data->req.keepon &= ~KEEP_SEND_HOLD;
drain_stream(cf, data, stream);
break;
case NGHTTP2_WINDOW_UPDATE:
- if((data->req.keepon & KEEP_SEND_HOLD) &&
- (data->req.keepon & KEEP_SEND)) {
- data->req.keepon &= ~KEEP_SEND_HOLD;
+ if(CURL_WANT_SEND(data)) {
drain_stream(cf, data, stream);
- CURL_TRC_CF(data, cf, "[%d] un-holding after win update",
- stream_id);
}
break;
default:
@@ -1230,15 +1232,10 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
* window and *assume* that we treat this like a WINDOW_UPDATE. Some
* servers send an explicit WINDOW_UPDATE, but not all seem to do that.
* To be safe, we UNHOLD a stream in order not to stall. */
- if((data->req.keepon & KEEP_SEND_HOLD) &&
- (data->req.keepon & KEEP_SEND)) {
+ if(CURL_WANT_SEND(data)) {
struct stream_ctx *stream = H2_STREAM_CTX(data);
- data->req.keepon &= ~KEEP_SEND_HOLD;
- if(stream) {
+ if(stream)
drain_stream(cf, data, stream);
- CURL_TRC_CF(data, cf, "[%d] un-holding after SETTINGS",
- stream_id);
- }
}
}
break;
@@ -1318,27 +1315,43 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *userp)
{
struct Curl_cfilter *cf = userp;
- struct Curl_easy *data_s;
+ struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf);
struct stream_ctx *stream;
int rv;
(void)session;
+ DEBUGASSERT(call_data);
/* 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) {
+ CURL_TRC_CF(call_data, cf,
+ "[%d] on_stream_close, no easy set on stream", stream_id);
return 0;
}
+ if(!GOOD_EASY_HANDLE(data_s)) {
+ /* nghttp2 still has an easy registered for the stream which has
+ * been freed be libcurl. This points to a code path that does not
+ * trigger DONE or DETACH events as it must. */
+ CURL_TRC_CF(call_data, cf,
+ "[%d] on_stream_close, not a GOOD easy on stream", stream_id);
+ (void)nghttp2_session_set_stream_user_data(session, stream_id, 0);
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
stream = H2_STREAM_CTX(data_s);
- if(!stream)
+ if(!stream) {
+ CURL_TRC_CF(data_s, cf,
+ "[%d] on_stream_close, GOOD easy but no stream", stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE;
+ }
stream->closed = TRUE;
stream->error = error_code;
- if(stream->error)
+ if(stream->error) {
stream->reset = TRUE;
- data_s->req.keepon &= ~KEEP_SEND_HOLD;
+ stream->send_closed = TRUE;
+ }
if(stream->error)
CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %d)",
@@ -1602,10 +1615,10 @@ static int error_callback(nghttp2_session *session,
size_t len,
void *userp)
{
+ struct Curl_cfilter *cf = userp;
+ struct Curl_easy *data = CF_DATA_CURRENT(cf);
(void)session;
- (void)msg;
- (void)len;
- (void)userp;
+ failf(data, "%.*s", (int)len, msg);
return 0;
}
#endif
@@ -1621,7 +1634,7 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
size_t blen;
struct SingleRequest *k = &data->req;
uint8_t binsettings[H2_BINSETTINGS_LEN];
- size_t binlen; /* length of the binsettings data */
+ ssize_t binlen; /* length of the binsettings data */
binlen = populate_binsettings(binsettings, data);
if(binlen <= 0) {
@@ -2052,23 +2065,13 @@ static ssize_t h2_submit(struct stream_ctx **pstream,
/* no longer needed */
Curl_h1_req_parse_free(&stream->h1);
- nheader = Curl_dynhds_count(&h2_headers);
- nva = malloc(sizeof(nghttp2_nv) * nheader);
+ nva = Curl_dynhds_to_nva(&h2_headers, &nheader);
if(!nva) {
*err = CURLE_OUT_OF_MEMORY;
nwritten = -1;
goto out;
}
- for(i = 0; i < nheader; ++i) {
- struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
- nva[i].name = (unsigned char *)e->name;
- nva[i].namelen = e->namelen;
- nva[i].value = (unsigned char *)e->value;
- nva[i].valuelen = e->valuelen;
- nva[i].flags = NGHTTP2_NV_FLAG_NONE;
- }
-
h2_pri_spec(data, &pri_spec);
if(!nghttp2_session_check_request_allowed(ctx->h2))
CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)");
@@ -2272,14 +2275,6 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
* frame buffer or our network out buffer. */
size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2,
stream->id);
- if(rwin == 0) {
- /* H2 flow window exhaustion. We need to HOLD upload until we get
- * a WINDOW_UPDATE from the server. */
- data->req.keepon |= KEEP_SEND_HOLD;
- CURL_TRC_CF(data, cf, "[%d] holding send as remote flow "
- "window is exhausted", stream->id);
- }
-
/* Whatever the cause, we need to return CURL_EAGAIN for this call.
* We have unwritten state that needs us being invoked again and EAGAIN
* is the only way to ensure that. */
@@ -2331,37 +2326,37 @@ out:
return nwritten;
}
-static int cf_h2_get_select_socks(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- curl_socket_t *sock)
+static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct easy_pollset *ps)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct SingleRequest *k = &data->req;
- struct stream_ctx *stream = H2_STREAM_CTX(data);
- int bitmap = GETSOCK_BLANK;
- struct cf_call_data save;
+ curl_socket_t sock;
+ bool want_recv, want_send;
- CF_DATA_SAVE(save, cf, data);
- sock[0] = Curl_conn_cf_get_socket(cf, data);
-
- if(!(k->keepon & (KEEP_RECV_PAUSE|KEEP_RECV_HOLD)))
- /* Unless paused - in an HTTP/2 connection we can basically always get a
- frame so we should always be ready for one */
- bitmap |= GETSOCK_READSOCK(0);
-
- /* 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_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,
- stream->id)))
- bitmap |= GETSOCK_WRITESOCK(0);
+ if(!ctx->h2)
+ return;
- CF_DATA_RESTORE(cf, save);
- return bitmap;
-}
+ sock = Curl_conn_cf_get_socket(cf, data);
+ Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
+ if(want_recv || want_send) {
+ struct stream_ctx *stream = H2_STREAM_CTX(data);
+ struct cf_call_data save;
+ bool c_exhaust, s_exhaust;
+ CF_DATA_SAVE(save, cf, data);
+ c_exhaust = want_send && !nghttp2_session_get_remote_window_size(ctx->h2);
+ s_exhaust = want_send && stream && stream->id >= 0 &&
+ !nghttp2_session_get_stream_remote_window_size(ctx->h2,
+ stream->id);
+ want_recv = (want_recv || c_exhaust || s_exhaust);
+ want_send = (!s_exhaust && want_send) ||
+ (!c_exhaust && nghttp2_session_want_write(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,
struct Curl_easy *data,
@@ -2511,14 +2506,15 @@ static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf,
case CF_CTRL_DATA_PAUSE:
result = http2_data_pause(cf, data, (arg1 != 0));
break;
- case CF_CTRL_DATA_DONE_SEND: {
+ case CF_CTRL_DATA_DONE_SEND:
result = http2_data_done_send(cf, data);
break;
- }
- case CF_CTRL_DATA_DONE: {
+ case CF_CTRL_DATA_DETACH:
+ http2_data_done(cf, data, TRUE);
+ break;
+ case CF_CTRL_DATA_DONE:
http2_data_done(cf, data, arg1 != 0);
break;
- }
default:
break;
}
@@ -2606,7 +2602,7 @@ struct Curl_cftype Curl_cft_nghttp2 = {
cf_h2_connect,
cf_h2_close,
Curl_cf_def_get_host,
- cf_h2_get_select_socks,
+ cf_h2_adjust_pollset,
cf_h2_data_pending,
cf_h2_send,
cf_h2_recv,
@@ -2626,7 +2622,7 @@ static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
CURLcode result = CURLE_OUT_OF_MEMORY;
DEBUGASSERT(data->conn);
- ctx = calloc(sizeof(*ctx), 1);
+ ctx = calloc(1, sizeof(*ctx));
if(!ctx)
goto out;
@@ -2652,7 +2648,7 @@ static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
CURLcode result = CURLE_OUT_OF_MEMORY;
(void)data;
- ctx = calloc(sizeof(*ctx), 1);
+ ctx = calloc(1, sizeof(*ctx));
if(!ctx)
goto out;