net_http: a streamed body reaches the sink in pieces of 32 KB or more

A body streamed to a sink went to it after every read, so the sink
was called once per TLS record, or once per few hundred bytes on a
slow link.  A body framed by Content-Length or by the close now stays
in the receive buffer, where it was read, until the window left
behind it would fall below the 32 KB floor; the sink then gets it in
one piece.  What is still held when the body ends goes with it, at
the last byte of a Content-Length body or at the close.

Nothing is copied to hold it and the buffer does not grow for it: the
hand-off comes before the window would shrink enough to trigger a
grow.  Progress already counts the held bytes (flushed + pos).  A
transfer that breaks loses at most what was held, which a resume asks
for again from the length on disk.

Chunked bodies are unchanged for now: they still go to the sink after
every read.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-11 06:41:49 +01:00
1 parent a5668b1a9a
commit ac3ca0bc44
1 file changed
+24 -4
+24 -4
View File
@@ -2244,6 +2244,17 @@ static bool net_http_body_streams(const struct http_t *state)
return state->sink || state->response.discard;
}
/* A streamed body of known framing (Content-Length or close) stays in
* the receive buffer, where it was read, until the window left behind
* it would fall below the floor: the sink then gets it in one piece of
* 32 KB or more instead of one piece per read, which over TLS or a
* slow link is a few hundred bytes to one record at a time. Nothing
* is copied to hold it, and the buffer never grows for it. */
static bool net_http_sink_due(const struct response *response)
{
return response->buflen - response->pos < NET_HTTP_MIN_RECV_WINDOW;
}
static ssize_t net_http_receive_header(struct http_t *state, ssize_t len)
{
struct response *response = (struct response*)&state->response;
@@ -2606,6 +2617,14 @@ static bool net_http_receive_body(struct http_t *state, ssize_t newlen)
{
if (response->bodytype != T_FULL)
return false;
/* What is still held of a streamed body goes to the sink first */
if (net_http_body_streams(state) && response->pos)
{
if (!net_http_sink_flush(state, response->pos))
return false;
response->pos = 0;
response->len = 0;
}
response->part = P_DONE;
/* The close that ended the body ended the connection too. */
response->conn_close = true;
@@ -2856,7 +2875,7 @@ static bool net_http_receive_body(struct http_t *state, ssize_t newlen)
* function, which sets P_DONE and shrinks the buffer. */
response->pos += newlen;
response->len = response->pos;
if (net_http_body_streams(state))
if (net_http_body_streams(state) && net_http_sink_due(response))
{
if (!net_http_sink_flush(state, response->pos))
return false;
@@ -2901,12 +2920,13 @@ static bool net_http_receive_body(struct http_t *state, ssize_t newlen)
}
return true;
}
if (net_http_body_streams(state))
if (net_http_body_streams(state) && net_http_sink_due(response))
{
/* T_LEN's `len` is the outstanding count once streaming, so
* decrement it by what we hand off; the "pos == len"
* completion test above keeps working unchanged. The
* advertised total lives in content_len for progress. */
* completion test above keeps working unchanged, held bytes
* included. The advertised total lives in content_len for
* progress. */
if (!net_http_sink_flush(state, response->pos))
return false;
response->len -= response->pos;