struct Curl_easy *data);
/**
- * All about the H3 internals of a stream
+ * All about the H2 internals of a stream
*/
-struct stream_ctx {
- /*********** for HTTP/2 we store stream-local data here *************/
+struct h2_stream_ctx {
int32_t id; /* HTTP/2 protocol identifier for stream */
struct bufq recvbuf; /* response buffer */
struct bufq sendbuf; /* request buffer */
size_t resp_hds_len; /* amount of response header bytes in recvbuf */
size_t upload_blocked_len;
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 */
size_t push_headers_used; /* number of entries filled in */
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 \
: NULL))
#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx
*/
static void drain_stream(struct Curl_cfilter *cf,
struct Curl_easy *data,
- struct stream_ctx *stream)
+ struct h2_stream_ctx *stream)
{
unsigned char bits;
static CURLcode http2_data_setup(struct Curl_cfilter *cf,
struct Curl_easy *data,
- struct stream_ctx **pstream)
+ struct h2_stream_ctx **pstream)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct stream_ctx *stream;
+ struct h2_stream_ctx *stream;
(void)cf;
DEBUGASSERT(data);
stream->id = -1;
Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
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_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
stream->resp_hds_len = 0;
stream->error = NGHTTP2_NO_ERROR;
stream->local_window_size = H2_STREAM_WINDOW_SIZE;
stream->upload_left = 0;
+ stream->nrcvd_data = 0;
H2_STREAM_LCTX(data) = stream;
*pstream = stream;
struct Curl_easy *data, bool premature)
{
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);
(void)premature;
stream->id, NGHTTP2_STREAM_CLOSED);
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)
nghttp2_session_send(ctx->h2);
}
Curl_bufq_free(&stream->sendbuf);
- Curl_bufq_free(&stream->recvbuf);
Curl_h1_req_parse_free(&stream->h1);
Curl_dynhds_free(&stream->resp_trailers);
if(stream->push_headers) {
bool via_h1_upgrade)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct stream_ctx *stream;
+ struct h2_stream_ctx *stream;
CURLcode result = CURLE_OUT_OF_MEMORY;
int rc;
nghttp2_session_callbacks *cbs = NULL;
if(!h || !GOOD_EASY_HANDLE(h->data))
return NULL;
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)
return stream->push_headers[num];
}
*/
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 i;
/* Verify that we got a good easy handle in the push header struct,
(void)Curl_close(&second);
}
else {
- struct stream_ctx *second_stream;
+ struct h2_stream_ctx *second_stream;
second->req.p.http = http;
http2_data_setup(cf, second, &second_stream);
CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE received",
frame->promised_stream_id);
if(data->multi->push_cb) {
- struct stream_ctx *stream;
- struct stream_ctx *newstream;
+ struct h2_stream_ctx *stream;
+ struct h2_stream_ctx *newstream;
struct curl_pushheaders heads;
CURLMcode rc;
CURLcode result;
struct Curl_easy *data,
const char *buf, size_t blen)
{
- struct stream_ctx *stream = H2_STREAM_CTX(data);
- ssize_t nwritten;
- CURLcode result;
+ bool done;
(void)cf;
- nwritten = Curl_bufq_write(&stream->recvbuf,
- (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;
+ return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE, &done);
}
static CURLcode on_stream_frame(struct Curl_cfilter *cf,
const nghttp2_frame *frame)
{
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;
CURLcode result;
- size_t rbuflen;
int rv;
if(!stream) {
switch(frame->hd.type) {
case NGHTTP2_DATA:
- rbuflen = Curl_bufq_len(&stream->recvbuf);
- CURL_TRC_CF(data, cf, "[%d] DATA, buffered=%zu, window=%d/%d",
- stream_id, rbuflen,
+ CURL_TRC_CF(data, cf, "[%d] DATA, window=%d/%d",
+ stream_id,
nghttp2_session_get_stream_effective_recv_data_length(
ctx->h2, stream->id),
nghttp2_session_get_stream_effective_local_window_size(
if(frame->hd.flags & NGHTTP2_FLAG_END_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;
case NGHTTP2_HEADERS:
if(stream->bodystarted) {
* 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 stream_ctx *stream = H2_STREAM_CTX(data);
+ struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
if(stream)
drain_stream(cf, data, stream);
}
const uint8_t *mem, size_t len, void *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;
- ssize_t nwritten;
CURLcode result;
+ bool done;
(void)flags;
DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
- nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result);
- if(nwritten < 0) {
- if(result != CURLE_AGAIN)
- return NGHTTP2_ERR_CALLBACK_FAILURE;
+ result = Curl_xfer_write_resp(data_s, (char *)mem, len, FALSE, &done);
+ if(result && result != CURLE_AGAIN)
+ 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 */
drain_stream(cf, data_s, stream);
-
- DEBUGASSERT((size_t)nwritten == len);
return 0;
}
{
struct Curl_cfilter *cf = userp;
struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf);
- struct stream_ctx *stream;
+ struct h2_stream_ctx *stream;
int rv;
(void)session;
const nghttp2_frame *frame, void *userp)
{
struct Curl_cfilter *cf = userp;
- struct stream_ctx *stream;
+ struct h2_stream_ctx *stream;
struct Curl_easy *data_s = NULL;
(void)cf;
void *userp)
{
struct Curl_cfilter *cf = userp;
- struct stream_ctx *stream;
+ struct h2_stream_ctx *stream;
struct Curl_easy *data_s;
int32_t stream_id = frame->hd.stream_id;
CURLcode result;
{
struct Curl_cfilter *cf = userp;
struct Curl_easy *data_s;
- struct stream_ctx *stream = NULL;
+ struct h2_stream_ctx *stream = NULL;
CURLcode result;
ssize_t nread;
(void)source;
{
struct cf_h2_ctx *ctx = cf->ctx;
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)
goto out;
static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
struct Curl_easy *data,
- struct stream_ctx *stream,
+ struct h2_stream_ctx *stream,
CURLcode *err)
{
ssize_t rv = 0;
nghttp2_priority_spec *pri_spec)
{
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;
nghttp2_priority_spec_init(pri_spec, depstream_id,
sweight_wanted(data),
struct Curl_easy *data)
{
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;
if(stream && stream->id > 0 &&
}
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)
{
struct cf_h2_ctx *ctx = cf->ctx;
ssize_t nread = -1;
+ (void)buf;
*err = CURLE_AGAIN;
- if(!Curl_bufq_is_empty(&stream->recvbuf)) {
- nread = Curl_bufq_read(&stream->recvbuf,
- (unsigned char *)buf, len, err);
- if(nread < 0)
- goto out;
- DEBUGASSERT(nread > 0);
- }
-
- if(nread < 0) {
- if(stream->closed) {
- CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id);
- nread = http2_handle_stream_close(cf, data, stream, err);
- }
- 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;
+ if(stream->closed) {
+ CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id);
+ nread = http2_handle_stream_close(cf, data, stream, err);
+ }
+ 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;
}
-out:
if(nread < 0 && *err != CURLE_AGAIN)
CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d",
stream->id, len, nread, *err);
}
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 stream_ctx *stream;
+ struct h2_stream_ctx *stream;
CURLcode result = CURLE_OK;
ssize_t nread;
* all network input */
while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
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
* the transfer loop can handle the data/close here. However,
* this may leave data in underlying buffers that will not
* be consumed. */
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(result != CURLE_AGAIN) {
failf(data, "Failed receiving HTTP2 data: %d(%s)", result,
break;
}
else {
- CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes",
- nread);
+ CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", 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))
char *buf, size_t len, CURLcode *err)
{
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;
CURLcode result;
struct cf_call_data save;
goto out;
if(nread < 0) {
- *err = h2_progress_ingress(cf, data);
+ *err = h2_progress_ingress(cf, data, len);
if(*err)
goto out;
nread = -1;
}
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,
- Curl_bufq_len(&stream->recvbuf),
nghttp2_session_get_stream_effective_recv_data_length(
ctx->h2, stream->id),
nghttp2_session_get_stream_effective_local_window_size(
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,
const void *buf, size_t len, CURLcode *err)
{
struct cf_h2_ctx *ctx = cf->ctx;
- struct stream_ctx *stream = NULL;
+ struct h2_stream_ctx *stream = NULL;
struct dynhds h2_headers;
nghttp2_nv *nva = NULL;
const void *body = NULL;
const void *buf, size_t len, CURLcode *err)
{
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;
int rv;
ssize_t nwritten;
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 h2_stream_ctx *stream = H2_STREAM_CTX(data);
struct cf_call_data save;
bool c_exhaust, s_exhaust;
goto out;
}
- result = h2_progress_ingress(cf, data);
+ result = h2_progress_ingress(cf, data, H2_CHUNK_SIZE);
if(result)
goto out;
{
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
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);
if(ctx && ctx->h2 && stream) {
const struct Curl_easy *data)
{
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)
- || (stream && !Curl_bufq_is_empty(&stream->sendbuf))
- || (stream && !Curl_bufq_is_empty(&stream->recvbuf))))
+ || (stream && !Curl_bufq_is_empty(&stream->sendbuf))))
return TRUE;
return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
}
static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- int sockindex)
+ int sockindex,
+ bool via_h1_upgrade)
{
struct Curl_cfilter *cf = NULL;
struct cf_h2_ctx *ctx;
if(result)
goto out;
+ ctx = NULL;
Curl_conn_cf_add(data, conn, sockindex, cf);
- result = CURLE_OK;
+ result = cf_h2_ctx_init(cf, data, via_h1_upgrade);
out:
if(result)
}
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 cf_h2_ctx *ctx;
if(result)
goto out;
+ ctx = NULL;
Curl_conn_cf_insert_after(cf, cf_h2);
- result = CURLE_OK;
+ result = cf_h2_ctx_init(cf_h2, data, via_h1_upgrade);
out:
if(result)
DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
DEBUGF(infof(data, "switching to HTTP/2"));
- result = http2_cfilter_add(&cf, data, conn, sockindex);
- if(result)
- return result;
-
- result = cf_h2_ctx_init(cf, data, FALSE);
+ result = http2_cfilter_add(&cf, data, conn, sockindex, FALSE);
if(result)
return result;
DEBUGASSERT(!Curl_cf_is_http2(cf, data));
- result = http2_cfilter_insert_after(cf, data);
+ result = http2_cfilter_insert_after(cf, data, FALSE);
if(result)
return result;
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->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
DEBUGF(infof(data, "upgrading to HTTP/2"));
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)
return result;
DEBUGASSERT(cf->cft == &Curl_cft_nghttp2);
ctx = cf->ctx;
- result = cf_h2_ctx_init(cf, data, TRUE);
- if(result)
- return result;
-
if(nread > 0) {
/* Remaining data from the protocol switch reply is already using
* the switched protocol, ie. HTTP/2. We add that to the network
CURLE_HTTP2_STREAM error! */
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);
}