|
@@ -29,6 +29,7 @@
|
|
|
#include <nghttp2/nghttp2.h>
|
|
|
#include "urldata.h"
|
|
|
#include "bufq.h"
|
|
|
+#include "hash.h"
|
|
|
#include "http1.h"
|
|
|
#include "http2.h"
|
|
|
#include "http.h"
|
|
@@ -127,7 +128,9 @@ struct cf_h2_ctx {
|
|
|
struct bufq inbufq; /* network input */
|
|
|
struct bufq outbufq; /* network output */
|
|
|
struct bufc_pool stream_bufcp; /* spares for stream buffers */
|
|
|
+ struct dynbuf scratch; /* scratch buffer for temp use */
|
|
|
|
|
|
+ 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;
|
|
@@ -153,6 +156,9 @@ static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx)
|
|
|
Curl_bufq_free(&ctx->inbufq);
|
|
|
Curl_bufq_free(&ctx->outbufq);
|
|
|
Curl_bufcp_free(&ctx->stream_bufcp);
|
|
|
+ Curl_dyn_free(&ctx->scratch);
|
|
|
+ Curl_hash_clean(&ctx->streams);
|
|
|
+ Curl_hash_destroy(&ctx->streams);
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
ctx->call_data = save;
|
|
|
}
|
|
@@ -187,6 +193,7 @@ struct h2_stream_ctx {
|
|
|
|
|
|
int status_code; /* HTTP response status code */
|
|
|
uint32_t error; /* stream error code */
|
|
|
+ CURLcode xfer_result; /* Result of writing out response */
|
|
|
uint32_t local_window_size; /* the local recv window size */
|
|
|
int32_t id; /* HTTP/2 protocol identifier for stream */
|
|
|
BIT(resp_hds_complete); /* we have a complete, final response */
|
|
@@ -198,13 +205,58 @@ struct h2_stream_ctx {
|
|
|
buffered data in stream->sendbuf to upload. */
|
|
|
};
|
|
|
|
|
|
-#define H2_STREAM_CTX(d) ((struct h2_stream_ctx *)(((d) && \
|
|
|
- (d)->req.p.http)? \
|
|
|
- ((struct HTTP *)(d)->req.p.http)->h2_ctx \
|
|
|
- : NULL))
|
|
|
-#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx
|
|
|
-#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \
|
|
|
- H2_STREAM_CTX(d)->id : -2)
|
|
|
+#define H2_STREAM_CTX(ctx,data) ((struct h2_stream_ctx *)(\
|
|
|
+ data? Curl_hash_offt_get(&(ctx)->streams, (data)->id) : NULL))
|
|
|
+
|
|
|
+static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx)
|
|
|
+{
|
|
|
+ struct h2_stream_ctx *stream;
|
|
|
+
|
|
|
+ (void)ctx;
|
|
|
+ stream = calloc(1, sizeof(*stream));
|
|
|
+ if(!stream)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ stream->id = -1;
|
|
|
+ Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
|
|
|
+ H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
|
|
|
+ Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
|
|
|
+ Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
|
|
|
+ stream->resp_hds_len = 0;
|
|
|
+ stream->bodystarted = FALSE;
|
|
|
+ stream->status_code = -1;
|
|
|
+ stream->closed = FALSE;
|
|
|
+ stream->close_handled = FALSE;
|
|
|
+ stream->error = NGHTTP2_NO_ERROR;
|
|
|
+ stream->local_window_size = H2_STREAM_WINDOW_SIZE;
|
|
|
+ stream->upload_left = 0;
|
|
|
+ stream->nrcvd_data = 0;
|
|
|
+ return stream;
|
|
|
+}
|
|
|
+
|
|
|
+static void free_push_headers(struct h2_stream_ctx *stream)
|
|
|
+{
|
|
|
+ size_t i;
|
|
|
+ for(i = 0; i<stream->push_headers_used; i++)
|
|
|
+ free(stream->push_headers[i]);
|
|
|
+ Curl_safefree(stream->push_headers);
|
|
|
+ stream->push_headers_used = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void h2_stream_ctx_free(struct h2_stream_ctx *stream)
|
|
|
+{
|
|
|
+ Curl_bufq_free(&stream->sendbuf);
|
|
|
+ Curl_h1_req_parse_free(&stream->h1);
|
|
|
+ Curl_dynhds_free(&stream->resp_trailers);
|
|
|
+ free_push_headers(stream);
|
|
|
+ free(stream);
|
|
|
+}
|
|
|
+
|
|
|
+static void h2_stream_hash_free(void *stream)
|
|
|
+{
|
|
|
+ DEBUGASSERT(stream);
|
|
|
+ h2_stream_ctx_free((struct h2_stream_ctx *)stream);
|
|
|
+}
|
|
|
|
|
|
/*
|
|
|
* Mark this transfer to get "drained".
|
|
@@ -241,49 +293,29 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf,
|
|
|
failf(data, "initialization failure, transfer not http initialized");
|
|
|
return CURLE_FAILED_INIT;
|
|
|
}
|
|
|
- stream = H2_STREAM_CTX(data);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data);
|
|
|
if(stream) {
|
|
|
*pstream = stream;
|
|
|
return CURLE_OK;
|
|
|
}
|
|
|
|
|
|
- stream = calloc(1, sizeof(*stream));
|
|
|
+ stream = h2_stream_ctx_create(ctx);
|
|
|
if(!stream)
|
|
|
return CURLE_OUT_OF_MEMORY;
|
|
|
|
|
|
- stream->id = -1;
|
|
|
- Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
|
|
|
- H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
|
|
|
- Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
|
|
|
- Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
|
|
|
- stream->resp_hds_len = 0;
|
|
|
- stream->bodystarted = FALSE;
|
|
|
- stream->status_code = -1;
|
|
|
- stream->closed = FALSE;
|
|
|
- stream->close_handled = FALSE;
|
|
|
- stream->error = NGHTTP2_NO_ERROR;
|
|
|
- stream->local_window_size = H2_STREAM_WINDOW_SIZE;
|
|
|
- stream->upload_left = 0;
|
|
|
- stream->nrcvd_data = 0;
|
|
|
+ if(!Curl_hash_offt_set(&ctx->streams, data->id, stream)) {
|
|
|
+ h2_stream_ctx_free(stream);
|
|
|
+ return CURLE_OUT_OF_MEMORY;
|
|
|
+ }
|
|
|
|
|
|
- H2_STREAM_LCTX(data) = stream;
|
|
|
*pstream = stream;
|
|
|
return CURLE_OK;
|
|
|
}
|
|
|
|
|
|
-static void free_push_headers(struct h2_stream_ctx *stream)
|
|
|
-{
|
|
|
- size_t i;
|
|
|
- for(i = 0; i<stream->push_headers_used; i++)
|
|
|
- free(stream->push_headers[i]);
|
|
|
- Curl_safefree(stream->push_headers);
|
|
|
- stream->push_headers_used = 0;
|
|
|
-}
|
|
|
-
|
|
|
static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
|
|
|
DEBUGASSERT(ctx);
|
|
|
if(!stream)
|
|
@@ -310,12 +342,7 @@ static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
|
nghttp2_session_send(ctx->h2);
|
|
|
}
|
|
|
|
|
|
- Curl_bufq_free(&stream->sendbuf);
|
|
|
- Curl_h1_req_parse_free(&stream->h1);
|
|
|
- Curl_dynhds_free(&stream->resp_trailers);
|
|
|
- free_push_headers(stream);
|
|
|
- free(stream);
|
|
|
- H2_STREAM_LCTX(data) = NULL;
|
|
|
+ Curl_hash_offt_remove(&ctx->streams, data->id);
|
|
|
}
|
|
|
|
|
|
static int h2_client_new(struct Curl_cfilter *cf,
|
|
@@ -408,6 +435,8 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
|
|
|
Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES);
|
|
|
Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0);
|
|
|
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;
|
|
|
|
|
|
rc = nghttp2_session_callbacks_new(&cbs);
|
|
@@ -706,6 +735,7 @@ static ssize_t send_callback(nghttp2_session *h2,
|
|
|
the struct are hidden from the user. */
|
|
|
struct curl_pushheaders {
|
|
|
struct Curl_easy *data;
|
|
|
+ struct h2_stream_ctx *stream;
|
|
|
const nghttp2_push_promise *frame;
|
|
|
};
|
|
|
|
|
@@ -719,9 +749,8 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num)
|
|
|
if(!h || !GOOD_EASY_HANDLE(h->data))
|
|
|
return NULL;
|
|
|
else {
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(h->data);
|
|
|
- if(stream && num < stream->push_headers_used)
|
|
|
- return stream->push_headers[num];
|
|
|
+ if(h->stream && num < h->stream->push_headers_used)
|
|
|
+ return h->stream->push_headers[num];
|
|
|
}
|
|
|
return NULL;
|
|
|
}
|
|
@@ -744,7 +773,7 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
|
|
|
!strcmp(header, ":") || strchr(header + 1, ':'))
|
|
|
return NULL;
|
|
|
|
|
|
- stream = H2_STREAM_CTX(h->data);
|
|
|
+ stream = h->stream;
|
|
|
if(!stream)
|
|
|
return NULL;
|
|
|
|
|
@@ -804,7 +833,7 @@ static int set_transfer_url(struct Curl_easy *data,
|
|
|
|
|
|
v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY);
|
|
|
if(v) {
|
|
|
- uc = Curl_url_set_authority(u, v, CURLU_DISALLOW_USER);
|
|
|
+ uc = Curl_url_set_authority(u, v);
|
|
|
if(uc) {
|
|
|
rc = 2;
|
|
|
goto fail;
|
|
@@ -867,12 +896,10 @@ static int push_promise(struct Curl_cfilter *cf,
|
|
|
goto fail;
|
|
|
}
|
|
|
|
|
|
- heads.data = data;
|
|
|
- heads.frame = frame;
|
|
|
/* ask the application */
|
|
|
CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application");
|
|
|
|
|
|
- stream = H2_STREAM_CTX(data);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data);
|
|
|
if(!stream) {
|
|
|
failf(data, "Internal NULL stream");
|
|
|
discard_newhandle(cf, newhandle);
|
|
@@ -880,6 +907,10 @@ static int push_promise(struct Curl_cfilter *cf,
|
|
|
goto fail;
|
|
|
}
|
|
|
|
|
|
+ heads.data = data;
|
|
|
+ heads.stream = stream;
|
|
|
+ heads.frame = frame;
|
|
|
+
|
|
|
rv = set_transfer_url(newhandle, &heads);
|
|
|
if(rv) {
|
|
|
discard_newhandle(cf, newhandle);
|
|
@@ -945,12 +976,39 @@ fail:
|
|
|
return rv;
|
|
|
}
|
|
|
|
|
|
-static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf,
|
|
|
+static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf,
|
|
|
struct Curl_easy *data,
|
|
|
- const char *buf, size_t blen)
|
|
|
+ struct h2_stream_ctx *stream,
|
|
|
+ const char *buf, size_t blen, bool eos)
|
|
|
{
|
|
|
- (void)cf;
|
|
|
- return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE);
|
|
|
+
|
|
|
+ /* If we already encountered an error, skip further writes */
|
|
|
+ if(!stream->xfer_result) {
|
|
|
+ stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos);
|
|
|
+ if(stream->xfer_result)
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of headers",
|
|
|
+ stream->id, stream->xfer_result, blen);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void h2_xfer_write_resp(struct Curl_cfilter *cf,
|
|
|
+ struct Curl_easy *data,
|
|
|
+ struct h2_stream_ctx *stream,
|
|
|
+ const char *buf, size_t blen, bool eos)
|
|
|
+{
|
|
|
+
|
|
|
+ /* If we already encountered an error, skip further writes */
|
|
|
+ if(!stream->xfer_result)
|
|
|
+ stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos);
|
|
|
+ /* If the transfer write is errored, we do not want any more data */
|
|
|
+ if(stream->xfer_result) {
|
|
|
+ struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of data, "
|
|
|
+ "RST-ing stream",
|
|
|
+ stream->id, stream->xfer_result, blen);
|
|
|
+ nghttp2_submit_rst_stream(ctx->h2, 0, stream->id,
|
|
|
+ NGHTTP2_ERR_CALLBACK_FAILURE);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
|
@@ -958,9 +1016,8 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
|
|
const nghttp2_frame *frame)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
int32_t stream_id = frame->hd.stream_id;
|
|
|
- CURLcode result;
|
|
|
int rv;
|
|
|
|
|
|
if(!stream) {
|
|
@@ -1008,9 +1065,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
|
|
stream->status_code = -1;
|
|
|
}
|
|
|
|
|
|
- result = recvbuf_write_hds(cf, data, STRCONST("\r\n"));
|
|
|
- if(result)
|
|
|
- return result;
|
|
|
+ h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed);
|
|
|
|
|
|
if(stream->status_code / 100 != 1) {
|
|
|
stream->resp_hds_complete = TRUE;
|
|
@@ -1189,7 +1244,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
* 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(CURL_WANT_SEND(data)) {
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
if(stream)
|
|
|
drain_stream(cf, data, stream);
|
|
|
}
|
|
@@ -1229,7 +1284,6 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream;
|
|
|
struct Curl_easy *data_s;
|
|
|
- CURLcode result;
|
|
|
(void)flags;
|
|
|
|
|
|
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
|
|
@@ -1248,13 +1302,11 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- stream = H2_STREAM_CTX(data_s);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data_s);
|
|
|
if(!stream)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
|
|
- result = Curl_xfer_write_resp(data_s, (char *)mem, len, FALSE);
|
|
|
- if(result && result != CURLE_AGAIN)
|
|
|
- return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
+ h2_xfer_write_resp(cf, data_s, stream, (char *)mem, len, FALSE);
|
|
|
|
|
|
nghttp2_session_consume(ctx->h2, stream_id, len);
|
|
|
stream->nrcvd_data += (curl_off_t)len;
|
|
@@ -1268,6 +1320,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
|
|
|
uint32_t error_code, void *userp)
|
|
|
{
|
|
|
struct Curl_cfilter *cf = userp;
|
|
|
+ struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf);
|
|
|
struct h2_stream_ctx *stream;
|
|
|
int rv;
|
|
@@ -1292,7 +1345,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
|
|
|
(void)nghttp2_session_set_stream_user_data(session, stream_id, 0);
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
}
|
|
|
- stream = H2_STREAM_CTX(data_s);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data_s);
|
|
|
if(!stream) {
|
|
|
CURL_TRC_CF(data_s, cf,
|
|
|
"[%d] on_stream_close, GOOD easy but no stream", stream_id);
|
|
@@ -1327,6 +1380,7 @@ static int on_begin_headers(nghttp2_session *session,
|
|
|
const nghttp2_frame *frame, void *userp)
|
|
|
{
|
|
|
struct Curl_cfilter *cf = userp;
|
|
|
+ struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream;
|
|
|
struct Curl_easy *data_s = NULL;
|
|
|
|
|
@@ -1340,7 +1394,7 @@ static int on_begin_headers(nghttp2_session *session,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
- stream = H2_STREAM_CTX(data_s);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data_s);
|
|
|
if(!stream || !stream->bodystarted) {
|
|
|
return 0;
|
|
|
}
|
|
@@ -1356,6 +1410,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
void *userp)
|
|
|
{
|
|
|
struct Curl_cfilter *cf = userp;
|
|
|
+ struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream;
|
|
|
struct Curl_easy *data_s;
|
|
|
int32_t stream_id = frame->hd.stream_id;
|
|
@@ -1371,7 +1426,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
internal error more than anything else! */
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
|
|
- stream = H2_STREAM_CTX(data_s);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data_s);
|
|
|
if(!stream) {
|
|
|
failf(data_s, "Internal NULL stream");
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
@@ -1465,14 +1520,15 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO);
|
|
|
if(result)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
- result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 "));
|
|
|
- if(result)
|
|
|
- return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
- result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen);
|
|
|
- if(result)
|
|
|
- return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
- /* the space character after the status code is mandatory */
|
|
|
- result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n"));
|
|
|
+ Curl_dyn_reset(&ctx->scratch);
|
|
|
+ result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 "));
|
|
|
+ if(!result)
|
|
|
+ result = Curl_dyn_addn(&ctx->scratch, value, valuelen);
|
|
|
+ if(!result)
|
|
|
+ result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n"));
|
|
|
+ if(!result)
|
|
|
+ h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch),
|
|
|
+ Curl_dyn_len(&ctx->scratch), FALSE);
|
|
|
if(result)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
/* if we receive data for another handle, wake that up */
|
|
@@ -1487,16 +1543,17 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
/* nghttp2 guarantees that namelen > 0, and :status was already
|
|
|
received, and this is not pseudo-header field . */
|
|
|
/* convert to an HTTP1-style header */
|
|
|
- result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen);
|
|
|
- if(result)
|
|
|
- return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
- result = recvbuf_write_hds(cf, data_s, STRCONST(": "));
|
|
|
- if(result)
|
|
|
- return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
- result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen);
|
|
|
- if(result)
|
|
|
- return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
- result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n"));
|
|
|
+ Curl_dyn_reset(&ctx->scratch);
|
|
|
+ result = Curl_dyn_addn(&ctx->scratch, (const char *)name, namelen);
|
|
|
+ if(!result)
|
|
|
+ result = Curl_dyn_addn(&ctx->scratch, STRCONST(": "));
|
|
|
+ if(!result)
|
|
|
+ result = Curl_dyn_addn(&ctx->scratch, (const char *)value, valuelen);
|
|
|
+ if(!result)
|
|
|
+ result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n"));
|
|
|
+ if(!result)
|
|
|
+ h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch),
|
|
|
+ Curl_dyn_len(&ctx->scratch), FALSE);
|
|
|
if(result)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
/* if we receive data for another handle, wake that up */
|
|
@@ -1517,6 +1574,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session,
|
|
|
void *userp)
|
|
|
{
|
|
|
struct Curl_cfilter *cf = userp;
|
|
|
+ struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct Curl_easy *data_s;
|
|
|
struct h2_stream_ctx *stream = NULL;
|
|
|
CURLcode result;
|
|
@@ -1533,7 +1591,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session,
|
|
|
internal error more than anything else! */
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
|
|
- stream = H2_STREAM_CTX(data_s);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data_s);
|
|
|
if(!stream)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
}
|
|
@@ -1620,7 +1678,7 @@ static CURLcode http2_data_done_send(struct Curl_cfilter *cf,
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
CURLcode result = CURLE_OK;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
|
|
|
if(!ctx || !ctx->h2 || !stream)
|
|
|
goto out;
|
|
@@ -1658,6 +1716,15 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
|
|
|
return -1;
|
|
|
}
|
|
|
else if(stream->error != NGHTTP2_NO_ERROR) {
|
|
|
+ if(stream->resp_hds_complete && data->req.no_body) {
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] error after response headers, but we did "
|
|
|
+ "not want a body anyway, ignore: %s (err %u)",
|
|
|
+ stream->id, nghttp2_http2_strerror(stream->error),
|
|
|
+ stream->error);
|
|
|
+ stream->close_handled = TRUE;
|
|
|
+ *err = CURLE_OK;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
|
|
|
stream->id, nghttp2_http2_strerror(stream->error),
|
|
|
stream->error);
|
|
@@ -1736,11 +1803,12 @@ static int sweight_in_effect(const struct Curl_easy *data)
|
|
|
* struct.
|
|
|
*/
|
|
|
|
|
|
-static void h2_pri_spec(struct Curl_easy *data,
|
|
|
+static void h2_pri_spec(struct cf_h2_ctx *ctx,
|
|
|
+ struct Curl_easy *data,
|
|
|
nghttp2_priority_spec *pri_spec)
|
|
|
{
|
|
|
struct Curl_data_priority *prio = &data->set.priority;
|
|
|
- struct h2_stream_ctx *depstream = H2_STREAM_CTX(prio->parent);
|
|
|
+ struct h2_stream_ctx *depstream = H2_STREAM_CTX(ctx, prio->parent);
|
|
|
int32_t depstream_id = depstream? depstream->id:0;
|
|
|
nghttp2_priority_spec_init(pri_spec, depstream_id,
|
|
|
sweight_wanted(data),
|
|
@@ -1758,7 +1826,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
|
|
|
struct Curl_easy *data)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
int rv = 0;
|
|
|
|
|
|
if(stream && stream->id > 0 &&
|
|
@@ -1768,7 +1836,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
|
|
|
/* send new weight and/or dependency */
|
|
|
nghttp2_priority_spec pri_spec;
|
|
|
|
|
|
- h2_pri_spec(data, &pri_spec);
|
|
|
+ h2_pri_spec(ctx, data, &pri_spec);
|
|
|
CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id);
|
|
|
DEBUGASSERT(stream->id != -1);
|
|
|
rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE,
|
|
@@ -1799,7 +1867,12 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
|
|
|
(void)buf;
|
|
|
*err = CURLE_AGAIN;
|
|
|
- if(stream->closed) {
|
|
|
+ if(stream->xfer_result) {
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] xfer write failed", stream->id);
|
|
|
+ *err = stream->xfer_result;
|
|
|
+ nread = -1;
|
|
|
+ }
|
|
|
+ else if(stream->closed) {
|
|
|
CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id);
|
|
|
nread = http2_handle_stream_close(cf, data, stream, err);
|
|
|
}
|
|
@@ -1838,7 +1911,7 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
|
|
* it is time to stop due to connection close or us not processing
|
|
|
* all network input */
|
|
|
while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
|
|
|
- stream = H2_STREAM_CTX(data);
|
|
|
+ stream = H2_STREAM_CTX(ctx, data);
|
|
|
if(stream && (stream->closed || !data_max_bytes)) {
|
|
|
/* We would like to abort here and stop processing, so that
|
|
|
* the transfer loop can handle the data/close here. However,
|
|
@@ -1884,7 +1957,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
char *buf, size_t len, CURLcode *err)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
ssize_t nread = -1;
|
|
|
CURLcode result;
|
|
|
struct cf_call_data save;
|
|
@@ -2016,7 +2089,7 @@ static ssize_t h2_submit(struct h2_stream_ctx **pstream,
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
- h2_pri_spec(data, &pri_spec);
|
|
|
+ h2_pri_spec(ctx, data, &pri_spec);
|
|
|
if(!nghttp2_session_check_request_allowed(ctx->h2))
|
|
|
CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)");
|
|
|
|
|
@@ -2113,7 +2186,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
const void *buf, size_t len, CURLcode *err)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
struct cf_call_data save;
|
|
|
int rv;
|
|
|
ssize_t nwritten;
|
|
@@ -2294,7 +2367,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
|
|
|
sock = Curl_conn_cf_get_socket(cf, data);
|
|
|
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
|
|
|
if(want_recv || want_send) {
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
struct cf_call_data save;
|
|
|
bool c_exhaust, s_exhaust;
|
|
|
|
|
@@ -2395,7 +2468,7 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
|
|
|
{
|
|
|
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
|
|
|
DEBUGASSERT(data);
|
|
|
if(ctx && ctx->h2 && stream) {
|
|
@@ -2480,7 +2553,7 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf,
|
|
|
const struct Curl_easy *data)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
|
|
|
if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq)
|
|
|
|| (stream && !Curl_bufq_is_empty(&stream->sendbuf))))
|
|
@@ -2539,6 +2612,11 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf,
|
|
|
*pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max;
|
|
|
CF_DATA_RESTORE(cf, save);
|
|
|
return CURLE_OK;
|
|
|
+ case CF_QUERY_STREAM_ERROR: {
|
|
|
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
+ *pres1 = stream? (int)stream->error : 0;
|
|
|
+ return CURLE_OK;
|
|
|
+ }
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
@@ -2768,8 +2846,11 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
|
|
|
CURLE_HTTP2_STREAM error! */
|
|
|
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
|
|
|
{
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
|
|
- return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
|
|
|
+ if(Curl_conn_is_http2(data, data->conn, FIRSTSOCKET)) {
|
|
|
+ int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET);
|
|
|
+ return (err == NGHTTP2_HTTP_1_1_REQUIRED);
|
|
|
+ }
|
|
|
+ return FALSE;
|
|
|
}
|
|
|
|
|
|
#else /* !USE_NGHTTP2 */
|