AI automatically translates all comments in the code into English (#3917)

This commit is contained in:
alex
2024-09-19 14:53:50 +08:00
committed by GitHub
parent 046de691cb
commit 4152dcd409
279 changed files with 10602 additions and 3038 deletions

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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()) {

View File

@@ -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];

View File

@@ -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();

View File

@@ -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 ;

View File

@@ -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;
}

View File

@@ -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)>;

View File

@@ -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);

View File

@@ -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; };

View File

@@ -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 {

View File

@@ -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);
};

View File

@@ -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;
}

View File

@@ -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:

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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;
});
});

View File

@@ -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;
};
// 声明后续都是bodyHttp body在本对象缓冲不通过HttpRequestSplitter保存
// 声明后续都是bodyHttp 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);
}
}

View File

@@ -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;
};

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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));

View File

@@ -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;

View File

@@ -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){};