diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 6e51f666..0dfb8303 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -213,6 +213,27 @@ void api_regist(const string &api_path, const function; +static std::map 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] // Get URL parameters and content parameters from the HTTP request ApiArgsType getAllArgs(const Parser &parser) { @@ -321,6 +342,19 @@ static inline void addHttpListener(){ } },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] @@ -771,6 +805,7 @@ void check_secret(toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &he if (api_secret != allArgs["secret"]) { throw AuthException("Incorrect secret"); } + val.removeMember("cookie"); return; } catch (...) { // 未提供secret或secret不匹配,这个异常隐藏 diff --git a/server/WebApi.h b/server/WebApi.h index 6c405f72..51605bcf 100755 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -192,6 +192,9 @@ using ArgsString = HttpAllArgs; #define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker #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类型的http api [AUTO-TRANSLATED:8a273897] // Register http request parameters as map type http api void api_regist(const std::string &api_path, const std::function &func); diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index 47f3c2d8..ad4a42a3 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -130,12 +130,17 @@ const string &Parser::content() const { return _content; } +const HttpBody::Ptr &Parser::body() const { + return _body; +} + void Parser::clear() { _method.clear(); _url.clear(); _params.clear(); _protocol.clear(); _content.clear(); + _body = nullptr; _headers.clear(); _url_args.clear(); } @@ -152,6 +157,10 @@ void Parser::setContent(string content) { _content = std::move(content); } +void Parser::setBody(HttpBody::Ptr body) { + _body = std::move(body); +} + StrCaseMap &Parser::getHeader() const { return _headers; } diff --git a/src/Common/Parser.h b/src/Common/Parser.h index 3bf23826..ed2564a7 100644 --- a/src/Common/Parser.h +++ b/src/Common/Parser.h @@ -14,6 +14,7 @@ #include #include #include "Util/util.h" +#include "Http/HttpBody.h" namespace mediakit { @@ -95,6 +96,7 @@ public: // 获取http body或sdp [AUTO-TRANSLATED:d6fd1803] // Get http body or sdp const std::string &content() const; + const HttpBody::Ptr &body() const; // 清空,为了重用 [AUTO-TRANSLATED:cb7a16dd] // Clear, for reuse @@ -111,6 +113,7 @@ public: // 重新设置content [AUTO-TRANSLATED:ac8fc8c0] // Reset content void setContent(std::string content); + void setBody(HttpBody::Ptr body); // 获取header列表 [AUTO-TRANSLATED:90d90b03] // Get header list @@ -131,6 +134,7 @@ private: std::string _url; std::string _protocol; std::string _content; + HttpBody::Ptr _body; std::string _params; mutable StrCaseMap _headers; mutable StrCaseMap _url_args; diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 591e42df..309dae69 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -61,6 +61,7 @@ const string kBroadcastMediaChanged = "kBroadcastMediaChanged"; const string kBroadcastRecordMP4 = "kBroadcastRecordMP4"; const string kBroadcastRecordTs = "kBroadcastRecordTs"; const string kBroadcastHttpRequest = "kBroadcastHttpRequest"; +const string kBroadcastBeforeHttpRequest = "kBroadcastBeforeHttpRequest"; const string kBroadcastHttpAccess = "kBroadcastHttpAccess"; const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm"; const string kBroadcastOnRtspAuth = "kBroadcastOnRtspAuth"; diff --git a/src/Common/config.h b/src/Common/config.h index e3f85844..2e2ebeb6 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -57,6 +57,11 @@ extern const std::string kBroadcastRecordTs; extern const std::string kBroadcastHttpRequest; #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] // 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; diff --git a/src/Http/HttpBody.cpp b/src/Http/HttpBody.cpp index f7b0d3d0..fd57e742 100644 --- a/src/Http/HttpBody.cpp +++ b/src/Http/HttpBody.cpp @@ -400,4 +400,50 @@ Buffer::Ptr HttpBufferBody::readData(size_t size) { 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 diff --git a/src/Http/HttpBody.h b/src/Http/HttpBody.h index fad045f9..deea1d22 100644 --- a/src/Http/HttpBody.h +++ b/src/Http/HttpBody.h @@ -33,7 +33,11 @@ namespace mediakit { class HttpBody : public std::enable_shared_from_this{ public: using Ptr = std::shared_ptr; - virtual ~HttpBody() = default; + virtual ~HttpBody() { + if (_on_completed) { + _on_completed(); + } + } /** * 剩余数据大小,如果返回-1, 那么就不设置content-length @@ -88,6 +92,19 @@ public: virtual int sendFile(int fd) { 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 on_completed) { _on_completed = std::move(on_completed); } + +private: + std::function _on_completed; }; /** @@ -127,13 +144,21 @@ private: toolkit::Buffer::Ptr _buffer; }; +class HttpFileBodyBase : public HttpBody { +public: + using Ptr = std::shared_ptr; + + virtual void setRange(uint64_t offset, uint64_t max_size) = 0; + virtual ~HttpFileBodyBase() = default; +}; + /** * 文件类型的content * File type content * [AUTO-TRANSLATED:baf9c0f3] */ -class HttpFileBody : public HttpBody { +class HttpFileBody : public HttpFileBodyBase { public: using Ptr = std::shared_ptr; @@ -159,7 +184,7 @@ public: * [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; toolkit::Buffer::Ptr readData(size_t size) override; @@ -173,6 +198,32 @@ private: toolkit::ResourcePool _pool; }; +/** + * 文件写入类型的content,支持通过writeData向文件追加写入 + * File storage content, supports appending data to a file via writeData + */ +class HttpFileStorage : public HttpBody { +public: + using Ptr = std::shared_ptr; + + /** + * @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; /** @@ -212,7 +263,7 @@ private: int64_t _totalSize; std::string _bodyPrefix; std::string _bodySuffix; - HttpFileBody::Ptr _fileBody; + HttpFileBodyBase::Ptr _fileBody; }; }//namespace mediakit diff --git a/src/Http/HttpRequestSplitter.cpp b/src/Http/HttpRequestSplitter.cpp index 65a6c650..240353ce 100644 --- a/src/Http/HttpRequestSplitter.cpp +++ b/src/Http/HttpRequestSplitter.cpp @@ -138,6 +138,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) { // _content_len < 0; Data is processed according to variable length content onRecvContent(ptr,_remain_data_size);//消费掉所有剩余数据 _remain_data.clear(); + _remain_data_size = 0; } void HttpRequestSplitter::setContentLen(ssize_t content_len) { diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 77162e20..0323e521 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -109,19 +109,28 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) { 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] // // 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 << ", please set " << Http::kMaxReqSize << " in config.ini file."; } size_t received = 0; - auto parser = std::move(_parser); - _on_recv_body = [this, parser, received, content_len](const char *data, size_t len) mutable { + _on_recv_body = [this, received, content_len, body, it](const char *data, size_t len) mutable { 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) { // 还没收满 [AUTO-TRANSLATED:cecc867e] // Not yet received @@ -131,6 +140,13 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) { // 收满了 [AUTO-TRANSLATED:0c9cebd7] // Received full setContentLen(0); + + if (body) { + _parser.setBody(std::move(body)); + (this->*(it->second))(); + } + _parser.clear(); + return false; }; // 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存 [AUTO-TRANSLATED:0012b6c1]