PR #23599 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23599 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23599.patch
Split off from #23334 >From 748feef553111f2fe33fd5c6f2f97666494e8f87 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 4 Jun 2026 13:18:33 +0200 Subject: [PATCH 1/4] avformat/http: organize HTTP context fields into sections (cosmetic) This is a best-effort attempt at re-organizing these fields a bit to make it more clear what's used where and why. Sponsored-by: nxtedition AB Signed-off-by: Niklas Haas <[email protected]> --- libavformat/http.c | 113 +++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index 3d0656d046..7f7e69c886 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -71,18 +71,13 @@ typedef enum { typedef struct HTTPContext { const AVClass *class; - URLContext *hd; unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end; - int line_count; - int http_code; - /* Used if "Transfer-Encoding: chunked" otherwise -1. */ - uint64_t chunksize; - int chunkend; - uint64_t off, end_off, filesize, range_end; - char *uri; + + /************************* + * Configuration options * + *************************/ + uint64_t off, end_off; /* `off` is also mutated by seeking / reading */ char *location; - HTTPAuthState auth_state; - HTTPAuthState proxy_auth_state; char *http_proxy; char *headers; char *mime_type; @@ -90,38 +85,16 @@ typedef struct HTTPContext { char *user_agent; char *referer; char *content_type; - /* Set if the server correctly handles Connection: close and will close - * the connection after feeding us the content. */ - int willclose; int seekable; /**< Control seekability, 0 = disable, 1 = enable, -1 = probe. */ int chunked_post; - /* A flag which indicates if the end of chunked encoding has been sent. */ - int end_chunked_post; - /* A flag which indicates we have finished to read POST reply. */ - int end_header; - /* A flag which indicates if we use persistent connections. */ - int multiple_requests; + int multiple_requests; /**< A flag which indicates if we use persistent connections. */ uint8_t *post_data; int post_datalen; - int is_akamai; - int is_mediagateway; char *cookies; ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name) - /* A dictionary containing cookies keyed by cookie name */ - AVDictionary *cookie_dict; int icy; - /* how much data was read since the last ICY metadata packet */ - uint64_t icy_data_read; - /* after how many bytes of read data a new metadata packet will be found */ - uint64_t icy_metaint; char *icy_metadata_headers; char *icy_metadata_packet; AVDictionary *metadata; -#if CONFIG_ZLIB - int compressed; - z_stream inflate_stream; - uint8_t *inflate_buffer; -#endif /* CONFIG_ZLIB */ - AVDictionary *chained_options; /* -1 = try to send if applicable, 0 = always disabled, 1 = always enabled */ int send_expect_100; char *method; @@ -129,26 +102,32 @@ typedef struct HTTPContext { int reconnect_at_eof; int reconnect_on_network_error; int reconnect_streamed; + int reconnect_max_retries; int reconnect_delay_max; + int reconnect_delay_total_max; char *reconnect_on_http_error; int listen; char *resource; int reply_code; - int is_multi_client; - HandshakeState handshake_step; - int is_connected_server; int short_seek_size; - int64_t expires; - char *new_location; - AVDictionary *redirect_cache; - uint64_t filesize_from_content_range; + int max_redirects; int respect_retry_after; - unsigned int retry_after; - int reconnect_max_retries; - int reconnect_delay_total_max; - uint64_t initial_request_size; uint64_t request_size; - int initial_requests; /* whether or not to limit requests to initial_request_size */ + uint64_t initial_request_size; + + /********************** + * Context-wide state * + **********************/ + HTTPAuthState auth_state; /* auth_state.auth_type is also a config option */ + HTTPAuthState proxy_auth_state; + uint64_t filesize; + int is_akamai; + int is_mediagateway; + /* A dictionary containing cookies keyed by cookie name */ + AVDictionary *cookie_dict; + AVDictionary *chained_options; + AVDictionary *redirect_cache; + /* Connection statistics */ int nb_connections; int nb_requests; @@ -157,7 +136,49 @@ typedef struct HTTPContext { int nb_redirects; int64_t sum_latency; /* divide by nb_requests */ int64_t max_latency; - int max_redirects; + + /************************ + * Per-connection state * + ************************/ + URLContext *hd; + char *uri; + char *new_location; + int http_code; + int64_t expires; + /* Used if "Transfer-Encoding: chunked" otherwise -1. */ + uint64_t chunksize; + int chunkend; + uint64_t range_end; + /* Set if the server correctly handles Connection: close and will close + * the connection after feeding us the content. */ + int willclose; + /* A flag which indicates if the end of chunked encoding has been sent. */ + int end_chunked_post; + /* A flag which indicates we have finished to read POST reply. */ + int end_header; + /* how much data was read since the last ICY metadata packet */ + uint64_t icy_data_read; + /* after how many bytes of read data a new metadata packet will be found */ + uint64_t icy_metaint; +#if CONFIG_ZLIB + int compressed; + z_stream inflate_stream; + uint8_t *inflate_buffer; +#endif /* CONFIG_ZLIB */ + unsigned int retry_after; + int initial_requests; /* whether or not to limit requests to initial_request_size */ + + /* Temporary during header parsing */ + uint64_t filesize_from_content_range; + int line_count; + + /****************** + * Listener state * + ******************/ + /* URLContext *hd; */ + HandshakeState handshake_step; + int is_multi_client; + int is_connected_server; } HTTPContext; #define OFFSET(x) offsetof(HTTPContext, x) -- 2.52.0 >From 2a0234d280868e9638edff1f1058aa755f4e237b Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 4 Jun 2026 17:02:18 +0200 Subject: [PATCH 2/4] avformat/http: split request size determination to separate function Sponsored-by: nxtedition AB Signed-off-by: Niklas Haas <[email protected]> --- libavformat/http.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index 7f7e69c886..2d1d0ff18b 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -1546,6 +1546,14 @@ static void bprint_escaped_path(AVBPrint *bp, const char *path) } } +static uint64_t request_size(URLContext *h) +{ + HTTPContext *s = h->priv_data; + if (s->initial_requests) + return s->initial_request_size; + return s->request_size; +} + static int http_connect(URLContext *h, const char *path, const char *local_path, const char *hoststr, const char *auth, const char *proxyauth) @@ -1617,8 +1625,8 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, // server supports seeking by analysing the reply headers. if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable != 0)) { av_bprintf(&request, "Range: bytes=%"PRIu64"-", s->off); - if ((s->initial_requests || s->request_size) && s->seekable != 0) { - uint64_t req_size = s->initial_requests ? s->initial_request_size : s->request_size; + uint64_t req_size = request_size(h); + if (req_size && s->seekable != 0) { uint64_t target_off = s->off + req_size; if (target_off < s->off) /* overflow */ target_off = UINT64_MAX; -- 2.52.0 >From 60c8826e5f4570d07b2acc54bce5f09713298901 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 4 Jun 2026 14:41:13 +0200 Subject: [PATCH 3/4] avformat/http: make reconnection message less misleading If using a keepalive connection, this does not actually reconnect. Sponsored-by: nxtedition AB Signed-off-by: Niklas Haas <[email protected]> --- libavformat/http.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libavformat/http.c b/libavformat/http.c index 2d1d0ff18b..01efdd5f17 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -470,7 +470,8 @@ redo: s->nb_retries++; } - av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s).\n", off, reconnect_delay); + av_log(h, AV_LOG_WARNING, "Will %s at %"PRIu64" in %d second(s).\n", + s->willclose ? "reconnect" : "retry", off, reconnect_delay); ret = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, &h->interrupt_callback); if (ret != AVERROR(ETIMEDOUT)) goto fail; @@ -1911,7 +1912,8 @@ retry: reconnect_delay_total > s->reconnect_delay_total_max) return AVERROR(EIO); - av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s), error=%s.\n", s->off, reconnect_delay, av_err2str(read_ret)); + av_log(h, AV_LOG_WARNING, "Will %s at %"PRIu64" in %d second(s), error=%s.\n", s->willclose ? "reconnect" : "retry", + s->off, reconnect_delay, av_err2str(read_ret)); err = ff_network_sleep_interruptible(1000U*1000*reconnect_delay, &h->interrupt_callback); if (err != AVERROR(ETIMEDOUT)) return err; -- 2.52.0 >From 4336cb648e28845989c6ffea63d66af81142b1ef Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 4 Jun 2026 16:12:34 +0200 Subject: [PATCH 4/4] avformat/http: imply keep-alive by default when beneficial By turning it into a tristate that defaults to -1 (auto). Users can still force a particular value for debugging. Impactful for the following commit. Sponsored-by: nxtedition AB Signed-off-by: Niklas Haas <[email protected]> --- doc/protocols.texi | 7 ++++--- libavformat/http.c | 15 +++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/doc/protocols.texi b/doc/protocols.texi index 368ae005f8..71ebd726d0 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -453,15 +453,16 @@ string describing the libavformat build. ("Lavf/<version>") Set the Referer header. Include 'Referer: URL' header in HTTP request. @item multiple_requests -Use persistent connections if set to 1, default is 0. +Force persistent connections if set to 1, or disable if 0. Default is -1, +which means auto (implies keep-alive when using -multiple_requests or +encountering partial files). @item request_size Limit the size of requests made. This is useful for some pathological servers that throttle unbounded range requests, as well as when expecting to seek frequently. Disabled (set to 0) by default. -Note that if enabling this option, it's strongly recommended to also enable -the @option{multiple_requests} option, as well as setting +Note that if enabling this option, it's strongly recommended to also set @option{short_seek_size} to the same value or higher. Doing so allows FFmpeg to reuse a single HTTP connection wherever possible. diff --git a/libavformat/http.c b/libavformat/http.c index 01efdd5f17..095f76da1e 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -194,7 +194,7 @@ static const AVOption http_options[] = { { "content_type", "set a specific content type for the POST messages", OFFSET(content_type), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "user_agent", "override User-Agent header", OFFSET(user_agent), AV_OPT_TYPE_STRING, { .str = DEFAULT_USER_AGENT }, 0, 0, D }, { "referer", "override referer header", OFFSET(referer), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D }, - { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D | E }, + { "multiple_requests", "use persistent connections", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, D | E }, { "request_size", "size (in bytes) of requests to make", OFFSET(request_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "initial_request_size", "size (in bytes) of initial requests made during probing / header parsing", OFFSET(initial_request_size), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, D }, { "post_data", "set custom HTTP post data", OFFSET(post_data), AV_OPT_TYPE_BINARY, .flags = D | E }, @@ -1624,6 +1624,7 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, // Note: we send the Range header on purpose, even when we're probing, // since it allows us to detect more reliably if a (non-conforming) // server supports seeking by analysing the reply headers. + int is_partial_request = 0; if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable != 0)) { av_bprintf(&request, "Range: bytes=%"PRIu64"-", s->off); uint64_t req_size = request_size(h); @@ -1633,8 +1634,10 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, target_off = UINT64_MAX; if (s->end_off) target_off = FFMIN(target_off, s->end_off); - if (target_off != UINT64_MAX) + if (target_off != UINT64_MAX) { av_bprintf(&request, "%"PRId64, target_off - 1); + is_partial_request = 1; + } } else if (s->end_off) av_bprintf(&request, "%"PRId64, s->end_off - 1); av_bprintf(&request, "\r\n"); @@ -1642,8 +1645,12 @@ static int http_connect(URLContext *h, const char *path, const char *local_path, if (send_expect_100 && !has_header(s->headers, "\r\nExpect: ")) av_bprintf(&request, "Expect: 100-continue\r\n"); - if (!has_header(s->headers, "\r\nConnection: ")) - av_bprintf(&request, "Connection: %s\r\n", s->multiple_requests ? "keep-alive" : "close"); + if (!has_header(s->headers, "\r\nConnection: ")) { + int keep_alive = s->multiple_requests > 0; + if (s->multiple_requests < 0 /* auto */ && is_partial_request) + keep_alive = 1; + av_bprintf(&request, "Connection: %s\r\n", keep_alive ? "keep-alive" : "close"); + } if (!has_header(s->headers, "\r\nHost: ")) av_bprintf(&request, "Host: %s\r\n", hoststr); -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
