From fde5593c7ce5288ae1333e9dc3ca4f95caa69444 Mon Sep 17 00:00:00 2001 From: erankor Date: Thu, 3 Dec 2020 10:42:52 +0200 Subject: [PATCH] avformat/http: support retry on connection error Add 2 new options: - reconnect_on_http_error - a list of http status codes that should be retried. the list can contain explicit status codes / the strings 4xx/5xx. - reconnect_on_network_error - reconnects on arbitrary errors during connect, e.g. ECONNRESET/ETIMEDOUT the retry employs the same exponential backoff logic as the existing reconnect/reconnect_at_eof flags. related tickets: https://trac.ffmpeg.org/ticket/6066 https://trac.ffmpeg.org/ticket/7768 Signed-off-by: Marton Balint --- doc/protocols.texi | 7 +++++ libavformat/http.c | 60 +++++++++++++++++++++++++++++++++++++++++-- libavformat/version.h | 2 +- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/doc/protocols.texi b/doc/protocols.texi index 00a66654827..af887761e91 100644 --- a/doc/protocols.texi +++ b/doc/protocols.texi @@ -410,6 +410,13 @@ for live / endless streams. @item reconnect_streamed If set then even streamed/non seekable streams will be reconnected on errors. +@item reconnect_on_network_error +Reconnect automatically in case of TCP/TLS errors during connect. + +@item reconnect_on_http_error +A comma separated list of HTTP status codes to reconnect on. The list can +include specific status codes (e.g. '503') or the strings '4xx' / '5xx'. + @item reconnect_delay_max Sets the maximum delay in seconds after which to give up reconnecting diff --git a/libavformat/http.c b/libavformat/http.c index 2d24c00e183..76f0a67c61e 100644 --- a/libavformat/http.c +++ b/libavformat/http.c @@ -119,8 +119,10 @@ typedef struct HTTPContext { char *method; int reconnect; int reconnect_at_eof; + int reconnect_on_network_error; int reconnect_streamed; int reconnect_delay_max; + char *reconnect_on_http_error; int listen; char *resource; int reply_code; @@ -164,6 +166,8 @@ static const AVOption options[] = { { "method", "Override the HTTP method or set the expected HTTP method from a client", OFFSET(method), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, { "reconnect", "auto reconnect after disconnect before EOF", OFFSET(reconnect), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_at_eof", "auto reconnect at EOF", OFFSET(reconnect_at_eof), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, + { "reconnect_on_network_error", "auto reconnect in case of tcp/tls error during connect", OFFSET(reconnect_on_network_error), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, + { "reconnect_on_http_error", "list of http status codes to reconnect on", OFFSET(reconnect_on_http_error), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D }, { "reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, { "reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 120 }, 0, UINT_MAX/1000/1000, D }, { "listen", "listen on HTTP", OFFSET(listen), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D | E }, @@ -258,21 +262,73 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options) return location_changed; } +static int http_should_reconnect(HTTPContext *s, int err) +{ + const char *status_group; + char http_code[4]; + + switch (err) { + case AVERROR_HTTP_BAD_REQUEST: + case AVERROR_HTTP_UNAUTHORIZED: + case AVERROR_HTTP_FORBIDDEN: + case AVERROR_HTTP_NOT_FOUND: + case AVERROR_HTTP_OTHER_4XX: + status_group = "4xx"; + break; + + case AVERROR_HTTP_SERVER_ERROR: + status_group = "5xx"; + break; + + default: + return s->reconnect_on_network_error; + } + + if (!s->reconnect_on_http_error) + return 0; + + if (av_match_list(status_group, s->reconnect_on_http_error, ',') > 0) + return 1; + + snprintf(http_code, sizeof(http_code), "%d", s->http_code); + + return av_match_list(http_code, s->reconnect_on_http_error, ',') > 0; +} + /* return non zero if error */ static int http_open_cnx(URLContext *h, AVDictionary **options) { HTTPAuthType cur_auth_type, cur_proxy_auth_type; HTTPContext *s = h->priv_data; int location_changed, attempts = 0, redirects = 0; + int reconnect_delay = 0; + uint64_t off; + redo: av_dict_copy(options, s->chained_options, 0); cur_auth_type = s->auth_state.auth_type; cur_proxy_auth_type = s->auth_state.auth_type; + off = s->off; location_changed = http_open_cnx_internal(h, options); - if (location_changed < 0) - goto fail; + if (location_changed < 0) { + if (!http_should_reconnect(s, location_changed) || + reconnect_delay > s->reconnect_delay_max) + goto fail; + + av_log(h, AV_LOG_WARNING, "Will reconnect at %"PRIu64" in %d second(s).\n", off, reconnect_delay); + location_changed = ff_network_sleep_interruptible(1000U * 1000 * reconnect_delay, &h->interrupt_callback); + if (location_changed != AVERROR(ETIMEDOUT)) + goto fail; + reconnect_delay = 1 + 2 * reconnect_delay; + + /* restore the offset (http_connect resets it) */ + s->off = off; + + ffurl_closep(&s->hd); + goto redo; + } attempts++; if (s->http_code == 401) { diff --git a/libavformat/version.h b/libavformat/version.h index b43193bcb17..a2b5901e74e 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -33,7 +33,7 @@ // Also please add any ticket numbers that you believe might be affected here #define LIBAVFORMAT_VERSION_MAJOR 58 #define LIBAVFORMAT_VERSION_MINOR 65 -#define LIBAVFORMAT_VERSION_MICRO 100 +#define LIBAVFORMAT_VERSION_MICRO 101 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \ -- 2.39.2