mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-06-15 04:25:58 +08:00
AI automatically translates all comments in the code into English (#3917)
This commit is contained in:
@@ -39,10 +39,12 @@ bool HlsParser::parse(const string &http_url, const string &m3u8) {
|
||||
segment.duration = extinf_dur;
|
||||
segment.url = Parser::mergeUrl(http_url, line);
|
||||
if (!_is_m3u8_inner) {
|
||||
//ts按照先后顺序排序
|
||||
// ts按照先后顺序排序 [AUTO-TRANSLATED:c34f8c9d]
|
||||
// Sort by order of appearance
|
||||
ts_map.emplace(index++, segment);
|
||||
} else {
|
||||
//子m3u8按照带宽排序
|
||||
// 子m3u8按照带宽排序 [AUTO-TRANSLATED:749cb42b]
|
||||
// Sort sub m3u8 by bandwidth
|
||||
ts_map.emplace(segment.bandwidth, segment);
|
||||
}
|
||||
extinf_dur = 0;
|
||||
@@ -91,7 +93,8 @@ bool HlsParser::parse(const string &http_url, const string &m3u8) {
|
||||
}
|
||||
|
||||
if (line.find("#EXT-X-ENDLIST") == 0) {
|
||||
//点播
|
||||
// 点播 [AUTO-TRANSLATED:a64427bc]
|
||||
// On-demand
|
||||
_is_live = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -18,19 +18,26 @@
|
||||
namespace mediakit {
|
||||
|
||||
typedef struct{
|
||||
//url地址
|
||||
// url地址 [AUTO-TRANSLATED:64a1b5d1]
|
||||
// URL address
|
||||
std::string url;
|
||||
//ts切片长度
|
||||
// ts切片长度 [AUTO-TRANSLATED:9d5545f8]
|
||||
// TS segment length
|
||||
float duration;
|
||||
|
||||
//////内嵌m3u8//////
|
||||
//节目id
|
||||
// ////内嵌m3u8////// [AUTO-TRANSLATED:c3fabbfd]
|
||||
// //// Embedded m3u8 //////
|
||||
// 节目id [AUTO-TRANSLATED:8c6000cc]
|
||||
// Program ID
|
||||
int program_id;
|
||||
//带宽
|
||||
// 带宽 [AUTO-TRANSLATED:5f852828]
|
||||
// Bandwidth
|
||||
int bandwidth;
|
||||
//宽度
|
||||
// 宽度 [AUTO-TRANSLATED:06ad2724]
|
||||
// Width
|
||||
int width;
|
||||
//高度
|
||||
// 高度 [AUTO-TRANSLATED:87a07641]
|
||||
// Height
|
||||
int height;
|
||||
} ts_segment;
|
||||
|
||||
@@ -40,41 +47,65 @@ public:
|
||||
|
||||
/**
|
||||
* 是否存在#EXTM3U字段,是否为m3u8文件
|
||||
* Whether the #EXTM3U field exists, whether it is an m3u8 file
|
||||
|
||||
* [AUTO-TRANSLATED:ac1bf089]
|
||||
*/
|
||||
bool isM3u8() const;
|
||||
|
||||
/**
|
||||
* #EXT-X-ALLOW-CACHE值,是否允许cache
|
||||
* #EXT-X-ALLOW-CACHE value, whether caching is allowed
|
||||
|
||||
* [AUTO-TRANSLATED:90e88422]
|
||||
*/
|
||||
bool allowCache() const;
|
||||
|
||||
/**
|
||||
* 是否存在#EXT-X-ENDLIST字段,是否为直播
|
||||
* Whether the #EXT-X-ENDLIST field exists, whether it is a live stream
|
||||
|
||||
* [AUTO-TRANSLATED:f18e3c44]
|
||||
*/
|
||||
bool isLive() const ;
|
||||
|
||||
/**
|
||||
* #EXT-X-VERSION值,版本号
|
||||
* #EXT-X-VERSION value, version number
|
||||
|
||||
* [AUTO-TRANSLATED:89a99b3d]
|
||||
*/
|
||||
int getVersion() const;
|
||||
|
||||
/**
|
||||
* #EXT-X-TARGETDURATION字段值
|
||||
* #EXT-X-TARGETDURATION field value
|
||||
|
||||
* [AUTO-TRANSLATED:6720dc84]
|
||||
*/
|
||||
int getTargetDur() const;
|
||||
|
||||
/**
|
||||
* #EXT-X-MEDIA-SEQUENCE字段值,该m3u8序号
|
||||
* #EXT-X-MEDIA-SEQUENCE field value, the sequence number of this m3u8
|
||||
|
||||
* [AUTO-TRANSLATED:1a75250a]
|
||||
*/
|
||||
int64_t getSequence() const;
|
||||
|
||||
/**
|
||||
* 内部是否含有子m3u8
|
||||
* Whether it contains sub-m3u8 internally
|
||||
|
||||
* [AUTO-TRANSLATED:67b4a20c]
|
||||
*/
|
||||
bool isM3u8Inner() const;
|
||||
|
||||
/**
|
||||
* 得到总时间
|
||||
* Get the total time
|
||||
|
||||
* [AUTO-TRANSLATED:aa5e797b]
|
||||
*/
|
||||
float getTotalDuration() const;
|
||||
|
||||
@@ -85,6 +116,13 @@ protected:
|
||||
* @param sequence ts序号
|
||||
* @param ts_list ts地址列表
|
||||
* @return 是否解析成功,返回false时,将导致HlsParser::parse返回false
|
||||
* Callback for parsing the m3u8 file
|
||||
* @param is_m3u8_inner Whether this m3u8 file contains multiple HLS addresses
|
||||
* @param sequence TS sequence number
|
||||
* @param ts_list TS address list
|
||||
* @return Whether the parsing is successful, returning false will cause HlsParser::parse to return false
|
||||
|
||||
* [AUTO-TRANSLATED:be34e59f]
|
||||
*/
|
||||
virtual bool onParsed(bool is_m3u8_inner, int64_t sequence, const std::map<int, ts_segment> &ts_list) = 0;
|
||||
|
||||
@@ -96,7 +134,8 @@ private:
|
||||
int _target_dur = 0;
|
||||
float _total_dur = 0;
|
||||
int64_t _sequence = 0;
|
||||
//每部是否有m3u8
|
||||
// 每部是否有m3u8 [AUTO-TRANSLATED:c0d01536]
|
||||
// Whether each part has an m3u8
|
||||
bool _is_m3u8_inner = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,10 +44,12 @@ void HlsPlayer::teardown_l(const SockException &ex) {
|
||||
_play_result = true;
|
||||
onPlayResult(ex);
|
||||
} else {
|
||||
// 如果不是主动关闭的,则重新拉取索引文件
|
||||
// 如果不是主动关闭的,则重新拉取索引文件 [AUTO-TRANSLATED:e187c069]
|
||||
// If it is not actively closed, then re-pull the index file
|
||||
// if not actively closed, re-fetch the index file
|
||||
if (ex.getErrCode() != Err_shutdown && HlsParser::isLive()) {
|
||||
// 如果重试次数已经达到最大次数时, 且切片列表已空, 而且没有正在下载的切片, 则认为失败关闭播放器
|
||||
// 如果重试次数已经达到最大次数时, 且切片列表已空, 而且没有正在下载的切片, 则认为失败关闭播放器 [AUTO-TRANSLATED:2afe6c3a]
|
||||
// If the retry count has reached the maximum number of times, and the slice list is empty, and there are no slices being downloaded, then it is considered a failure to close the player
|
||||
// If the retry count has reached the maximum number of times, and the segments list is empty, and there is no segment being downloaded,
|
||||
// the player is considered to be closed due to failure
|
||||
if (_ts_list.empty() && !(_http_ts_player && _http_ts_player->waitResponse()) && _try_fetch_index_times >= MAX_TRY_FETCH_INDEX_TIMES) {
|
||||
@@ -56,11 +58,14 @@ void HlsPlayer::teardown_l(const SockException &ex) {
|
||||
_try_fetch_index_times += 1;
|
||||
shutdown(ex);
|
||||
WarnL << "Attempt to pull the m3u8 file again[" << _try_fetch_index_times << "]:" << _play_url;
|
||||
// 当网络波动时有可能拉取m3u8文件失败, 因此快速重试拉取m3u8文件, 而不是直接关闭播放器
|
||||
// 这里增加一个延时是为了防止_http_ts_player的socket还保持alive状态,就多次拉取m3u8文件了
|
||||
// 当网络波动时有可能拉取m3u8文件失败, 因此快速重试拉取m3u8文件, 而不是直接关闭播放器 [AUTO-TRANSLATED:0cb45f5f]
|
||||
// When the network fluctuates, it is possible that the m3u8 file will fail to be pulled, so quickly retry pulling the m3u8 file instead of directly closing the player
|
||||
// 这里增加一个延时是为了防止_http_ts_player的socket还保持alive状态,就多次拉取m3u8文件了 [AUTO-TRANSLATED:f779e7e9]
|
||||
// A delay is added here to prevent the _http_ts_player socket from remaining alive and pulling the m3u8 file multiple times
|
||||
// When the network fluctuates, it is possible to fail to pull the m3u8 file, so quickly retry to pull the m3u8 file instead of closing the player directly
|
||||
// The delay here is to prevent the socket of _http_ts_player from still keeping alive state, and pull the m3u8 file multiple times
|
||||
//todo _http_ts_player->waitResponse()这个判断条件是否有必要?因为有时候存在_complete==true,但是_http_ts_player->alive()为true的情况
|
||||
// todo _http_ts_player->waitResponse()这个判断条件是否有必要?因为有时候存在_complete==true,但是_http_ts_player->alive()为true的情况 [AUTO-TRANSLATED:a92efd3e]
|
||||
// todo Is the _http_ts_player->waitResponse() condition necessary? Because sometimes there is _complete==true, but _http_ts_player->alive() is true
|
||||
playDelay(0.3);
|
||||
return;
|
||||
}
|
||||
@@ -80,20 +85,23 @@ void HlsPlayer::teardown() {
|
||||
|
||||
void HlsPlayer::fetchSegment() {
|
||||
if (_ts_list.empty()) {
|
||||
// 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628
|
||||
// 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628 [AUTO-TRANSLATED:c2d0b647]
|
||||
// If it is an on-demand file, an empty playlist means that the file playback is finished, and the player is closed: #2628
|
||||
// If it is a video-on-demand file, the playlist is empty means the file is finished playing, close the player: #2628
|
||||
if (!HlsParser::isLive()) {
|
||||
teardown();
|
||||
return;
|
||||
}
|
||||
// 播放列表为空,那么立即重新下载m3u8文件
|
||||
// 播放列表为空,那么立即重新下载m3u8文件 [AUTO-TRANSLATED:e01943f3]
|
||||
// If the playlist is empty, then immediately re-download the m3u8 file
|
||||
// The playlist is empty, so download the m3u8 file immediately
|
||||
_timer.reset();
|
||||
fetchIndexFile();
|
||||
return;
|
||||
}
|
||||
if (_http_ts_player && _http_ts_player->waitResponse()) {
|
||||
// 播放器目前还存活,正在下载中
|
||||
// 播放器目前还存活,正在下载中 [AUTO-TRANSLATED:c18d8446]
|
||||
// The player is still alive and is currently downloading
|
||||
return;
|
||||
}
|
||||
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
|
||||
@@ -115,7 +123,8 @@ void HlsPlayer::fetchSegment() {
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
// 收到ts包
|
||||
// 收到ts包 [AUTO-TRANSLATED:334862da]
|
||||
// Received ts packet
|
||||
// Received ts package
|
||||
strong_self->onPacket(data, len);
|
||||
});
|
||||
@@ -152,19 +161,23 @@ void HlsPlayer::fetchSegment() {
|
||||
} else {
|
||||
strong_self->_ts_download_failed_count = 0;
|
||||
}
|
||||
// 提前0.5秒下载好,支持点播文件控制下载速度: #2628
|
||||
// 提前0.5秒下载好,支持点播文件控制下载速度: #2628 [AUTO-TRANSLATED:82247326]
|
||||
// Download 0.5 seconds in advance to support on-demand file download speed control: #2628
|
||||
// Download 0.5 seconds in advance to support video-on-demand files to control download speed: #2628
|
||||
auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f;
|
||||
if (delay > 2.0) {
|
||||
// 提前1秒下载
|
||||
// 提前1秒下载 [AUTO-TRANSLATED:852349aa]
|
||||
// Download 1 second in advance
|
||||
// Download 1 second in advance
|
||||
delay -= 1.0;
|
||||
} else if (delay <= 0) {
|
||||
// 延时最小10ms
|
||||
// 延时最小10ms [AUTO-TRANSLATED:fbb3665e]
|
||||
// Delay a minimum of 10ms
|
||||
// Delay at least 10ms
|
||||
delay = 0.01;
|
||||
}
|
||||
// 延时下载下一个切片
|
||||
// 延时下载下一个切片 [AUTO-TRANSLATED:26eb528d]
|
||||
// Delay downloading the next slice
|
||||
strong_self->_timer_ts.reset(new Timer(delay, [weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (strong_self) {
|
||||
@@ -175,7 +188,8 @@ void HlsPlayer::fetchSegment() {
|
||||
});
|
||||
|
||||
_http_ts_player->setMethod("GET");
|
||||
// ts切片必须在其时长的2-5倍内下载完毕
|
||||
// ts切片必须在其时长的2-5倍内下载完毕 [AUTO-TRANSLATED:d458e7b5]
|
||||
// The ts slice must be downloaded within 2-5 times its duration
|
||||
// The ts segment must be downloaded within 2-5 times its duration
|
||||
_http_ts_player->setCompleteTimeout(_timeout_multiple * duration * 1000);
|
||||
_http_ts_player->sendRequest(url);
|
||||
@@ -183,12 +197,16 @@ void HlsPlayer::fetchSegment() {
|
||||
|
||||
bool HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts_segment> &ts_map) {
|
||||
if (!is_m3u8_inner) {
|
||||
// 这是ts播放列表
|
||||
// 这是ts播放列表 [AUTO-TRANSLATED:7ce3d81b]
|
||||
// This is the ts playlist
|
||||
// This is the ts playlist
|
||||
if (_last_sequence == sequence) {
|
||||
// 如果是重复的ts列表,那么忽略
|
||||
// 但是需要注意, 如果当前ts列表为空了, 那么表明直播结束了或者m3u8文件有问题,需要重新拉流
|
||||
// 这里的5倍是为了防止m3u8文件有问题导致的无限重试
|
||||
// 如果是重复的ts列表,那么忽略 [AUTO-TRANSLATED:d15a47f3]
|
||||
// If it is a duplicate ts list, then ignore it
|
||||
// 但是需要注意, 如果当前ts列表为空了, 那么表明直播结束了或者m3u8文件有问题,需要重新拉流 [AUTO-TRANSLATED:438a8df0]
|
||||
// However, it should be noted that if the current ts list is empty, then it means that the live broadcast has ended or the m3u8 file has a problem, and the stream needs to be re-pulled
|
||||
// 这里的5倍是为了防止m3u8文件有问题导致的无限重试 [AUTO-TRANSLATED:3c8d073d]
|
||||
// The 5 times here is to prevent infinite retries caused by problems with the m3u8 file
|
||||
// If it is a duplicate ts list, ignore it
|
||||
// But it should be noted that if the current ts list is empty, it means that the live broadcast is over or the m3u8 file is problematic, and you need to re-pull the stream
|
||||
// The 5 times here is to prevent infinite retries caused by problems with the m3u8 file
|
||||
@@ -206,23 +224,27 @@ bool HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts
|
||||
for (auto &pr : ts_map) {
|
||||
auto &ts = pr.second;
|
||||
if (_ts_url_cache.emplace(ts.url).second) {
|
||||
// 该ts未重复
|
||||
// 该ts未重复 [AUTO-TRANSLATED:4b6fab6b]
|
||||
// This ts is not duplicated
|
||||
// The ts is not repeated
|
||||
_ts_list.emplace_back(ts);
|
||||
// 按时间排序
|
||||
// 按时间排序 [AUTO-TRANSLATED:7b61e414]
|
||||
// Sort by time
|
||||
// Sort by time
|
||||
_ts_url_sort.emplace_back(ts.url);
|
||||
}
|
||||
}
|
||||
if (_ts_url_sort.size() > 2 * ts_map.size()) {
|
||||
// 去除防重列表中过多的数据
|
||||
// 去除防重列表中过多的数据 [AUTO-TRANSLATED:94173d03]
|
||||
// Remove excessive data from the anti-repetition list
|
||||
// Remove too much data from the anti-repetition list
|
||||
_ts_url_cache.erase(_ts_url_sort.front());
|
||||
_ts_url_sort.pop_front();
|
||||
}
|
||||
fetchSegment();
|
||||
} else {
|
||||
// 这是m3u8列表,我们播放最高清的子hls
|
||||
// 这是m3u8列表,我们播放最高清的子hls [AUTO-TRANSLATED:6e6981ef]
|
||||
// This is the m3u8 list, we play the highest definition sub-hls
|
||||
// This is the m3u8 list, we play the highest quality sub-hls
|
||||
if (ts_map.empty()) {
|
||||
throw invalid_argument("empty sub hls list:" + getUrl());
|
||||
@@ -242,7 +264,8 @@ bool HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts
|
||||
|
||||
void HlsPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &headers) {
|
||||
if (status != "200" && status != "206") {
|
||||
// 失败
|
||||
// 失败 [AUTO-TRANSLATED:ba46763c]
|
||||
// Failure
|
||||
// Failed
|
||||
throw invalid_argument("bad http status code:" + status);
|
||||
}
|
||||
@@ -266,7 +289,8 @@ void HlsPlayer::onResponseCompleted(const SockException &ex) {
|
||||
teardown_l(SockException(Err_other, "parse m3u8 failed:" + _play_url));
|
||||
return;
|
||||
}
|
||||
// 如果有或取到新的切片, 那么就算成功, 应该重置失败次数
|
||||
// 如果有或取到新的切片, 那么就算成功, 应该重置失败次数 [AUTO-TRANSLATED:ae8dad10]
|
||||
// If there are or new slices are obtained, then it is considered successful, and the failure count should be reset
|
||||
// if there are new segments or get new segments, it is considered successful, and the number of failures should be reset
|
||||
if (!_ts_list.empty()) {
|
||||
_try_fetch_index_times = 0;
|
||||
@@ -283,21 +307,25 @@ float HlsPlayer::delaySecond() {
|
||||
float targetOffset;
|
||||
if (HlsParser::isLive()) {
|
||||
// see RFC 8216, Section 4.4.3.8.
|
||||
// 根据rfc刷新index列表的周期应该是分段时间x3, 因为根据规范播放器只处理最后3个Segment
|
||||
// 根据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());
|
||||
} else {
|
||||
// 点播则一般m3u8文件不会在改变了, 没必要频繁的刷新, 所以按照总时间来进行刷新
|
||||
// 点播则一般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;
|
||||
@@ -331,7 +359,8 @@ void HlsDemuxer::start(const EventPoller::Ptr &poller, TrackListener *listener)
|
||||
_frame_cache.clear();
|
||||
_delegate.setTrackListener(listener);
|
||||
|
||||
// 每50毫秒执行一次
|
||||
// 每50毫秒执行一次 [AUTO-TRANSLATED:e32f2140]
|
||||
// Execute once every 50 milliseconds
|
||||
// Execute every 50 milliseconds
|
||||
weak_ptr<HlsDemuxer> weak_self = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(0.05f, [weak_self]() {
|
||||
@@ -353,7 +382,8 @@ void HlsDemuxer::pushTask(std::function<void()> task) {
|
||||
}
|
||||
|
||||
bool HlsDemuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
// 为了避免track准备时间过长, 因此在没准备好之前, 直接消费掉所有的帧
|
||||
// 为了避免track准备时间过长, 因此在没准备好之前, 直接消费掉所有的帧 [AUTO-TRANSLATED:72b35430]
|
||||
// To avoid the track preparation time being too long, all frames are directly consumed before it is ready
|
||||
// In order to avoid the track preparation time is too long, so before it is ready, all frames are consumed directly
|
||||
if (!_delegate.isAllTrackReady()) {
|
||||
_delegate.inputFrame(frame);
|
||||
@@ -361,11 +391,13 @@ bool HlsDemuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
|
||||
if (_frame_cache.empty()) {
|
||||
// 设置当前播放位置时间戳
|
||||
// 设置当前播放位置时间戳 [AUTO-TRANSLATED:14799e6c]
|
||||
// Set the current playback position timestamp
|
||||
// Set the current playback position timestamp
|
||||
setPlayPosition(frame->dts());
|
||||
}
|
||||
// 根据时间戳缓存frame
|
||||
// 根据时间戳缓存frame [AUTO-TRANSLATED:f84d3698]
|
||||
// Cache frames based on the timestamp
|
||||
// Cache frame according to timestamp
|
||||
auto cached_frame = Frame::getCacheAbleFrame(frame);
|
||||
_frame_cache.emplace_back(std::make_pair(frame->dts(), [cached_frame, this]() {
|
||||
@@ -373,13 +405,15 @@ bool HlsDemuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
}));
|
||||
|
||||
if (getBufferMS() > 30 * 1000) {
|
||||
// 缓存超过30秒,强制消费至15秒(减少延时或内存占用)
|
||||
// 缓存超过30秒,强制消费至15秒(减少延时或内存占用) [AUTO-TRANSLATED:d6d58dde]
|
||||
// If the cache exceeds 30 seconds, force consumption to 15 seconds (reduce latency or memory usage)
|
||||
// The cache exceeds 30 seconds, and the consumption is forced to 15 seconds (reduce delay or memory usage)
|
||||
while (getBufferMS() > 15 * 1000) {
|
||||
_frame_cache.begin()->second();
|
||||
_frame_cache.erase(_frame_cache.begin());
|
||||
}
|
||||
// 接着播放缓存中最早的帧
|
||||
// 接着播放缓存中最早的帧 [AUTO-TRANSLATED:a1c76e0e]
|
||||
// Then play the earliest frame in the cache
|
||||
// Then play the earliest frame in the cache
|
||||
setPlayPosition(_frame_cache.begin()->first);
|
||||
}
|
||||
@@ -406,20 +440,24 @@ void HlsDemuxer::onTick() {
|
||||
auto it = _frame_cache.begin();
|
||||
while (it != _frame_cache.end()) {
|
||||
if (it->first > getPlayPosition()) {
|
||||
// 这些帧还未到时间播放
|
||||
// 这些帧还未到时间播放 [AUTO-TRANSLATED:e1ef7fe2]
|
||||
// These frames have not yet reached their playback time
|
||||
// These frames are not yet time to play
|
||||
break;
|
||||
}
|
||||
|
||||
if (getBufferMS() < 3 * 1000) {
|
||||
// 缓存小于3秒,那么降低定时器消费速度(让剩余的数据在3秒后消费完毕)
|
||||
// 目的是为了防止定时器长时间干等后,数据瞬间消费完毕
|
||||
// 缓存小于3秒,那么降低定时器消费速度(让剩余的数据在3秒后消费完毕) [AUTO-TRANSLATED:bc14fe02]
|
||||
// If the cache is less than 3 seconds, then reduce the timer consumption speed (so that the remaining data is consumed after 3 seconds)
|
||||
// 目的是为了防止定时器长时间干等后,数据瞬间消费完毕 [AUTO-TRANSLATED:55ac9c3d]
|
||||
// The goal is to prevent the timer from waiting for a long time before the data is consumed instantly
|
||||
// If the cache is less than 3 seconds, then reduce the speed of the timer to consume (let the remaining data be consumed after 3 seconds)
|
||||
// The purpose is to prevent the timer from waiting for a long time, and the data is consumed instantly
|
||||
setPlayPosition(_frame_cache.begin()->first);
|
||||
}
|
||||
|
||||
// 消费掉已经到期的帧
|
||||
// 消费掉已经到期的帧 [AUTO-TRANSLATED:f2d1230a]
|
||||
// Consume expired frames
|
||||
// Consume expired frames
|
||||
it->second();
|
||||
it = _frame_cache.erase(it);
|
||||
@@ -458,13 +496,15 @@ void HlsPlayerImp::onPlayResult(const SockException &ex) {
|
||||
void HlsPlayerImp::onShutdown(const SockException &ex) {
|
||||
while (_demuxer) {
|
||||
try {
|
||||
// shared_from_this()可能抛异常
|
||||
// shared_from_this()可能抛异常 [AUTO-TRANSLATED:c57c464a]
|
||||
// shared_from_this() may throw an exception
|
||||
// shared_from_this() may throw an exception
|
||||
std::weak_ptr<HlsPlayerImp> weak_self = static_pointer_cast<HlsPlayerImp>(shared_from_this());
|
||||
if (_decoder) {
|
||||
_decoder->flush();
|
||||
}
|
||||
// 等待所有frame flush输出后,再触发onShutdown事件
|
||||
// 等待所有frame flush输出后,再触发onShutdown事件 [AUTO-TRANSLATED:6db59f15]
|
||||
// Wait for all frames to be flushed before triggering the onShutdown event
|
||||
// Wait for all frame flush output, then trigger the onShutdown event
|
||||
static_pointer_cast<HlsDemuxer>(_demuxer)->pushTask([weak_self, ex]() {
|
||||
if (auto strong_self = weak_self.lock()) {
|
||||
|
||||
@@ -56,12 +56,20 @@ public:
|
||||
/**
|
||||
* 开始播放
|
||||
* start play
|
||||
* Start playing
|
||||
* start play
|
||||
|
||||
* [AUTO-TRANSLATED:03d41cf7]
|
||||
*/
|
||||
void play(const std::string &url) override;
|
||||
|
||||
/**
|
||||
* 停止播放
|
||||
* stop play
|
||||
* Stop playing
|
||||
* stop play
|
||||
|
||||
* [AUTO-TRANSLATED:88068dac]
|
||||
*/
|
||||
void teardown() override;
|
||||
|
||||
@@ -71,6 +79,12 @@ protected:
|
||||
* Received ts package
|
||||
* @param data ts数据负载 ts data payload
|
||||
* @param len ts包长度 ts package length
|
||||
* Received ts package
|
||||
* Received ts package
|
||||
* @param data ts data payload
|
||||
* @param len ts package length
|
||||
|
||||
* [AUTO-TRANSLATED:159a6559]
|
||||
*/
|
||||
virtual void onPacket(const char *data, size_t len) = 0;
|
||||
|
||||
@@ -90,7 +104,8 @@ private:
|
||||
|
||||
private:
|
||||
struct UrlComp {
|
||||
// url忽略?后面的参数
|
||||
// url忽略?后面的参数 [AUTO-TRANSLATED:788784c3]
|
||||
// url ignore? parameters after
|
||||
// Ignore the parameters after the url?
|
||||
bool operator()(const std::string& __x, const std::string& __y) const {
|
||||
return toolkit::split(__x,"?")[0] < toolkit::split(__y,"?")[0];
|
||||
|
||||
@@ -44,7 +44,8 @@ int64_t HttpStringBody::remainSize() {
|
||||
Buffer::Ptr HttpStringBody::readData(size_t size) {
|
||||
size = MIN((size_t)remainSize(), size);
|
||||
if (!size) {
|
||||
//没有剩余字节了
|
||||
// 没有剩余字节了 [AUTO-TRANSLATED:7bbaa343]
|
||||
// No remaining bytes
|
||||
return nullptr;
|
||||
}
|
||||
auto ret = std::make_shared<BufferString>(_str, _offset, size);
|
||||
@@ -72,7 +73,8 @@ static void mmap_close(HANDLE _hfile, HANDLE _hmapping, void *_addr) {
|
||||
}
|
||||
#endif
|
||||
|
||||
//删除mmap记录
|
||||
// 删除mmap记录 [AUTO-TRANSLATED:c956201d]
|
||||
// Delete mmap record
|
||||
static void delSharedMmap(const string &file_path, char *ptr) {
|
||||
lock_guard<mutex> lck(s_mtx);
|
||||
auto it = s_shared_mmap.find(file_path);
|
||||
@@ -88,21 +90,24 @@ static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &fil
|
||||
if (it != s_shared_mmap.end()) {
|
||||
auto ret = std::get<2>(it->second).lock();
|
||||
if (ret) {
|
||||
//命中mmap缓存
|
||||
// 命中mmap缓存 [AUTO-TRANSLATED:95131a66]
|
||||
// Hit mmap cache
|
||||
file_size = std::get<1>(it->second);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//打开文件
|
||||
// 打开文件 [AUTO-TRANSLATED:55bfe68a]
|
||||
// Open file
|
||||
std::shared_ptr<FILE> fp(fopen(file_path.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (!fp) {
|
||||
//文件不存在
|
||||
// 文件不存在 [AUTO-TRANSLATED:ed160bcf]
|
||||
// File does not exist
|
||||
file_size = -1;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -111,7 +116,8 @@ static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &fil
|
||||
#if defined(_WIN32)
|
||||
auto fd = _fileno(fp.get());
|
||||
#else
|
||||
//获取文件大小
|
||||
// 获取文件大小 [AUTO-TRANSLATED:82974eea]
|
||||
// Get file size
|
||||
file_size = File::fileSize(fp.get());
|
||||
auto fd = fileno(fp.get());
|
||||
#endif
|
||||
@@ -171,7 +177,8 @@ static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &fil
|
||||
|
||||
#if 0
|
||||
if (file_size < 10 * 1024 * 1024 && file_path.rfind(".ts") != string::npos) {
|
||||
//如果是小ts文件,那么尝试先加载到内存
|
||||
// 如果是小ts文件,那么尝试先加载到内存 [AUTO-TRANSLATED:0d96c5cd]
|
||||
// If it is a small ts file, try to load it into memory first
|
||||
auto buf = BufferRaw::create();
|
||||
buf->assign(ret.get(), file_size);
|
||||
ret.reset(buf->data(), [buf, file_path](char *ptr) {
|
||||
@@ -192,20 +199,24 @@ HttpFileBody::HttpFileBody(const string &file_path, bool use_mmap) {
|
||||
}
|
||||
|
||||
if (!_map_addr && _read_to != -1) {
|
||||
//mmap失败(且不是由于文件不存在导致的)或未执行mmap时,才进入fread逻辑分支
|
||||
// mmap失败(且不是由于文件不存在导致的)或未执行mmap时,才进入fread逻辑分支 [AUTO-TRANSLATED:8c7efed5]
|
||||
// Only enter the fread logic branch when mmap fails (and is not due to file not existing) or when mmap is not executed
|
||||
_fp.reset(fopen(file_path.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (!_fp) {
|
||||
//文件不存在
|
||||
// 文件不存在 [AUTO-TRANSLATED:ed160bcf]
|
||||
// File does not exist
|
||||
_read_to = -1;
|
||||
return;
|
||||
}
|
||||
if (!_read_to) {
|
||||
//_read_to等于0时,说明还未尝试获取文件大小
|
||||
//加上该判断逻辑,在mmap失败时,可以省去一次该操作
|
||||
// _read_to等于0时,说明还未尝试获取文件大小 [AUTO-TRANSLATED:4e3ef6ca]
|
||||
// When _read_to equals 0, it means that the file size has not been attempted to be obtained yet
|
||||
// 加上该判断逻辑,在mmap失败时,可以省去一次该操作 [AUTO-TRANSLATED:b9b585de]
|
||||
// Adding this judgment logic can save one operation when mmap fails
|
||||
_read_to = File::fileSize(_fp.get());
|
||||
}
|
||||
}
|
||||
@@ -241,7 +252,8 @@ public:
|
||||
_data = map_addr.get() + offset;
|
||||
_size = size;
|
||||
}
|
||||
//返回数据长度
|
||||
// 返回数据长度 [AUTO-TRANSLATED:955f731c]
|
||||
// Return data length
|
||||
char *data() const override { return _data; }
|
||||
size_t size() const override { return _size; }
|
||||
|
||||
@@ -258,11 +270,13 @@ int64_t HttpFileBody::remainSize() {
|
||||
Buffer::Ptr HttpFileBody::readData(size_t size) {
|
||||
size = (size_t)(MIN(remainSize(), (int64_t)size));
|
||||
if (!size) {
|
||||
//没有剩余字节了
|
||||
// 没有剩余字节了 [AUTO-TRANSLATED:7bbaa343]
|
||||
// No remaining bytes
|
||||
return nullptr;
|
||||
}
|
||||
if (!_map_addr) {
|
||||
// fread模式
|
||||
// fread模式 [AUTO-TRANSLATED:c4dee2a3]
|
||||
// fread mode
|
||||
ssize_t iRead;
|
||||
auto ret = _pool.obtain2();
|
||||
ret->setCapacity(size + 1);
|
||||
@@ -271,18 +285,21 @@ Buffer::Ptr HttpFileBody::readData(size_t size) {
|
||||
} while (-1 == iRead && UV_EINTR == get_uv_error(false));
|
||||
|
||||
if (iRead > 0) {
|
||||
//读到数据了
|
||||
// 读到数据了 [AUTO-TRANSLATED:7e5ada62]
|
||||
// Data is read
|
||||
ret->setSize(iRead);
|
||||
_file_offset += iRead;
|
||||
return std::move(ret);
|
||||
}
|
||||
//读取文件异常,文件真实长度小于声明长度
|
||||
// 读取文件异常,文件真实长度小于声明长度 [AUTO-TRANSLATED:89d09f9b]
|
||||
// File reading exception, the actual length of the file is less than the declared length
|
||||
_file_offset = _read_to;
|
||||
WarnL << "read file err:" << get_uv_errmsg();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// mmap模式
|
||||
// mmap模式 [AUTO-TRANSLATED:b8d616f1]
|
||||
// mmap mode
|
||||
auto ret = std::make_shared<BufferMmap>(_map_addr, _file_offset, size);
|
||||
_file_offset += size;
|
||||
return ret;
|
||||
@@ -321,7 +338,8 @@ Buffer::Ptr HttpMultiFormBody::readData(size_t size) {
|
||||
if (_fileBody->remainSize()) {
|
||||
auto ret = _fileBody->readData(size);
|
||||
if (!ret) {
|
||||
//读取文件出现异常,提前中断
|
||||
// 读取文件出现异常,提前中断 [AUTO-TRANSLATED:5b8052d9]
|
||||
// An exception occurred while reading the file, and the process was interrupted prematurely
|
||||
_offset = _totalSize;
|
||||
} else {
|
||||
_offset += ret->size();
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace mediakit {
|
||||
|
||||
/**
|
||||
* http content部分基类定义
|
||||
* Base class definition for http content part
|
||||
|
||||
* [AUTO-TRANSLATED:1eee419a]
|
||||
*/
|
||||
class HttpBody : public std::enable_shared_from_this<HttpBody>{
|
||||
public:
|
||||
@@ -34,6 +37,9 @@ public:
|
||||
|
||||
/**
|
||||
* 剩余数据大小,如果返回-1, 那么就不设置content-length
|
||||
* Remaining data size, if -1 is returned, then content-length is not set
|
||||
|
||||
* [AUTO-TRANSLATED:75375ce7]
|
||||
*/
|
||||
virtual int64_t remainSize() { return 0;};
|
||||
|
||||
@@ -41,6 +47,11 @@ public:
|
||||
* 读取一定字节数,返回大小可能小于size
|
||||
* @param size 请求大小
|
||||
* @return 字节对象,如果读完了,那么请返回nullptr
|
||||
* Read a certain number of bytes, the returned size may be less than size
|
||||
* @param size Request size
|
||||
* @return Byte object, if it is read, please return nullptr
|
||||
|
||||
* [AUTO-TRANSLATED:6fd85f91]
|
||||
*/
|
||||
virtual toolkit::Buffer::Ptr readData(size_t size) { return nullptr;};
|
||||
|
||||
@@ -48,11 +59,19 @@ public:
|
||||
* 异步请求读取一定字节数,返回大小可能小于size
|
||||
* @param size 请求大小
|
||||
* @param cb 回调函数
|
||||
* Asynchronously request to read a certain number of bytes, the returned size may be less than size
|
||||
* @param size Request size
|
||||
* @param cb Callback function
|
||||
|
||||
* [AUTO-TRANSLATED:a5304046]
|
||||
*/
|
||||
virtual void readDataAsync(size_t size,const std::function<void(const toolkit::Buffer::Ptr &buf)> &cb){
|
||||
//由于unix和linux是通过mmap的方式读取文件,所以把读文件操作放在后台线程并不能提高性能
|
||||
//反而会由于频繁的线程切换导致性能降低以及延时增加,所以我们默认同步获取文件内容
|
||||
//(其实并没有读,拷贝文件数据时在内核态完成文件读)
|
||||
// 由于unix和linux是通过mmap的方式读取文件,所以把读文件操作放在后台线程并不能提高性能 [AUTO-TRANSLATED:59ef443d]
|
||||
// Since unix and linux read files through mmap, putting file reading operations in the background thread does not improve performance
|
||||
// 反而会由于频繁的线程切换导致性能降低以及延时增加,所以我们默认同步获取文件内容 [AUTO-TRANSLATED:93d2a0b5]
|
||||
// On the contrary, frequent thread switching will lead to performance degradation and increased latency, so we get the file content synchronously by default
|
||||
// (其实并没有读,拷贝文件数据时在内核态完成文件读) [AUTO-TRANSLATED:6eb98a5d]
|
||||
// (Actually, there is no reading, the file data is copied in the kernel state when copying)
|
||||
cb(readData(size));
|
||||
}
|
||||
|
||||
@@ -60,6 +79,11 @@ public:
|
||||
* 使用sendfile优化文件发送
|
||||
* @param fd socket fd
|
||||
* @return 0成功,其他为错误代码
|
||||
* Use sendfile to optimize file sending
|
||||
* @param fd socket fd
|
||||
* @return 0 success, other error codes
|
||||
|
||||
* [AUTO-TRANSLATED:eacc5f98]
|
||||
*/
|
||||
virtual int sendFile(int fd) {
|
||||
return -1;
|
||||
@@ -68,6 +92,9 @@ public:
|
||||
|
||||
/**
|
||||
* std::string类型的content
|
||||
* std::string type content
|
||||
|
||||
* [AUTO-TRANSLATED:59fc3e5b]
|
||||
*/
|
||||
class HttpStringBody : public HttpBody{
|
||||
public:
|
||||
@@ -84,6 +111,9 @@ private:
|
||||
|
||||
/**
|
||||
* Buffer类型的content
|
||||
* Buffer type content
|
||||
|
||||
* [AUTO-TRANSLATED:350b9513]
|
||||
*/
|
||||
class HttpBufferBody : public HttpBody{
|
||||
public:
|
||||
@@ -99,6 +129,9 @@ private:
|
||||
|
||||
/**
|
||||
* 文件类型的content
|
||||
* File type content
|
||||
|
||||
* [AUTO-TRANSLATED:baf9c0f3]
|
||||
*/
|
||||
class HttpFileBody : public HttpBody {
|
||||
public:
|
||||
@@ -108,6 +141,11 @@ public:
|
||||
* 构造函数
|
||||
* @param file_path 文件路径
|
||||
* @param use_mmap 是否使用mmap方式访问文件
|
||||
* Constructor
|
||||
* @param file_path File path
|
||||
* @param use_mmap Whether to use mmap to access the file
|
||||
|
||||
* [AUTO-TRANSLATED:40c85c53]
|
||||
*/
|
||||
HttpFileBody(const std::string &file_path, bool use_mmap = true);
|
||||
|
||||
@@ -115,6 +153,11 @@ public:
|
||||
* 设置读取范围
|
||||
* @param offset 相对文件头的偏移量
|
||||
* @param max_size 最大读取字节数
|
||||
* Set the reading range
|
||||
* @param offset Offset relative to the file header
|
||||
* @param max_size Maximum number of bytes to read
|
||||
|
||||
* [AUTO-TRANSLATED:30532a4e]
|
||||
*/
|
||||
void setRange(uint64_t offset, uint64_t max_size);
|
||||
|
||||
@@ -134,6 +177,9 @@ class HttpArgs;
|
||||
|
||||
/**
|
||||
* http MultiForm 方式提交的http content
|
||||
* http MultiForm way to submit http content
|
||||
|
||||
* [AUTO-TRANSLATED:211a2d8e]
|
||||
*/
|
||||
class HttpMultiFormBody : public HttpBody {
|
||||
public:
|
||||
@@ -144,6 +190,13 @@ public:
|
||||
* @param args http提交参数列表
|
||||
* @param filePath 文件路径
|
||||
* @param boundary boundary字符串
|
||||
* Constructor
|
||||
* @param args http submission parameter list
|
||||
* @param filePath File path
|
||||
* @param boundary Boundary string
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:d093cfa7]
|
||||
*/
|
||||
HttpMultiFormBody(const HttpArgs &args,const std::string &filePath,const std::string &boundary = "0xKhTmLbOuNdArY");
|
||||
int64_t remainSize() override ;
|
||||
|
||||
@@ -33,7 +33,8 @@ void HttpChunkedSplitter::onRecvContent(const char *data, size_t len) {
|
||||
ssize_t HttpChunkedSplitter::onRecvHeader(const char *data, size_t len) {
|
||||
int size;
|
||||
CHECK(sscanf(data, "%X", &size) == 1 && size >= 0);
|
||||
//包括后面\r\n两个字节
|
||||
// 包括后面\r\n两个字节 [AUTO-TRANSLATED:f5567007]
|
||||
// Including the following two bytes \r\n
|
||||
return size + 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ class HttpChunkedSplitter : public HttpRequestSplitter {
|
||||
public:
|
||||
/**
|
||||
* len == 0时代表结束
|
||||
* When len == 0, it represents the end.
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:1607d203]
|
||||
*/
|
||||
using onChunkData = std::function<void(const char *data, size_t len)>;
|
||||
|
||||
|
||||
@@ -43,11 +43,13 @@ void HttpClient::sendRequest(const string &url) {
|
||||
if (_path.empty()) {
|
||||
_path = "/";
|
||||
}
|
||||
//重新设置header,防止上次请求的header干扰
|
||||
// 重新设置header,防止上次请求的header干扰 [AUTO-TRANSLATED:d8d06841]
|
||||
// Reset the header to prevent interference from the previous request's header
|
||||
_header = _user_set_header;
|
||||
auto pos = host.find('@');
|
||||
if (pos != string::npos) {
|
||||
//去除?后面的字符串
|
||||
// 去除?后面的字符串 [AUTO-TRANSLATED:0ccb41c2]
|
||||
// Remove the string after the "?"
|
||||
auto authStr = host.substr(0, pos);
|
||||
host = host.substr(pos + 1, host.size());
|
||||
_header.emplace("Authorization", "Basic " + encodeBase64(authStr));
|
||||
@@ -168,7 +170,8 @@ void HttpClient::onConnect_l(const SockException &ex) {
|
||||
return;
|
||||
}
|
||||
_StrPrinter printer;
|
||||
//不使用代理或者代理服务器已经连接成功
|
||||
// 不使用代理或者代理服务器已经连接成功 [AUTO-TRANSLATED:e051567c]
|
||||
// No proxy is used or the proxy server has connected successfully
|
||||
if (_proxy_connected || !isUsedProxy()) {
|
||||
printer << _method + " " << _path + " HTTP/1.1\r\n";
|
||||
for (auto &pr : _header) {
|
||||
@@ -195,8 +198,10 @@ 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.
|
||||
// If it is a persistent connection, then we can solve this problem by reconnecting
|
||||
@@ -226,7 +231,8 @@ ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
|
||||
_header_recved = true;
|
||||
|
||||
if (_parser["Transfer-Encoding"] == "chunked") {
|
||||
//如果Transfer-Encoding字段等于chunked,则认为后续的content是不限制长度的
|
||||
// 如果Transfer-Encoding字段等于chunked,则认为后续的content是不限制长度的 [AUTO-TRANSLATED:ebbcb35c]
|
||||
// If the Transfer-Encoding field is equal to chunked, it is considered that the subsequent content is unlimited in length
|
||||
_total_body_size = -1;
|
||||
_chunked_splitter = std::make_shared<HttpChunkedSplitter>([this](const char *data, size_t len) {
|
||||
if (len > 0) {
|
||||
@@ -241,27 +247,34 @@ ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
|
||||
}
|
||||
}
|
||||
});
|
||||
//后续为源源不断的body
|
||||
// 后续为源源不断的body [AUTO-TRANSLATED:bf551bbd]
|
||||
// The following is a continuous body
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!_parser["Content-Length"].empty()) {
|
||||
//有Content-Length字段时忽略onResponseHeader的返回值
|
||||
// 有Content-Length字段时忽略onResponseHeader的返回值 [AUTO-TRANSLATED:50380ba8]
|
||||
// Ignore the return value of onResponseHeader when there is a Content-Length field
|
||||
_total_body_size = atoll(_parser["Content-Length"].data());
|
||||
} else {
|
||||
_total_body_size = -1;
|
||||
}
|
||||
|
||||
if (_total_body_size == 0) {
|
||||
//后续没content,本次http请求结束
|
||||
// 后续没content,本次http请求结束 [AUTO-TRANSLATED:8532172f]
|
||||
// There is no content afterwards, this http request ends
|
||||
onResponseCompleted_l(SockException(Err_success, "The request is successful but has no body"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
//当_total_body_size != 0时到达这里,代表后续有content
|
||||
//虽然我们在_total_body_size >0 时知道content的确切大小,
|
||||
//但是由于我们没必要等content接收完毕才回调onRecvContent(因为这样浪费内存并且要多次拷贝数据)
|
||||
//所以返回-1代表我们接下来分段接收content
|
||||
// 当_total_body_size != 0时到达这里,代表后续有content [AUTO-TRANSLATED:3a55b268]
|
||||
// When _total_body_size != 0, it means there is content afterwards
|
||||
// 虽然我们在_total_body_size >0 时知道content的确切大小, [AUTO-TRANSLATED:af91f74f]
|
||||
// Although we know the exact size of the content when _total_body_size > 0,
|
||||
// 但是由于我们没必要等content接收完毕才回调onRecvContent(因为这样浪费内存并且要多次拷贝数据) [AUTO-TRANSLATED:fd71692c]
|
||||
// But because we don't need to wait for the content to be received before calling onRecvContent (because this wastes memory and requires multiple data copies)
|
||||
// 所以返回-1代表我们接下来分段接收content [AUTO-TRANSLATED:388756f6]
|
||||
// So returning -1 means we will receive the content in segments next
|
||||
_recved_body_size = 0;
|
||||
return -1;
|
||||
}
|
||||
@@ -273,26 +286,31 @@ void HttpClient::onRecvContent(const char *data, size_t len) {
|
||||
}
|
||||
_recved_body_size += len;
|
||||
if (_total_body_size < 0) {
|
||||
//不限长度的content
|
||||
// 不限长度的content [AUTO-TRANSLATED:325a9dbc]
|
||||
// Unlimited length content
|
||||
onResponseBody(data, len);
|
||||
return;
|
||||
}
|
||||
|
||||
//固定长度的content
|
||||
// 固定长度的content [AUTO-TRANSLATED:4d169746]
|
||||
// Fixed length content
|
||||
if (_recved_body_size < (size_t) _total_body_size) {
|
||||
//content还未接收完毕
|
||||
// content还未接收完毕 [AUTO-TRANSLATED:b30ca92c]
|
||||
// Content has not been received yet
|
||||
onResponseBody(data, len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_recved_body_size == (size_t)_total_body_size) {
|
||||
//content接收完毕
|
||||
// content接收完毕 [AUTO-TRANSLATED:e730ea8c]
|
||||
// Content received
|
||||
onResponseBody(data, len);
|
||||
onResponseCompleted_l(SockException(Err_success, "completed"));
|
||||
return;
|
||||
}
|
||||
|
||||
//声明的content数据比真实的小,断开链接
|
||||
// 声明的content数据比真实的小,断开链接 [AUTO-TRANSLATED:38204302]
|
||||
// The declared content data is smaller than the real one, disconnect
|
||||
onResponseBody(data, len);
|
||||
throw invalid_argument("http response content size bigger than expected");
|
||||
}
|
||||
@@ -302,40 +320,50 @@ void HttpClient::onFlush() {
|
||||
while (_body && _body->remainSize() && !isSocketBusy()) {
|
||||
auto buffer = _body->readData(send_buf_size);
|
||||
if (!buffer) {
|
||||
//数据发送结束或读取数据异常
|
||||
// 数据发送结束或读取数据异常 [AUTO-TRANSLATED:75179972]
|
||||
// Data transmission ends or data reading exception
|
||||
break;
|
||||
}
|
||||
if (send(buffer) <= 0) {
|
||||
//发送数据失败,不需要回滚数据,因为发送前已经通过isSocketBusy()判断socket可写
|
||||
//所以发送缓存区肯定未满,该buffer肯定已经写入socket
|
||||
// 发送数据失败,不需要回滚数据,因为发送前已经通过isSocketBusy()判断socket可写 [AUTO-TRANSLATED:30762202]
|
||||
// Data transmission failed, no need to roll back data, because the socket is writable before sending
|
||||
// 所以发送缓存区肯定未满,该buffer肯定已经写入socket [AUTO-TRANSLATED:769fff52]
|
||||
// So the send buffer is definitely not full, this buffer must have been written to the socket
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClient::onManager() {
|
||||
//onManager回调在连接中或已连接状态才会调用
|
||||
// onManager回调在连接中或已连接状态才会调用 [AUTO-TRANSLATED:acf86dce]
|
||||
// The onManager callback is only called when the connection is in progress or connected
|
||||
|
||||
if (_wait_complete_ms > 0) {
|
||||
//设置了总超时时间
|
||||
// 设置了总超时时间 [AUTO-TRANSLATED:ac47c234]
|
||||
// Total timeout is set
|
||||
if (!_complete && _wait_complete.elapsedTime() > _wait_complete_ms) {
|
||||
//等待http回复完毕超时
|
||||
// 等待http回复完毕超时 [AUTO-TRANSLATED:711ebc7b]
|
||||
// Timeout waiting for http reply to finish
|
||||
shutdown(SockException(Err_timeout, "wait http response complete timeout"));
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//未设置总超时时间
|
||||
// 未设置总超时时间 [AUTO-TRANSLATED:a936338f]
|
||||
// Total timeout is not set
|
||||
if (!_header_recved) {
|
||||
//等待header中
|
||||
// 等待header中 [AUTO-TRANSLATED:f8635de6]
|
||||
// Waiting for header
|
||||
if (_wait_header.elapsedTime() > _wait_header_ms) {
|
||||
//等待header中超时
|
||||
// 等待header中超时 [AUTO-TRANSLATED:860d3a16]
|
||||
// Timeout waiting for header
|
||||
shutdown(SockException(Err_timeout, "wait http response header timeout"));
|
||||
return;
|
||||
}
|
||||
} else if (_wait_body_ms > 0 && _wait_body.elapsedTime() > _wait_body_ms) {
|
||||
//等待body中,等待超时
|
||||
// 等待body中,等待超时 [AUTO-TRANSLATED:f9bb1d66]
|
||||
// Waiting for body, timeout
|
||||
shutdown(SockException(Err_timeout, "wait http response body timeout"));
|
||||
return;
|
||||
}
|
||||
@@ -349,25 +377,30 @@ void HttpClient::onResponseCompleted_l(const SockException &ex) {
|
||||
_wait_complete.resetTime();
|
||||
|
||||
if (!ex) {
|
||||
//确认无疑的成功
|
||||
// 确认无疑的成功 [AUTO-TRANSLATED:e1db8ce2]
|
||||
// Confirmed success
|
||||
onResponseCompleted(ex);
|
||||
return;
|
||||
}
|
||||
//可疑的失败
|
||||
// 可疑的失败 [AUTO-TRANSLATED:1258a436]
|
||||
// Suspicious failure
|
||||
|
||||
if (_total_body_size > 0 && _recved_body_size >= (size_t)_total_body_size) {
|
||||
//回复header中有content-length信息,那么收到的body大于等于声明值则认为成功
|
||||
// 回复header中有content-length信息,那么收到的body大于等于声明值则认为成功 [AUTO-TRANSLATED:2f813650]
|
||||
// If the response header contains content-length information, then the received body is considered successful if it is greater than or equal to the declared value
|
||||
onResponseCompleted(SockException(Err_success, "read body completed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_total_body_size == -1 && _recved_body_size > 0) {
|
||||
//回复header中无content-length信息,那么收到一点body也认为成功
|
||||
// 回复header中无content-length信息,那么收到一点body也认为成功 [AUTO-TRANSLATED:6c0e87fc]
|
||||
// If the response header does not contain content-length information, then receiving any body is considered successful
|
||||
onResponseCompleted(SockException(Err_success, ex.what()));
|
||||
return;
|
||||
}
|
||||
|
||||
//确认无疑的失败
|
||||
// 确认无疑的失败 [AUTO-TRANSLATED:33b216d9]
|
||||
// Confirmed failure
|
||||
onResponseCompleted(ex);
|
||||
}
|
||||
|
||||
@@ -409,7 +442,8 @@ void HttpClient::checkCookie(HttpClient::HttpHeader &headers) {
|
||||
}
|
||||
|
||||
if (!(*cookie)) {
|
||||
//无效的cookie
|
||||
// 无效的cookie [AUTO-TRANSLATED:5f06aec8]
|
||||
// Invalid cookie
|
||||
continue;
|
||||
}
|
||||
HttpCookieStorage::Instance().set(cookie);
|
||||
|
||||
@@ -52,23 +52,38 @@ public:
|
||||
/**
|
||||
* 发送http[s]请求
|
||||
* @param url 请求url
|
||||
* Send http[s] request
|
||||
* @param url Request url
|
||||
|
||||
* [AUTO-TRANSLATED:01b6c9ac]
|
||||
*/
|
||||
virtual void sendRequest(const std::string &url);
|
||||
|
||||
/**
|
||||
* 重置对象
|
||||
* Reset object
|
||||
|
||||
* [AUTO-TRANSLATED:d23b5bbb]
|
||||
*/
|
||||
virtual void clear();
|
||||
|
||||
/**
|
||||
* 设置http方法
|
||||
* @param method GET/POST等
|
||||
* Set http method
|
||||
* @param method GET/POST etc.
|
||||
|
||||
* [AUTO-TRANSLATED:5199546a]
|
||||
*/
|
||||
void setMethod(std::string method);
|
||||
|
||||
/**
|
||||
* 覆盖http头
|
||||
* @param header
|
||||
* Override http header
|
||||
* @param header
|
||||
|
||||
* [AUTO-TRANSLATED:ea31a471]
|
||||
*/
|
||||
void setHeader(HttpHeader header);
|
||||
|
||||
@@ -77,48 +92,78 @@ public:
|
||||
/**
|
||||
* 设置http content
|
||||
* @param body http content
|
||||
* Set http content
|
||||
* @param body http content
|
||||
|
||||
* [AUTO-TRANSLATED:9993580c]
|
||||
*/
|
||||
void setBody(std::string body);
|
||||
|
||||
/**
|
||||
* 设置http content
|
||||
* @param body http content
|
||||
* Set http content
|
||||
* @param body http content
|
||||
|
||||
* [AUTO-TRANSLATED:9993580c]
|
||||
*/
|
||||
void setBody(HttpBody::Ptr body);
|
||||
|
||||
/**
|
||||
* 获取回复,在收到完整回复后有效
|
||||
* Get response, valid after receiving the complete response
|
||||
|
||||
* [AUTO-TRANSLATED:b107995e]
|
||||
*/
|
||||
const Parser &response() const;
|
||||
|
||||
/**
|
||||
* 获取回复header声明的body大小
|
||||
* Get the body size declared in the response header
|
||||
|
||||
* [AUTO-TRANSLATED:65f8e782]
|
||||
*/
|
||||
ssize_t responseBodyTotalSize() const;
|
||||
|
||||
/**
|
||||
* 获取已经下载body的大小
|
||||
* Get the size of the body that has been downloaded
|
||||
|
||||
* [AUTO-TRANSLATED:a3cde7b4]
|
||||
*/
|
||||
size_t responseBodySize() const;
|
||||
|
||||
/**
|
||||
* 获取请求url
|
||||
* Get the request url
|
||||
|
||||
* [AUTO-TRANSLATED:cc7fe537]
|
||||
*/
|
||||
const std::string &getUrl() const;
|
||||
|
||||
/**
|
||||
* 判断是否正在等待响应
|
||||
* Determine if the response is pending
|
||||
|
||||
* [AUTO-TRANSLATED:058719d7]
|
||||
*/
|
||||
bool waitResponse() const;
|
||||
|
||||
/**
|
||||
* 判断是否为https
|
||||
* Determine if it is https
|
||||
|
||||
* [AUTO-TRANSLATED:9b3a0254]
|
||||
*/
|
||||
bool isHttps() const;
|
||||
|
||||
/**
|
||||
* 设置从发起连接到接收header完毕的延时,默认10秒
|
||||
* 此参数必须大于0
|
||||
* Set the delay from initiating the connection to receiving the header, default 10 seconds
|
||||
* This parameter must be greater than 0
|
||||
|
||||
* [AUTO-TRANSLATED:4cce3e85]
|
||||
*/
|
||||
void setHeaderTimeout(size_t timeout_ms);
|
||||
|
||||
@@ -126,17 +171,29 @@ public:
|
||||
* 设置接收body数据超时时间, 默认5秒
|
||||
* 此参数可以用于处理超大body回复的超时问题
|
||||
* 此参数可以等于0
|
||||
* Set the timeout for receiving body data, default 5 seconds
|
||||
* This parameter can be used to handle timeout issues for large body responses
|
||||
* This parameter can be equal to 0
|
||||
|
||||
* [AUTO-TRANSLATED:48585852]
|
||||
*/
|
||||
void setBodyTimeout(size_t timeout_ms);
|
||||
|
||||
/**
|
||||
* 设置整个链接超时超时时间, 默认0
|
||||
* 该值设置不为0后,HeaderTimeout和BodyTimeout无效
|
||||
* Set the timeout for the entire link, default 0
|
||||
* After this value is set to non-zero, HeaderTimeout and BodyTimeout are invalid
|
||||
|
||||
* [AUTO-TRANSLATED:df094868]
|
||||
*/
|
||||
void setCompleteTimeout(size_t timeout_ms);
|
||||
|
||||
/**
|
||||
* 设置http代理url
|
||||
* Set http proxy url
|
||||
|
||||
* [AUTO-TRANSLATED:95df17e7]
|
||||
*/
|
||||
void setProxyUrl(std::string proxy_url);
|
||||
|
||||
@@ -144,6 +201,10 @@ public:
|
||||
* 当重用连接失败时, 是否允许重新发起请求
|
||||
* If the reuse connection fails, whether to allow the request to be resent
|
||||
* @param allow true:允许重新发起请求 / true: allow the request to be resent
|
||||
* When the reuse connection fails, whether to allow the request to be resent
|
||||
* @param allow true: allow the request to be resent
|
||||
|
||||
* [AUTO-TRANSLATED:71bd8e67]
|
||||
*/
|
||||
void setAllowResendRequest(bool allow);
|
||||
|
||||
@@ -152,6 +213,11 @@ protected:
|
||||
* 收到http回复头
|
||||
* @param status 状态码,譬如:200 OK
|
||||
* @param headers http头
|
||||
* Receive http response header
|
||||
* @param status Status code, such as: 200 OK
|
||||
* @param headers http header
|
||||
|
||||
* [AUTO-TRANSLATED:a685f8ef]
|
||||
*/
|
||||
virtual void onResponseHeader(const std::string &status, const HttpHeader &headers) = 0;
|
||||
|
||||
@@ -159,11 +225,19 @@ protected:
|
||||
* 收到http conten数据
|
||||
* @param buf 数据指针
|
||||
* @param size 数据大小
|
||||
* Receive http content data
|
||||
* @param buf Data pointer
|
||||
* @param size Data size
|
||||
|
||||
* [AUTO-TRANSLATED:bee3bf62]
|
||||
*/
|
||||
virtual void onResponseBody(const char *buf, size_t size) = 0;
|
||||
|
||||
/**
|
||||
* 接收http回复完毕,
|
||||
* Receive http response complete,
|
||||
|
||||
* [AUTO-TRANSLATED:b96ed715]
|
||||
*/
|
||||
virtual void onResponseCompleted(const toolkit::SockException &ex) = 0;
|
||||
|
||||
@@ -172,6 +246,11 @@ protected:
|
||||
* @param url 重定向url
|
||||
* @param temporary 是否为临时重定向
|
||||
* @return 是否继续
|
||||
* Redirect event
|
||||
* @param url Redirect url
|
||||
* @param temporary Whether it is a temporary redirect
|
||||
* @return Whether to continue
|
||||
* [AUTO-TRANSLATED:b64d5f8b]
|
||||
*/
|
||||
virtual bool onRedirectUrl(const std::string &url, bool temporary) { return true; };
|
||||
|
||||
|
||||
@@ -16,12 +16,14 @@ namespace mediakit {
|
||||
|
||||
void HttpClientImp::onConnect(const SockException &ex) {
|
||||
if (isUsedProxy() && !isProxyConnected()) {
|
||||
// 连接代理服务器
|
||||
// 连接代理服务器 [AUTO-TRANSLATED:e7a8979a]
|
||||
// Connect to the proxy server
|
||||
setDoNotUseSSL();
|
||||
HttpClient::onConnect(ex);
|
||||
} else {
|
||||
if (!isHttps()) {
|
||||
// https 302跳转 http时,需要关闭ssl
|
||||
// https 302跳转 http时,需要关闭ssl [AUTO-TRANSLATED:2ba55daf]
|
||||
// When https 302 redirects to http, ssl needs to be closed
|
||||
setDoNotUseSSL();
|
||||
HttpClient::onConnect(ex);
|
||||
} else {
|
||||
|
||||
@@ -24,6 +24,11 @@ public:
|
||||
* 根据http错误代码获取字符说明
|
||||
* @param status 譬如404
|
||||
* @return 错误代码字符说明,譬如Not Found
|
||||
* Get character description based on http error code
|
||||
* @param status For example 404
|
||||
* @return Error code character description, for example Not Found
|
||||
|
||||
* [AUTO-TRANSLATED:7b844410]
|
||||
*/
|
||||
static const char *getHttpStatusMessage(int status);
|
||||
|
||||
@@ -31,6 +36,12 @@ public:
|
||||
* 根据文件后缀返回http mime
|
||||
* @param name 文件后缀,譬如html
|
||||
* @return mime值,譬如text/html
|
||||
* Return http mime based on file suffix
|
||||
* @param name File suffix, for example html
|
||||
* @return mime value, for example text/html
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:03d63e1f]
|
||||
*/
|
||||
static const std::string &getHttpContentType(const char *name);
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ static time_t time_to_epoch(const struct tm *ltm, int utcdiff) {
|
||||
|
||||
tyears = ltm->tm_year - 70; // tm->tm_year is from 1900.
|
||||
leaps = (tyears + 2) / 4; // no of next two lines until year 2100.
|
||||
// i = (ltm->tm_year – 100) / 100; [AUTO-TRANSLATED:12beea30]
|
||||
// i = (ltm->tm_year – 100) / 100;
|
||||
// leaps -= ( (i/4)*3 + i%4 );
|
||||
tdays = 0;
|
||||
@@ -53,7 +54,8 @@ static time_t time_to_epoch(const struct tm *ltm, int utcdiff) {
|
||||
static time_t timeStrToInt(const string &date) {
|
||||
struct tm tt;
|
||||
strptime(date.data(), "%a, %b %d %Y %H:%M:%S %Z", &tt);
|
||||
// mktime内部有使用互斥锁,非常影响性能
|
||||
// mktime内部有使用互斥锁,非常影响性能 [AUTO-TRANSLATED:b3270635]
|
||||
// mktime uses mutex internally, which significantly affects performance
|
||||
return time_to_epoch(&tt, getGMTOff() / 3600); // mktime(&tt);
|
||||
}
|
||||
|
||||
@@ -99,23 +101,29 @@ vector<HttpCookie::Ptr> HttpCookieStorage::get(const string &host, const string
|
||||
lock_guard<mutex> lck(_mtx_cookie);
|
||||
auto it = _all_cookie.find(host);
|
||||
if (it == _all_cookie.end()) {
|
||||
//未找到该host相关记录
|
||||
// 未找到该host相关记录 [AUTO-TRANSLATED:0655542a]
|
||||
// No record found for this host
|
||||
return ret;
|
||||
}
|
||||
//遍历该host下所有path
|
||||
// 遍历该host下所有path [AUTO-TRANSLATED:94ca2180]
|
||||
// Traverse all paths under this host
|
||||
for (auto &pr : it->second) {
|
||||
if (path.find(pr.first) != 0) {
|
||||
//这个path不匹配
|
||||
// 这个path不匹配 [AUTO-TRANSLATED:3ec99732]
|
||||
// This path does not match
|
||||
continue;
|
||||
}
|
||||
//遍历该path下的各个cookie
|
||||
// 遍历该path下的各个cookie [AUTO-TRANSLATED:ceab9c83]
|
||||
// Traverse all cookies under this path
|
||||
for (auto it_cookie = pr.second.begin(); it_cookie != pr.second.end();) {
|
||||
if (!*(it_cookie->second)) {
|
||||
//该cookie已经过期,移除之
|
||||
// 该cookie已经过期,移除之 [AUTO-TRANSLATED:52762286]
|
||||
// This cookie has expired, remove it
|
||||
it_cookie = pr.second.erase(it_cookie);
|
||||
continue;
|
||||
}
|
||||
//保存有效cookie
|
||||
// 保存有效cookie [AUTO-TRANSLATED:bd875507]
|
||||
// Save valid cookies
|
||||
ret.emplace_back(it_cookie->second);
|
||||
++it_cookie;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace mediakit {
|
||||
|
||||
/**
|
||||
* http客户端cookie对象
|
||||
* http client cookie object
|
||||
|
||||
* [AUTO-TRANSLATED:5c1840bb]
|
||||
*/
|
||||
class HttpCookie {
|
||||
public:
|
||||
@@ -47,6 +50,10 @@ private:
|
||||
|
||||
/**
|
||||
* http客户端cookie全局保存器
|
||||
* http client cookie global saver
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:cac4a704]
|
||||
*/
|
||||
class HttpCookieStorage{
|
||||
public:
|
||||
|
||||
@@ -75,7 +75,8 @@ string HttpServerCookie::cookieExpireTime() const {
|
||||
INSTANCE_IMP(HttpCookieManager);
|
||||
|
||||
HttpCookieManager::HttpCookieManager() {
|
||||
//定时删除过期的cookie,防止内存膨胀
|
||||
// 定时删除过期的cookie,防止内存膨胀 [AUTO-TRANSLATED:dd9dc9c0]
|
||||
// Delete expired cookies periodically to prevent memory bloat
|
||||
_timer = std::make_shared<Timer>(
|
||||
10.0f,
|
||||
[this]() {
|
||||
@@ -91,12 +92,15 @@ HttpCookieManager::~HttpCookieManager() {
|
||||
|
||||
void HttpCookieManager::onManager() {
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
//先遍历所有类型
|
||||
// 先遍历所有类型 [AUTO-TRANSLATED:4917ee89]
|
||||
// First iterate through all types
|
||||
for (auto it_name = _map_cookie.begin(); it_name != _map_cookie.end();) {
|
||||
//再遍历该类型下的所有cookie
|
||||
// 再遍历该类型下的所有cookie [AUTO-TRANSLATED:0aab9e18]
|
||||
// Then iterate through all cookies under that type
|
||||
for (auto it_cookie = it_name->second.begin(); it_cookie != it_name->second.end();) {
|
||||
if (it_cookie->second->isExpired()) {
|
||||
// cookie过期,移除记录
|
||||
// cookie过期,移除记录 [AUTO-TRANSLATED:8b48b8a2]
|
||||
// Cookie expired, remove record
|
||||
DebugL << it_cookie->second->getUid() << " cookie过期:" << it_cookie->second->getCookie();
|
||||
it_cookie = it_name->second.erase(it_cookie);
|
||||
continue;
|
||||
@@ -105,7 +109,8 @@ void HttpCookieManager::onManager() {
|
||||
}
|
||||
|
||||
if (it_name->second.empty()) {
|
||||
//该类型下没有任何cookie记录,移除之
|
||||
// 该类型下没有任何cookie记录,移除之 [AUTO-TRANSLATED:92e3b783]
|
||||
// There are no cookie records under this type, remove it
|
||||
DebugL << "该path下没有任何cookie记录:" << it_name->first;
|
||||
it_name = _map_cookie.erase(it_name);
|
||||
continue;
|
||||
@@ -120,13 +125,16 @@ HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, co
|
||||
auto uid = uid_in.empty() ? cookie : uid_in;
|
||||
auto oldCookie = getOldestCookie(cookie_name, uid, max_client);
|
||||
if (!oldCookie.empty()) {
|
||||
//假如该账号已经登录了,那么删除老的cookie。
|
||||
//目的是实现单账号多地登录时挤占登录
|
||||
// 假如该账号已经登录了,那么删除老的cookie。 [AUTO-TRANSLATED:f18d826d]
|
||||
// If the account has already logged in, delete the old cookie.
|
||||
// 目的是实现单账号多地登录时挤占登录 [AUTO-TRANSLATED:8a64aec7]
|
||||
// The purpose is to achieve login squeeze when multiple devices log in with the same account
|
||||
delCookie(cookie_name, oldCookie);
|
||||
}
|
||||
HttpServerCookie::Ptr data(new HttpServerCookie(shared_from_this(), cookie_name, uid, cookie, max_elapsed));
|
||||
data->setAttach(std::move(attach));
|
||||
//保存该账号下的新cookie
|
||||
// 保存该账号下的新cookie [AUTO-TRANSLATED:e476c9c8]
|
||||
// Save the new cookie under this account
|
||||
_map_cookie[cookie_name][cookie] = data;
|
||||
return data;
|
||||
}
|
||||
@@ -135,16 +143,19 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name, co
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
auto it_name = _map_cookie.find(cookie_name);
|
||||
if (it_name == _map_cookie.end()) {
|
||||
//不存在该类型的cookie
|
||||
// 不存在该类型的cookie [AUTO-TRANSLATED:d32b0997]
|
||||
// There is no cookie of this type
|
||||
return nullptr;
|
||||
}
|
||||
auto it_cookie = it_name->second.find(cookie);
|
||||
if (it_cookie == it_name->second.end()) {
|
||||
//该类型下没有对应的cookie
|
||||
// 该类型下没有对应的cookie [AUTO-TRANSLATED:62caa764]
|
||||
// There is no corresponding cookie under this type
|
||||
return nullptr;
|
||||
}
|
||||
if (it_cookie->second->isExpired()) {
|
||||
// cookie过期
|
||||
// cookie过期 [AUTO-TRANSLATED:a980453f]
|
||||
// Cookie expired
|
||||
DebugL << "cookie过期:" << it_cookie->second->getCookie();
|
||||
it_name->second.erase(it_cookie);
|
||||
return nullptr;
|
||||
@@ -195,48 +206,58 @@ bool HttpCookieManager::delCookie(const string &cookie_name, const string &cooki
|
||||
}
|
||||
|
||||
void HttpCookieManager::onAddCookie(const string &cookie_name, const string &uid, const string &cookie) {
|
||||
//添加新的cookie,我们记录下这个uid下有哪些cookie,目的是实现单账号多地登录时挤占登录
|
||||
// 添加新的cookie,我们记录下这个uid下有哪些cookie,目的是实现单账号多地登录时挤占登录 [AUTO-TRANSLATED:60b752e9]
|
||||
// Add a new cookie, we record which cookies are under this uid, the purpose is to achieve login squeeze when multiple devices log in with the same account
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
//相同用户下可以存在多个cookie(意味多地登录),这些cookie根据登录时间的早晚依次排序
|
||||
// 相同用户下可以存在多个cookie(意味多地登录),这些cookie根据登录时间的早晚依次排序 [AUTO-TRANSLATED:1e0b93b9]
|
||||
// Multiple cookies can exist under the same user (meaning multiple devices log in), these cookies are sorted in order of login time
|
||||
_map_uid_to_cookie[cookie_name][uid][getCurrentMillisecond()] = cookie;
|
||||
}
|
||||
|
||||
void HttpCookieManager::onDelCookie(const string &cookie_name, const string &uid, const string &cookie) {
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
//回收随机字符串
|
||||
// 回收随机字符串 [AUTO-TRANSLATED:18a699ff]
|
||||
// Recycle random string
|
||||
_generator.release(cookie);
|
||||
|
||||
auto it_name = _map_uid_to_cookie.find(cookie_name);
|
||||
if (it_name == _map_uid_to_cookie.end()) {
|
||||
//该类型下未有任意用户登录
|
||||
// 该类型下未有任意用户登录 [AUTO-TRANSLATED:8ba458b9]
|
||||
// No user has logged in under this type
|
||||
return;
|
||||
}
|
||||
auto it_uid = it_name->second.find(uid);
|
||||
if (it_uid == it_name->second.end()) {
|
||||
//该用户尚未登录
|
||||
// 该用户尚未登录 [AUTO-TRANSLATED:ec07ce1b]
|
||||
// This user has not logged in yet
|
||||
return;
|
||||
}
|
||||
|
||||
//遍历同一名用户下的所有客户端,移除命中的客户端
|
||||
// 遍历同一名用户下的所有客户端,移除命中的客户端 [AUTO-TRANSLATED:cae6e264]
|
||||
// Iterate through all clients under the same user and remove the matching client
|
||||
for (auto it_cookie = it_uid->second.begin(); it_cookie != it_uid->second.end(); ++it_cookie) {
|
||||
if (it_cookie->second != cookie) {
|
||||
//不是该cookie
|
||||
// 不是该cookie [AUTO-TRANSLATED:cf5eca3b]
|
||||
// Not this cookie
|
||||
continue;
|
||||
}
|
||||
//移除该用户名下的某个cookie,这个设备cookie将失效
|
||||
// 移除该用户名下的某个cookie,这个设备cookie将失效 [AUTO-TRANSLATED:bf2de2a0]
|
||||
// Remove a cookie under this username, this device cookie will become invalid
|
||||
it_uid->second.erase(it_cookie);
|
||||
|
||||
if (!it_uid->second.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
//该用户名下没有任何设备在线,移除之
|
||||
// 该用户名下没有任何设备在线,移除之 [AUTO-TRANSLATED:6a8a2305]
|
||||
// There are no devices online under this username, remove it
|
||||
it_name->second.erase(it_uid);
|
||||
|
||||
if (!it_name->second.empty()) {
|
||||
break;
|
||||
}
|
||||
//该类型下未有任何用户在线,移除之
|
||||
// 该类型下未有任何用户在线,移除之 [AUTO-TRANSLATED:e705cfe6]
|
||||
// There are no users online under this type, remove it
|
||||
_map_uid_to_cookie.erase(it_name);
|
||||
break;
|
||||
}
|
||||
@@ -246,29 +267,35 @@ string HttpCookieManager::getOldestCookie(const string &cookie_name, const strin
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
auto it_name = _map_uid_to_cookie.find(cookie_name);
|
||||
if (it_name == _map_uid_to_cookie.end()) {
|
||||
//不存在该类型的cookie
|
||||
// 不存在该类型的cookie [AUTO-TRANSLATED:d32b0997]
|
||||
// There is no cookie of this type
|
||||
return "";
|
||||
}
|
||||
auto it_uid = it_name->second.find(uid);
|
||||
if (it_uid == it_name->second.end()) {
|
||||
//该用户从未登录过
|
||||
// 该用户从未登录过 [AUTO-TRANSLATED:fc6dbcf6]
|
||||
// This user has never logged in
|
||||
return "";
|
||||
}
|
||||
if ((int)it_uid->second.size() < MAX(1, max_client)) {
|
||||
//同一名用户下,客户端个数还没达到限制个数
|
||||
// 同一名用户下,客户端个数还没达到限制个数 [AUTO-TRANSLATED:a31f6ada]
|
||||
// Under the same user, the number of clients has not reached the limit
|
||||
return "";
|
||||
}
|
||||
//客户端个数超过限制,移除最先登录的客户端
|
||||
// 客户端个数超过限制,移除最先登录的客户端 [AUTO-TRANSLATED:a284ce91]
|
||||
// The number of clients exceeds the limit, remove the first client to log in
|
||||
return it_uid->second.begin()->second;
|
||||
}
|
||||
|
||||
/////////////////////////////////RandStrGenerator////////////////////////////////////
|
||||
string RandStrGenerator::obtain() {
|
||||
//获取唯一的防膨胀的随机字符串
|
||||
// 获取唯一的防膨胀的随机字符串 [AUTO-TRANSLATED:1306465c]
|
||||
// Get a unique anti-bloating random string
|
||||
while (true) {
|
||||
auto str = obtain_l();
|
||||
if (_obtained.find(str) == _obtained.end()) {
|
||||
//没有重复
|
||||
// 没有重复 [AUTO-TRANSLATED:16af311b]
|
||||
// No duplicates
|
||||
_obtained.emplace(str);
|
||||
return str;
|
||||
}
|
||||
@@ -276,12 +303,14 @@ string RandStrGenerator::obtain() {
|
||||
}
|
||||
|
||||
void RandStrGenerator::release(const string &str) {
|
||||
//从防膨胀库中移除
|
||||
// 从防膨胀库中移除 [AUTO-TRANSLATED:1165d5fe]
|
||||
// Remove from the anti-bloating library
|
||||
_obtained.erase(str);
|
||||
}
|
||||
|
||||
string RandStrGenerator::obtain_l() {
|
||||
// 12个伪随机字节 + 4个递增的整形字节,然后md5即为随机字符串
|
||||
// 12个伪随机字节 + 4个递增的整形字节,然后md5即为随机字符串 [AUTO-TRANSLATED:8571a327]
|
||||
// 12 pseudo-random bytes + 4 incrementing integer bytes, then md5 is the random string
|
||||
auto str = makeRandStr(12, false);
|
||||
str.append((char *)&_index, sizeof(_index));
|
||||
++_index;
|
||||
|
||||
@@ -27,6 +27,9 @@ class HttpCookieManager;
|
||||
|
||||
/**
|
||||
* cookie对象,用于保存cookie的一些相关属性
|
||||
* cookie object, used to store some related attributes of the cookie
|
||||
|
||||
* [AUTO-TRANSLATED:267fbbc3]
|
||||
*/
|
||||
class HttpServerCookie : public toolkit::noncopyable {
|
||||
public:
|
||||
@@ -38,6 +41,14 @@ public:
|
||||
* @param uid 用户唯一id
|
||||
* @param cookie cookie随机字符串
|
||||
* @param max_elapsed 最大过期时间,单位秒
|
||||
* Construct cookie
|
||||
* @param manager cookie manager object
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param uid user unique id
|
||||
* @param cookie cookie random string
|
||||
* @param max_elapsed maximum expiration time, in seconds
|
||||
|
||||
* [AUTO-TRANSLATED:a24f209d]
|
||||
*/
|
||||
|
||||
HttpServerCookie(
|
||||
@@ -48,6 +59,10 @@ public:
|
||||
/**
|
||||
* 获取uid
|
||||
* @return uid
|
||||
* Get uid
|
||||
* @return uid
|
||||
|
||||
* [AUTO-TRANSLATED:71a3afab]
|
||||
*/
|
||||
const std::string &getUid() const;
|
||||
|
||||
@@ -56,39 +71,67 @@ public:
|
||||
* @param cookie_name 该cookie的名称,譬如 MY_SESSION
|
||||
* @param path http访问路径
|
||||
* @return 例如 MY_SESSION=XXXXXX;expires=Wed, Jun 12 2019 06:30:48 GMT;path=/index/files/
|
||||
* Get the value of the Set-Cookie field in http
|
||||
* @param cookie_name the name of this cookie, such as MY_SESSION
|
||||
* @param path http access path
|
||||
* @return For example, MY_SESSION=XXXXXX;expires=Wed, Jun 12 2019 06:30:48 GMT;path=/index/files/
|
||||
|
||||
* [AUTO-TRANSLATED:8699036b]
|
||||
*/
|
||||
std::string getCookie(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* 获取cookie随机字符串
|
||||
* @return cookie随机字符串
|
||||
* Get cookie random string
|
||||
* @return cookie random string
|
||||
|
||||
* [AUTO-TRANSLATED:1853611a]
|
||||
*/
|
||||
const std::string &getCookie() const;
|
||||
|
||||
/**
|
||||
* 获取该cookie名
|
||||
* @return
|
||||
* Get the name of this cookie
|
||||
* @return
|
||||
|
||||
* [AUTO-TRANSLATED:6251f9f5]
|
||||
*/
|
||||
const std::string &getCookieName() const;
|
||||
|
||||
/**
|
||||
* 更新该cookie的过期时间,可以让此cookie不失效
|
||||
* Update the expiration time of this cookie, so that this cookie will not expire
|
||||
|
||||
* [AUTO-TRANSLATED:d3a3300b]
|
||||
*/
|
||||
void updateTime();
|
||||
|
||||
/**
|
||||
* 判断该cookie是否过期
|
||||
* @return
|
||||
* Determine whether this cookie has expired
|
||||
* @return
|
||||
|
||||
* [AUTO-TRANSLATED:3b0d3d59]
|
||||
*/
|
||||
bool isExpired();
|
||||
|
||||
/**
|
||||
* 设置附加数据
|
||||
* Set additional data
|
||||
|
||||
* [AUTO-TRANSLATED:afde9874]
|
||||
*/
|
||||
void setAttach(toolkit::Any attach);
|
||||
|
||||
/*
|
||||
* 获取附加数据
|
||||
/*
|
||||
* Get additional data
|
||||
|
||||
* [AUTO-TRANSLATED:e277d75d]
|
||||
*/
|
||||
template <class T>
|
||||
T& getAttach() {
|
||||
@@ -110,6 +153,9 @@ private:
|
||||
|
||||
/**
|
||||
* cookie随机字符串生成器
|
||||
* cookie random string generator
|
||||
|
||||
* [AUTO-TRANSLATED:501ea34c]
|
||||
*/
|
||||
class RandStrGenerator {
|
||||
public:
|
||||
@@ -117,12 +163,20 @@ public:
|
||||
/**
|
||||
* 获取不碰撞的随机字符串
|
||||
* @return 随机字符串
|
||||
* Get a random string that does not collide
|
||||
* @return random string
|
||||
|
||||
* [AUTO-TRANSLATED:6daa3fd8]
|
||||
*/
|
||||
std::string obtain();
|
||||
|
||||
/**
|
||||
* 释放随机字符串
|
||||
* @param str 随机字符串
|
||||
* Release random string
|
||||
* @param str random string
|
||||
|
||||
* [AUTO-TRANSLATED:90ea164a]
|
||||
*/
|
||||
void release(const std::string &str);
|
||||
|
||||
@@ -130,15 +184,21 @@ private:
|
||||
std::string obtain_l();
|
||||
|
||||
private:
|
||||
//碰撞库
|
||||
// 碰撞库 [AUTO-TRANSLATED:25a2ca2b]
|
||||
// Collision library
|
||||
std::unordered_set<std::string> _obtained;
|
||||
//增长index,防止碰撞用
|
||||
// 增长index,防止碰撞用 [AUTO-TRANSLATED:85778468]
|
||||
// Increase index, used to prevent collisions
|
||||
int _index = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* cookie管理器,用于管理cookie的生成以及过期管理,同时实现了同账号异地挤占登录功能
|
||||
* 该对象实现了同账号最多登录若干个设备
|
||||
* Cookie manager, used to manage cookie generation and expiration management, and also implements the function of occupying login from different locations with the same account
|
||||
* This object implements the function that the same account can log in to at most several devices
|
||||
|
||||
* [AUTO-TRANSLATED:ad6008e8]
|
||||
*/
|
||||
class HttpCookieManager : public std::enable_shared_from_this<HttpCookieManager> {
|
||||
public:
|
||||
@@ -148,6 +208,9 @@ public:
|
||||
|
||||
/**
|
||||
* 获取单例
|
||||
* Get singleton
|
||||
|
||||
* [AUTO-TRANSLATED:d082a6ee]
|
||||
*/
|
||||
static HttpCookieManager &Instance();
|
||||
|
||||
@@ -158,6 +221,14 @@ public:
|
||||
* @param max_client 该账号最多登录多少个设备
|
||||
* @param max_elapsed 该cookie过期时间,单位秒
|
||||
* @return cookie对象
|
||||
* Add cookie
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param uid user id, if empty, it is anonymous login
|
||||
* @param max_client the maximum number of devices that this account can log in to
|
||||
* @param max_elapsed the expiration time of this cookie, in seconds
|
||||
* @return cookie object
|
||||
|
||||
* [AUTO-TRANSLATED:c23f2321]
|
||||
*/
|
||||
HttpServerCookie::Ptr addCookie(
|
||||
const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,
|
||||
@@ -169,6 +240,12 @@ public:
|
||||
* @param cookie_name cookie名,例如MY_SESSION
|
||||
* @param cookie cookie随机字符串
|
||||
* @return cookie对象,可以为nullptr
|
||||
* Find cookie object by cookie random string
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param cookie cookie random string
|
||||
* @return cookie object, can be nullptr
|
||||
|
||||
* [AUTO-TRANSLATED:a0c7ed63]
|
||||
*/
|
||||
HttpServerCookie::Ptr getCookie(const std::string &cookie_name, const std::string &cookie);
|
||||
|
||||
@@ -177,6 +254,12 @@ public:
|
||||
* @param cookie_name cookie名,例如MY_SESSION
|
||||
* @param http_header http头
|
||||
* @return cookie对象
|
||||
* Get cookie object from http header
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param http_header http header
|
||||
* @return cookie object
|
||||
|
||||
* [AUTO-TRANSLATED:93661474]
|
||||
*/
|
||||
HttpServerCookie::Ptr getCookie(const std::string &cookie_name, const StrCaseMap &http_header);
|
||||
|
||||
@@ -185,6 +268,12 @@ public:
|
||||
* @param cookie_name cookie名,例如MY_SESSION
|
||||
* @param uid 用户id
|
||||
* @return cookie对象
|
||||
* Get cookie by uid
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param uid user id
|
||||
* @return cookie object
|
||||
|
||||
* [AUTO-TRANSLATED:623277e4]
|
||||
*/
|
||||
HttpServerCookie::Ptr getCookieByUid(const std::string &cookie_name, const std::string &uid);
|
||||
|
||||
@@ -192,6 +281,11 @@ public:
|
||||
* 删除cookie,用户登出时使用
|
||||
* @param cookie cookie对象,可以为nullptr
|
||||
* @return
|
||||
* Delete cookie, used when user logs out
|
||||
* @param cookie cookie object, can be nullptr
|
||||
* @return
|
||||
|
||||
* [AUTO-TRANSLATED:f80c6974]
|
||||
*/
|
||||
bool delCookie(const HttpServerCookie::Ptr &cookie);
|
||||
|
||||
@@ -204,6 +298,12 @@ private:
|
||||
* @param cookie_name cookie名,例如MY_SESSION
|
||||
* @param uid 用户id
|
||||
* @param cookie cookie随机字符串
|
||||
* Triggered when constructing a cookie object, the purpose is to record multiple cookies under a certain account
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param uid user id
|
||||
* @param cookie cookie random string
|
||||
|
||||
* [AUTO-TRANSLATED:bb2bb670]
|
||||
*/
|
||||
void onAddCookie(const std::string &cookie_name, const std::string &uid, const std::string &cookie);
|
||||
|
||||
@@ -212,6 +312,12 @@ private:
|
||||
* @param cookie_name cookie名,例如MY_SESSION
|
||||
* @param uid 用户id
|
||||
* @param cookie cookie随机字符串
|
||||
* Triggered when destructing a cookie object
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param uid user id
|
||||
* @param cookie cookie random string
|
||||
|
||||
* [AUTO-TRANSLATED:bdf9cce5]
|
||||
*/
|
||||
void onDelCookie(const std::string &cookie_name, const std::string &uid, const std::string &cookie);
|
||||
|
||||
@@ -221,6 +327,13 @@ private:
|
||||
* @param uid 用户id
|
||||
* @param max_client 最多登录的设备个数
|
||||
* @return 最早的cookie随机字符串
|
||||
* Get the cookie that logged in first under a certain username, the purpose is to implement the function that at most several devices can log in under a certain user
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param uid user id
|
||||
* @param max_client the maximum number of devices that can log in
|
||||
* @return the earliest cookie random string
|
||||
|
||||
* [AUTO-TRANSLATED:431b0732]
|
||||
*/
|
||||
std::string getOldestCookie(const std::string &cookie_name, const std::string &uid, int max_client = 1);
|
||||
|
||||
@@ -229,6 +342,12 @@ private:
|
||||
* @param cookie_name cookie名,例如MY_SESSION
|
||||
* @param cookie cookie随机字符串
|
||||
* @return 成功true
|
||||
* Delete cookie
|
||||
* @param cookie_name cookie name, such as MY_SESSION
|
||||
* @param cookie cookie random string
|
||||
* @return success true
|
||||
|
||||
* [AUTO-TRANSLATED:09fa1e44]
|
||||
*/
|
||||
bool delCookie(const std::string &cookie_name, const std::string &cookie);
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ void HttpDownloader::startDownload(const string &url, const string &file_path, b
|
||||
if (append) {
|
||||
auto currentLen = ftell(_save_file);
|
||||
if (currentLen) {
|
||||
//最少续传一个字节,怕遇到http 416的错误
|
||||
// 最少续传一个字节,怕遇到http 416的错误 [AUTO-TRANSLATED:8a3c5303]
|
||||
// Resume downloading at least one byte to avoid encountering a http 416 error
|
||||
currentLen -= 1;
|
||||
fseek(_save_file, -1, SEEK_CUR);
|
||||
}
|
||||
@@ -45,7 +46,8 @@ void HttpDownloader::startDownload(const string &url, const string &file_path, b
|
||||
|
||||
void HttpDownloader::onResponseHeader(const string &status, const HttpHeader &headers) {
|
||||
if (status != "200" && status != "206") {
|
||||
//失败
|
||||
// 失败 [AUTO-TRANSLATED:27ec5fb1]
|
||||
// Failure
|
||||
throw std::invalid_argument("bad http status: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ public:
|
||||
* @param url 下载http url
|
||||
* @param file_path 文件保存地址,置空则选择默认文件路径
|
||||
* @param append 如果文件已经存在,是否断点续传方式下载
|
||||
* Start downloading the file, default to resume download
|
||||
* @param url Download http url
|
||||
* @param file_path File save address, leave blank to choose the default file path
|
||||
* @param append If the file already exists, whether to download in resume mode
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:6f651882]
|
||||
*/
|
||||
void startDownload(const std::string &url, const std::string &file_path = "", bool append = false);
|
||||
|
||||
|
||||
@@ -23,9 +23,12 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
// hls的播放cookie缓存时间默认60秒,
|
||||
// 每次访问一次该cookie,那么将重新刷新cookie有效期
|
||||
// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权
|
||||
// hls的播放cookie缓存时间默认60秒, [AUTO-TRANSLATED:88198dfa]
|
||||
// The default cache time for the hls playback cookie is 60 seconds.
|
||||
// 每次访问一次该cookie,那么将重新刷新cookie有效期 [AUTO-TRANSLATED:a1b76209]
|
||||
// Each time this cookie is accessed, the cookie's validity period will be refreshed.
|
||||
// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权 [AUTO-TRANSLATED:55000c94]
|
||||
// If the player does not access the cookie within 60 seconds, the hls playback authentication will be triggered again.
|
||||
static size_t kHlsCookieSecond = 60;
|
||||
static size_t kFindSrcIntervalSecond = 3;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
@@ -33,15 +36,20 @@ static const string kHlsSuffix = "/hls.m3u8";
|
||||
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
|
||||
|
||||
struct HttpCookieAttachment {
|
||||
// 是否已经查找到过MediaSource
|
||||
// 是否已经查找到过MediaSource [AUTO-TRANSLATED:b5b9922a]
|
||||
// Whether the MediaSource has been found
|
||||
bool _find_src = false;
|
||||
// 查找MediaSource计时
|
||||
// 查找MediaSource计时 [AUTO-TRANSLATED:39904ba9]
|
||||
// MediaSource search timing
|
||||
Ticker _find_src_ticker;
|
||||
//cookie生效作用域,本cookie只对该目录下的文件生效
|
||||
// cookie生效作用域,本cookie只对该目录下的文件生效 [AUTO-TRANSLATED:7a59ad9a]
|
||||
// Cookie effective scope, this cookie only takes effect for files under this directory
|
||||
string _path;
|
||||
//上次鉴权失败信息,为空则上次鉴权成功
|
||||
// 上次鉴权失败信息,为空则上次鉴权成功 [AUTO-TRANSLATED:de48b753]
|
||||
// Last authentication failure information, empty means last authentication succeeded
|
||||
string _err_msg;
|
||||
//hls直播时的其他一些信息,主要用于播放器个数计数以及流量计数
|
||||
// hls直播时的其他一些信息,主要用于播放器个数计数以及流量计数 [AUTO-TRANSLATED:790de53a]
|
||||
// Other information during hls live broadcast, mainly used for player count and traffic count
|
||||
HlsCookieData::Ptr _hls_data;
|
||||
};
|
||||
|
||||
@@ -182,11 +190,13 @@ static string searchIndexFile(const string &dir) {
|
||||
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
|
||||
GET_CONFIG(bool, dirMenu, Http::kDirMenu);
|
||||
if (!dirMenu) {
|
||||
//不允许浏览文件夹
|
||||
// 不允许浏览文件夹 [AUTO-TRANSLATED:a0c30a94]
|
||||
// Not allowed to browse folders
|
||||
return false;
|
||||
}
|
||||
string strPathPrefix(strFullPath);
|
||||
//url后缀有没有'/'访问文件夹,处理逻辑不一致
|
||||
// url后缀有没有'/'访问文件夹,处理逻辑不一致 [AUTO-TRANSLATED:39c6a933]
|
||||
// Whether the url suffix has '/' to access the folder, the processing logic is inconsistent
|
||||
string last_dir_name;
|
||||
if (strPathPrefix.back() == '/') {
|
||||
strPathPrefix.pop_back();
|
||||
@@ -231,7 +241,8 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
|
||||
file_map.emplace(strCoding::UrlEncodePath(name), std::make_pair(name, path));
|
||||
return true;
|
||||
});
|
||||
//如果是root目录,添加虚拟目录
|
||||
// 如果是root目录,添加虚拟目录 [AUTO-TRANSLATED:3149d7f9]
|
||||
// If it is the root directory, add a virtual directory
|
||||
if (httpPath == "/") {
|
||||
GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) {
|
||||
return Parser::parseArgs(str, ";", ",");
|
||||
@@ -246,7 +257,8 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
|
||||
bool isDir = File::is_dir(strAbsolutePath);
|
||||
ss << "<li><span>" << i++ << "</span>\t";
|
||||
ss << "<a href=\"";
|
||||
//路径链接地址
|
||||
// 路径链接地址 [AUTO-TRANSLATED:33bc5f41]
|
||||
// Path link address
|
||||
if (!last_dir_name.empty()) {
|
||||
ss << last_dir_name << "/" << pr.first;
|
||||
} else {
|
||||
@@ -257,13 +269,15 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
|
||||
ss << "/";
|
||||
}
|
||||
ss << "\">";
|
||||
//路径名称
|
||||
// 路径名称 [AUTO-TRANSLATED:4dae8790]
|
||||
// Path name
|
||||
ss << pr.second.first;
|
||||
if (isDir) {
|
||||
ss << "/</a></li>\r\n";
|
||||
continue;
|
||||
}
|
||||
//是文件
|
||||
// 是文件 [AUTO-TRANSLATED:70473f2f]
|
||||
// It's a file
|
||||
auto fileSize = File::fileSize(strAbsolutePath);
|
||||
if (fileSize < 1024) {
|
||||
ss << " (" << fileSize << "B)" << endl;
|
||||
@@ -282,16 +296,20 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
|
||||
return true;
|
||||
}
|
||||
|
||||
//拦截hls的播放请求
|
||||
// 拦截hls的播放请求 [AUTO-TRANSLATED:dd1bbeec]
|
||||
// Intercept the hls playback request
|
||||
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, const HttpSession::HttpAccessPathInvoker &invoker,Session &sender){
|
||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||
// 访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件 [AUTO-TRANSLATED:b7a67c84]
|
||||
// The hls.m3u8 ending of the access, we convert it to the kBroadcastMediaPlayed event
|
||||
Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) {
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
// cookie有效期为kHlsCookieSecond [AUTO-TRANSLATED:a0026dcd]
|
||||
// The cookie validity period is kHlsCookieSecond
|
||||
invoker(err, "", kHlsCookieSecond);
|
||||
};
|
||||
bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender);
|
||||
if (!flag) {
|
||||
//未开启鉴权,那么允许播放
|
||||
// 未开启鉴权,那么允许播放 [AUTO-TRANSLATED:077feed1]
|
||||
// Authentication is not enabled, so playback is allowed
|
||||
auth_invoker("");
|
||||
}
|
||||
return flag;
|
||||
@@ -335,19 +353,31 @@ public:
|
||||
* 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件
|
||||
* 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码
|
||||
* 5、触发kBroadcastHttpAccess事件
|
||||
* The logical steps to determine whether the http client has permission to access the file
|
||||
* 1. Find the cookie according to the http request header, find it and enter step 3
|
||||
* 2. Find the cookie according to the http url parameter, if the cookie is still not found, enter step 5
|
||||
* 3. Whether the cookie mark has permission to access the file, if it has permission, return the file directly
|
||||
* 4. Whether the url parameter recorded in the cookie is consistent with the current url parameter, if it is consistent, return the client error code directly
|
||||
* 5. Trigger the kBroadcastHttpAccess event
|
||||
|
||||
* [AUTO-TRANSLATED:dfc0f15f]
|
||||
*/
|
||||
static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir,
|
||||
const function<void(const string &err_msg, const HttpServerCookie::Ptr &cookie)> &callback) {
|
||||
//获取用户唯一id
|
||||
// 获取用户唯一id [AUTO-TRANSLATED:5b1cf4bf]
|
||||
// Get the user's unique id
|
||||
auto uid = parser.params();
|
||||
auto path = parser.url();
|
||||
|
||||
//先根据http头中的cookie字段获取cookie
|
||||
// 先根据http头中的cookie字段获取cookie [AUTO-TRANSLATED:155cf682]
|
||||
// First get the cookie according to the cookie field in the http header
|
||||
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader());
|
||||
//是否需要更新cookie
|
||||
// 是否需要更新cookie [AUTO-TRANSLATED:b95121d5]
|
||||
// Whether to update the cookie
|
||||
bool update_cookie = false;
|
||||
if (!cookie && !uid.empty()) {
|
||||
//客户端请求中无cookie,再根据该用户的用户id获取cookie
|
||||
// 客户端请求中无cookie,再根据该用户的用户id获取cookie [AUTO-TRANSLATED:42cb8ade]
|
||||
// There is no cookie in the client request, then get the cookie according to the user id of the user
|
||||
cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid);
|
||||
update_cookie = true;
|
||||
}
|
||||
@@ -355,25 +385,31 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||
if (cookie) {
|
||||
auto& attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
if (path.find(attach._path) == 0) {
|
||||
//上次cookie是限定本目录
|
||||
// 上次cookie是限定本目录 [AUTO-TRANSLATED:a5c40abf]
|
||||
// The last cookie is limited to this directory
|
||||
if (attach._err_msg.empty()) {
|
||||
//上次鉴权成功
|
||||
// 上次鉴权成功 [AUTO-TRANSLATED:1a23f781]
|
||||
// Last authentication succeeded
|
||||
if (attach._hls_data) {
|
||||
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
|
||||
// 如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新) [AUTO-TRANSLATED:02acac59]
|
||||
// If the playback is hls, then refresh the hls cookie (getting the ts file will also refresh)
|
||||
cookie->updateTime();
|
||||
update_cookie = true;
|
||||
}
|
||||
callback("", update_cookie ? cookie : nullptr);
|
||||
return;
|
||||
}
|
||||
//上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
|
||||
// 上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下 [AUTO-TRANSLATED:df9bd345]
|
||||
// Last authentication failed, but if the url parameter changes, then re-authenticate
|
||||
if (parser.params().empty() || parser.params() == cookie->getUid()) {
|
||||
//url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
|
||||
// url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限 [AUTO-TRANSLATED:f46b4fca]
|
||||
// The url parameter has not changed, or there is no url parameter at all, then determine that the current request is a duplicate request and has no access permission
|
||||
callback(attach._err_msg, update_cookie ? cookie : nullptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权
|
||||
// 如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权 [AUTO-TRANSLATED:acf6d49e]
|
||||
// If the url parameter changes or is not limited to this directory, then the old cookie expires and re-authentication is required
|
||||
HttpCookieManager::Instance().delCookie(cookie);
|
||||
}
|
||||
|
||||
@@ -386,25 +422,31 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||
info->_local_ip = sender.get_local_ip();
|
||||
info->_local_port = sender.get_local_port();
|
||||
|
||||
//该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
|
||||
// 该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录 [AUTO-TRANSLATED:8f4b3dd2]
|
||||
// This user has never obtained a cookie, at this time we broadcast whether to allow this user to access this http directory
|
||||
HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, media_info, info]
|
||||
(const string &err_msg, const string &cookie_path_in, int life_second) {
|
||||
HttpServerCookie::Ptr cookie;
|
||||
if (life_second) {
|
||||
//本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
|
||||
// 本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中 [AUTO-TRANSLATED:5a12f48e]
|
||||
// This authentication has an expiration date, we cache the authentication result in the cookie
|
||||
string cookie_path = cookie_path_in;
|
||||
if (cookie_path.empty()) {
|
||||
//如果未设置鉴权目录,那么我们采用当前目录
|
||||
// 如果未设置鉴权目录,那么我们采用当前目录 [AUTO-TRANSLATED:701ada2d]
|
||||
// If no authentication directory is set, we use the current directory
|
||||
cookie_path = is_dir ? path : path.substr(0, path.rfind("/") + 1);
|
||||
}
|
||||
|
||||
auto attach = std::make_shared<HttpCookieAttachment>();
|
||||
//记录用户能访问的路径
|
||||
// 记录用户能访问的路径 [AUTO-TRANSLATED:80a2ba33]
|
||||
// Record the paths that the user can access
|
||||
attach->_path = cookie_path;
|
||||
//记录能否访问
|
||||
// 记录能否访问 [AUTO-TRANSLATED:972f6fc5]
|
||||
// Record whether access is allowed
|
||||
attach->_err_msg = err_msg;
|
||||
if (is_hls) {
|
||||
// hls相关信息
|
||||
// hls相关信息 [AUTO-TRANSLATED:37893a71]
|
||||
// hls related information
|
||||
attach->_hls_data = std::make_shared<HlsCookieData>(media_info, info);
|
||||
}
|
||||
toolkit::Any any;
|
||||
@@ -416,21 +458,27 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||
};
|
||||
|
||||
if (is_hls) {
|
||||
//是hls的播放鉴权,拦截之
|
||||
// 是hls的播放鉴权,拦截之 [AUTO-TRANSLATED:c5ba86bb]
|
||||
// This is hls playback authentication, intercept it
|
||||
emitHlsPlayed(parser, media_info, accessPathInvoker, sender);
|
||||
return;
|
||||
}
|
||||
|
||||
// 事件未被拦截,则认为是http下载请求
|
||||
// 事件未被拦截,则认为是http下载请求 [AUTO-TRANSLATED:7d449ccc]
|
||||
// The event was not intercepted, it is considered an http download request
|
||||
bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
|
||||
if (!flag) {
|
||||
// 此事件无人监听,我们默认都有权限访问
|
||||
// 此事件无人监听,我们默认都有权限访问 [AUTO-TRANSLATED:e1524c0f]
|
||||
// No one is listening to this event, we assume that everyone has permission to access it by default
|
||||
callback("", nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送404 Not Found
|
||||
* Send 404 Not Found
|
||||
|
||||
* [AUTO-TRANSLATED:1297f2e7]
|
||||
*/
|
||||
static void sendNotFound(const HttpFileManager::invoker &cb) {
|
||||
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||
@@ -439,6 +487,9 @@ static void sendNotFound(const HttpFileManager::invoker &cb) {
|
||||
|
||||
/**
|
||||
* 拼接文件路径
|
||||
* Concatenate the file path
|
||||
|
||||
* [AUTO-TRANSLATED:cf6f5c53]
|
||||
*/
|
||||
static string pathCat(const string &a, const string &b){
|
||||
if (a.back() == '/') {
|
||||
@@ -454,16 +505,26 @@ static string pathCat(const string &a, const string &b){
|
||||
* @param media_info http url信息
|
||||
* @param file_path 文件绝对路径
|
||||
* @param cb 回调对象
|
||||
* Access the file
|
||||
* @param sender Event trigger
|
||||
* @param parser http request
|
||||
* @param media_info http url information
|
||||
* @param file_path Absolute file path
|
||||
* @param cb Callback object
|
||||
|
||||
* [AUTO-TRANSLATED:2d840fe6]
|
||||
*/
|
||||
static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) {
|
||||
bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix);
|
||||
if (!is_hls && !File::fileExist(file_path)) {
|
||||
//文件不存在且不是hls,那么直接返回404
|
||||
// 文件不存在且不是hls,那么直接返回404 [AUTO-TRANSLATED:7aae578b]
|
||||
// The file does not exist and is not hls, so directly return 404
|
||||
sendNotFound(cb);
|
||||
return;
|
||||
}
|
||||
if (is_hls) {
|
||||
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS [AUTO-TRANSLATED:94b5818a]
|
||||
// hls, then remove the suffix to get the real stream_id and change the protocol to HLS
|
||||
if (end_with(file_path, kHlsSuffix)) {
|
||||
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
|
||||
@@ -474,15 +535,18 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
||||
}
|
||||
|
||||
weak_ptr<Session> weakSession = static_pointer_cast<Session>(sender.shared_from_this());
|
||||
//判断是否有权限访问该文件
|
||||
// 判断是否有权限访问该文件 [AUTO-TRANSLATED:b7f595f5]
|
||||
// Determine whether you have permission to access this file
|
||||
canAccessPath(sender, parser, media_info, false, [cb, file_path, parser, is_hls, media_info, weakSession](const string &err_msg, const HttpServerCookie::Ptr &cookie) {
|
||||
auto strongSession = weakSession.lock();
|
||||
if (!strongSession) {
|
||||
// http客户端已经断开,不需要回复
|
||||
// http客户端已经断开,不需要回复 [AUTO-TRANSLATED:9a252e21]
|
||||
// The http client has disconnected and does not need to reply
|
||||
return;
|
||||
}
|
||||
if (!err_msg.empty()) {
|
||||
//文件鉴权失败
|
||||
// 文件鉴权失败 [AUTO-TRANSLATED:0feb8885]
|
||||
// File authentication failed
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
headerOut["Set-Cookie"] = cookie->getCookie(cookie->getAttach<HttpCookieAttachment>()._path);
|
||||
@@ -519,7 +583,8 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
||||
};
|
||||
|
||||
if (!is_hls || !cookie) {
|
||||
//不是hls或访问m3u8文件不带cookie, 直接回复文件或404
|
||||
// 不是hls或访问m3u8文件不带cookie, 直接回复文件或404 [AUTO-TRANSLATED:64e5d19b]
|
||||
// Not hls or accessing m3u8 files without cookies, directly reply to the file or 404
|
||||
response_file(cookie, cb, file_path, parser);
|
||||
if (is_hls) {
|
||||
WarnL << "access m3u8 file without cookie:" << file_path;
|
||||
@@ -530,36 +595,44 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
||||
auto &attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
auto src = attach._hls_data->getMediaSource();
|
||||
if (src) {
|
||||
// 直接从内存获取m3u8索引文件(而不是从文件系统)
|
||||
// 直接从内存获取m3u8索引文件(而不是从文件系统) [AUTO-TRANSLATED:c772e342]
|
||||
// Get the m3u8 index file directly from memory (instead of from the file system)
|
||||
response_file(cookie, cb, file_path, parser, src->getIndexFile());
|
||||
return;
|
||||
}
|
||||
if (attach._find_src && attach._find_src_ticker.elapsedTime() < kFindSrcIntervalSecond * 1000) {
|
||||
// 最近已经查找过MediaSource了,为了防止频繁查找导致占用全局互斥锁的问题,我们尝试直接从磁盘返回hls索引文件
|
||||
// 最近已经查找过MediaSource了,为了防止频繁查找导致占用全局互斥锁的问题,我们尝试直接从磁盘返回hls索引文件 [AUTO-TRANSLATED:a33d5e4d]
|
||||
// MediaSource has been searched recently, in order to prevent frequent searches from occupying the global mutex, we try to return the hls index file directly from the disk
|
||||
response_file(cookie, cb, file_path, parser);
|
||||
return;
|
||||
}
|
||||
|
||||
//hls流可能未注册,MediaSource::findAsync可以触发not_found事件,然后再按需推拉流
|
||||
// hls流可能未注册,MediaSource::findAsync可以触发not_found事件,然后再按需推拉流 [AUTO-TRANSLATED:f4acd717]
|
||||
// The hls stream may not be registered, MediaSource::findAsync can trigger the not_found event, and then push and pull the stream on demand
|
||||
MediaSource::findAsync(media_info, strongSession, [response_file, cookie, cb, file_path, parser](const MediaSource::Ptr &src) {
|
||||
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
|
||||
if (!hls) {
|
||||
//流不在线
|
||||
// 流不在线 [AUTO-TRANSLATED:5a6a5695]
|
||||
// The stream is not online
|
||||
response_file(cookie, cb, file_path, parser);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
attach._hls_data->setMediaSource(hls);
|
||||
// 添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
|
||||
// 添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成) [AUTO-TRANSLATED:bd98e100]
|
||||
// Add the number of viewers of HlsMediaSource (HLS is generated on demand, so this can trigger the generation of HLS files)
|
||||
attach._hls_data->addByteUsage(0);
|
||||
// 标记找到MediaSource
|
||||
// 标记找到MediaSource [AUTO-TRANSLATED:1e298005]
|
||||
// Mark that MediaSource has been found
|
||||
attach._find_src = true;
|
||||
|
||||
// 重置查找MediaSource计时
|
||||
// 重置查找MediaSource计时 [AUTO-TRANSLATED:d1e47e07]
|
||||
// Reset the MediaSource search timer
|
||||
attach._find_src_ticker.resetTime();
|
||||
|
||||
// m3u8文件可能不存在, 等待m3u8索引文件按需生成
|
||||
// m3u8文件可能不存在, 等待m3u8索引文件按需生成 [AUTO-TRANSLATED:0dbd4df2]
|
||||
// The m3u8 file may not exist, wait for the m3u8 index file to be generated on demand
|
||||
hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) {
|
||||
response_file(cookie, cb, file_path, parser, file);
|
||||
});
|
||||
@@ -577,28 +650,33 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
|
||||
string url, path, virtual_app;
|
||||
auto it = virtualPathMap.find(media_info.app);
|
||||
if (it != virtualPathMap.end()) {
|
||||
//访问的是virtualPath
|
||||
// 访问的是virtualPath [AUTO-TRANSLATED:a36c7b20]
|
||||
// Accessing virtualPath
|
||||
path = it->second;
|
||||
url = parser.url().substr(1 + media_info.app.size());
|
||||
virtual_app = media_info.app + "/";
|
||||
} else {
|
||||
//访问的是rootPath
|
||||
// 访问的是rootPath [AUTO-TRANSLATED:600765f0]
|
||||
// Accessing rootPath
|
||||
path = rootPath;
|
||||
url = parser.url();
|
||||
}
|
||||
for (auto &ch : url) {
|
||||
if (ch == '\\') {
|
||||
//如果url中存在"\",这种目录是Windows样式的;需要批量转换为标准的"/"; 防止访问目录权限外的文件
|
||||
// 如果url中存在"\",这种目录是Windows样式的;需要批量转换为标准的"/"; 防止访问目录权限外的文件 [AUTO-TRANSLATED:fd6b5900]
|
||||
// If the url contains "\", this directory is in Windows style; it needs to be converted to standard "/" in batches; prevent access to files outside the directory permissions
|
||||
ch = '/';
|
||||
}
|
||||
}
|
||||
auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path);
|
||||
auto http_root = File::absolutePath(enableVhost ? media_info.vhost + "/" : "/", path);
|
||||
if (!start_with(ret, http_root)) {
|
||||
// 访问的http文件不得在http根目录之外
|
||||
// 访问的http文件不得在http根目录之外 [AUTO-TRANSLATED:7d85a8f9]
|
||||
// The accessed http file must not be outside the http root directory
|
||||
throw std::runtime_error("Attempting to access files outside of the http root directory");
|
||||
}
|
||||
// 替换url,防止返回的目录索引网页被注入非法内容
|
||||
// 替换url,防止返回的目录索引网页被注入非法内容 [AUTO-TRANSLATED:463ad1b1]
|
||||
// Replace the url to prevent the returned directory index page from being injected with illegal content
|
||||
const_cast<Parser&>(parser).setUrl("/" + virtual_app + ret.substr(http_root.size()));
|
||||
NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender);
|
||||
return ret;
|
||||
@@ -609,6 +687,12 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
|
||||
* @param sender 事件触发者
|
||||
* @param parser http请求
|
||||
* @param cb 回调对象
|
||||
* Access file or folder
|
||||
* @param sender Event trigger
|
||||
* @param parser http request
|
||||
* @param cb Callback object
|
||||
|
||||
* [AUTO-TRANSLATED:a79c824d]
|
||||
*/
|
||||
void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &cb) {
|
||||
auto fullUrl = "http://" + parser["Host"] + parser.fullUrl();
|
||||
@@ -618,27 +702,33 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi
|
||||
sendNotFound(cb);
|
||||
return;
|
||||
}
|
||||
//访问的是文件夹
|
||||
// 访问的是文件夹 [AUTO-TRANSLATED:279974bb]
|
||||
// Accessing a folder
|
||||
if (File::is_dir(file_path)) {
|
||||
auto indexFile = searchIndexFile(file_path);
|
||||
if (!indexFile.empty()) {
|
||||
// 发现该文件夹下有index文件
|
||||
// 发现该文件夹下有index文件 [AUTO-TRANSLATED:4a697758]
|
||||
// Found index file in this folder
|
||||
file_path = pathCat(file_path, indexFile);
|
||||
if (!File::is_dir(file_path)) {
|
||||
// 不是文件夹
|
||||
// 不是文件夹 [AUTO-TRANSLATED:af893469]
|
||||
// Not a folder
|
||||
parser.setUrl(pathCat(parser.url(), indexFile));
|
||||
accessFile(sender, parser, media_info, file_path, cb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
string strMenu;
|
||||
//生成文件夹菜单索引
|
||||
// 生成文件夹菜单索引 [AUTO-TRANSLATED:04150cc8]
|
||||
// Generate folder menu index
|
||||
if (!makeFolderMenu(parser.url(), file_path, strMenu)) {
|
||||
//文件夹不存在
|
||||
// 文件夹不存在 [AUTO-TRANSLATED:a2dc6c89]
|
||||
// Folder does not exist
|
||||
sendNotFound(cb);
|
||||
return;
|
||||
}
|
||||
//判断是否有权限访问该目录
|
||||
// 判断是否有权限访问该目录 [AUTO-TRANSLATED:963d02a6]
|
||||
// Determine if there is permission to access this directory
|
||||
canAccessPath(sender, parser, media_info, true, [strMenu, cb](const string &err_msg, const HttpServerCookie::Ptr &cookie) mutable{
|
||||
if (!err_msg.empty()) {
|
||||
strMenu = err_msg;
|
||||
@@ -652,7 +742,8 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi
|
||||
return;
|
||||
}
|
||||
|
||||
//访问的是文件
|
||||
// 访问的是文件 [AUTO-TRANSLATED:7a400b3c]
|
||||
// Accessing a file
|
||||
accessFile(sender, parser, media_info, file_path, cb);
|
||||
};
|
||||
|
||||
@@ -697,17 +788,20 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
bool use_mmap,
|
||||
bool is_path) const {
|
||||
if (!is_path) {
|
||||
//file是文件内容
|
||||
// file是文件内容 [AUTO-TRANSLATED:61d0be82]
|
||||
// file is the file content
|
||||
(*this)(200, responseHeader, std::make_shared<HttpStringBody>(file));
|
||||
return;
|
||||
}
|
||||
|
||||
//file是文件路径
|
||||
// file是文件路径 [AUTO-TRANSLATED:28dcac38]
|
||||
// file is the file path
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap &>(responseHeader);
|
||||
auto fileBody = std::make_shared<HttpFileBody>(file, use_mmap);
|
||||
if (fileBody->remainSize() < 0) {
|
||||
//打开文件失败
|
||||
// 打开文件失败 [AUTO-TRANSLATED:1f0405cb]
|
||||
// Failed to open file
|
||||
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||
|
||||
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
|
||||
@@ -716,13 +810,15 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试添加Content-Type
|
||||
// 尝试添加Content-Type [AUTO-TRANSLATED:2c08b371]
|
||||
// Try to add Content-Type
|
||||
httpHeader.emplace("Content-Type", HttpConst::getHttpContentType(file.data()) + "; charset=" + charSet);
|
||||
|
||||
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
||||
int code = 200;
|
||||
if (!strRange.empty()) {
|
||||
//分节下载
|
||||
// 分节下载 [AUTO-TRANSLATED:01920230]
|
||||
// Segmented download
|
||||
code = 206;
|
||||
auto iRangeStart = atoll(findSubString(strRange.data(), "bytes=", "-").data());
|
||||
auto iRangeEnd = atoll(findSubString(strRange.data(), "-", nullptr).data());
|
||||
@@ -730,13 +826,16 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
if (iRangeEnd == 0) {
|
||||
iRangeEnd = fileSize - 1;
|
||||
}
|
||||
//设置文件范围
|
||||
// 设置文件范围 [AUTO-TRANSLATED:aa51fd28]
|
||||
// Set file range
|
||||
fileBody->setRange(iRangeStart, iRangeEnd - iRangeStart + 1);
|
||||
//分节下载返回Content-Range头
|
||||
// 分节下载返回Content-Range头 [AUTO-TRANSLATED:4b78e7b6]
|
||||
// Segmented download returns Content-Range header
|
||||
httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << fileSize << endl);
|
||||
}
|
||||
|
||||
//回复文件
|
||||
// 回复文件 [AUTO-TRANSLATED:5d91a916]
|
||||
// Reply file
|
||||
(*this)(code, httpHeader, fileBody);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ private:
|
||||
|
||||
/**
|
||||
* 该对象用于控制http静态文件夹服务器的访问权限
|
||||
* This object is used to control access permissions for the http static folder server.
|
||||
|
||||
* [AUTO-TRANSLATED:2eb7e5f2]
|
||||
*/
|
||||
class HttpFileManager {
|
||||
public:
|
||||
@@ -51,6 +54,12 @@ public:
|
||||
* @param sender 事件触发者
|
||||
* @param parser http请求
|
||||
* @param cb 回调对象
|
||||
* Access files or folders
|
||||
* @param sender Event trigger
|
||||
* @param parser http request
|
||||
* @param cb Callback object
|
||||
|
||||
* [AUTO-TRANSLATED:669301a8]
|
||||
*/
|
||||
static void onAccessPath(toolkit::Session &sender, Parser &parser, const invoker &cb);
|
||||
|
||||
@@ -58,12 +67,22 @@ public:
|
||||
* 获取mime值
|
||||
* @param name 文件后缀
|
||||
* @return mime值
|
||||
* Get mime value
|
||||
* @param name File suffix
|
||||
* @return mime value
|
||||
|
||||
* [AUTO-TRANSLATED:f1d25b59]
|
||||
*/
|
||||
static const std::string &getContentType(const char *name);
|
||||
|
||||
/**
|
||||
* 该ip是否再白名单中
|
||||
* @param ip 支持ipv4和ipv6
|
||||
* Whether this ip is in the whitelist
|
||||
* @param ip Supports ipv4 and ipv6
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:4c7756c3]
|
||||
*/
|
||||
static bool isIPAllowed(const std::string &ip);
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
using namespace toolkit;
|
||||
using namespace std;
|
||||
|
||||
//协议解析最大缓存4兆数据
|
||||
// 协议解析最大缓存4兆数据 [AUTO-TRANSLATED:75159526]
|
||||
// Protocol parsing maximum cache 4MB data
|
||||
static constexpr size_t kMaxCacheSize = 4 * 1024 * 1024;
|
||||
|
||||
namespace mediakit {
|
||||
@@ -23,7 +24,8 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
|
||||
{
|
||||
auto size = remainDataSize();
|
||||
if (size > _max_cache_size) {
|
||||
//缓存太多数据无法处理则上抛异常
|
||||
// 缓存太多数据无法处理则上抛异常 [AUTO-TRANSLATED:30e48e9e]
|
||||
// If too much data is cached and cannot be processed, throw an exception
|
||||
reset();
|
||||
throw std::out_of_range("remain data size is too huge, now cleared:" + to_string(size));
|
||||
}
|
||||
@@ -41,13 +43,20 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
|
||||
*由于ZLToolKit确保内存最后一个字节是保留未使用字节并置0,
|
||||
*所以此处可以不用再次置0
|
||||
*但是上层数据可能来自其他渠道,保险起见还是置0
|
||||
*Ensure the last byte of ptr is 0 to prevent strstr from going out of bounds
|
||||
* Since ZLToolKit ensures that the last byte of memory is a reserved unused byte and set to 0,
|
||||
* so there is no need to set it to 0 again here
|
||||
* But the upper layer data may come from other channels, so it is better to set it to 0 for safety
|
||||
|
||||
* [AUTO-TRANSLATED:28ff47a5]
|
||||
*/
|
||||
|
||||
char &tail_ref = ((char *) ptr)[len];
|
||||
char tail_tmp = tail_ref;
|
||||
tail_ref = 0;
|
||||
|
||||
//数据按照请求头处理
|
||||
// 数据按照请求头处理 [AUTO-TRANSLATED:e7a0dbb4]
|
||||
// Data is processed according to the request header
|
||||
const char *index = nullptr;
|
||||
_remain_data_size = len;
|
||||
while (_content_len == 0 && _remain_data_size > 0 && (index = onSearchPacketTail(ptr,_remain_data_size)) != nullptr) {
|
||||
@@ -57,7 +66,8 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
|
||||
if (index < ptr || index > ptr + _remain_data_size) {
|
||||
throw std::out_of_range("上层分包逻辑异常");
|
||||
}
|
||||
//_content_len == 0,这是请求头
|
||||
// _content_len == 0,这是请求头 [AUTO-TRANSLATED:32af637b]
|
||||
// _content_len == 0, this is the request header
|
||||
const char *header_ptr = ptr;
|
||||
ssize_t header_size = index - ptr;
|
||||
ptr = index;
|
||||
@@ -68,39 +78,52 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
|
||||
/*
|
||||
* 恢复末尾字节
|
||||
* 移动到这来,目的是防止HttpRequestSplitter::reset()导致内存失效
|
||||
/*
|
||||
* Restore the last byte
|
||||
* Move it here to prevent HttpRequestSplitter::reset() from causing memory failure
|
||||
|
||||
* [AUTO-TRANSLATED:9c3e0597]
|
||||
*/
|
||||
tail_ref = tail_tmp;
|
||||
|
||||
if(_remain_data_size <= 0){
|
||||
//没有剩余数据,清空缓存
|
||||
// 没有剩余数据,清空缓存 [AUTO-TRANSLATED:16613daa]
|
||||
// No remaining data, clear the cache
|
||||
_remain_data.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if(_content_len == 0){
|
||||
//尚未找到http头,缓存定位到剩余数据部分
|
||||
// 尚未找到http头,缓存定位到剩余数据部分 [AUTO-TRANSLATED:7a9d6205]
|
||||
// HTTP header not found yet, cache is located at the remaining data part
|
||||
_remain_data.assign(ptr,_remain_data_size);
|
||||
return;
|
||||
}
|
||||
|
||||
//已经找到http头了
|
||||
// 已经找到http头了 [AUTO-TRANSLATED:df166db7]
|
||||
// HTTP header has been found
|
||||
if(_content_len > 0){
|
||||
//数据按照固定长度content处理
|
||||
// 数据按照固定长度content处理 [AUTO-TRANSLATED:7272b7e7]
|
||||
// Data is processed according to fixed length content
|
||||
if(_remain_data_size < (size_t)_content_len){
|
||||
//数据不够,缓存定位到剩余数据部分
|
||||
// 数据不够,缓存定位到剩余数据部分 [AUTO-TRANSLATED:61c32f5c]
|
||||
// Insufficient data, cache is located at the remaining data part
|
||||
_remain_data.assign(ptr, _remain_data_size);
|
||||
return;
|
||||
}
|
||||
//收到content数据,并且接收content完毕
|
||||
// 收到content数据,并且接收content完毕 [AUTO-TRANSLATED:0342dc0e]
|
||||
// Content data received and content reception completed
|
||||
onRecvContent(ptr,_content_len);
|
||||
|
||||
_remain_data_size -= _content_len;
|
||||
ptr += _content_len;
|
||||
//content处理完毕,后面数据当做请求头处理
|
||||
// content处理完毕,后面数据当做请求头处理 [AUTO-TRANSLATED:d268dfe4]
|
||||
// Content processing completed, subsequent data is treated as request header
|
||||
_content_len = 0;
|
||||
|
||||
if(_remain_data_size > 0){
|
||||
//还有数据没有处理完毕
|
||||
// 还有数据没有处理完毕 [AUTO-TRANSLATED:1cac6727]
|
||||
// There is still data that has not been processed
|
||||
_remain_data.assign(ptr,_remain_data_size);
|
||||
data = ptr = (char *)_remain_data.data();
|
||||
len = _remain_data.size();
|
||||
@@ -111,7 +134,8 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
|
||||
}
|
||||
|
||||
|
||||
//_content_len < 0;数据按照不固定长度content处理
|
||||
// _content_len < 0;数据按照不固定长度content处理 [AUTO-TRANSLATED:68d6a4d0]
|
||||
// _content_len < 0; Data is processed according to variable length content
|
||||
onRecvContent(ptr,_remain_data_size);//消费掉所有剩余数据
|
||||
_remain_data.clear();
|
||||
}
|
||||
|
||||
@@ -26,26 +26,44 @@ public:
|
||||
* @param data 需要添加的数据
|
||||
* @param len 数据长度
|
||||
* @warning 实际内存需保证不小于 len + 1, 内部使用 strstr 进行查找, 为防止查找越界, 会在 @p len + 1 的位置设置 '\0' 结束符.
|
||||
* Add data
|
||||
* @param data Data to be added
|
||||
* @param len Data length
|
||||
* @warning Actual memory must be no less than len + 1. strstr is used internally for searching. To prevent out-of-bounds search, a '\0' terminator is set at the @p len + 1 position.
|
||||
|
||||
* [AUTO-TRANSLATED:3bbfc2ab]
|
||||
*/
|
||||
virtual void input(const char *data, size_t len);
|
||||
|
||||
/**
|
||||
* 恢复初始设置
|
||||
* Restore initial settings
|
||||
|
||||
* [AUTO-TRANSLATED:f797ec5a]
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* 剩余数据大小
|
||||
* Remaining data size
|
||||
|
||||
* [AUTO-TRANSLATED:808a9399]
|
||||
*/
|
||||
size_t remainDataSize();
|
||||
|
||||
/**
|
||||
* 获取剩余数据指针
|
||||
* Get remaining data pointer
|
||||
|
||||
* [AUTO-TRANSLATED:e419f28a]
|
||||
*/
|
||||
const char *remainData() const;
|
||||
|
||||
/**
|
||||
* 设置最大缓存大小
|
||||
* Set maximum cache size
|
||||
|
||||
* [AUTO-TRANSLATED:19333c32]
|
||||
*/
|
||||
void setMaxCacheSize(size_t max_cache_size);
|
||||
|
||||
@@ -59,6 +77,16 @@ protected:
|
||||
* <0 : 代表后面所有数据都是content,此时后面的content将分段通过onRecvContent函数回调出去
|
||||
* 0 : 代表为后面数据还是请求头,
|
||||
* >0 : 代表后面数据为固定长度content,此时将缓存content并等到所有content接收完毕一次性通过onRecvContent函数回调出去
|
||||
* Receive request header
|
||||
* @param data Request header data
|
||||
* @param len Request header length
|
||||
*
|
||||
* @return Content length after request header,
|
||||
* <0 : Represents that all subsequent data is content, in which case the subsequent content will be called back in segments through the onRecvContent function
|
||||
* 0 : Represents that the subsequent data is still the request header,
|
||||
* >0 : Represents that the subsequent data is fixed-length content, in which case the content will be cached and called back through the onRecvContent function once all content is received
|
||||
|
||||
* [AUTO-TRANSLATED:f185e6c5]
|
||||
*/
|
||||
virtual ssize_t onRecvHeader(const char *data,size_t len) = 0;
|
||||
|
||||
@@ -67,6 +95,12 @@ protected:
|
||||
* onRecvHeader函数返回>0,则为全部数据
|
||||
* @param data content分片或全部数据
|
||||
* @param len 数据长度
|
||||
* Receive content fragments or all data
|
||||
* onRecvHeader function returns >0, then it is all data
|
||||
* @param data Content fragments or all data
|
||||
* @param len Data length
|
||||
|
||||
* [AUTO-TRANSLATED:2ef280fb]
|
||||
*/
|
||||
virtual void onRecvContent(const char *data,size_t len) {};
|
||||
|
||||
@@ -75,11 +109,21 @@ protected:
|
||||
* @param data 数据指针
|
||||
* @param len 数据长度
|
||||
* @return nullptr代表未找到包位,否则返回包尾指针
|
||||
* Determine if there is a packet tail in the data
|
||||
* @param data Data pointer
|
||||
* @param len Data length
|
||||
* @return nullptr represents that the packet position is not found, otherwise returns the packet tail pointer
|
||||
|
||||
* [AUTO-TRANSLATED:f7190dec]
|
||||
*/
|
||||
virtual const char *onSearchPacketTail(const char *data, size_t len);
|
||||
|
||||
/**
|
||||
* 设置content len
|
||||
* Set content len
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:6dce48f8]
|
||||
*/
|
||||
void setContentLen(ssize_t content_len);
|
||||
|
||||
|
||||
@@ -251,7 +251,8 @@ static std::string httpBody() {
|
||||
|
||||
args["rand_str"] = makeRandStr(32);
|
||||
for (auto &pr : mINI::Instance()) {
|
||||
// 只获取转协议相关配置
|
||||
// 只获取转协议相关配置 [AUTO-TRANSLATED:4e1e5840]
|
||||
// Only get the configuration related to protocol conversion
|
||||
if (pr.first.find("protocol.") == 0) {
|
||||
args[pr.first] = pr.second;
|
||||
}
|
||||
@@ -261,21 +262,25 @@ static std::string httpBody() {
|
||||
|
||||
static void sendReport() {
|
||||
static HttpRequester::Ptr requester = std::make_shared<HttpRequester>();
|
||||
// 获取一次静态信息,定时上报主要方便统计在线实例个数
|
||||
// 获取一次静态信息,定时上报主要方便统计在线实例个数 [AUTO-TRANSLATED:1612d609]
|
||||
// Get static information once, and report it regularly to facilitate statistics of the number of online instances
|
||||
static auto body = httpBody();
|
||||
|
||||
requester->setMethod("POST");
|
||||
requester->setBody(body);
|
||||
// http超时时间设置为30秒
|
||||
// http超时时间设置为30秒 [AUTO-TRANSLATED:466f9b71]
|
||||
// Set the http timeout to 30 seconds
|
||||
requester->startRequester(s_report_url, nullptr, 30);
|
||||
}
|
||||
|
||||
static toolkit::onceToken s_token([]() {
|
||||
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) {
|
||||
// 第一次汇报在程序启动后5分钟
|
||||
// 第一次汇报在程序启动后5分钟 [AUTO-TRANSLATED:02e6c508]
|
||||
// The first report is 5 minutes after the program starts
|
||||
pool.getPoller()->doDelayTask(5 * 60 * 1000, []() {
|
||||
sendReport();
|
||||
// 后续每一个小时汇报一次
|
||||
// 后续每一个小时汇报一次 [AUTO-TRANSLATED:0322b8aa]
|
||||
// Report every hour afterwards
|
||||
return 60 * 60 * 1000;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,15 +24,19 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) {
|
||||
//设置默认参数
|
||||
// 设置默认参数 [AUTO-TRANSLATED:ae5b72e6]
|
||||
// Set default parameters
|
||||
setMaxReqSize(0);
|
||||
setTimeoutSec(0);
|
||||
}
|
||||
|
||||
void HttpSession::onHttpRequest_HEAD() {
|
||||
// 暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回
|
||||
// 如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效
|
||||
// 对于按需生成流的直播场景并不适用
|
||||
// 暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回 [AUTO-TRANSLATED:0ce05db5]
|
||||
// Temporarily return 200 OK for all, because HTTP GET has on-demand generation stream operations, so it cannot return according to the HTTP GET process
|
||||
// 如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效 [AUTO-TRANSLATED:ea2b6faa]
|
||||
// If you return 404 directly, it will also cause the on-demand generation stream logic to fail, so HTTP HEAD is only valid for static files or existing resources
|
||||
// 对于按需生成流的直播场景并不适用 [AUTO-TRANSLATED:5a47bf00]
|
||||
// Not applicable to live streaming scenarios that generate streams on demand
|
||||
sendResponse(200, false);
|
||||
}
|
||||
|
||||
@@ -57,7 +61,8 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
|
||||
static onceToken token([]() {
|
||||
s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET);
|
||||
s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST);
|
||||
// DELETE命令用于whip/whep用,只用于触发http api
|
||||
// DELETE命令用于whip/whep用,只用于触发http api [AUTO-TRANSLATED:f3b7aaea]
|
||||
// DELETE command is used for whip/whep, only used to trigger http api
|
||||
s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST);
|
||||
s_func_map.emplace("HEAD", &HttpSession::onHttpRequest_HEAD);
|
||||
s_func_map.emplace("OPTIONS", &HttpSession::onHttpRequest_OPTIONS);
|
||||
@@ -80,27 +85,32 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
|
||||
auto &content_len_str = _parser["Content-Length"];
|
||||
if (content_len_str.empty()) {
|
||||
if (it->first == "POST") {
|
||||
// Http post未指定长度,我们认为是不定长的body
|
||||
// Http post未指定长度,我们认为是不定长的body [AUTO-TRANSLATED:3578206b]
|
||||
// Http post does not specify length, we consider it to be an indefinite length body
|
||||
WarnL << "Received http post request without content-length, consider it to be unlimited length";
|
||||
content_len = SIZE_MAX;
|
||||
} else {
|
||||
content_len = 0;
|
||||
}
|
||||
} else {
|
||||
// 已经指定长度
|
||||
// 已经指定长度 [AUTO-TRANSLATED:a360c374]
|
||||
// Length has been specified
|
||||
content_len = atoll(content_len_str.data());
|
||||
}
|
||||
|
||||
if (content_len == 0) {
|
||||
//// 没有body的情况,直接触发回调 ////
|
||||
// // 没有body的情况,直接触发回调 //// [AUTO-TRANSLATED:f2988336]
|
||||
// // No body case, trigger callback directly ////
|
||||
(this->*(it->second))();
|
||||
_parser.clear();
|
||||
// 如果设置了_on_recv_body, 那么说明后续要处理body
|
||||
// 如果设置了_on_recv_body, 那么说明后续要处理body [AUTO-TRANSLATED:2dac5fc2]
|
||||
// If _on_recv_body is set, it means that the body will be processed later
|
||||
return _on_recv_body ? -1 : 0;
|
||||
}
|
||||
|
||||
if (content_len > _max_req_size) {
|
||||
//// 不定长body或超大body ////
|
||||
// // 不定长body或超大body //// [AUTO-TRANSLATED:8d66ee77]
|
||||
// // Indefinite length body or oversized body ////
|
||||
if (content_len != SIZE_MAX) {
|
||||
WarnL << "Http body size is too huge: " << content_len << " > " << _max_req_size
|
||||
<< ", please set " << Http::kMaxReqSize << " in config.ini file.";
|
||||
@@ -112,30 +122,37 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
|
||||
received += len;
|
||||
onRecvUnlimitedContent(parser, data, len, content_len, received);
|
||||
if (received < content_len) {
|
||||
// 还没收满
|
||||
// 还没收满 [AUTO-TRANSLATED:cecc867e]
|
||||
// Not yet received
|
||||
return true;
|
||||
}
|
||||
|
||||
// 收满了
|
||||
// 收满了 [AUTO-TRANSLATED:0c9cebd7]
|
||||
// Received full
|
||||
setContentLen(0);
|
||||
return false;
|
||||
};
|
||||
// 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存
|
||||
// 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存 [AUTO-TRANSLATED:0012b6c1]
|
||||
// Declare that the following is all body; Http body is buffered in this object, not saved through HttpRequestSplitter
|
||||
return -1;
|
||||
}
|
||||
|
||||
//// body size明确指定且小于最大值的情况 ////
|
||||
// // body size明确指定且小于最大值的情况 //// [AUTO-TRANSLATED:f1f1ee5d]
|
||||
// // Body size is explicitly specified and less than the maximum value ////
|
||||
_on_recv_body = [this, it](const char *data, size_t len) mutable {
|
||||
// 收集body完毕
|
||||
// 收集body完毕 [AUTO-TRANSLATED:981ad2c8]
|
||||
// Body collection complete
|
||||
_parser.setContent(std::string(data, len));
|
||||
(this->*(it->second))();
|
||||
_parser.clear();
|
||||
|
||||
// _on_recv_body置空
|
||||
// _on_recv_body置空 [AUTO-TRANSLATED:437a201a]
|
||||
// _on_recv_body is cleared
|
||||
return false;
|
||||
};
|
||||
|
||||
// 声明body长度,通过HttpRequestSplitter缓存然后一次性回调到_on_recv_body
|
||||
// 声明body长度,通过HttpRequestSplitter缓存然后一次性回调到_on_recv_body [AUTO-TRANSLATED:3b11cfb7]
|
||||
// Declare the body length, cache it through HttpRequestSplitter and then callback to _on_recv_body at once
|
||||
return content_len;
|
||||
}
|
||||
|
||||
@@ -152,7 +169,8 @@ void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||
|
||||
void HttpSession::onError(const SockException &err) {
|
||||
if (_is_live_stream) {
|
||||
// flv/ts播放器
|
||||
// flv/ts播放器 [AUTO-TRANSLATED:5b444fd9]
|
||||
// flv/ts player
|
||||
uint64_t duration = _ticker.createdTime() / 1000;
|
||||
WarnP(this) << "FLV/TS/FMP4播放器(" << _media_info.shortUrl() << ")断开:" << err << ",耗时(s):" << duration;
|
||||
|
||||
@@ -184,7 +202,8 @@ void HttpSession::setMaxReqSize(size_t max_req_size) {
|
||||
|
||||
void HttpSession::onManager() {
|
||||
if (_ticker.elapsedTime() > _keep_alive_sec * 1000) {
|
||||
//http超时
|
||||
// http超时 [AUTO-TRANSLATED:6f2fdd1f]
|
||||
// http timeout
|
||||
shutdown(SockException(Err_timeout, "session timeout"));
|
||||
}
|
||||
}
|
||||
@@ -215,25 +234,32 @@ bool HttpSession::checkWebSocket() {
|
||||
sendResponse(101, false, nullptr, headerOut, nullptr, true);
|
||||
};
|
||||
|
||||
// 判断是否为websocket-flv
|
||||
// 判断是否为websocket-flv [AUTO-TRANSLATED:31682d7a]
|
||||
// Determine whether it is websocket-flv
|
||||
if (checkLiveStreamFlv(res_cb_flv)) {
|
||||
// 这里是websocket-flv直播请求
|
||||
// 这里是websocket-flv直播请求 [AUTO-TRANSLATED:4bea5956]
|
||||
// This is a websocket-flv live request
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断是否为websocket-ts
|
||||
// 判断是否为websocket-ts [AUTO-TRANSLATED:9e8eb374]
|
||||
// Determine whether it is websocket-ts
|
||||
if (checkLiveStreamTS(res_cb)) {
|
||||
// 这里是websocket-ts直播请求
|
||||
// 这里是websocket-ts直播请求 [AUTO-TRANSLATED:8ab08dd6]
|
||||
// This is a websocket-ts live request
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断是否为websocket-fmp4
|
||||
// 判断是否为websocket-fmp4 [AUTO-TRANSLATED:318f793f]
|
||||
// Determine whether it is websocket-fmp4
|
||||
if (checkLiveStreamFMP4(res_cb)) {
|
||||
// 这里是websocket-fmp4直播请求
|
||||
// 这里是websocket-fmp4直播请求 [AUTO-TRANSLATED:ccf0c1e2]
|
||||
// This is a websocket-fmp4 live request
|
||||
return true;
|
||||
}
|
||||
|
||||
// 这是普通的websocket连接
|
||||
// 这是普通的websocket连接 [AUTO-TRANSLATED:754721f8]
|
||||
// This is a normal websocket connection
|
||||
if (!onWebSocketConnect(_parser)) {
|
||||
sendResponse(501, true, nullptr, headerOut);
|
||||
return true;
|
||||
@@ -253,57 +279,69 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix
|
||||
} else {
|
||||
auto prefix_size = url_suffix.size();
|
||||
if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) {
|
||||
// 未找到后缀
|
||||
// 未找到后缀 [AUTO-TRANSLATED:6635499a]
|
||||
// Suffix not found
|
||||
return false;
|
||||
}
|
||||
// url去除特殊后缀
|
||||
// url去除特殊后缀 [AUTO-TRANSLATED:31c0c080]
|
||||
// Remove special suffix from url
|
||||
url.resize(url.size() - prefix_size);
|
||||
}
|
||||
|
||||
// 带参数的url
|
||||
// 带参数的url [AUTO-TRANSLATED:074764b0]
|
||||
// Url with parameters
|
||||
if (!_parser.params().empty()) {
|
||||
url += "?";
|
||||
url += _parser.params();
|
||||
}
|
||||
|
||||
// 解析带上协议+参数完整的url
|
||||
// 解析带上协议+参数完整的url [AUTO-TRANSLATED:5cdc7e68]
|
||||
// Parse the complete url with protocol + parameters
|
||||
_media_info.parse(schema + "://" + _parser["Host"] + url);
|
||||
|
||||
if (_media_info.app.empty() || _media_info.stream.empty()) {
|
||||
// url不合法
|
||||
// url不合法 [AUTO-TRANSLATED:9aad134e]
|
||||
// URL is invalid
|
||||
return false;
|
||||
}
|
||||
|
||||
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
|
||||
// 鉴权结果回调
|
||||
// 鉴权结果回调 [AUTO-TRANSLATED:021df191]
|
||||
// Authentication result callback
|
||||
auto onRes = [cb, weak_self, close_flag](const string &err) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err.empty()) {
|
||||
// 播放鉴权失败
|
||||
// 播放鉴权失败 [AUTO-TRANSLATED:64f99eeb]
|
||||
// Playback authentication failed
|
||||
strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
return;
|
||||
}
|
||||
|
||||
// 异步查找直播流
|
||||
// 异步查找直播流 [AUTO-TRANSLATED:7cde5dac]
|
||||
// Asynchronously find live stream
|
||||
MediaSource::findAsync(strong_self->_media_info, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
if (!src) {
|
||||
// 未找到该流
|
||||
// 未找到该流 [AUTO-TRANSLATED:2699ef82]
|
||||
// Stream not found
|
||||
strong_self->sendNotFound(close_flag);
|
||||
} else {
|
||||
strong_self->_is_live_stream = true;
|
||||
// 触发回调
|
||||
// 触发回调 [AUTO-TRANSLATED:ae2ff258]
|
||||
// Trigger callback
|
||||
cb(src);
|
||||
}
|
||||
});
|
||||
@@ -317,26 +355,31 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix
|
||||
|
||||
auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this);
|
||||
if (!flag) {
|
||||
// 该事件无人监听,默认不鉴权
|
||||
// 该事件无人监听,默认不鉴权 [AUTO-TRANSLATED:e1fbc6ae]
|
||||
// No one is listening to this event, no authentication by default
|
||||
onRes("");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
|
||||
// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2 [AUTO-TRANSLATED:c0174f8f]
|
||||
// http-fmp4 link format: http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
|
||||
return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) {
|
||||
auto fmp4_src = dynamic_pointer_cast<FMP4MediaSource>(src);
|
||||
assert(fmp4_src);
|
||||
if (!cb) {
|
||||
// 找到源,发送http头,负载后续发送
|
||||
// 找到源,发送http头,负载后续发送 [AUTO-TRANSLATED:ac272410]
|
||||
// Found the source, send the http header, and send the load later
|
||||
sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
// 自定义发送http头
|
||||
// 自定义发送http头 [AUTO-TRANSLATED:b8a8f683]
|
||||
// Custom send http header
|
||||
cb();
|
||||
}
|
||||
|
||||
// 直播牺牲延时提升发送性能
|
||||
// 直播牺牲延时提升发送性能 [AUTO-TRANSLATED:7c6616c9]
|
||||
// Live streaming sacrifices delay to improve sending performance
|
||||
setSocketFlags();
|
||||
onWrite(std::make_shared<BufferString>(fmp4_src->getInitSegment()), true);
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
@@ -350,7 +393,8 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
|
||||
_fmp4_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached"));
|
||||
@@ -358,7 +402,8 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
|
||||
_fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
size_t i = 0;
|
||||
@@ -368,20 +413,24 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
|
||||
});
|
||||
}
|
||||
|
||||
// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
|
||||
// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2 [AUTO-TRANSLATED:aa1a9151]
|
||||
// http-ts link format: http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
|
||||
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
|
||||
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
|
||||
assert(ts_src);
|
||||
if (!cb) {
|
||||
// 找到源,发送http头,负载后续发送
|
||||
// 找到源,发送http头,负载后续发送 [AUTO-TRANSLATED:ac272410]
|
||||
// Found the source, send the http header, and send the load later
|
||||
sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
// 自定义发送http头
|
||||
// 自定义发送http头 [AUTO-TRANSLATED:b8a8f683]
|
||||
// Custom send http header
|
||||
cb();
|
||||
}
|
||||
|
||||
// 直播牺牲延时提升发送性能
|
||||
// 直播牺牲延时提升发送性能 [AUTO-TRANSLATED:7c6616c9]
|
||||
// Live streaming sacrifices delay to improve sending performance
|
||||
setSocketFlags();
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
ts_src->pause(false);
|
||||
@@ -394,7 +443,8 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
|
||||
_ts_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
strong_self->shutdown(SockException(Err_shutdown, "ts ring buffer detached"));
|
||||
@@ -402,7 +452,8 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
|
||||
_ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
size_t i = 0;
|
||||
@@ -412,25 +463,30 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
|
||||
});
|
||||
}
|
||||
|
||||
// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
|
||||
// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2 [AUTO-TRANSLATED:7e78aa20]
|
||||
// http-flv link format: http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb) {
|
||||
auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data());
|
||||
return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) {
|
||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||
assert(rtmp_src);
|
||||
if (!cb) {
|
||||
// 找到源,发送http头,负载后续发送
|
||||
// 找到源,发送http头,负载后续发送 [AUTO-TRANSLATED:ac272410]
|
||||
// Found the source, send the http header, and send the load later
|
||||
KeyValue headerOut;
|
||||
headerOut["Cache-Control"] = "no-store";
|
||||
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true);
|
||||
} else {
|
||||
// 自定义发送http头
|
||||
// 自定义发送http头 [AUTO-TRANSLATED:b8a8f683]
|
||||
// Custom send http header
|
||||
cb();
|
||||
}
|
||||
// 直播牺牲延时提升发送性能
|
||||
// 直播牺牲延时提升发送性能 [AUTO-TRANSLATED:7c6616c9]
|
||||
// Live streaming sacrifices delay to improve sending performance
|
||||
setSocketFlags();
|
||||
|
||||
// 非H264/AAC时打印警告日志,防止用户提无效问题
|
||||
// 非H264/AAC时打印警告日志,防止用户提无效问题 [AUTO-TRANSLATED:59ee60df]
|
||||
// Print warning log when it is not H264/AAC, to prevent users from raising invalid issues
|
||||
auto tracks = src->getTracks(false);
|
||||
for (auto &track : tracks) {
|
||||
switch (track->getCodecId()) {
|
||||
@@ -448,34 +504,41 @@ bool HttpSession::checkLiveStreamFlv(const function<void()> &cb) {
|
||||
}
|
||||
|
||||
void HttpSession::onHttpRequest_GET() {
|
||||
// 先看看是否为WebSocket请求
|
||||
// 先看看是否为WebSocket请求 [AUTO-TRANSLATED:98cd3a86]
|
||||
// First check if it is a WebSocket request
|
||||
if (checkWebSocket()) {
|
||||
// 后续都是websocket body数据
|
||||
// 后续都是websocket body数据 [AUTO-TRANSLATED:c4fcbdcf]
|
||||
// The following are all websocket body data
|
||||
_on_recv_body = [this](const char *data, size_t len) {
|
||||
WebSocketSplitter::decode((uint8_t *)data, len);
|
||||
// _contentCallBack是可持续的,后面还要处理后续数据
|
||||
// _contentCallBack是可持续的,后面还要处理后续数据 [AUTO-TRANSLATED:920e8c23]
|
||||
// _contentCallBack is sustainable, and subsequent data needs to be processed later
|
||||
return true;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (emitHttpEvent(false)) {
|
||||
// 拦截http api事件
|
||||
// 拦截http api事件 [AUTO-TRANSLATED:2f5e319d]
|
||||
// Intercept http api events
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamFlv()) {
|
||||
// 拦截http-flv播放器
|
||||
// 拦截http-flv播放器 [AUTO-TRANSLATED:299f6449]
|
||||
// Intercept http-flv player
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamTS()) {
|
||||
// 拦截http-ts播放器
|
||||
// 拦截http-ts播放器 [AUTO-TRANSLATED:d9e303e4]
|
||||
// Intercept http-ts player
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamFMP4()) {
|
||||
// 拦截http-fmp4播放器
|
||||
// 拦截http-fmp4播放器 [AUTO-TRANSLATED:78cdf3a1]
|
||||
// Intercept http-fmp4 player
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -527,7 +590,8 @@ public:
|
||||
static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
|
||||
if (data->_read_complete) {
|
||||
if (data->_close_when_complete) {
|
||||
// 发送完毕需要关闭socket
|
||||
// 发送完毕需要关闭socket [AUTO-TRANSLATED:fe660e55]
|
||||
// Close socket after sending is complete
|
||||
shutdown(data->_session.lock());
|
||||
}
|
||||
return false;
|
||||
@@ -537,13 +601,15 @@ public:
|
||||
data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
|
||||
auto session = data->_session.lock();
|
||||
if (!session) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
session->async([data, sendBuf]() {
|
||||
auto session = data->_session.lock();
|
||||
if (!session) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
onRequestData(data, session, sendBuf);
|
||||
@@ -556,14 +622,17 @@ private:
|
||||
static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr<HttpSession> &session, const Buffer::Ptr &sendBuf) {
|
||||
session->_ticker.resetTime();
|
||||
if (sendBuf && session->send(sendBuf) != -1) {
|
||||
// 文件还未读完,还需要继续发送
|
||||
// 文件还未读完,还需要继续发送 [AUTO-TRANSLATED:c454ca1a]
|
||||
// The file has not been read completely, and needs to be sent continuously
|
||||
if (!session->isSocketBusy()) {
|
||||
// socket还可写,继续请求数据
|
||||
// socket还可写,继续请求数据 [AUTO-TRANSLATED:041df414]
|
||||
// Socket can still write, continue to request data
|
||||
onSocketFlushed(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 文件写完了
|
||||
// 文件写完了 [AUTO-TRANSLATED:a9f8c117]
|
||||
// The file is written
|
||||
data->_read_complete = true;
|
||||
if (!session->isSocketBusy() && data->_close_when_complete) {
|
||||
shutdown(session);
|
||||
@@ -586,18 +655,22 @@ void HttpSession::sendResponse(int code,
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
|
||||
|
||||
// body默认为空
|
||||
// body默认为空 [AUTO-TRANSLATED:527ccb6f]
|
||||
// Body defaults to empty
|
||||
int64_t size = 0;
|
||||
if (body && body->remainSize()) {
|
||||
// 有body,获取body大小
|
||||
// 有body,获取body大小 [AUTO-TRANSLATED:0d5f4b9a]
|
||||
// There is a body, get the body size
|
||||
size = body->remainSize();
|
||||
}
|
||||
|
||||
if (no_content_length) {
|
||||
// http-flv直播是Keep-Alive类型
|
||||
// http-flv直播是Keep-Alive类型 [AUTO-TRANSLATED:0ef3adfe]
|
||||
// Http-flv live broadcast is Keep-Alive type
|
||||
bClose = false;
|
||||
} else if ((size_t)size >= SIZE_MAX || size < 0) {
|
||||
// 不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断
|
||||
// 不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断 [AUTO-TRANSLATED:fc714997]
|
||||
// If the body is not fixed length, then the socket should be closed after sending the body, so that the browser can judge the download completion
|
||||
bClose = true;
|
||||
}
|
||||
|
||||
@@ -620,24 +693,28 @@ void HttpSession::sendResponse(int code,
|
||||
}
|
||||
|
||||
if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) {
|
||||
// 文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||
// 文件长度为固定值,且不是http-flv强制设置Content-Length [AUTO-TRANSLATED:185c02a8]
|
||||
// The file length is a fixed value, and it is not http-flv that forcibly sets Content-Length
|
||||
headerOut["Content-Length"] = to_string(size);
|
||||
}
|
||||
|
||||
if (size && !pcContentType) {
|
||||
// 有body时,设置缺省类型
|
||||
// 有body时,设置缺省类型 [AUTO-TRANSLATED:21c9b233]
|
||||
// When there is a body, set the default type
|
||||
pcContentType = "text/plain";
|
||||
}
|
||||
|
||||
if ((size || no_content_length) && pcContentType) {
|
||||
// 有body时,设置文件类型
|
||||
// 有body时,设置文件类型 [AUTO-TRANSLATED:0dcbeecc]
|
||||
// When there is a body, set the file type
|
||||
string strContentType = pcContentType;
|
||||
strContentType += "; charset=";
|
||||
strContentType += charSet;
|
||||
headerOut.emplace("Content-Type", std::move(strContentType));
|
||||
}
|
||||
|
||||
// 发送http头
|
||||
// 发送http头 [AUTO-TRANSLATED:cca51598]
|
||||
// Send http header
|
||||
string str;
|
||||
str.reserve(256);
|
||||
str += "HTTP/1.1 ";
|
||||
@@ -656,7 +733,8 @@ void HttpSession::sendResponse(int code,
|
||||
_ticker.resetTime();
|
||||
|
||||
if (!size) {
|
||||
// 没有body
|
||||
// 没有body [AUTO-TRANSLATED:bf891e3a]
|
||||
// No body
|
||||
if (bClose) {
|
||||
shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http header completed with status code:" << code));
|
||||
}
|
||||
@@ -664,20 +742,24 @@ void HttpSession::sendResponse(int code,
|
||||
}
|
||||
|
||||
#if 0
|
||||
//sendfile跟共享mmap相比并没有性能上的优势,相反,sendfile还有功能上的缺陷,先屏蔽
|
||||
// sendfile跟共享mmap相比并没有性能上的优势,相反,sendfile还有功能上的缺陷,先屏蔽 [AUTO-TRANSLATED:4de77827]
|
||||
// Sendfile has no performance advantage over shared mmap, on the contrary, sendfile also has functional defects, so it is blocked first
|
||||
if (typeid(*this) == typeid(HttpSession) && !body->sendFile(getSock()->rawFD())) {
|
||||
// http支持sendfile优化
|
||||
// http支持sendfile优化 [AUTO-TRANSLATED:04f691f1]
|
||||
// Http supports sendfile optimization
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
|
||||
if (body->remainSize() > sendBufSize) {
|
||||
// 文件下载提升发送性能
|
||||
// 文件下载提升发送性能 [AUTO-TRANSLATED:500922cc]
|
||||
// File download improves sending performance
|
||||
setSocketFlags();
|
||||
}
|
||||
|
||||
// 发送http body
|
||||
// 发送http body [AUTO-TRANSLATED:e9fc35d6]
|
||||
// Send http body
|
||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(static_pointer_cast<HttpSession>(shared_from_this()), body, bClose);
|
||||
getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); });
|
||||
AsyncSender::onSocketFlushed(data);
|
||||
@@ -692,7 +774,8 @@ void HttpSession::urlDecode(Parser &parser) {
|
||||
|
||||
bool HttpSession::emitHttpEvent(bool doInvoke) {
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
|
||||
/////////////////////异步回复Invoker///////////////////////////////
|
||||
// ///////////////////异步回复Invoker/////////////////////////////// [AUTO-TRANSLATED:6d0c5fda]
|
||||
// ///////////////////Asynchronous reply Invoker///////////////////////////////
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpResponseInvoker invoker = [weak_self, bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body) {
|
||||
auto strong_self = weak_self.lock();
|
||||
@@ -702,17 +785,20 @@ bool HttpSession::emitHttpEvent(bool doInvoke) {
|
||||
strong_self->async([weak_self, bClose, code, headerOut, body]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
// 本对象已经销毁
|
||||
// 本对象已经销毁 [AUTO-TRANSLATED:713e0f23]
|
||||
// This object has been destroyed
|
||||
return;
|
||||
}
|
||||
strong_self->sendResponse(code, bClose, nullptr, headerOut, body);
|
||||
});
|
||||
};
|
||||
///////////////////广播HTTP事件///////////////////////////
|
||||
// /////////////////广播HTTP事件/////////////////////////// [AUTO-TRANSLATED:fff9769c]
|
||||
// /////////////////Broadcast HTTP event///////////////////////////
|
||||
bool consumed = false; // 该事件是否被消费
|
||||
NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this);
|
||||
if (!consumed && doInvoke) {
|
||||
// 该事件无人消费,所以返回404
|
||||
// 该事件无人消费,所以返回404 [AUTO-TRANSLATED:8a890dec]
|
||||
// This event is not consumed, so return 404
|
||||
invoker(404, KeyValue(), HttpBody::Ptr());
|
||||
}
|
||||
return consumed;
|
||||
@@ -738,16 +824,19 @@ void HttpSession::sendNotFound(bool bClose) {
|
||||
void HttpSession::setSocketFlags() {
|
||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||
if (mergeWriteMS > 0) {
|
||||
// 推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||
// 推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高 [AUTO-TRANSLATED:c8ec8fb8]
|
||||
// In push mode, closing TCP_NODELAY will increase the delay of the push end, but the server performance will be improved
|
||||
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
||||
// 播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||
// 播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能 [AUTO-TRANSLATED:7b558ab9]
|
||||
// In playback mode, enabling MSG_MORE will increase the delay, but it can improve sending performance
|
||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
||||
if (flush) {
|
||||
// 需要flush那么一次刷新缓存
|
||||
// 需要flush那么一次刷新缓存 [AUTO-TRANSLATED:8d1ec961]
|
||||
// Need to flush, then flush the cache once
|
||||
HttpSession::setSendFlushFlag(true);
|
||||
}
|
||||
|
||||
@@ -765,7 +854,8 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
||||
}
|
||||
|
||||
if (flush) {
|
||||
// 本次刷新缓存后,下次不用刷新缓存
|
||||
// 本次刷新缓存后,下次不用刷新缓存 [AUTO-TRANSLATED:f56139f7]
|
||||
// After this cache flush, the next time you don't need to flush the cache
|
||||
HttpSession::setSendFlushFlag(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ public:
|
||||
* @param errMsg 如果为空,则代表鉴权通过,否则为错误提示
|
||||
* @param accessPath 运行或禁止访问的根目录
|
||||
* @param cookieLifeSecond 鉴权cookie有效期
|
||||
* @param errMsg If empty, it means authentication passed, otherwise it is an error message
|
||||
* @param accessPath The root directory to run or prohibit access
|
||||
* @param cookieLifeSecond Authentication cookie validity period
|
||||
*
|
||||
* [AUTO-TRANSLATED:2e733a35]
|
||||
**/
|
||||
using HttpAccessPathInvoker = std::function<void(const std::string &errMsg,const std::string &accessPath, int cookieLifeSecond)>;
|
||||
|
||||
@@ -65,6 +70,15 @@ protected:
|
||||
* @param len content分片数据大小
|
||||
* @param totalSize content总大小,如果为0则是不限长度content
|
||||
* @param recvedSize 已收数据大小
|
||||
* Overload for handling indefinite length content
|
||||
* This function can be used to handle large file uploads, http-flv streaming
|
||||
* @param header http request header
|
||||
* @param data content fragment data
|
||||
* @param len content fragment data size
|
||||
* @param totalSize total content size, if 0, it is unlimited length content
|
||||
* @param recvedSize received data size
|
||||
|
||||
* [AUTO-TRANSLATED:ee75080d]
|
||||
*/
|
||||
virtual void onRecvUnlimitedContent(const Parser &header,
|
||||
const char *data,
|
||||
@@ -78,6 +92,11 @@ protected:
|
||||
* websocket客户端连接上事件
|
||||
* @param header http头
|
||||
* @return true代表允许websocket连接,否则拒绝
|
||||
* websocket client connection event
|
||||
* @param header http header
|
||||
* @return true means allow websocket connection, otherwise refuse
|
||||
|
||||
* [AUTO-TRANSLATED:d857fb0f]
|
||||
*/
|
||||
virtual bool onWebSocketConnect(const Parser &header){
|
||||
WarnP(this) << "http server do not support websocket default";
|
||||
@@ -88,16 +107,25 @@ protected:
|
||||
/**
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
* @param buffer websocket协议数据
|
||||
* Callback after sending data for websocket protocol packaging
|
||||
* @param buffer websocket protocol data
|
||||
|
||||
* [AUTO-TRANSLATED:48b3b028]
|
||||
*/
|
||||
void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer) override;
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
* Callback after receiving a complete webSocket data packet
|
||||
* @param header data packet header
|
||||
|
||||
* [AUTO-TRANSLATED:f506a7c5]
|
||||
*/
|
||||
void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override;
|
||||
|
||||
//重载获取客户端ip
|
||||
// 重载获取客户端ip [AUTO-TRANSLATED:6e497ea4]
|
||||
// Overload to get client ip
|
||||
std::string get_peer_ip() override;
|
||||
|
||||
private:
|
||||
@@ -120,7 +148,8 @@ private:
|
||||
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
|
||||
const HttpBody::Ptr &body = nullptr, bool no_content_length = false);
|
||||
|
||||
//设置socket标志
|
||||
// 设置socket标志 [AUTO-TRANSLATED:4086e686]
|
||||
// Set socket flag
|
||||
void setSocketFlags();
|
||||
|
||||
protected:
|
||||
@@ -129,19 +158,24 @@ protected:
|
||||
private:
|
||||
bool _is_live_stream = false;
|
||||
bool _live_over_websocket = false;
|
||||
//超时时间
|
||||
// 超时时间 [AUTO-TRANSLATED:f15e2672]
|
||||
// Timeout
|
||||
size_t _keep_alive_sec = 0;
|
||||
//最大http请求字节大小
|
||||
// 最大http请求字节大小 [AUTO-TRANSLATED:c1fbc8e5]
|
||||
// Maximum http request byte size
|
||||
size_t _max_req_size = 0;
|
||||
//消耗的总流量
|
||||
// 消耗的总流量 [AUTO-TRANSLATED:45ad2785]
|
||||
// Total traffic consumed
|
||||
uint64_t _total_bytes_usage = 0;
|
||||
// http请求中的 Origin字段
|
||||
// http请求中的 Origin字段 [AUTO-TRANSLATED:7b8dd2c0]
|
||||
// Origin field in http request
|
||||
std::string _origin;
|
||||
Parser _parser;
|
||||
toolkit::Ticker _ticker;
|
||||
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
|
||||
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
|
||||
//处理content数据的callback
|
||||
// 处理content数据的callback [AUTO-TRANSLATED:38890e8d]
|
||||
// Callback to handle content data
|
||||
std::function<bool (const char *data,size_t len) > _on_recv_body;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ HttpTSPlayer::HttpTSPlayer(const EventPoller::Ptr &poller) {
|
||||
|
||||
void HttpTSPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &header) {
|
||||
if (status != "200" && status != "206") {
|
||||
// http状态码不符合预期
|
||||
// http状态码不符合预期 [AUTO-TRANSLATED:2b6996f7]
|
||||
// HTTP status code is not as expected
|
||||
throw invalid_argument("bad http status code:" + status);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
//http-ts播发器,未实现ts解复用
|
||||
// http-ts播发器,未实现ts解复用 [AUTO-TRANSLATED:cecbd6e7]
|
||||
// http-ts broadcaster, ts demultiplexing not implemented
|
||||
class HttpTSPlayer : public HttpClientImp {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<HttpTSPlayer>;
|
||||
@@ -27,11 +28,18 @@ public:
|
||||
|
||||
/**
|
||||
* 设置下载完毕或异常断开回调
|
||||
* Set the callback for download completion or abnormal disconnection
|
||||
|
||||
* [AUTO-TRANSLATED:4f25d583]
|
||||
*/
|
||||
void setOnComplete(onComplete cb);
|
||||
|
||||
/**
|
||||
* 设置接收ts包回调
|
||||
* Set the callback for receiving ts packets
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:af3044a1]
|
||||
*/
|
||||
void setOnPacket(TSSegment::onSegment cb);
|
||||
|
||||
|
||||
@@ -22,11 +22,18 @@ public:
|
||||
|
||||
/**
|
||||
* 开始播放
|
||||
* Start playing
|
||||
|
||||
* [AUTO-TRANSLATED:53a212c5]
|
||||
*/
|
||||
void play(const std::string &url) override;
|
||||
|
||||
/**
|
||||
* 停止播放
|
||||
* Stop playing
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:db52bf15]
|
||||
*/
|
||||
void teardown() override;
|
||||
|
||||
|
||||
@@ -48,12 +48,14 @@ void TsPlayerImp::onPlayResult(const SockException &ex) {
|
||||
void TsPlayerImp::onShutdown(const SockException &ex) {
|
||||
while (_demuxer) {
|
||||
try {
|
||||
//shared_from_this()可能抛异常
|
||||
// shared_from_this()可能抛异常 [AUTO-TRANSLATED:6af9bd3c]
|
||||
// shared_from_this() may throw an exception
|
||||
std::weak_ptr<TsPlayerImp> weak_self = static_pointer_cast<TsPlayerImp>(shared_from_this());
|
||||
if (_decoder) {
|
||||
_decoder->flush();
|
||||
}
|
||||
//等待所有frame flush输出后,再触发onShutdown事件
|
||||
// 等待所有frame flush输出后,再触发onShutdown事件 [AUTO-TRANSLATED:93982eb3]
|
||||
// Wait for all frame flush output before triggering the onShutdown event
|
||||
static_pointer_cast<HlsDemuxer>(_demuxer)->pushTask([weak_self, ex]() {
|
||||
if (auto strong_self = weak_self.lock()) {
|
||||
strong_self->_demuxer = nullptr;
|
||||
|
||||
@@ -27,6 +27,11 @@ class HttpWsClient;
|
||||
* 辅助类,用于拦截TcpClient数据发送前的拦截
|
||||
* @tparam ClientType TcpClient派生类
|
||||
* @tparam DataType 这里无用,为了声明友元用
|
||||
* Helper class for intercepting data sent by TcpClient before sending
|
||||
* @tparam ClientType TcpClient derived class
|
||||
* @tparam DataType This is useless, used for declaring friends
|
||||
|
||||
* [AUTO-TRANSLATED:02cc7424]
|
||||
*/
|
||||
template <typename ClientType, WebSocketHeader::Type DataType>
|
||||
class ClientTypeImp : public ClientType {
|
||||
@@ -40,6 +45,9 @@ public:
|
||||
|
||||
/**
|
||||
* 发送前拦截并打包为websocket协议
|
||||
* Intercept before sending and package it into websocket protocol
|
||||
|
||||
* [AUTO-TRANSLATED:b43b6169]
|
||||
*/
|
||||
ssize_t send(toolkit::Buffer::Ptr buf) override {
|
||||
if (_beforeSendCB) {
|
||||
@@ -52,6 +60,10 @@ protected:
|
||||
/**
|
||||
* 设置发送数据截取回调函数
|
||||
* @param cb 截取回调函数
|
||||
* Set the data interception callback function
|
||||
* @param cb Interception callback function
|
||||
|
||||
* [AUTO-TRANSLATED:3e74fcdd]
|
||||
*/
|
||||
void setOnBeforeSendCB(const onBeforeSendCB &cb) { _beforeSendCB = cb; }
|
||||
|
||||
@@ -63,6 +75,11 @@ private:
|
||||
* 此对象完成了weksocket 客户端握手协议,以及到TcpClient派生类事件的桥接
|
||||
* @tparam ClientType TcpClient派生类
|
||||
* @tparam DataType websocket负载类型,是TEXT还是BINARY类型
|
||||
* This object completes the weksocket client handshake protocol and bridges to the TcpClient derived class events
|
||||
* @tparam ClientType TcpClient derived class
|
||||
* @tparam DataType websocket payload type, TEXT or BINARY type
|
||||
|
||||
* [AUTO-TRANSLATED:912c15f6]
|
||||
*/
|
||||
template <typename ClientType, WebSocketHeader::Type DataType = WebSocketHeader::TEXT>
|
||||
class HttpWsClient : public HttpClientImp, public WebSocketSplitter {
|
||||
@@ -78,6 +95,11 @@ public:
|
||||
* 发起ws握手
|
||||
* @param ws_url ws连接url
|
||||
* @param fTimeOutSec 超时时间
|
||||
* Initiate ws handshake
|
||||
* @param ws_url ws connection url
|
||||
* @param fTimeOutSec Timeout time
|
||||
|
||||
* [AUTO-TRANSLATED:453c027c]
|
||||
*/
|
||||
void startWsClient(const std::string &ws_url, float fTimeOutSec) {
|
||||
std::string http_url = ws_url;
|
||||
@@ -95,14 +117,16 @@ public:
|
||||
|
||||
void closeWsClient() {
|
||||
if (!_onRecv) {
|
||||
// 未连接
|
||||
// 未连接 [AUTO-TRANSLATED:94510177]
|
||||
// Not connected
|
||||
return;
|
||||
}
|
||||
WebSocketHeader header;
|
||||
header._fin = true;
|
||||
header._reserved = 0;
|
||||
header._opcode = CLOSE;
|
||||
// 客户端需要加密
|
||||
// 客户端需要加密 [AUTO-TRANSLATED:d6958acf]
|
||||
// Client needs encryption
|
||||
header._mask_flag = true;
|
||||
WebSocketSplitter::encode(header, nullptr);
|
||||
}
|
||||
@@ -114,6 +138,11 @@ protected:
|
||||
* 收到http回复头
|
||||
* @param status 状态码,譬如:200 OK
|
||||
* @param headers http头
|
||||
* Receive http response header
|
||||
* @param status Status code, such as: 200 OK
|
||||
* @param headers http header
|
||||
|
||||
* [AUTO-TRANSLATED:a685f8ef]
|
||||
*/
|
||||
void onResponseHeader(const std::string &status, const HttpHeader &headers) override {
|
||||
if (status == "101") {
|
||||
@@ -121,7 +150,8 @@ protected:
|
||||
if (Sec_WebSocket_Accept == const_cast<HttpHeader &>(headers)["Sec-WebSocket-Accept"]) {
|
||||
// success
|
||||
onWebSocketException(toolkit::SockException());
|
||||
// 防止ws服务器返回Content-Length
|
||||
// 防止ws服务器返回Content-Length [AUTO-TRANSLATED:f4454ae6]
|
||||
// Prevent ws server from returning Content-Length
|
||||
const_cast<HttpHeader &>(headers).erase("Content-Length");
|
||||
return;
|
||||
}
|
||||
@@ -134,15 +164,22 @@ protected:
|
||||
|
||||
/**
|
||||
* 接收http回复完毕,
|
||||
* Receive http response complete,
|
||||
|
||||
* [AUTO-TRANSLATED:b96ed715]
|
||||
*/
|
||||
void onResponseCompleted(const toolkit::SockException &ex) override {}
|
||||
|
||||
/**
|
||||
* 接收websocket负载数据
|
||||
* Receive websocket payload data
|
||||
|
||||
* [AUTO-TRANSLATED:55d403d9]
|
||||
*/
|
||||
void onResponseBody(const char *buf, size_t size) override {
|
||||
if (_onRecv) {
|
||||
// 完成websocket握手后,拦截websocket数据并解析
|
||||
// 完成websocket握手后,拦截websocket数据并解析 [AUTO-TRANSLATED:734280fe]
|
||||
// After completing the websocket handshake, intercept the websocket data and parse it
|
||||
_onRecv(buf, size);
|
||||
}
|
||||
};
|
||||
@@ -155,27 +192,34 @@ protected:
|
||||
|
||||
/**
|
||||
* 定时触发
|
||||
* Triggered periodically
|
||||
|
||||
* [AUTO-TRANSLATED:2a75dbf6]
|
||||
*/
|
||||
void onManager() override {
|
||||
if (_onRecv) {
|
||||
// websocket连接成功了
|
||||
// websocket连接成功了 [AUTO-TRANSLATED:45a9e005]
|
||||
// websocket connection succeeded
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->onManager();
|
||||
}
|
||||
} else {
|
||||
// websocket连接中...
|
||||
// websocket连接中... [AUTO-TRANSLATED:861cb158]
|
||||
// websocket connecting...
|
||||
HttpClientImp::onManager();
|
||||
}
|
||||
|
||||
if (!_onRecv) {
|
||||
// websocket尚未链接
|
||||
// websocket尚未链接 [AUTO-TRANSLATED:164129da]
|
||||
// websocket not yet connected
|
||||
return;
|
||||
}
|
||||
|
||||
if (_recv_ticker.elapsedTime() > 30 * 1000) {
|
||||
shutdown(toolkit::SockException(toolkit::Err_timeout, "websocket timeout"));
|
||||
} else if (_recv_ticker.elapsedTime() > 10 * 1000) {
|
||||
// 没收到回复,每10秒发送次ping 包
|
||||
// 没收到回复,每10秒发送次ping 包 [AUTO-TRANSLATED:31b4dc13]
|
||||
// No response received, send a ping packet every 10 seconds
|
||||
WebSocketHeader header;
|
||||
header._fin = true;
|
||||
header._reserved = 0;
|
||||
@@ -187,37 +231,51 @@ protected:
|
||||
|
||||
/**
|
||||
* 数据全部发送完毕后回调
|
||||
* Callback after all data has been sent
|
||||
|
||||
* [AUTO-TRANSLATED:8b2ba800]
|
||||
*/
|
||||
void onFlush() override {
|
||||
if (_onRecv) {
|
||||
// websocket连接成功了
|
||||
// websocket连接成功了 [AUTO-TRANSLATED:45a9e005]
|
||||
// websocket connection succeeded
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->onFlush();
|
||||
}
|
||||
} else {
|
||||
// websocket连接中...
|
||||
// websocket连接中... [AUTO-TRANSLATED:861cb158]
|
||||
// websocket connecting...
|
||||
HttpClientImp::onFlush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tcp连接结果
|
||||
* tcp connection result
|
||||
|
||||
* [AUTO-TRANSLATED:eaca9fcc]
|
||||
*/
|
||||
void onConnect(const toolkit::SockException &ex) override {
|
||||
if (ex) {
|
||||
// tcp连接失败,直接返回失败
|
||||
// tcp连接失败,直接返回失败 [AUTO-TRANSLATED:dcd81b67]
|
||||
// tcp connection failed, return failure directly
|
||||
onWebSocketException(ex);
|
||||
return;
|
||||
}
|
||||
// 开始websocket握手
|
||||
// 开始websocket握手 [AUTO-TRANSLATED:544a5ba3]
|
||||
// Start websocket handshake
|
||||
HttpClientImp::onConnect(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* tcp连接断开
|
||||
* tcp connection disconnected
|
||||
|
||||
* [AUTO-TRANSLATED:732b0740]
|
||||
*/
|
||||
void onError(const toolkit::SockException &ex) override {
|
||||
// tcp断开或者shutdown导致的断开
|
||||
// tcp断开或者shutdown导致的断开 [AUTO-TRANSLATED:5b6b7ad4]
|
||||
// Disconnection caused by tcp disconnection or shutdown
|
||||
onWebSocketException(ex);
|
||||
}
|
||||
|
||||
@@ -226,6 +284,10 @@ protected:
|
||||
/**
|
||||
* 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePayload回调
|
||||
* @param header 数据包头
|
||||
* Receive a webSocket data packet header, and then continue to trigger the onWebSocketDecodePayload callback
|
||||
* @param header Data packet header
|
||||
|
||||
* [AUTO-TRANSLATED:7bc6b7c6]
|
||||
*/
|
||||
void onWebSocketDecodeHeader(const WebSocketHeader &header) override { _payload_section.clear(); }
|
||||
|
||||
@@ -235,6 +297,13 @@ protected:
|
||||
* @param ptr 负载数据指针
|
||||
* @param len 负载数据长度
|
||||
* @param recved 已接收数据长度(包含本次数据长度),等于header._payload_len时则接受完毕
|
||||
* Receive webSocket data packet payload
|
||||
* @param header Data packet header
|
||||
* @param ptr Payload data pointer
|
||||
* @param len Payload data length
|
||||
* @param recved Received data length (including the current data length), equal to header._payload_len when the reception is complete
|
||||
|
||||
* [AUTO-TRANSLATED:ca056d2e]
|
||||
*/
|
||||
void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, size_t len, size_t recved) override {
|
||||
_payload_section.append((char *)ptr, len);
|
||||
@@ -243,23 +312,30 @@ protected:
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
* Callback after receiving a complete webSocket data packet
|
||||
* @param header Data packet header
|
||||
|
||||
* [AUTO-TRANSLATED:f506a7c5]
|
||||
*/
|
||||
void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override {
|
||||
WebSocketHeader &header = const_cast<WebSocketHeader &>(header_in);
|
||||
auto flag = header._mask_flag;
|
||||
// websocket客户端发送数据需要加密
|
||||
// websocket客户端发送数据需要加密 [AUTO-TRANSLATED:2bbbb390]
|
||||
// websocket client needs to encrypt data sent
|
||||
header._mask_flag = true;
|
||||
_recv_ticker.resetTime();
|
||||
switch (header._opcode) {
|
||||
case WebSocketHeader::CLOSE: {
|
||||
// 服务器主动关闭
|
||||
// 服务器主动关闭 [AUTO-TRANSLATED:5a59e1bf]
|
||||
// Server actively closes
|
||||
WebSocketSplitter::encode(header, nullptr);
|
||||
shutdown(toolkit::SockException(toolkit::Err_eof, "websocket server close the connection"));
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketHeader::PING: {
|
||||
// 心跳包
|
||||
// 心跳包 [AUTO-TRANSLATED:1b4b9ae4]
|
||||
// Heartbeat packet
|
||||
header._opcode = WebSocketHeader::PONG;
|
||||
WebSocketSplitter::encode(header, std::make_shared<toolkit::BufferString>(std::move(_payload_section)));
|
||||
break;
|
||||
@@ -269,25 +345,31 @@ protected:
|
||||
case WebSocketHeader::TEXT:
|
||||
case WebSocketHeader::BINARY: {
|
||||
if (!header._fin) {
|
||||
// 还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出
|
||||
// 还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出 [AUTO-TRANSLATED:0a237b29]
|
||||
// There are subsequent fragment data, we cache the data first, and output it all at once after all fragments are collected
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
if (_payload_cache.size() < MAX_WS_PACKET) {
|
||||
// 还有内存容量缓存分片数据
|
||||
// 还有内存容量缓存分片数据 [AUTO-TRANSLATED:8da8074a]
|
||||
// There is also memory capacity to cache fragment data
|
||||
break;
|
||||
}
|
||||
// 分片缓存太大,需要清空
|
||||
// 分片缓存太大,需要清空 [AUTO-TRANSLATED:a0d9c101]
|
||||
// Fragment cache is too large, need to clear
|
||||
}
|
||||
|
||||
// 最后一个包
|
||||
// 最后一个包 [AUTO-TRANSLATED:82e1bf79]
|
||||
// Last packet
|
||||
if (_payload_cache.empty()) {
|
||||
// 这个包是唯一个分片
|
||||
// 这个包是唯一个分片 [AUTO-TRANSLATED:079a9865]
|
||||
// This packet is the only fragment
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 这个包由多个分片组成
|
||||
// 这个包由多个分片组成 [AUTO-TRANSLATED:27fd75df]
|
||||
// This packet consists of multiple fragments
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
|
||||
@@ -306,14 +388,21 @@ protected:
|
||||
* websocket数据编码回调
|
||||
* @param ptr 数据指针
|
||||
* @param len 数据指针长度
|
||||
* websocket data encoding callback
|
||||
* @param ptr data pointer
|
||||
* @param len data pointer length
|
||||
|
||||
* [AUTO-TRANSLATED:7c940c67]
|
||||
*/
|
||||
void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer) override { HttpClientImp::send(std::move(buffer)); }
|
||||
|
||||
private:
|
||||
void onWebSocketException(const toolkit::SockException &ex) {
|
||||
if (!ex) {
|
||||
// websocket握手成功
|
||||
// 此处截取TcpClient派生类发送的数据并进行websocket协议打包
|
||||
// websocket握手成功 [AUTO-TRANSLATED:bceba441]
|
||||
// websocket handshake successful
|
||||
// 此处截取TcpClient派生类发送的数据并进行websocket协议打包 [AUTO-TRANSLATED:8cae42cd]
|
||||
// Here, the data sent by the TcpClient derived class is intercepted and packaged into the websocket protocol
|
||||
std::weak_ptr<HttpWsClient> weakSelf = std::static_pointer_cast<HttpWsClient>(shared_from_this());
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->setOnBeforeSendCB([weakSelf](const toolkit::Buffer::Ptr &buf) {
|
||||
@@ -323,29 +412,36 @@ private:
|
||||
header._fin = true;
|
||||
header._reserved = 0;
|
||||
header._opcode = DataType;
|
||||
// 客户端需要加密
|
||||
// 客户端需要加密 [AUTO-TRANSLATED:d6958acf]
|
||||
// Client needs encryption
|
||||
header._mask_flag = true;
|
||||
strong_self->WebSocketSplitter::encode(header, buf);
|
||||
}
|
||||
return buf->size();
|
||||
});
|
||||
// 设置sock,否则shutdown等接口都无效
|
||||
// 设置sock,否则shutdown等接口都无效 [AUTO-TRANSLATED:4586b98b]
|
||||
// Set sock, otherwise shutdown and other interfaces are invalid
|
||||
strong_ref->setSock(HttpClientImp::getSock());
|
||||
// 触发连接成功事件
|
||||
// 触发连接成功事件 [AUTO-TRANSLATED:0459f68f]
|
||||
// Trigger connection success event
|
||||
strong_ref->onConnect(ex);
|
||||
}
|
||||
|
||||
// 拦截websocket数据接收
|
||||
// 拦截websocket数据接收 [AUTO-TRANSLATED:fb93bbe9]
|
||||
// Intercept websocket data reception
|
||||
_onRecv = [this](const char *data, size_t len) {
|
||||
// 解析websocket数据包
|
||||
// 解析websocket数据包 [AUTO-TRANSLATED:656b8c89]
|
||||
// Parse websocket data packet
|
||||
this->WebSocketSplitter::decode((uint8_t *)data, len);
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// websocket握手失败或者tcp连接失败或者中途断开
|
||||
// websocket握手失败或者tcp连接失败或者中途断开 [AUTO-TRANSLATED:acf8d1ff]
|
||||
// websocket handshake failed or tcp connection failed or disconnected in the middle
|
||||
if (_onRecv) {
|
||||
// 握手成功之后的中途断开
|
||||
// 握手成功之后的中途断开 [AUTO-TRANSLATED:dd5d412c]
|
||||
// Disconnected in the middle after handshake success
|
||||
_onRecv = nullptr;
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->onError(ex);
|
||||
@@ -353,7 +449,8 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
// websocket握手失败或者tcp连接失败
|
||||
// websocket握手失败或者tcp连接失败 [AUTO-TRANSLATED:3f03cf1f]
|
||||
// websocket handshake failed or tcp connection failed
|
||||
if (auto strong_ref = _weak_delegate.lock()) {
|
||||
strong_ref->onConnect(ex);
|
||||
}
|
||||
@@ -374,6 +471,13 @@ private:
|
||||
* @tparam ClientType TcpClient派生类
|
||||
* @tparam DataType websocket负载类型,是TEXT还是BINARY类型
|
||||
* @tparam useWSS 是否使用ws还是wss连接
|
||||
* Tcp client to WebSocket client template,
|
||||
* Through this template, developers can quickly implement WebSocket protocol packaging without modifying any code of the TcpClient derived class
|
||||
* @tparam ClientType TcpClient derived class
|
||||
* @tparam DataType websocket payload type, is it TEXT or BINARY type
|
||||
* @tparam useWSS Whether to use ws or wss connection
|
||||
|
||||
* [AUTO-TRANSLATED:ac1516b8]
|
||||
*/
|
||||
template <typename ClientType, WebSocketHeader::Type DataType = WebSocketHeader::TEXT, bool useWSS = false>
|
||||
class WebSocketClient : public ClientTypeImp<ClientType, DataType> {
|
||||
@@ -391,14 +495,24 @@ public:
|
||||
* @param iPort websocket服务器端口
|
||||
* @param timeout_sec 超时时间
|
||||
* @param local_port 本地监听端口,此处不起作用
|
||||
* Overload the startConnect method,
|
||||
* The purpose is to replace the TcpClient's connection server behavior, so that it completes the WebSocket handshake first
|
||||
* @param host websocket server ip or domain name
|
||||
* @param iPort websocket server port
|
||||
* @param timeout_sec timeout time
|
||||
* @param local_port local listening port, which does not work here
|
||||
|
||||
* [AUTO-TRANSLATED:1aed295d]
|
||||
*/
|
||||
void startConnect(const std::string &host, uint16_t port, float timeout_sec = 3, uint16_t local_port = 0) override {
|
||||
std::string ws_url;
|
||||
if (useWSS) {
|
||||
// 加密的ws
|
||||
// 加密的ws [AUTO-TRANSLATED:d1385825]
|
||||
// Encrypted ws
|
||||
ws_url = StrPrinter << "wss://" + host << ":" << port << "/";
|
||||
} else {
|
||||
// 明文ws
|
||||
// 明文ws [AUTO-TRANSLATED:71aa82d1]
|
||||
// Plaintext ws
|
||||
ws_url = StrPrinter << "ws://" + host << ":" << port << "/";
|
||||
}
|
||||
startWebSocket(ws_url, timeout_sec);
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
/**
|
||||
* 数据发送拦截器
|
||||
* Data Send Interceptor
|
||||
|
||||
* [AUTO-TRANSLATED:5eaf7060]
|
||||
*/
|
||||
class SendInterceptor{
|
||||
public:
|
||||
@@ -28,6 +31,10 @@ public:
|
||||
/**
|
||||
* 该类实现了Session派生类发送数据的截取
|
||||
* 目的是发送业务数据前进行websocket协议的打包
|
||||
* This class implements the interception of data sent by the Session derived class.
|
||||
* The purpose is to package the websocket protocol before sending business data.
|
||||
|
||||
* [AUTO-TRANSLATED:15c96e5f]
|
||||
*/
|
||||
template <typename SessionType>
|
||||
class SessionTypeImp : public SessionType, public SendInterceptor{
|
||||
@@ -40,6 +47,10 @@ public:
|
||||
/**
|
||||
* 设置发送数据截取回调函数
|
||||
* @param cb 截取回调函数
|
||||
* Set the send data interception callback function
|
||||
* @param cb Interception callback function
|
||||
|
||||
* [AUTO-TRANSLATED:3e74fcdd]
|
||||
*/
|
||||
void setOnBeforeSendCB(const onBeforeSendCB &cb) override {
|
||||
_beforeSendCB = cb;
|
||||
@@ -50,6 +61,11 @@ protected:
|
||||
* 重载send函数截取数据
|
||||
* @param buf 需要截取的数据
|
||||
* @return 数据字节数
|
||||
* Overload the send function to intercept data
|
||||
* @param buf Data to be intercepted
|
||||
* @return Number of data bytes
|
||||
|
||||
* [AUTO-TRANSLATED:d3304949]
|
||||
*/
|
||||
ssize_t send(toolkit::Buffer::Ptr buf) override {
|
||||
if (_beforeSendCB) {
|
||||
@@ -65,7 +81,8 @@ private:
|
||||
template <typename SessionType>
|
||||
class SessionCreator {
|
||||
public:
|
||||
//返回的Session必须派生于SendInterceptor,可以返回null
|
||||
// 返回的Session必须派生于SendInterceptor,可以返回null [AUTO-TRANSLATED:6cc95812]
|
||||
// The returned Session must be derived from SendInterceptor, and can return null
|
||||
toolkit::Session::Ptr operator()(const mediakit::Parser &header, const mediakit::HttpSession &parent, const toolkit::Socket::Ptr &pSock, mediakit::WebSocketHeader::Type &data_type){
|
||||
return std::make_shared<SessionTypeImp<SessionType> >(header,parent,pSock);
|
||||
}
|
||||
@@ -74,20 +91,26 @@ public:
|
||||
/**
|
||||
* 通过该模板类可以透明化WebSocket协议,
|
||||
* 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等
|
||||
* Through this template class, the WebSocket protocol can be transparently implemented.
|
||||
* Users only need to implement specific business protocols under the WebSock protocol, such as the Rtmp protocol based on the WebSocket protocol.
|
||||
|
||||
* [AUTO-TRANSLATED:07e2e8a5]
|
||||
*/
|
||||
template<typename Creator, typename HttpSessionType = mediakit::HttpSession, mediakit::WebSocketHeader::Type DataType = mediakit::WebSocketHeader::TEXT>
|
||||
class WebSocketSessionBase : public HttpSessionType {
|
||||
public:
|
||||
WebSocketSessionBase(const toolkit::Socket::Ptr &pSock) : HttpSessionType(pSock){}
|
||||
|
||||
//收到eof或其他导致脱离TcpServer事件的回调
|
||||
// 收到eof或其他导致脱离TcpServer事件的回调 [AUTO-TRANSLATED:6d48b35c]
|
||||
// Callback when receiving eof or other events that cause disconnection from TcpServer
|
||||
void onError(const toolkit::SockException &err) override{
|
||||
HttpSessionType::onError(err);
|
||||
if(_session){
|
||||
_session->onError(err);
|
||||
}
|
||||
}
|
||||
//每隔一段时间触发,用来做超时管理
|
||||
// 每隔一段时间触发,用来做超时管理 [AUTO-TRANSLATED:823ffe1f]
|
||||
// Triggered every period of time, used for timeout management
|
||||
void onManager() override{
|
||||
if (_session) {
|
||||
_session->onManager();
|
||||
@@ -95,13 +118,15 @@ public:
|
||||
HttpSessionType::onManager();
|
||||
}
|
||||
if (!_session) {
|
||||
// websocket尚未链接
|
||||
// websocket尚未链接 [AUTO-TRANSLATED:164129da]
|
||||
// websocket is not yet connected
|
||||
return;
|
||||
}
|
||||
if (_recv_ticker.elapsedTime() > 30 * 1000) {
|
||||
HttpSessionType::shutdown(toolkit::SockException(toolkit::Err_timeout, "websocket timeout"));
|
||||
} else if (_recv_ticker.elapsedTime() > 10 * 1000) {
|
||||
// 没收到回复,每10秒发送次ping 包
|
||||
// 没收到回复,每10秒发送次ping 包 [AUTO-TRANSLATED:31b4dc13]
|
||||
// No reply received, send a ping packet every 10 seconds
|
||||
mediakit::WebSocketHeader header;
|
||||
header._fin = true;
|
||||
header._reserved = 0;
|
||||
@@ -121,13 +146,20 @@ protected:
|
||||
* websocket客户端连接上事件
|
||||
* @param header http头
|
||||
* @return true代表允许websocket连接,否则拒绝
|
||||
* websocket client connection event
|
||||
* @param header http header
|
||||
* @return true means allowing websocket connection, otherwise refuse
|
||||
|
||||
* [AUTO-TRANSLATED:d857fb0f]
|
||||
*/
|
||||
bool onWebSocketConnect(const mediakit::Parser &header) override{
|
||||
//创建websocket session类
|
||||
// 创建websocket session类 [AUTO-TRANSLATED:099f6963]
|
||||
// Create websocket session class
|
||||
auto data_type = DataType;
|
||||
_session = _creator(header, *this, HttpSessionType::getSock(), data_type);
|
||||
if (!_session) {
|
||||
// 此url不允许创建websocket连接
|
||||
// 此url不允许创建websocket连接 [AUTO-TRANSLATED:47480366]
|
||||
// This url is not allowed to create websocket connection
|
||||
return false;
|
||||
}
|
||||
auto strongServer = _weak_server.lock();
|
||||
@@ -135,7 +167,8 @@ protected:
|
||||
_session->attachServer(*strongServer);
|
||||
}
|
||||
|
||||
//此处截取数据并进行websocket协议打包
|
||||
// 此处截取数据并进行websocket协议打包 [AUTO-TRANSLATED:89053032]
|
||||
// Intercept data here and package it with websocket protocol
|
||||
std::weak_ptr<WebSocketSessionBase> weakSelf = std::static_pointer_cast<WebSocketSessionBase>(HttpSessionType::shared_from_this());
|
||||
std::dynamic_pointer_cast<SendInterceptor>(_session)->setOnBeforeSendCB([weakSelf, data_type](const toolkit::Buffer::Ptr &buf) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
@@ -150,20 +183,28 @@ protected:
|
||||
return buf->size();
|
||||
});
|
||||
|
||||
//允许websocket客户端
|
||||
// 允许websocket客户端 [AUTO-TRANSLATED:3a06f181]
|
||||
// Allow websocket client
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始收到一个webSocket数据包
|
||||
* Start receiving a webSocket data packet
|
||||
|
||||
* [AUTO-TRANSLATED:0f16a5b5]
|
||||
*/
|
||||
void onWebSocketDecodeHeader(const mediakit::WebSocketHeader &packet) override{
|
||||
//新包,原来的包残余数据清空掉
|
||||
// 新包,原来的包残余数据清空掉 [AUTO-TRANSLATED:0fd23412]
|
||||
// New package, the residual data of the original package is cleared
|
||||
_payload_section.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到websocket数据包负载
|
||||
* Receive websocket data packet payload
|
||||
|
||||
* [AUTO-TRANSLATED:b317988d]
|
||||
*/
|
||||
void onWebSocketDecodePayload(const mediakit::WebSocketHeader &packet,const uint8_t *ptr,size_t len,size_t recved) override {
|
||||
_payload_section.append((char *)ptr,len);
|
||||
@@ -172,6 +213,10 @@ protected:
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
* Callback after receiving a complete webSocket data packet
|
||||
* @param header Data packet header
|
||||
|
||||
* [AUTO-TRANSLATED:f506a7c5]
|
||||
*/
|
||||
void onWebSocketDecodeComplete(const mediakit::WebSocketHeader &header_in) override {
|
||||
auto header = const_cast<mediakit::WebSocketHeader&>(header_in);
|
||||
@@ -195,23 +240,29 @@ protected:
|
||||
case mediakit::WebSocketHeader::TEXT:
|
||||
case mediakit::WebSocketHeader::BINARY:{
|
||||
if (!header._fin) {
|
||||
//还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出
|
||||
// 还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出 [AUTO-TRANSLATED:75d21e17]
|
||||
// There is subsequent fragment data, we cache the data first, and output it all at once after all fragments are collected
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
if (_payload_cache.size() < MAX_WS_PACKET) {
|
||||
//还有内存容量缓存分片数据
|
||||
// 还有内存容量缓存分片数据 [AUTO-TRANSLATED:621da1f9]
|
||||
// There is memory capacity to cache fragment data
|
||||
break;
|
||||
}
|
||||
//分片缓存太大,需要清空
|
||||
// 分片缓存太大,需要清空 [AUTO-TRANSLATED:98882d1f]
|
||||
// Fragment cache is too large, need to be cleared
|
||||
}
|
||||
|
||||
//最后一个包
|
||||
// 最后一个包 [AUTO-TRANSLATED:dcf860cf]
|
||||
// Last package
|
||||
if (_payload_cache.empty()) {
|
||||
//这个包是唯一个分片
|
||||
// 这个包是唯一个分片 [AUTO-TRANSLATED:94802e24]
|
||||
// This package is the only fragment
|
||||
_session->onRecv(std::make_shared<mediakit::WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
|
||||
break;
|
||||
}
|
||||
|
||||
//这个包由多个分片组成
|
||||
// 这个包由多个分片组成 [AUTO-TRANSLATED:044123f1]
|
||||
// This package consists of multiple fragments
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
_session->onRecv(std::make_shared<mediakit::WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
|
||||
_payload_cache.clear();
|
||||
@@ -226,6 +277,9 @@ protected:
|
||||
|
||||
/**
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
* Callback after sending data and packaging it with websocket protocol
|
||||
|
||||
* [AUTO-TRANSLATED:3327ce78]
|
||||
*/
|
||||
void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer) override{
|
||||
HttpSessionType::send(std::move(buffer));
|
||||
|
||||
@@ -58,7 +58,8 @@ do{ \
|
||||
void WebSocketSplitter::decode(uint8_t *data, size_t len) {
|
||||
uint8_t *ptr = data;
|
||||
if(!_got_header) {
|
||||
//还没有获取数据头
|
||||
// 还没有获取数据头 [AUTO-TRANSLATED:2b50f282]
|
||||
// No data header has been obtained yet
|
||||
if(!_remain_data.empty()){
|
||||
_remain_data.append((char *)data,len);
|
||||
data = ptr = (uint8_t *)_remain_data.data();
|
||||
@@ -107,7 +108,8 @@ begin_decode:
|
||||
}
|
||||
}
|
||||
|
||||
//进入后面逻辑代表已经获取到了webSocket协议头,
|
||||
// 进入后面逻辑代表已经获取到了webSocket协议头, [AUTO-TRANSLATED:e6bd2556]
|
||||
// Entering the following logic means that the webSocket protocol header has been obtained,
|
||||
|
||||
auto remain = len - (ptr - data);
|
||||
if(remain > 0){
|
||||
@@ -121,13 +123,15 @@ begin_decode:
|
||||
if(_payload_offset == _payload_len){
|
||||
onWebSocketDecodeComplete(*this);
|
||||
|
||||
//这是下一个包
|
||||
// 这是下一个包 [AUTO-TRANSLATED:bf657413]
|
||||
// This is the next package
|
||||
remain -= payload_slice_len;
|
||||
ptr += payload_slice_len;
|
||||
_got_header = false;
|
||||
|
||||
if(remain > 0){
|
||||
//剩余数据是下一个包,把它的数据放置在缓存中
|
||||
// 剩余数据是下一个包,把它的数据放置在缓存中 [AUTO-TRANSLATED:7b2ebfad]
|
||||
// The remaining data is the next package, place its data in the cache
|
||||
string str((char *)ptr,remain);
|
||||
_remain_data = str;
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
#include <memory>
|
||||
#include "Network/Buffer.h"
|
||||
|
||||
//websocket组合包最大不得超过4MB(防止内存爆炸)
|
||||
// websocket组合包最大不得超过4MB(防止内存爆炸) [AUTO-TRANSLATED:99c11a1d]
|
||||
// websocket combined package size must not exceed 4MB (to prevent memory explosion)
|
||||
#define MAX_WS_PACKET (4 * 1024 * 1024)
|
||||
|
||||
namespace mediakit {
|
||||
@@ -46,9 +47,11 @@ public:
|
||||
public:
|
||||
|
||||
WebSocketHeader() : _mask(4){
|
||||
//获取_mask内部buffer的内存地址,该内存是malloc开辟的,地址为随机
|
||||
// 获取_mask内部buffer的内存地址,该内存是malloc开辟的,地址为随机 [AUTO-TRANSLATED:9406f0b6]
|
||||
// Get the memory address of the internal buffer of _mask, the memory is allocated by malloc, and the address is random
|
||||
uint64_t ptr = (uint64_t)(&_mask[0]);
|
||||
//根据内存地址设置掩码随机数
|
||||
// 根据内存地址设置掩码随机数 [AUTO-TRANSLATED:47881295]
|
||||
// Set the mask random number according to the memory address
|
||||
_mask.assign((uint8_t*)(&ptr), (uint8_t*)(&ptr) + 4);
|
||||
}
|
||||
|
||||
@@ -63,7 +66,8 @@ public:
|
||||
std::vector<uint8_t > _mask;
|
||||
};
|
||||
|
||||
//websocket协议收到的字符串类型缓存,用户协议层获取该数据传输的方式
|
||||
// websocket协议收到的字符串类型缓存,用户协议层获取该数据传输的方式 [AUTO-TRANSLATED:a66e0177]
|
||||
// String type cache received by the websocket protocol, the way the user protocol layer obtains this data transmission
|
||||
class WebSocketBuffer : public toolkit::BufferString {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebSocketBuffer>;
|
||||
@@ -88,6 +92,12 @@ public:
|
||||
* 可能触发onWebSocketDecodeHeader和onWebSocketDecodePayload回调
|
||||
* @param data 需要解包的数据,可能是不完整的包或多个包
|
||||
* @param len 数据长度
|
||||
* Input data to unpack webSocket data and handle sticky packet problems
|
||||
* May trigger onWebSocketDecodeHeader and onWebSocketDecodePayload callbacks
|
||||
* @param data Data to be unpacked, may be incomplete packets or multiple packets
|
||||
* @param len Data length
|
||||
|
||||
* [AUTO-TRANSLATED:e5f2c2c6]
|
||||
*/
|
||||
void decode(uint8_t *data, size_t len);
|
||||
|
||||
@@ -96,6 +106,12 @@ public:
|
||||
* 将触发2次onWebSocketEncodeData回调
|
||||
* @param header 数据头
|
||||
* @param buffer 负载数据
|
||||
* Encode a data packet
|
||||
* Will trigger 2 onWebSocketEncodeData callbacks
|
||||
* @param header Data header
|
||||
* @param buffer Payload data
|
||||
|
||||
* [AUTO-TRANSLATED:f308e552]
|
||||
*/
|
||||
void encode(const WebSocketHeader &header,const toolkit::Buffer::Ptr &buffer);
|
||||
|
||||
@@ -103,6 +119,10 @@ protected:
|
||||
/**
|
||||
* 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePayload回调
|
||||
* @param header 数据包头
|
||||
* Receive a webSocket data packet header, and will continue to trigger onWebSocketDecodePayload callback
|
||||
* @param header Data packet header
|
||||
|
||||
* [AUTO-TRANSLATED:7bc6b7c6]
|
||||
*/
|
||||
virtual void onWebSocketDecodeHeader(const WebSocketHeader &header) {};
|
||||
|
||||
@@ -112,12 +132,23 @@ protected:
|
||||
* @param ptr 负载数据指针
|
||||
* @param len 负载数据长度
|
||||
* @param recved 已接收数据长度(包含本次数据长度),等于header._payload_len时则接受完毕
|
||||
* Receive webSocket data packet payload
|
||||
* @param header Data packet header
|
||||
* @param ptr Payload data pointer
|
||||
* @param len Payload data length
|
||||
* @param recved Received data length (including the length of this data), equals header._payload_len when the reception is complete
|
||||
|
||||
* [AUTO-TRANSLATED:ca056d2e]
|
||||
*/
|
||||
virtual void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, size_t len, size_t recved) {};
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
* Callback after receiving a complete webSocket data packet
|
||||
* @param header Data packet header
|
||||
|
||||
* [AUTO-TRANSLATED:f506a7c5]
|
||||
*/
|
||||
virtual void onWebSocketDecodeComplete(const WebSocketHeader &header) {};
|
||||
|
||||
@@ -125,6 +156,12 @@ protected:
|
||||
* websocket数据编码回调
|
||||
* @param ptr 数据指针
|
||||
* @param len 数据指针长度
|
||||
* websocket data encoding callback
|
||||
* @param ptr Data pointer
|
||||
* @param len Data pointer length
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:7c940c67]
|
||||
*/
|
||||
virtual void onWebSocketEncodeData(toolkit::Buffer::Ptr buffer){};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user