mirror of
https://github.com/curl/curl.git
synced 2025-09-13 23:52:42 +03:00
HTTP/2: write response directly
- use the new `Curl_xfer_write_resp()` to write incoming responses directly to the client - eliminates `stream->recvbuf` - memory consumption on parallel transfers minimized Closes #12828
This commit is contained in:
parent
8911d86719
commit
0dc036225b
220
lib/http2.c
220
lib/http2.c
|
@ -169,10 +169,9 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data);
|
struct Curl_easy *data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All about the H3 internals of a stream
|
* All about the H2 internals of a stream
|
||||||
*/
|
*/
|
||||||
struct stream_ctx {
|
struct h2_stream_ctx {
|
||||||
/*********** for HTTP/2 we store stream-local data here *************/
|
|
||||||
int32_t id; /* HTTP/2 protocol identifier for stream */
|
int32_t id; /* HTTP/2 protocol identifier for stream */
|
||||||
struct bufq recvbuf; /* response buffer */
|
struct bufq recvbuf; /* response buffer */
|
||||||
struct bufq sendbuf; /* request buffer */
|
struct bufq sendbuf; /* request buffer */
|
||||||
|
@ -181,6 +180,7 @@ struct stream_ctx {
|
||||||
size_t resp_hds_len; /* amount of response header bytes in recvbuf */
|
size_t resp_hds_len; /* amount of response header bytes in recvbuf */
|
||||||
size_t upload_blocked_len;
|
size_t upload_blocked_len;
|
||||||
curl_off_t upload_left; /* number of request bytes left to upload */
|
curl_off_t upload_left; /* number of request bytes left to upload */
|
||||||
|
curl_off_t nrcvd_data; /* number of DATA bytes received */
|
||||||
|
|
||||||
char **push_headers; /* allocated array */
|
char **push_headers; /* allocated array */
|
||||||
size_t push_headers_used; /* number of entries filled in */
|
size_t push_headers_used; /* number of entries filled in */
|
||||||
|
@ -198,7 +198,8 @@ struct stream_ctx {
|
||||||
buffered data in stream->sendbuf to upload. */
|
buffered data in stream->sendbuf to upload. */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define H2_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \
|
#define H2_STREAM_CTX(d) ((struct h2_stream_ctx *)(((d) && \
|
||||||
|
(d)->req.p.http)? \
|
||||||
((struct HTTP *)(d)->req.p.http)->h2_ctx \
|
((struct HTTP *)(d)->req.p.http)->h2_ctx \
|
||||||
: NULL))
|
: NULL))
|
||||||
#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx
|
#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx
|
||||||
|
@ -210,7 +211,7 @@ struct stream_ctx {
|
||||||
*/
|
*/
|
||||||
static void drain_stream(struct Curl_cfilter *cf,
|
static void drain_stream(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data,
|
struct Curl_easy *data,
|
||||||
struct stream_ctx *stream)
|
struct h2_stream_ctx *stream)
|
||||||
{
|
{
|
||||||
unsigned char bits;
|
unsigned char bits;
|
||||||
|
|
||||||
|
@ -229,10 +230,10 @@ static void drain_stream(struct Curl_cfilter *cf,
|
||||||
|
|
||||||
static CURLcode http2_data_setup(struct Curl_cfilter *cf,
|
static CURLcode http2_data_setup(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data,
|
struct Curl_easy *data,
|
||||||
struct stream_ctx **pstream)
|
struct h2_stream_ctx **pstream)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
|
|
||||||
(void)cf;
|
(void)cf;
|
||||||
DEBUGASSERT(data);
|
DEBUGASSERT(data);
|
||||||
|
@ -253,8 +254,6 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf,
|
||||||
stream->id = -1;
|
stream->id = -1;
|
||||||
Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
|
Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
|
||||||
H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
|
H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
|
||||||
Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
|
|
||||||
H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
|
|
||||||
Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
|
Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
|
||||||
Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
|
Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
|
||||||
stream->resp_hds_len = 0;
|
stream->resp_hds_len = 0;
|
||||||
|
@ -265,6 +264,7 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf,
|
||||||
stream->error = NGHTTP2_NO_ERROR;
|
stream->error = NGHTTP2_NO_ERROR;
|
||||||
stream->local_window_size = H2_STREAM_WINDOW_SIZE;
|
stream->local_window_size = H2_STREAM_WINDOW_SIZE;
|
||||||
stream->upload_left = 0;
|
stream->upload_left = 0;
|
||||||
|
stream->nrcvd_data = 0;
|
||||||
|
|
||||||
H2_STREAM_LCTX(data) = stream;
|
H2_STREAM_LCTX(data) = stream;
|
||||||
*pstream = stream;
|
*pstream = stream;
|
||||||
|
@ -275,7 +275,7 @@ static void http2_data_done(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data, bool premature)
|
struct Curl_easy *data, bool premature)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
|
|
||||||
DEBUGASSERT(ctx);
|
DEBUGASSERT(ctx);
|
||||||
(void)premature;
|
(void)premature;
|
||||||
|
@ -298,23 +298,12 @@ static void http2_data_done(struct Curl_cfilter *cf,
|
||||||
stream->id, NGHTTP2_STREAM_CLOSED);
|
stream->id, NGHTTP2_STREAM_CLOSED);
|
||||||
flush_egress = TRUE;
|
flush_egress = TRUE;
|
||||||
}
|
}
|
||||||
if(!Curl_bufq_is_empty(&stream->recvbuf)) {
|
|
||||||
/* Anything in the recvbuf is still being counted
|
|
||||||
* in stream and connection window flow control. Need
|
|
||||||
* to free that space or the connection window might get
|
|
||||||
* exhausted eventually. */
|
|
||||||
nghttp2_session_consume(ctx->h2, stream->id,
|
|
||||||
Curl_bufq_len(&stream->recvbuf));
|
|
||||||
/* give WINDOW_UPATE a chance to be sent, but ignore any error */
|
|
||||||
flush_egress = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(flush_egress)
|
if(flush_egress)
|
||||||
nghttp2_session_send(ctx->h2);
|
nghttp2_session_send(ctx->h2);
|
||||||
}
|
}
|
||||||
|
|
||||||
Curl_bufq_free(&stream->sendbuf);
|
Curl_bufq_free(&stream->sendbuf);
|
||||||
Curl_bufq_free(&stream->recvbuf);
|
|
||||||
Curl_h1_req_parse_free(&stream->h1);
|
Curl_h1_req_parse_free(&stream->h1);
|
||||||
Curl_dynhds_free(&stream->resp_trailers);
|
Curl_dynhds_free(&stream->resp_trailers);
|
||||||
if(stream->push_headers) {
|
if(stream->push_headers) {
|
||||||
|
@ -411,7 +400,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
|
||||||
bool via_h1_upgrade)
|
bool via_h1_upgrade)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
CURLcode result = CURLE_OUT_OF_MEMORY;
|
CURLcode result = CURLE_OUT_OF_MEMORY;
|
||||||
int rc;
|
int rc;
|
||||||
nghttp2_session_callbacks *cbs = NULL;
|
nghttp2_session_callbacks *cbs = NULL;
|
||||||
|
@ -731,7 +720,7 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num)
|
||||||
if(!h || !GOOD_EASY_HANDLE(h->data))
|
if(!h || !GOOD_EASY_HANDLE(h->data))
|
||||||
return NULL;
|
return NULL;
|
||||||
else {
|
else {
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(h->data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(h->data);
|
||||||
if(stream && num < stream->push_headers_used)
|
if(stream && num < stream->push_headers_used)
|
||||||
return stream->push_headers[num];
|
return stream->push_headers[num];
|
||||||
}
|
}
|
||||||
|
@ -743,7 +732,7 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num)
|
||||||
*/
|
*/
|
||||||
char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
|
char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
|
||||||
{
|
{
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
size_t len;
|
size_t len;
|
||||||
size_t i;
|
size_t i;
|
||||||
/* Verify that we got a good easy handle in the push header struct,
|
/* Verify that we got a good easy handle in the push header struct,
|
||||||
|
@ -783,7 +772,7 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf,
|
||||||
(void)Curl_close(&second);
|
(void)Curl_close(&second);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
struct stream_ctx *second_stream;
|
struct h2_stream_ctx *second_stream;
|
||||||
|
|
||||||
second->req.p.http = http;
|
second->req.p.http = http;
|
||||||
http2_data_setup(cf, second, &second_stream);
|
http2_data_setup(cf, second, &second_stream);
|
||||||
|
@ -867,8 +856,8 @@ static int push_promise(struct Curl_cfilter *cf,
|
||||||
CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE received",
|
CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE received",
|
||||||
frame->promised_stream_id);
|
frame->promised_stream_id);
|
||||||
if(data->multi->push_cb) {
|
if(data->multi->push_cb) {
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
struct stream_ctx *newstream;
|
struct h2_stream_ctx *newstream;
|
||||||
struct curl_pushheaders heads;
|
struct curl_pushheaders heads;
|
||||||
CURLMcode rc;
|
CURLMcode rc;
|
||||||
CURLcode result;
|
CURLcode result;
|
||||||
|
@ -967,18 +956,10 @@ static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data,
|
struct Curl_easy *data,
|
||||||
const char *buf, size_t blen)
|
const char *buf, size_t blen)
|
||||||
{
|
{
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
bool done;
|
||||||
ssize_t nwritten;
|
|
||||||
CURLcode result;
|
|
||||||
|
|
||||||
(void)cf;
|
(void)cf;
|
||||||
nwritten = Curl_bufq_write(&stream->recvbuf,
|
return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE, &done);
|
||||||
(const unsigned char *)buf, blen, &result);
|
|
||||||
if(nwritten < 0)
|
|
||||||
return result;
|
|
||||||
stream->resp_hds_len += (size_t)nwritten;
|
|
||||||
DEBUGASSERT((size_t)nwritten == blen);
|
|
||||||
return CURLE_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
||||||
|
@ -986,10 +967,9 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
||||||
const nghttp2_frame *frame)
|
const nghttp2_frame *frame)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
int32_t stream_id = frame->hd.stream_id;
|
int32_t stream_id = frame->hd.stream_id;
|
||||||
CURLcode result;
|
CURLcode result;
|
||||||
size_t rbuflen;
|
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
if(!stream) {
|
if(!stream) {
|
||||||
|
@ -999,9 +979,8 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
||||||
|
|
||||||
switch(frame->hd.type) {
|
switch(frame->hd.type) {
|
||||||
case NGHTTP2_DATA:
|
case NGHTTP2_DATA:
|
||||||
rbuflen = Curl_bufq_len(&stream->recvbuf);
|
CURL_TRC_CF(data, cf, "[%d] DATA, window=%d/%d",
|
||||||
CURL_TRC_CF(data, cf, "[%d] DATA, buffered=%zu, window=%d/%d",
|
stream_id,
|
||||||
stream_id, rbuflen,
|
|
||||||
nghttp2_session_get_stream_effective_recv_data_length(
|
nghttp2_session_get_stream_effective_recv_data_length(
|
||||||
ctx->h2, stream->id),
|
ctx->h2, stream->id),
|
||||||
nghttp2_session_get_stream_effective_local_window_size(
|
nghttp2_session_get_stream_effective_local_window_size(
|
||||||
|
@ -1018,20 +997,6 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
||||||
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
drain_stream(cf, data, stream);
|
drain_stream(cf, data, stream);
|
||||||
}
|
}
|
||||||
else if(rbuflen > stream->local_window_size) {
|
|
||||||
int32_t wsize = nghttp2_session_get_stream_local_window_size(
|
|
||||||
ctx->h2, stream->id);
|
|
||||||
if(wsize > 0 && (uint32_t)wsize != stream->local_window_size) {
|
|
||||||
/* H2 flow control is not absolute, as the server might not have the
|
|
||||||
* same view, yet. When we receive more than we want, we enforce
|
|
||||||
* the local window size again to make nghttp2 send WINDOW_UPATEs
|
|
||||||
* accordingly. */
|
|
||||||
nghttp2_session_set_local_window_size(ctx->h2,
|
|
||||||
NGHTTP2_FLAG_NONE,
|
|
||||||
stream->id,
|
|
||||||
stream->local_window_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case NGHTTP2_HEADERS:
|
case NGHTTP2_HEADERS:
|
||||||
if(stream->bodystarted) {
|
if(stream->bodystarted) {
|
||||||
|
@ -1233,7 +1198,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.
|
* 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. */
|
* To be safe, we UNHOLD a stream in order not to stall. */
|
||||||
if(CURL_WANT_SEND(data)) {
|
if(CURL_WANT_SEND(data)) {
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
if(stream)
|
if(stream)
|
||||||
drain_stream(cf, data, stream);
|
drain_stream(cf, data, stream);
|
||||||
}
|
}
|
||||||
|
@ -1270,10 +1235,11 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
|
||||||
const uint8_t *mem, size_t len, void *userp)
|
const uint8_t *mem, size_t len, void *userp)
|
||||||
{
|
{
|
||||||
struct Curl_cfilter *cf = userp;
|
struct Curl_cfilter *cf = userp;
|
||||||
struct stream_ctx *stream;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
|
struct h2_stream_ctx *stream;
|
||||||
struct Curl_easy *data_s;
|
struct Curl_easy *data_s;
|
||||||
ssize_t nwritten;
|
|
||||||
CURLcode result;
|
CURLcode result;
|
||||||
|
bool done;
|
||||||
(void)flags;
|
(void)flags;
|
||||||
|
|
||||||
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
|
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
|
||||||
|
@ -1296,18 +1262,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
|
||||||
if(!stream)
|
if(!stream)
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
|
||||||
nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result);
|
result = Curl_xfer_write_resp(data_s, (char *)mem, len, FALSE, &done);
|
||||||
if(nwritten < 0) {
|
if(result && result != CURLE_AGAIN)
|
||||||
if(result != CURLE_AGAIN)
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
||||||
|
|
||||||
nwritten = 0;
|
nghttp2_session_consume(ctx->h2, stream_id, len);
|
||||||
}
|
stream->nrcvd_data += (curl_off_t)len;
|
||||||
|
|
||||||
/* if we receive data for another handle, wake that up */
|
/* if we receive data for another handle, wake that up */
|
||||||
drain_stream(cf, data_s, stream);
|
drain_stream(cf, data_s, stream);
|
||||||
|
|
||||||
DEBUGASSERT((size_t)nwritten == len);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1316,7 +1279,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
|
||||||
{
|
{
|
||||||
struct Curl_cfilter *cf = userp;
|
struct Curl_cfilter *cf = userp;
|
||||||
struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf);
|
struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf);
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
int rv;
|
int rv;
|
||||||
(void)session;
|
(void)session;
|
||||||
|
|
||||||
|
@ -1374,7 +1337,7 @@ static int on_begin_headers(nghttp2_session *session,
|
||||||
const nghttp2_frame *frame, void *userp)
|
const nghttp2_frame *frame, void *userp)
|
||||||
{
|
{
|
||||||
struct Curl_cfilter *cf = userp;
|
struct Curl_cfilter *cf = userp;
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
struct Curl_easy *data_s = NULL;
|
struct Curl_easy *data_s = NULL;
|
||||||
|
|
||||||
(void)cf;
|
(void)cf;
|
||||||
|
@ -1403,7 +1366,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
void *userp)
|
void *userp)
|
||||||
{
|
{
|
||||||
struct Curl_cfilter *cf = userp;
|
struct Curl_cfilter *cf = userp;
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
struct Curl_easy *data_s;
|
struct Curl_easy *data_s;
|
||||||
int32_t stream_id = frame->hd.stream_id;
|
int32_t stream_id = frame->hd.stream_id;
|
||||||
CURLcode result;
|
CURLcode result;
|
||||||
|
@ -1565,7 +1528,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session,
|
||||||
{
|
{
|
||||||
struct Curl_cfilter *cf = userp;
|
struct Curl_cfilter *cf = userp;
|
||||||
struct Curl_easy *data_s;
|
struct Curl_easy *data_s;
|
||||||
struct stream_ctx *stream = NULL;
|
struct h2_stream_ctx *stream = NULL;
|
||||||
CURLcode result;
|
CURLcode result;
|
||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
(void)source;
|
(void)source;
|
||||||
|
@ -1667,7 +1630,7 @@ static CURLcode http2_data_done_send(struct Curl_cfilter *cf,
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
CURLcode result = CURLE_OK;
|
CURLcode result = CURLE_OK;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
|
|
||||||
if(!ctx || !ctx->h2 || !stream)
|
if(!ctx || !ctx->h2 || !stream)
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -1691,7 +1654,7 @@ out:
|
||||||
|
|
||||||
static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
|
static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data,
|
struct Curl_easy *data,
|
||||||
struct stream_ctx *stream,
|
struct h2_stream_ctx *stream,
|
||||||
CURLcode *err)
|
CURLcode *err)
|
||||||
{
|
{
|
||||||
ssize_t rv = 0;
|
ssize_t rv = 0;
|
||||||
|
@ -1787,7 +1750,7 @@ static void h2_pri_spec(struct Curl_easy *data,
|
||||||
nghttp2_priority_spec *pri_spec)
|
nghttp2_priority_spec *pri_spec)
|
||||||
{
|
{
|
||||||
struct Curl_data_priority *prio = &data->set.priority;
|
struct Curl_data_priority *prio = &data->set.priority;
|
||||||
struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent);
|
struct h2_stream_ctx *depstream = H2_STREAM_CTX(prio->parent);
|
||||||
int32_t depstream_id = depstream? depstream->id:0;
|
int32_t depstream_id = depstream? depstream->id:0;
|
||||||
nghttp2_priority_spec_init(pri_spec, depstream_id,
|
nghttp2_priority_spec_init(pri_spec, depstream_id,
|
||||||
sweight_wanted(data),
|
sweight_wanted(data),
|
||||||
|
@ -1805,7 +1768,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data)
|
struct Curl_easy *data)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
int rv = 0;
|
int rv = 0;
|
||||||
|
|
||||||
if(stream && stream->id > 0 &&
|
if(stream && stream->id > 0 &&
|
||||||
|
@ -1838,40 +1801,26 @@ out:
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||||
struct stream_ctx *stream,
|
struct h2_stream_ctx *stream,
|
||||||
char *buf, size_t len, CURLcode *err)
|
char *buf, size_t len, CURLcode *err)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
ssize_t nread = -1;
|
ssize_t nread = -1;
|
||||||
|
|
||||||
|
(void)buf;
|
||||||
*err = CURLE_AGAIN;
|
*err = CURLE_AGAIN;
|
||||||
if(!Curl_bufq_is_empty(&stream->recvbuf)) {
|
if(stream->closed) {
|
||||||
nread = Curl_bufq_read(&stream->recvbuf,
|
CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id);
|
||||||
(unsigned char *)buf, len, err);
|
nread = http2_handle_stream_close(cf, data, stream, err);
|
||||||
if(nread < 0)
|
|
||||||
goto out;
|
|
||||||
DEBUGASSERT(nread > 0);
|
|
||||||
}
|
}
|
||||||
|
else if(stream->reset ||
|
||||||
if(nread < 0) {
|
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
|
||||||
if(stream->closed) {
|
(ctx->goaway && ctx->last_stream_id < stream->id)) {
|
||||||
CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id);
|
CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id);
|
||||||
nread = http2_handle_stream_close(cf, data, stream, err);
|
*err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
|
||||||
}
|
|
||||||
else if(stream->reset ||
|
|
||||||
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
|
|
||||||
(ctx->goaway && ctx->last_stream_id < stream->id)) {
|
|
||||||
CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id);
|
|
||||||
*err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
|
|
||||||
nread = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(nread == 0) {
|
|
||||||
*err = CURLE_AGAIN;
|
|
||||||
nread = -1;
|
nread = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
|
||||||
if(nread < 0 && *err != CURLE_AGAIN)
|
if(nread < 0 && *err != CURLE_AGAIN)
|
||||||
CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d",
|
CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d",
|
||||||
stream->id, len, nread, *err);
|
stream->id, len, nread, *err);
|
||||||
|
@ -1879,10 +1828,11 @@ out:
|
||||||
}
|
}
|
||||||
|
|
||||||
static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data)
|
struct Curl_easy *data,
|
||||||
|
size_t data_max_bytes)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream;
|
struct h2_stream_ctx *stream;
|
||||||
CURLcode result = CURLE_OK;
|
CURLcode result = CURLE_OK;
|
||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
|
|
||||||
|
@ -1899,16 +1849,17 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
||||||
* all network input */
|
* all network input */
|
||||||
while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
|
while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
|
||||||
stream = H2_STREAM_CTX(data);
|
stream = H2_STREAM_CTX(data);
|
||||||
if(stream && (stream->closed || Curl_bufq_is_full(&stream->recvbuf))) {
|
if(stream && (stream->closed || !data_max_bytes)) {
|
||||||
/* We would like to abort here and stop processing, so that
|
/* We would like to abort here and stop processing, so that
|
||||||
* the transfer loop can handle the data/close here. However,
|
* the transfer loop can handle the data/close here. However,
|
||||||
* this may leave data in underlying buffers that will not
|
* this may leave data in underlying buffers that will not
|
||||||
* be consumed. */
|
* be consumed. */
|
||||||
if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data))
|
if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data))
|
||||||
break;
|
drain_stream(cf, data, stream);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result);
|
nread = Curl_bufq_sipn(&ctx->inbufq, 0, nw_in_reader, cf, &result);
|
||||||
if(nread < 0) {
|
if(nread < 0) {
|
||||||
if(result != CURLE_AGAIN) {
|
if(result != CURLE_AGAIN) {
|
||||||
failf(data, "Failed receiving HTTP2 data: %d(%s)", result,
|
failf(data, "Failed receiving HTTP2 data: %d(%s)", result,
|
||||||
|
@ -1923,8 +1874,9 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes",
|
CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", nread);
|
||||||
nread);
|
data_max_bytes = (data_max_bytes > (size_t)nread)?
|
||||||
|
(data_max_bytes - (size_t)nread) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(h2_process_pending_input(cf, data, &result))
|
if(h2_process_pending_input(cf, data, &result))
|
||||||
|
@ -1942,7 +1894,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||||
char *buf, size_t len, CURLcode *err)
|
char *buf, size_t len, CURLcode *err)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
ssize_t nread = -1;
|
ssize_t nread = -1;
|
||||||
CURLcode result;
|
CURLcode result;
|
||||||
struct cf_call_data save;
|
struct cf_call_data save;
|
||||||
|
@ -1966,7 +1918,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
if(nread < 0) {
|
if(nread < 0) {
|
||||||
*err = h2_progress_ingress(cf, data);
|
*err = h2_progress_ingress(cf, data, len);
|
||||||
if(*err)
|
if(*err)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
@ -2011,9 +1963,8 @@ out:
|
||||||
nread = -1;
|
nread = -1;
|
||||||
}
|
}
|
||||||
CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, "
|
CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, "
|
||||||
"buffered=%zu, window=%d/%d, connection %d/%d",
|
"window=%d/%d, connection %d/%d",
|
||||||
stream->id, len, nread, *err,
|
stream->id, len, nread, *err,
|
||||||
Curl_bufq_len(&stream->recvbuf),
|
|
||||||
nghttp2_session_get_stream_effective_recv_data_length(
|
nghttp2_session_get_stream_effective_recv_data_length(
|
||||||
ctx->h2, stream->id),
|
ctx->h2, stream->id),
|
||||||
nghttp2_session_get_stream_effective_local_window_size(
|
nghttp2_session_get_stream_effective_local_window_size(
|
||||||
|
@ -2025,12 +1976,12 @@ out:
|
||||||
return nread;
|
return nread;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t h2_submit(struct stream_ctx **pstream,
|
static ssize_t h2_submit(struct h2_stream_ctx **pstream,
|
||||||
struct Curl_cfilter *cf, struct Curl_easy *data,
|
struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||||
const void *buf, size_t len, CURLcode *err)
|
const void *buf, size_t len, CURLcode *err)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = NULL;
|
struct h2_stream_ctx *stream = NULL;
|
||||||
struct dynhds h2_headers;
|
struct dynhds h2_headers;
|
||||||
nghttp2_nv *nva = NULL;
|
nghttp2_nv *nva = NULL;
|
||||||
const void *body = NULL;
|
const void *body = NULL;
|
||||||
|
@ -2169,7 +2120,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
||||||
const void *buf, size_t len, CURLcode *err)
|
const void *buf, size_t len, CURLcode *err)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
struct cf_call_data save;
|
struct cf_call_data save;
|
||||||
int rv;
|
int rv;
|
||||||
ssize_t nwritten;
|
ssize_t nwritten;
|
||||||
|
@ -2340,7 +2291,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf,
|
||||||
sock = Curl_conn_cf_get_socket(cf, data);
|
sock = Curl_conn_cf_get_socket(cf, data);
|
||||||
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
|
Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
|
||||||
if(want_recv || want_send) {
|
if(want_recv || want_send) {
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
struct cf_call_data save;
|
struct cf_call_data save;
|
||||||
bool c_exhaust, s_exhaust;
|
bool c_exhaust, s_exhaust;
|
||||||
|
|
||||||
|
@ -2387,7 +2338,7 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = h2_progress_ingress(cf, data);
|
result = h2_progress_ingress(cf, data, H2_CHUNK_SIZE);
|
||||||
if(result)
|
if(result)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
@ -2441,7 +2392,7 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
|
||||||
{
|
{
|
||||||
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
|
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
|
|
||||||
DEBUGASSERT(data);
|
DEBUGASSERT(data);
|
||||||
if(ctx && ctx->h2 && stream) {
|
if(ctx && ctx->h2 && stream) {
|
||||||
|
@ -2526,11 +2477,10 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf,
|
||||||
const struct Curl_easy *data)
|
const struct Curl_easy *data)
|
||||||
{
|
{
|
||||||
struct cf_h2_ctx *ctx = cf->ctx;
|
struct cf_h2_ctx *ctx = cf->ctx;
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
|
|
||||||
if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq)
|
if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq)
|
||||||
|| (stream && !Curl_bufq_is_empty(&stream->sendbuf))
|
|| (stream && !Curl_bufq_is_empty(&stream->sendbuf))))
|
||||||
|| (stream && !Curl_bufq_is_empty(&stream->recvbuf))))
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
|
return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
|
||||||
}
|
}
|
||||||
|
@ -2615,7 +2565,8 @@ struct Curl_cftype Curl_cft_nghttp2 = {
|
||||||
static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
|
static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
|
||||||
struct Curl_easy *data,
|
struct Curl_easy *data,
|
||||||
struct connectdata *conn,
|
struct connectdata *conn,
|
||||||
int sockindex)
|
int sockindex,
|
||||||
|
bool via_h1_upgrade)
|
||||||
{
|
{
|
||||||
struct Curl_cfilter *cf = NULL;
|
struct Curl_cfilter *cf = NULL;
|
||||||
struct cf_h2_ctx *ctx;
|
struct cf_h2_ctx *ctx;
|
||||||
|
@ -2630,8 +2581,9 @@ static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
|
||||||
if(result)
|
if(result)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
ctx = NULL;
|
||||||
Curl_conn_cf_add(data, conn, sockindex, cf);
|
Curl_conn_cf_add(data, conn, sockindex, cf);
|
||||||
result = CURLE_OK;
|
result = cf_h2_ctx_init(cf, data, via_h1_upgrade);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if(result)
|
if(result)
|
||||||
|
@ -2641,7 +2593,8 @@ out:
|
||||||
}
|
}
|
||||||
|
|
||||||
static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
|
static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data)
|
struct Curl_easy *data,
|
||||||
|
bool via_h1_upgrade)
|
||||||
{
|
{
|
||||||
struct Curl_cfilter *cf_h2 = NULL;
|
struct Curl_cfilter *cf_h2 = NULL;
|
||||||
struct cf_h2_ctx *ctx;
|
struct cf_h2_ctx *ctx;
|
||||||
|
@ -2656,8 +2609,9 @@ static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
|
||||||
if(result)
|
if(result)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
ctx = NULL;
|
||||||
Curl_conn_cf_insert_after(cf, cf_h2);
|
Curl_conn_cf_insert_after(cf, cf_h2);
|
||||||
result = CURLE_OK;
|
result = cf_h2_ctx_init(cf_h2, data, via_h1_upgrade);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if(result)
|
if(result)
|
||||||
|
@ -2714,11 +2668,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data,
|
||||||
DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
|
DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
|
||||||
DEBUGF(infof(data, "switching to HTTP/2"));
|
DEBUGF(infof(data, "switching to HTTP/2"));
|
||||||
|
|
||||||
result = http2_cfilter_add(&cf, data, conn, sockindex);
|
result = http2_cfilter_add(&cf, data, conn, sockindex, FALSE);
|
||||||
if(result)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
result = cf_h2_ctx_init(cf, data, FALSE);
|
|
||||||
if(result)
|
if(result)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
@ -2741,15 +2691,11 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||||
|
|
||||||
DEBUGASSERT(!Curl_cf_is_http2(cf, data));
|
DEBUGASSERT(!Curl_cf_is_http2(cf, data));
|
||||||
|
|
||||||
result = http2_cfilter_insert_after(cf, data);
|
result = http2_cfilter_insert_after(cf, data, FALSE);
|
||||||
if(result)
|
if(result)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
cf_h2 = cf->next;
|
cf_h2 = cf->next;
|
||||||
result = cf_h2_ctx_init(cf_h2, data, FALSE);
|
|
||||||
if(result)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */
|
cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */
|
||||||
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
|
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
|
||||||
cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
|
cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
|
||||||
|
@ -2774,17 +2720,13 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
|
||||||
DEBUGF(infof(data, "upgrading to HTTP/2"));
|
DEBUGF(infof(data, "upgrading to HTTP/2"));
|
||||||
DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
|
DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
|
||||||
|
|
||||||
result = http2_cfilter_add(&cf, data, conn, sockindex);
|
result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE);
|
||||||
if(result)
|
if(result)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
DEBUGASSERT(cf->cft == &Curl_cft_nghttp2);
|
DEBUGASSERT(cf->cft == &Curl_cft_nghttp2);
|
||||||
ctx = cf->ctx;
|
ctx = cf->ctx;
|
||||||
|
|
||||||
result = cf_h2_ctx_init(cf, data, TRUE);
|
|
||||||
if(result)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
if(nread > 0) {
|
if(nread > 0) {
|
||||||
/* Remaining data from the protocol switch reply is already using
|
/* Remaining data from the protocol switch reply is already using
|
||||||
* the switched protocol, ie. HTTP/2. We add that to the network
|
* the switched protocol, ie. HTTP/2. We add that to the network
|
||||||
|
@ -2823,7 +2765,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
|
||||||
CURLE_HTTP2_STREAM error! */
|
CURLE_HTTP2_STREAM error! */
|
||||||
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
|
bool Curl_h2_http_1_1_error(struct Curl_easy *data)
|
||||||
{
|
{
|
||||||
struct stream_ctx *stream = H2_STREAM_CTX(data);
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
|
||||||
return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
|
return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user