summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/http.h18
-rw-r--r--lib/http2.c229
2 files changed, 133 insertions, 114 deletions
diff --git a/lib/http.h b/lib/http.h
index 1d373e8f4..c917510ae 100644
--- a/lib/http.h
+++ b/lib/http.h
@@ -124,6 +124,20 @@ CURLcode Curl_http_perhapsrewind(struct connectdata *conn);
#endif /* CURL_DISABLE_HTTP */
+#ifdef USE_NGHTTP2
+/* Allocate one of these for each HTTP/2 stream we put on the connection.
+ Tell nghttp2 to associate each stream_id with this node. To clear the
+ association, blank the 'easy' field.
+
+ The node is removed again from the list when nghttp2 calls on_stream_close.
+*/
+struct easymap {
+ struct curl_llist_element node;
+ struct Curl_easy *easy;
+ uint32_t stream_id;
+};
+#endif
+
/****************************************************************************
* HTTP unique setup
***************************************************************************/
@@ -160,7 +174,7 @@ struct HTTP {
#ifdef USE_NGHTTP2
/*********** for HTTP/2 we store stream-local data here *************/
int32_t stream_id; /* stream we are interested in */
-
+ struct easymap *emap; /* The map used for this stream */
bool bodystarted;
/* We store non-final and final response headers here, per-stream */
Curl_send_buffer *header_recvbuf;
@@ -221,6 +235,7 @@ struct http_conn {
nghttp2_settings_entry local_settings[3];
size_t local_settings_num;
uint32_t error_code; /* HTTP/2 error code */
+ struct curl_llist streamlist; /* a list of stream2easy nodes */
#else
int unused; /* prevent a compiler warning */
#endif
@@ -253,4 +268,3 @@ Curl_http_output_auth(struct connectdata *conn,
up the proxy tunnel */
#endif /* HEADER_CURL_HTTP_H */
-
diff --git a/lib/http2.c b/lib/http2.c
index 350642019..702a71731 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -81,6 +81,45 @@ static int h2_process_pending_input(struct connectdata *conn,
struct http_conn *httpc,
CURLcode *err);
+static struct easymap *add_easymap(struct Curl_easy *data,
+ struct http_conn *httpc,
+ uint32_t stream_id)
+{
+ struct easymap *m = malloc(sizeof(struct easymap));
+ struct HTTP *stream = data->req.protop;
+ if(m) {
+ Curl_llist_insert_next(&httpc->streamlist, NULL, m, &m->node);
+ m->easy = data;
+ m->stream_id = stream_id;
+ DEBUGASSERT(!stream->emap);
+ stream->emap = m;
+ }
+ return m;
+}
+
+static void del_easymap(struct http_conn *httpc,
+ struct easymap *m)
+{
+ struct HTTP *http;
+ struct Curl_easy *easy = m->easy;
+ Curl_llist_remove(&httpc->streamlist, &m->node, NULL);
+ if(easy) {
+ /* if the association is already removed, this can't be done */
+ http = easy->req.protop;
+ http->emap = NULL;
+ }
+ free(m);
+}
+
+static void disassociate_easymap(struct easymap *m)
+{
+ struct HTTP *http;
+ DEBUGASSERT(m && m->easy);
+ http = m->easy->req.protop;
+ http->emap = NULL;
+ m->easy = NULL;
+}
+
/*
* Curl_http2_init_state() is called when the easy handle is created and
* allows for HTTP/2 specific init of state.
@@ -168,7 +207,8 @@ static CURLcode http2_disconnect(struct connectdata *conn,
nghttp2_session_del(c->h2);
Curl_safefree(c->inbuf);
-
+ /* make sure there's no trailing stream nodes */
+ DEBUGASSERT(!Curl_llist_count(&c->streamlist));
H2BUGF(infof(conn->data, "HTTP/2 DISCONNECT done\n"));
return CURLE_OK;
@@ -507,6 +547,7 @@ static int push_promise(struct Curl_easy *data,
CURLMcode rc;
struct http_conn *httpc;
size_t i;
+ struct easymap *m;
/* clone the parent */
struct Curl_easy *newhandle = duphandle(data);
if(!newhandle) {
@@ -567,9 +608,15 @@ static int push_promise(struct Curl_easy *data,
}
httpc = &conn->proto.httpc;
+
+ m = add_easymap(newhandle, httpc, frame->promised_stream_id);
+ if(!m) {
+ rv = 1;
+ goto fail;
+ }
+
rv = nghttp2_session_set_stream_user_data(httpc->h2,
- frame->promised_stream_id,
- newhandle);
+ frame->promised_stream_id, m);
if(rv) {
infof(data, "failed to set user_data for stream %u\n",
frame->promised_stream_id);
@@ -591,6 +638,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
struct connectdata *conn = (struct connectdata *)userp;
struct http_conn *httpc = &conn->proto.httpc;
struct Curl_easy *data_s = NULL;
+ struct easymap *m;
struct HTTP *stream = NULL;
int rv;
size_t left, ncopy;
@@ -621,13 +669,20 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
}
return 0;
}
- data_s = nghttp2_session_get_stream_user_data(session, stream_id);
- if(!data_s) {
+ m = nghttp2_session_get_stream_user_data(session, stream_id);
+ if(!m) {
H2BUGF(infof(conn->data,
- "No Curl_easy associated with stream: %x\n",
+ "No easy map associated with stream: %x\n",
stream_id));
return 0;
}
+ if(!m->easy) {
+ H2BUGF(infof(conn->data,
+ "No easy handle associated with stream: %x\n",
+ stream_id));
+ return 0;
+ }
+ data_s = m->easy;
stream = data_s->req.protop;
if(!stream) {
@@ -715,25 +770,6 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
-static int on_invalid_frame_recv(nghttp2_session *session,
- const nghttp2_frame *frame,
- int lib_error_code, void *userp)
-{
- struct Curl_easy *data_s = NULL;
- (void)userp;
-#if !defined(DEBUG_HTTP2) || defined(CURL_DISABLE_VERBOSE_STRINGS)
- (void)lib_error_code;
-#endif
-
- data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
- if(data_s) {
- H2BUGF(infof(data_s,
- "on_invalid_frame_recv() was called, error=%d:%s\n",
- lib_error_code, nghttp2_strerror(lib_error_code)));
- }
- return 0;
-}
-
static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
int32_t stream_id,
const uint8_t *data, size_t len, void *userp)
@@ -742,6 +778,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
struct Curl_easy *data_s;
size_t nread;
struct connectdata *conn = (struct connectdata *)userp;
+ struct easymap *m;
(void)session;
(void)flags;
(void)data;
@@ -749,12 +786,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
/* get the stream from the hash based on Stream ID */
- data_s = nghttp2_session_get_stream_user_data(session, stream_id);
- if(!data_s)
+ m = nghttp2_session_get_stream_user_data(session, stream_id);
+ if(!m)
/* Receiving a Stream ID not in the hash should not happen, this is an
internal error more than anything else! */
return NGHTTP2_ERR_CALLBACK_FAILURE;
-
+ if(!m->easy)
+ /* Receiving a Stream ID with a cleared association, ignore it */
+ return 0;
+ data_s = m->easy;
stream = data_s->req.protop;
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -799,59 +839,13 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
return 0;
}
-static int before_frame_send(nghttp2_session *session,
- const nghttp2_frame *frame,
- void *userp)
-{
- struct Curl_easy *data_s;
- (void)userp;
-
- data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
- if(data_s) {
- H2BUGF(infof(data_s, "before_frame_send() was called\n"));
- }
-
- return 0;
-}
-static int on_frame_send(nghttp2_session *session,
- const nghttp2_frame *frame,
- void *userp)
-{
- struct Curl_easy *data_s;
- (void)userp;
-
- data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
- if(data_s) {
- H2BUGF(infof(data_s, "on_frame_send() was called, length = %zd\n",
- frame->hd.length));
- }
- return 0;
-}
-static int on_frame_not_send(nghttp2_session *session,
- const nghttp2_frame *frame,
- int lib_error_code, void *userp)
-{
- struct Curl_easy *data_s;
- (void)userp;
-#if !defined(DEBUG_HTTP2) || defined(CURL_DISABLE_VERBOSE_STRINGS)
- (void)lib_error_code;
-#endif
-
- data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
- if(data_s) {
- H2BUGF(infof(data_s,
- "on_frame_not_send() was called, lib_error_code = %d\n",
- lib_error_code));
- }
- return 0;
-}
static int on_stream_close(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *userp)
{
struct Curl_easy *data_s;
struct HTTP *stream;
struct connectdata *conn = (struct connectdata *)userp;
- int rv;
+ struct easymap *m;
(void)session;
(void)stream_id;
@@ -859,31 +853,28 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
struct http_conn *httpc;
/* 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
+ m = nghttp2_session_get_stream_user_data(session, stream_id);
+ if(!m)
+ /* We could get stream ID not in the map. For example, if we
decided to reject stream (e.g., PUSH_PROMISE). */
return 0;
- }
- H2BUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u\n",
- Curl_http2_strerror(error_code), error_code, stream_id));
+ if(!m->easy)
+ /* already cleared mapping, just skip */
+ return 0;
+ data_s = m->easy;
+ infof(data_s, "on_stream_close(), %s (err %d), stream %u\n",
+ Curl_http2_strerror(error_code), error_code, stream_id);
stream = data_s->req.protop;
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
+ infof(data_s, "Removing stream %u, easy %p!\n", stream_id, m->easy);
stream->closed = TRUE;
httpc = &conn->proto.httpc;
drain_this(data_s, httpc);
httpc->error_code = error_code;
- /* 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!\n",
- stream_id);
- DEBUGASSERT(0);
- }
- H2BUGF(infof(data_s, "Removed stream %u hash!\n", stream_id));
+ del_easymap(httpc, m); /* remove mapping node from stream list */
stream->stream_id = 0; /* cleared */
}
return 0;
@@ -894,12 +885,14 @@ static int on_begin_headers(nghttp2_session *session,
{
struct HTTP *stream;
struct Curl_easy *data_s = NULL;
+ struct easymap *m;
(void)userp;
- data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
- if(!data_s) {
+ m = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+ if(!m || !m->easy) {
return 0;
}
+ data_s = m->easy;
H2BUGF(infof(data_s, "on_begin_headers() was called\n"));
@@ -959,16 +952,20 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
struct Curl_easy *data_s;
int32_t stream_id = frame->hd.stream_id;
struct connectdata *conn = (struct connectdata *)userp;
+ struct easymap *m;
(void)flags;
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
/* get the stream from the hash based on Stream ID */
- data_s = nghttp2_session_get_stream_user_data(session, stream_id);
- if(!data_s)
+ m = nghttp2_session_get_stream_user_data(session, stream_id);
+ if(!m)
/* Receiving a Stream ID not in the hash should not happen, this is an
internal error more than anything else! */
return NGHTTP2_ERR_CALLBACK_FAILURE;
+ if(!m->easy)
+ return 0;
+ data_s = m->easy;
stream = data_s->req.protop;
if(!stream) {
@@ -1070,18 +1067,22 @@ static ssize_t data_source_read_callback(nghttp2_session *session,
struct Curl_easy *data_s;
struct HTTP *stream = NULL;
size_t nread;
+ struct easymap *m;
(void)source;
(void)userp;
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)
+ m = nghttp2_session_get_stream_user_data(session, stream_id);
+ if(!m)
/* Receiving a Stream ID not in the hash should not happen, this is an
internal error more than anything else! */
return NGHTTP2_ERR_CALLBACK_FAILURE;
+ if(!m->easy)
+ return 0;
+ data_s = m->easy;
stream = data_s->req.protop;
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -1178,6 +1179,9 @@ void Curl_http2_done(struct connectdata *conn, bool premature)
httpc->pause_stream_id = 0;
}
}
+ if(http->emap)
+ /* remove stream <=> easy association */
+ disassociate_easymap(http->emap);
/* -1 means unassigned and 0 means cleared */
if(http->stream_id > 0) {
int rv = nghttp2_session_set_stream_user_data(httpc->h2,
@@ -1216,21 +1220,9 @@ CURLcode Curl_http2_init(struct connectdata *conn)
/* nghttp2_on_frame_recv_callback */
nghttp2_session_callbacks_set_on_frame_recv_callback
(callbacks, on_frame_recv);
- /* nghttp2_on_invalid_frame_recv_callback */
- nghttp2_session_callbacks_set_on_invalid_frame_recv_callback
- (callbacks, on_invalid_frame_recv);
/* nghttp2_on_data_chunk_recv_callback */
nghttp2_session_callbacks_set_on_data_chunk_recv_callback
(callbacks, on_data_chunk_recv);
- /* nghttp2_before_frame_send_callback */
- nghttp2_session_callbacks_set_before_frame_send_callback
- (callbacks, before_frame_send);
- /* nghttp2_on_frame_send_callback */
- nghttp2_session_callbacks_set_on_frame_send_callback
- (callbacks, on_frame_send);
- /* nghttp2_on_frame_not_send_callback */
- nghttp2_session_callbacks_set_on_frame_not_send_callback
- (callbacks, on_frame_not_send);
/* nghttp2_on_stream_close_callback */
nghttp2_session_callbacks_set_on_stream_close_callback
(callbacks, on_stream_close);
@@ -1816,6 +1808,7 @@ static header_instruction inspect_header(const char *name, size_t namelen,
}
}
+
static ssize_t http2_send(struct connectdata *conn, int sockindex,
const void *mem, size_t len, CURLcode *err)
{
@@ -1837,6 +1830,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
int32_t stream_id;
nghttp2_session *h2 = httpc->h2;
nghttp2_priority_spec pri_spec;
+ struct easymap *m;
(void)sockindex;
@@ -2055,6 +2049,12 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
}
}
+ m = add_easymap(conn->data, httpc, 0 /* unknown still */);
+ if(!m) {
+ Curl_safefree(nva);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
h2_pri_spec(conn->data, &pri_spec);
switch(conn->data->set.httpreq) {
@@ -2071,13 +2071,13 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
data_prd.read_callback = data_source_read_callback;
data_prd.source.ptr = NULL;
stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
- &data_prd, conn->data);
+ &data_prd, m);
break;
default:
stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
- NULL, conn->data);
+ NULL, m);
}
-
+ m->stream_id = stream_id;
Curl_safefree(nva);
if(stream_id < 0) {
@@ -2166,6 +2166,7 @@ CURLcode Curl_http2_setup(struct connectdata *conn)
infof(conn->data, "Connection state changed (HTTP/2 confirmed)\n");
Curl_multi_connchanged(conn->data->multi);
+ Curl_llist_init(&httpc->streamlist, NULL);
return CURLE_OK;
}
@@ -2190,6 +2191,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
conn->send[FIRSTSOCKET] = http2_send;
if(conn->data->req.upgr101 == UPGR101_RECEIVED) {
+ struct easymap *m;
/* stream 1 is opened implicitly on upgrade */
stream->stream_id = 1;
/* queue SETTINGS frame (again) */
@@ -2201,9 +2203,12 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
return CURLE_HTTP2;
}
+ m = add_easymap(data, httpc, stream->stream_id);
+ if(!m)
+ return CURLE_OUT_OF_MEMORY;
+
rv = nghttp2_session_set_stream_user_data(httpc->h2,
- stream->stream_id,
- data);
+ stream->stream_id, m);
if(rv) {
infof(data, "http/2: failed to set user_data for stream %u!\n",
stream->stream_id);