mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-06 10:57:50 +08:00
avoid reusing non-persistent connections during 302 redirects (#4690)
Some checks failed
Android / build (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Docker / build (push) Has been cancelled
Linux / build (push) Has been cancelled
Linux_Python / build (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS_Python / build (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows_Python / build (push) Has been cancelled
Some checks failed
Android / build (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Docker / build (push) Has been cancelled
Linux / build (push) Has been cancelled
Linux_Python / build (push) Has been cancelled
macOS / build (push) Has been cancelled
macOS_Python / build (push) Has been cancelled
Windows / build (push) Has been cancelled
Windows_Python / build (push) Has been cancelled
When performing a 302 redirect, if the server explicitly indicates that the connection is not a persistent connection, the current connection should not be reused; this prevents potential failures caused by certain servers actively closing the connection. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
899d9653a4
commit
4daa30e229
@ -22,6 +22,8 @@ HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller) {
|
||||
void HlsPlayer::play(const string &url) {
|
||||
_play_result = false;
|
||||
_play_url = url;
|
||||
_last_sequence = -1;
|
||||
_playlist_reload_changed = true;
|
||||
setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
setAllowResendRequest(true);
|
||||
fetchIndexFile();
|
||||
@ -202,10 +204,12 @@ void HlsPlayer::fetchSegment() {
|
||||
|
||||
bool HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts_segment> &ts_map) {
|
||||
if (!is_m3u8_inner) {
|
||||
auto playlist_changed = _last_sequence != sequence;
|
||||
_playlist_reload_changed = playlist_changed;
|
||||
// 这是ts播放列表 [AUTO-TRANSLATED:7ce3d81b]
|
||||
// This is the ts playlist
|
||||
// This is the ts playlist
|
||||
if (_last_sequence == sequence) {
|
||||
if (!playlist_changed) {
|
||||
// 如果是重复的ts列表,那么忽略 [AUTO-TRANSLATED:d15a47f3]
|
||||
// If it is a duplicate ts list, then ignore it
|
||||
// 但是需要注意, 如果当前ts列表为空了, 那么表明直播结束了或者m3u8文件有问题,需要重新拉流 [AUTO-TRANSLATED:438a8df0]
|
||||
@ -312,27 +316,23 @@ float HlsPlayer::delaySecond() {
|
||||
if (HlsParser::isM3u8() && HlsParser::getTargetDur() > 0) {
|
||||
float targetOffset;
|
||||
if (HlsParser::isLive()) {
|
||||
// see RFC 8216, Section 4.4.3.8.
|
||||
// 根据rfc刷新index列表的周期应该是分段时间x3, 因为根据规范播放器只处理最后3个Segment [AUTO-TRANSLATED:07168708]
|
||||
// According to the rfc, the refresh cycle of the index list should be 3 times the segment time, because according to the specification, the player only processes the last 3 Segments
|
||||
// refresh the index list according to rfc cycle should be the segment time x3,
|
||||
// because according to the specification, the player only handles the last 3 segments
|
||||
targetOffset = (float)(3 * HlsParser::getTargetDur());
|
||||
// RFC 8216 Section 6.3.4:
|
||||
// after a changed playlist reload, wait at least one target
|
||||
// duration; after an unchanged reload, wait half a target duration.
|
||||
return _playlist_reload_changed ? (float) HlsParser::getTargetDur()
|
||||
: (float) HlsParser::getTargetDur() / 2.0f;
|
||||
} else {
|
||||
// 点播则一般m3u8文件不会在改变了, 没必要频繁的刷新, 所以按照总时间来进行刷新 [AUTO-TRANSLATED:2ac0a29e]
|
||||
// On-demand generally does not change the m3u8 file, there is no need to refresh frequently, so refresh according to the total time
|
||||
// On-demand, the m3u8 file will generally not change, so there is no need to refresh frequently,
|
||||
targetOffset = HlsParser::getTotalDuration();
|
||||
}
|
||||
// 取最小值, 避免因为分段时长不规则而导致的问题 [AUTO-TRANSLATED:073dff48]
|
||||
// Take the minimum value to avoid problems caused by irregular segment durations
|
||||
// Take the minimum value to avoid problems caused by irregular segment duration
|
||||
if (targetOffset > HlsParser::getTotalDuration()) {
|
||||
targetOffset = HlsParser::getTotalDuration();
|
||||
}
|
||||
// 根据规范为一半的时间 [AUTO-TRANSLATED:07652637]
|
||||
// According to the specification, it is half the time
|
||||
// According to the specification, it is half the time
|
||||
if (targetOffset / 2 > 1.0f) {
|
||||
return targetOffset / 2;
|
||||
}
|
||||
|
||||
@ -130,6 +130,10 @@ private:
|
||||
int _timeout_multiple = MIN_TIMEOUT_MULTIPLE;
|
||||
int _try_fetch_index_times = 0;
|
||||
int _ts_download_failed_count = 0;
|
||||
// RFC 8216 reload interval depends on whether the last media sequence
|
||||
// changed. We intentionally keep this lightweight and only track the
|
||||
// sequence number for live playlist refresh timing.
|
||||
bool _playlist_reload_changed = true;
|
||||
|
||||
protected:
|
||||
size_t _recvtotalbytes = 0;
|
||||
|
||||
@ -18,6 +18,15 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
static bool connectionContainsClose(const string &connection) {
|
||||
for (auto token : split(connection, ",")) {
|
||||
if (strToLower(trim(std::move(token))) == "close") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HttpClient::sendRequest(const string &url) {
|
||||
clearResponse();
|
||||
_url = url;
|
||||
@ -56,26 +65,24 @@ void HttpClient::sendRequest(const string &url) {
|
||||
}
|
||||
auto host_header = host;
|
||||
splitUrl(host, host, port);
|
||||
auto keep_alive = _request_keep_alive;
|
||||
auto persistent = _http_persistent && keep_alive;
|
||||
bool protocol_changed = (_is_https != is_https);
|
||||
bool host_changed = (_last_host != host + ":" + to_string(port)) || protocol_changed;
|
||||
_last_host = host + ":" + to_string(port);
|
||||
_is_https = is_https;
|
||||
|
||||
_header.emplace("Host", host_header);
|
||||
_header.emplace("User-Agent", kServerName);
|
||||
_header.emplace("Accept", "*/*");
|
||||
_header.emplace("Accept-Language", "zh-CN,zh;q=0.8");
|
||||
if (_http_persistent) {
|
||||
_header.emplace("Connection", "keep-alive");
|
||||
} else {
|
||||
_header.emplace("Connection", "close");
|
||||
}
|
||||
_http_persistent = true;
|
||||
_header.emplace("Connection", keep_alive ? "keep-alive" : "close");
|
||||
if (_body && _body->remainSize()) {
|
||||
_header.emplace("Content-Length", to_string(_body->remainSize()));
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
_header.emplace("Content-Type", "application/x-www-form-urlencoded; charset=" + charSet);
|
||||
}
|
||||
|
||||
bool host_changed = (_last_host != host + ":" + to_string(port)) || (_is_https != is_https);
|
||||
_last_host = host + ":" + to_string(port);
|
||||
_is_https = is_https;
|
||||
|
||||
auto cookies = HttpCookieStorage::Instance().get(_last_host, _path);
|
||||
_StrPrinter printer;
|
||||
for (auto &cookie : cookies) {
|
||||
@ -85,13 +92,24 @@ void HttpClient::sendRequest(const string &url) {
|
||||
printer.pop_back();
|
||||
_header.emplace("Cookie", printer);
|
||||
}
|
||||
if (!alive() || host_changed || !_http_persistent) {
|
||||
if (isUsedProxy()) {
|
||||
if (isUsedProxy()) {
|
||||
// All proxy traffic uses CONNECT, so reuse is limited to the same tunnel target.
|
||||
bool proxy_reuse = alive() && persistent && !host_changed && _proxy_connected;
|
||||
|
||||
if (!proxy_reuse) {
|
||||
_http_persistent = keep_alive;
|
||||
_proxy_connected = false;
|
||||
startConnect(_proxy_host, _proxy_port, _wait_header_ms / 1000.0f);
|
||||
startConnectWithProxy(host, _proxy_host, _proxy_port, _wait_header_ms / 1000.0f);
|
||||
} else {
|
||||
startConnect(host, port, _wait_header_ms / 1000.0f);
|
||||
SockException ex;
|
||||
onConnect_l(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alive() || host_changed || !persistent) {
|
||||
_http_persistent = keep_alive;
|
||||
startConnect(host, port, _wait_header_ms / 1000.0f);
|
||||
} else {
|
||||
SockException ex;
|
||||
onConnect_l(ex);
|
||||
@ -103,8 +121,9 @@ void HttpClient::clear() {
|
||||
_user_set_header.clear();
|
||||
_body.reset();
|
||||
_method.clear();
|
||||
// 重置代理连接状态
|
||||
_proxy_connected = false;
|
||||
_request_keep_alive = true;
|
||||
// Keep transport-level state so a live direct/proxy connection can still
|
||||
// be reused after the caller resets only the per-request state.
|
||||
clearResponse();
|
||||
}
|
||||
|
||||
@ -202,9 +221,8 @@ void HttpClient::onRecv(const Buffer::Ptr &pBuf) {
|
||||
|
||||
void HttpClient::onError(const SockException &ex) {
|
||||
if (ex.getErrCode() == Err_reset && _allow_resend_request && _http_persistent && _recved_body_size == 0 && !_header_recved) {
|
||||
// 连接被重置,可能是服务器主动断开了连接, 或者服务器内核参数或防火墙的持久连接空闲时间超时或不一致. [AUTO-TRANSLATED:8a78f452]
|
||||
// The connection was reset, possibly because the server actively closed the connection, or the server kernel parameters or firewall's persistent connection idle timeout or inconsistency.
|
||||
// 如果是持久化连接,那么我们可以通过重连来解决这个问题 [AUTO-TRANSLATED:6c113e17]
|
||||
// 连接被重置,可能是服务器主动断开了连接, 或者服务器内核参数或防火墙的持久连接空闲时间超时或不一致.
|
||||
// 如果是持久化连接,那么我们可以通过重连来解决这个问题
|
||||
// If it is a persistent connection, we can solve this problem by reconnecting
|
||||
// The connection was reset, possibly because the server actively disconnected the connection,
|
||||
// or the persistent connection idle time of the server kernel parameters or firewall timed out or inconsistent.
|
||||
@ -219,12 +237,17 @@ void HttpClient::onError(const SockException &ex) {
|
||||
|
||||
ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
|
||||
_parser.parse(data, len);
|
||||
if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303") {
|
||||
auto connection_close = connectionContainsClose(_parser["Connection"]);
|
||||
if (connection_close) {
|
||||
_http_persistent = false;
|
||||
}
|
||||
if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303" || _parser.status() == "307") {
|
||||
auto new_url = Parser::mergeUrl(_url, _parser["Location"]);
|
||||
if (new_url.empty()) {
|
||||
throw invalid_argument("未找到Location字段(跳转url)");
|
||||
}
|
||||
if (onRedirectUrl(new_url, _parser.status() == "302")) {
|
||||
bool temporary_redirect = _parser.status() == "302" || _parser.status() == "307";
|
||||
if (onRedirectUrl(new_url, temporary_redirect)) {
|
||||
HttpClient::sendRequest(new_url);
|
||||
return 0;
|
||||
}
|
||||
@ -467,6 +490,13 @@ void HttpClient::setCompleteTimeout(size_t timeout_ms) {
|
||||
_wait_complete_ms = timeout_ms;
|
||||
}
|
||||
|
||||
void HttpClient::setRequestKeepAlive(bool enable) {
|
||||
_request_keep_alive = enable;
|
||||
if (!enable) {
|
||||
_http_persistent = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpClient::isUsedProxy() const {
|
||||
return _used_proxy;
|
||||
}
|
||||
@ -476,12 +506,34 @@ bool HttpClient::isProxyConnected() const {
|
||||
}
|
||||
|
||||
void HttpClient::setProxyUrl(string proxy_url) {
|
||||
auto old_used_proxy = _used_proxy;
|
||||
auto old_proxy_host = _proxy_host;
|
||||
auto old_proxy_port = _proxy_port;
|
||||
auto old_proxy_auth = _proxy_auth;
|
||||
|
||||
_proxy_url = std::move(proxy_url);
|
||||
if (!_proxy_url.empty()) {
|
||||
_proxy_host.clear();
|
||||
_proxy_port = 0;
|
||||
_proxy_auth.clear();
|
||||
parseProxyUrl(_proxy_url, _proxy_host, _proxy_port, _proxy_auth);
|
||||
_used_proxy = true;
|
||||
} else {
|
||||
_used_proxy = false;
|
||||
_proxy_host.clear();
|
||||
_proxy_port = 0;
|
||||
_proxy_auth.clear();
|
||||
_proxy_connected = false;
|
||||
}
|
||||
|
||||
auto proxy_config_changed = old_used_proxy != _used_proxy
|
||||
|| old_proxy_host != _proxy_host
|
||||
|| old_proxy_port != _proxy_port
|
||||
|| old_proxy_auth != _proxy_auth;
|
||||
if (proxy_config_changed) {
|
||||
// A proxy mode or endpoint change must not reuse the previous transport.
|
||||
_http_persistent = false;
|
||||
_proxy_connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,6 +545,10 @@ bool HttpClient::checkProxyConnected(const char *data, size_t len) {
|
||||
}
|
||||
|
||||
_proxy_connected = false;
|
||||
// CONNECT failed, which usually means the proxy rejected the tunnel request,
|
||||
// does not support CONNECT for this target, or the proxy authentication is invalid.
|
||||
WarnL << "proxy CONNECT failed, status line: "
|
||||
<< response.substr(0, response.find("\r\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -60,8 +60,8 @@ public:
|
||||
virtual void sendRequest(const std::string &url);
|
||||
|
||||
/**
|
||||
* 重置对象
|
||||
* Reset object
|
||||
* 重置当前请求相关状态,并尽量保留可复用的传输状态
|
||||
* Reset per-request state while preserving reusable transport state when possible
|
||||
|
||||
* [AUTO-TRANSLATED:d23b5bbb]
|
||||
*/
|
||||
@ -168,10 +168,10 @@ public:
|
||||
void setHeaderTimeout(size_t timeout_ms);
|
||||
|
||||
/**
|
||||
* 设置接收body数据超时时间, 默认5秒
|
||||
* 设置接收body数据超时时间, 默认10秒
|
||||
* 此参数可以用于处理超大body回复的超时问题
|
||||
* 此参数可以等于0
|
||||
* Set the timeout for receiving body data, default 5 seconds
|
||||
* Set the timeout for receiving body data, default 10 seconds
|
||||
* This parameter can be used to handle timeout issues for large body responses
|
||||
* This parameter can be equal to 0
|
||||
|
||||
@ -189,6 +189,14 @@ public:
|
||||
*/
|
||||
void setCompleteTimeout(size_t timeout_ms);
|
||||
|
||||
/**
|
||||
* 设置请求头中的 keep-alive 语义,默认启用
|
||||
* Set whether requests should advertise keep-alive semantics, enabled by default
|
||||
|
||||
* [AUTO-TRANSLATED:6f62f63c]
|
||||
*/
|
||||
void setRequestKeepAlive(bool enable);
|
||||
|
||||
/**
|
||||
* 设置http代理url
|
||||
* Set http proxy url
|
||||
@ -276,7 +284,6 @@ private:
|
||||
void onResponseCompleted_l(const toolkit::SockException &ex);
|
||||
void onConnect_l(const toolkit::SockException &ex);
|
||||
void checkCookie(HttpHeader &headers);
|
||||
|
||||
private:
|
||||
//for http response
|
||||
bool _complete = false;
|
||||
@ -289,7 +296,7 @@ private:
|
||||
std::shared_ptr<HttpChunkedSplitter> _chunked_splitter;
|
||||
|
||||
//for request args
|
||||
bool _is_https;
|
||||
bool _is_https = false;
|
||||
std::string _url;
|
||||
HttpHeader _user_set_header;
|
||||
HttpBody::Ptr _body;
|
||||
@ -308,9 +315,10 @@ private:
|
||||
toolkit::Ticker _wait_body;
|
||||
toolkit::Ticker _wait_complete;
|
||||
|
||||
bool _request_keep_alive = true;
|
||||
bool _used_proxy = false;
|
||||
bool _proxy_connected = false;
|
||||
uint16_t _proxy_port;
|
||||
uint16_t _proxy_port = 0;
|
||||
std::string _proxy_url;
|
||||
std::string _proxy_host;
|
||||
std::string _proxy_auth;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user