新增支持http文件上传功能

This commit is contained in:
xiongziliang
2026-07-08 10:45:55 +08:00
parent b44e5b0731
commit 92681fe6e1
10 changed files with 181 additions and 10 deletions

View File

@@ -213,6 +213,27 @@ void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYN
s_map_api.emplace(api_path, toApi(func)); s_map_api.emplace(api_path, toApi(func));
} }
// ─────────────────────────────────────────────────────────────────────────────
// 文件上传处理器注册机制
// File upload handler registration: intercepts PUT/POST body via kBroadcastBeforeHttpRequest
// ─────────────────────────────────────────────────────────────────────────────
/// Handler signature: same as API_ARGS_MAP + HttpBody::Ptr &body, so CHECK_SECRET() etc. work inside.
using HttpBodyHandler = std::function<void(UPLOAD_ARGS_MAP)>;
static std::map<std::string, HttpBodyHandler, StrCaseCompare> s_map_file_handler;
/**
* Register a handler for a URL path. When a PUT/POST request arrives at that path,
* the handler is invoked to create an HttpBody (e.g. HttpFileStorage) that receives
* the request body stream directly. CHECK_SECRET() can be used inside the handler.
*
* @param url_path the HTTP URL path to match
* @param handler callback that sets body; may throw AuthException to reject
*/
void upload_regist(const std::string &url_path, HttpBodyHandler handler) {
s_map_file_handler.emplace(url_path, std::move(handler));
}
// 获取HTTP请求中url参数、content参数 [AUTO-TRANSLATED:d161a1e1] // 获取HTTP请求中url参数、content参数 [AUTO-TRANSLATED:d161a1e1]
// Get URL parameters and content parameters from the HTTP request // Get URL parameters and content parameters from the HTTP request
ApiArgsType getAllArgs(const Parser &parser) { ApiArgsType getAllArgs(const Parser &parser) {
@@ -321,6 +342,19 @@ static inline void addHttpListener(){
} }
},false); },false);
}); });
// 监听 kBroadcastBeforeHttpRequest对已注册的 URL 路径设置 HttpBody 以接管 PUT/POST 请求体
// Listen for kBroadcastBeforeHttpRequest: set HttpBody for registered URL paths to take over PUT/POST body
NoticeCenter::Instance().addListener(&web_api_tag, Broadcast::kBroadcastBeforeHttpRequest, [](BroadcastBeforeHttpRequestArgs) {
auto it = s_map_file_handler.find(parser.url());
if (it == s_map_file_handler.end()) {
return;
}
HttpSession::KeyValue headerOut;
Json::Value val;
auto args = getAllArgs(parser);
it->second(sender, headerOut, ArgsMap(parser, args), val, body);
});
} }
// 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f] // 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f]
@@ -771,6 +805,7 @@ void check_secret(toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &he
if (api_secret != allArgs["secret"]) { if (api_secret != allArgs["secret"]) {
throw AuthException("Incorrect secret"); throw AuthException("Incorrect secret");
} }
val.removeMember("cookie");
return; return;
} catch (...) { } catch (...) {
// 未提供secret或secret不匹配这个异常隐藏 // 未提供secret或secret不匹配这个异常隐藏

View File

@@ -192,6 +192,9 @@ using ArgsString = HttpAllArgs<std::string>;
#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker #define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker
#define API_ARGS_VALUE sender, headerOut, allArgs, val #define API_ARGS_VALUE sender, headerOut, allArgs, val
// upload_regist 参数宏,与 API_ARGS_MAP 相同 + HttpBody::Ptr &body支持 CHECK_SECRET()
#define UPLOAD_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val, mediakit::HttpBody::Ptr &body
// 注册http请求参数是map<string, variant, StrCaseCompare>类型的http api [AUTO-TRANSLATED:8a273897] // 注册http请求参数是map<string, variant, StrCaseCompare>类型的http api [AUTO-TRANSLATED:8a273897]
// Register http request parameters as map<string, variant, StrCaseCompare> type http api // Register http request parameters as map<string, variant, StrCaseCompare> type http api
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_MAP)> &func); void api_regist(const std::string &api_path, const std::function<void(API_ARGS_MAP)> &func);

View File

@@ -130,12 +130,17 @@ const string &Parser::content() const {
return _content; return _content;
} }
const HttpBody::Ptr &Parser::body() const {
return _body;
}
void Parser::clear() { void Parser::clear() {
_method.clear(); _method.clear();
_url.clear(); _url.clear();
_params.clear(); _params.clear();
_protocol.clear(); _protocol.clear();
_content.clear(); _content.clear();
_body = nullptr;
_headers.clear(); _headers.clear();
_url_args.clear(); _url_args.clear();
} }
@@ -152,6 +157,10 @@ void Parser::setContent(string content) {
_content = std::move(content); _content = std::move(content);
} }
void Parser::setBody(HttpBody::Ptr body) {
_body = std::move(body);
}
StrCaseMap &Parser::getHeader() const { StrCaseMap &Parser::getHeader() const {
return _headers; return _headers;
} }

View File

@@ -14,6 +14,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include "Util/util.h" #include "Util/util.h"
#include "Http/HttpBody.h"
namespace mediakit { namespace mediakit {
@@ -95,6 +96,7 @@ public:
// 获取http body或sdp [AUTO-TRANSLATED:d6fd1803] // 获取http body或sdp [AUTO-TRANSLATED:d6fd1803]
// Get http body or sdp // Get http body or sdp
const std::string &content() const; const std::string &content() const;
const HttpBody::Ptr &body() const;
// 清空,为了重用 [AUTO-TRANSLATED:cb7a16dd] // 清空,为了重用 [AUTO-TRANSLATED:cb7a16dd]
// Clear, for reuse // Clear, for reuse
@@ -111,6 +113,7 @@ public:
// 重新设置content [AUTO-TRANSLATED:ac8fc8c0] // 重新设置content [AUTO-TRANSLATED:ac8fc8c0]
// Reset content // Reset content
void setContent(std::string content); void setContent(std::string content);
void setBody(HttpBody::Ptr body);
// 获取header列表 [AUTO-TRANSLATED:90d90b03] // 获取header列表 [AUTO-TRANSLATED:90d90b03]
// Get header list // Get header list
@@ -131,6 +134,7 @@ private:
std::string _url; std::string _url;
std::string _protocol; std::string _protocol;
std::string _content; std::string _content;
HttpBody::Ptr _body;
std::string _params; std::string _params;
mutable StrCaseMap _headers; mutable StrCaseMap _headers;
mutable StrCaseMap _url_args; mutable StrCaseMap _url_args;

View File

@@ -61,6 +61,7 @@ const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4"; const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
const string kBroadcastRecordTs = "kBroadcastRecordTs"; const string kBroadcastRecordTs = "kBroadcastRecordTs";
const string kBroadcastHttpRequest = "kBroadcastHttpRequest"; const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
const string kBroadcastBeforeHttpRequest = "kBroadcastBeforeHttpRequest";
const string kBroadcastHttpAccess = "kBroadcastHttpAccess"; const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm"; const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm";
const string kBroadcastOnRtspAuth = "kBroadcastOnRtspAuth"; const string kBroadcastOnRtspAuth = "kBroadcastOnRtspAuth";

View File

@@ -57,6 +57,11 @@ extern const std::string kBroadcastRecordTs;
extern const std::string kBroadcastHttpRequest; extern const std::string kBroadcastHttpRequest;
#define BroadcastHttpRequestArgs const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, toolkit::SockInfo &sender #define BroadcastHttpRequestArgs const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, toolkit::SockInfo &sender
// 收到http PUT/POST请求body前的广播监听者可以设置body以接管请求体
// Broadcast before receiving http PUT/POST request body, listener can set body to take over the request body
extern const std::string kBroadcastBeforeHttpRequest;
#define BroadcastBeforeHttpRequestArgs const Parser &parser, HttpBody::Ptr &body, HttpSession &sender
// 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 [AUTO-TRANSLATED:2de426b4] // 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 [AUTO-TRANSLATED:2de426b4]
// In the http file server, broadcast for receiving http access to files or directories. Control access permissions to the http directory through this event. // In the http file server, broadcast for receiving http access to files or directories. Control access permissions to the http directory through this event.
extern const std::string kBroadcastHttpAccess; extern const std::string kBroadcastHttpAccess;

View File

@@ -400,4 +400,50 @@ Buffer::Ptr HttpBufferBody::readData(size_t size) {
return Buffer::Ptr(std::move(_buffer)); return Buffer::Ptr(std::move(_buffer));
} }
} // namespace mediakit // ─────────────────────────────────────────────────────────────────────────────
// HttpFileStorage — write-only body backed by a file on disk
// ─────────────────────────────────────────────────────────────────────────────
HttpFileStorage::HttpFileStorage(std::string file_path) {
_fp = fopen(file_path.data(), "ab");
if (!_fp) {
throw std::runtime_error("HttpFileStorage: failed to open file: " + file_path + ", err: " + toolkit::get_uv_errmsg());
}
_path = std::move(file_path);
}
HttpFileStorage::~HttpFileStorage() {
if (_fp) {
fflush(_fp);
fclose(_fp);
_fp = nullptr;
}
}
void HttpFileStorage::writeData(const char *data, size_t size) {
if (!_fp) {
throw std::runtime_error("HttpFileStorage: file not open");
}
if (size == 0) {
return;
}
auto written = fwrite(data, 1, size, _fp);
if (written != size) {
throw std::runtime_error("HttpFileStorage: fwrite failed, expected " + std::to_string(size) + ", got " + std::to_string(written));
}
_written += written;
}
int64_t HttpFileStorage::remainSize() {
return 0;
}
Buffer::Ptr HttpFileStorage::readData(size_t size) {
return nullptr;
}
const std::string &HttpFileStorage::filePath() const {
return _path;
}
} // namespace kule

View File

@@ -33,7 +33,11 @@ namespace mediakit {
class HttpBody : public std::enable_shared_from_this<HttpBody>{ class HttpBody : public std::enable_shared_from_this<HttpBody>{
public: public:
using Ptr = std::shared_ptr<HttpBody>; using Ptr = std::shared_ptr<HttpBody>;
virtual ~HttpBody() = default; virtual ~HttpBody() {
if (_on_completed) {
_on_completed();
}
}
/** /**
* 剩余数据大小,如果返回-1, 那么就不设置content-length * 剩余数据大小,如果返回-1, 那么就不设置content-length
@@ -88,6 +92,19 @@ public:
virtual int sendFile(int fd) { virtual int sendFile(int fd) {
return -1; return -1;
} }
/**
* 写入数据,默认抛出异常表示不支持写入
* Write data, default throws exception indicating write not supported
*/
virtual void writeData(const char *data, size_t size) {
throw std::runtime_error("HttpBody::writeData not supported");
}
void setOnCompleted(std::function<void()> on_completed) { _on_completed = std::move(on_completed); }
private:
std::function<void()> _on_completed;
}; };
/** /**
@@ -127,13 +144,21 @@ private:
toolkit::Buffer::Ptr _buffer; toolkit::Buffer::Ptr _buffer;
}; };
class HttpFileBodyBase : public HttpBody {
public:
using Ptr = std::shared_ptr<HttpFileBodyBase>;
virtual void setRange(uint64_t offset, uint64_t max_size) = 0;
virtual ~HttpFileBodyBase() = default;
};
/** /**
* 文件类型的content * 文件类型的content
* File type content * File type content
* [AUTO-TRANSLATED:baf9c0f3] * [AUTO-TRANSLATED:baf9c0f3]
*/ */
class HttpFileBody : public HttpBody { class HttpFileBody : public HttpFileBodyBase {
public: public:
using Ptr = std::shared_ptr<HttpFileBody>; using Ptr = std::shared_ptr<HttpFileBody>;
@@ -159,7 +184,7 @@ public:
* [AUTO-TRANSLATED:30532a4e] * [AUTO-TRANSLATED:30532a4e]
*/ */
void setRange(uint64_t offset, uint64_t max_size); void setRange(uint64_t offset, uint64_t max_size) override;
int64_t remainSize() override; int64_t remainSize() override;
toolkit::Buffer::Ptr readData(size_t size) override; toolkit::Buffer::Ptr readData(size_t size) override;
@@ -173,6 +198,32 @@ private:
toolkit::ResourcePool<toolkit::BufferRaw> _pool; toolkit::ResourcePool<toolkit::BufferRaw> _pool;
}; };
/**
* 文件写入类型的content支持通过writeData向文件追加写入
* File storage content, supports appending data to a file via writeData
*/
class HttpFileStorage : public HttpBody {
public:
using Ptr = std::shared_ptr<HttpFileStorage>;
/**
* @param file_path 文件路径,文件以二进制追加模式打开
* @param file_path File path, opened in binary append mode
*/
HttpFileStorage(std::string file_path);
~HttpFileStorage() override;
void writeData(const char *data, size_t size) override;
int64_t remainSize() override;
toolkit::Buffer::Ptr readData(size_t size) override;
const std::string& filePath() const;
private:
FILE *_fp = nullptr;
int64_t _written = 0;
std::string _path;
};
class HttpArgs; class HttpArgs;
/** /**
@@ -212,7 +263,7 @@ private:
int64_t _totalSize; int64_t _totalSize;
std::string _bodyPrefix; std::string _bodyPrefix;
std::string _bodySuffix; std::string _bodySuffix;
HttpFileBody::Ptr _fileBody; HttpFileBodyBase::Ptr _fileBody;
}; };
}//namespace mediakit }//namespace mediakit

View File

@@ -138,6 +138,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
// _content_len < 0; Data is processed according to variable length content // _content_len < 0; Data is processed according to variable length content
onRecvContent(ptr,_remain_data_size);//消费掉所有剩余数据 onRecvContent(ptr,_remain_data_size);//消费掉所有剩余数据
_remain_data.clear(); _remain_data.clear();
_remain_data_size = 0;
} }
void HttpRequestSplitter::setContentLen(ssize_t content_len) { void HttpRequestSplitter::setContentLen(ssize_t content_len) {

View File

@@ -109,19 +109,28 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
return _on_recv_body ? -1 : 0; return _on_recv_body ? -1 : 0;
} }
if (content_len > _max_req_size) { HttpBody::Ptr body;
if (_parser.method() == "PUT" || _parser.method() == "POST") {
NOTICE_EMIT(BroadcastBeforeHttpRequestArgs, Broadcast::kBroadcastBeforeHttpRequest, _parser, body, *this);
}
if (content_len > _max_req_size || body) {
// // 不定长body或超大body //// [AUTO-TRANSLATED:8d66ee77] // // 不定长body或超大body //// [AUTO-TRANSLATED:8d66ee77]
// // Indefinite length body or oversized body //// // // Indefinite length body or oversized body ////
if (content_len != SIZE_MAX) { if (content_len != SIZE_MAX && !body) {
WarnL << "Http body size is too huge: " << content_len << " > " << _max_req_size WarnL << "Http body size is too huge: " << content_len << " > " << _max_req_size
<< ", please set " << Http::kMaxReqSize << " in config.ini file."; << ", please set " << Http::kMaxReqSize << " in config.ini file.";
} }
size_t received = 0; size_t received = 0;
auto parser = std::move(_parser); _on_recv_body = [this, received, content_len, body, it](const char *data, size_t len) mutable {
_on_recv_body = [this, parser, received, content_len](const char *data, size_t len) mutable {
received += len; received += len;
onRecvUnlimitedContent(parser, data, len, content_len, received); if (body) {
body->writeData(data, len);
} else {
onRecvUnlimitedContent(_parser, data, len, content_len, received);
}
if (received < content_len) { if (received < content_len) {
// 还没收满 [AUTO-TRANSLATED:cecc867e] // 还没收满 [AUTO-TRANSLATED:cecc867e]
// Not yet received // Not yet received
@@ -131,6 +140,13 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
// 收满了 [AUTO-TRANSLATED:0c9cebd7] // 收满了 [AUTO-TRANSLATED:0c9cebd7]
// Received full // Received full
setContentLen(0); setContentLen(0);
if (body) {
_parser.setBody(std::move(body));
(this->*(it->second))();
}
_parser.clear();
return false; return false;
}; };
// 声明后续都是bodyHttp body在本对象缓冲不通过HttpRequestSplitter保存 [AUTO-TRANSLATED:0012b6c1] // 声明后续都是bodyHttp body在本对象缓冲不通过HttpRequestSplitter保存 [AUTO-TRANSLATED:0012b6c1]