diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit
index bb1a3826..00f56528 160000
--- a/3rdpart/ZLToolKit
+++ b/3rdpart/ZLToolKit
@@ -1 +1 @@
-Subproject commit bb1a382601e7cdeeb70d3bc978731c2648fd88b4
+Subproject commit 00f56528d28b5f4aaa94c175fe90ea9bdab4b17f
diff --git a/conf/config.ini b/conf/config.ini
index ec3a557a..72600377 100644
--- a/conf/config.ini
+++ b/conf/config.ini
@@ -401,6 +401,10 @@ keepAliveSecond=30
# http请求体最大字节数,如果post的body太大,则不适合缓存body在内存
# Maximum number of bytes for the HTTP request body. If the POST body is too large, it is not suitable to cache the body in memory.
maxReqSize=40960
+
+# 自定义HttpBody流式接收请求体的最大字节数;maxReqSize仍仅控制内存缓存阈值
+# Maximum request body size streamed into a custom HttpBody; maxReqSize remains an in-memory buffering threshold
+maxUploadSize=1073741824
# 404网页内容,用户可以自定义404网页
# Custom 404 page content. Users can customize the 404 response page here.
#notFound=
404 Not Found您访问的资源不存在!
ZLMediaKit-4.0
diff --git a/server/WebApi.cpp b/server/WebApi.cpp
index 0dfb8303..7439f3e9 100755
--- a/server/WebApi.cpp
+++ b/server/WebApi.cpp
@@ -1488,7 +1488,8 @@ void installWebApi() {
// Compatible with old version requests, the new version removes the only_audio parameter and adds the only_track parameter
only_track = 1;
}
- GET_CONFIG(std::string, local_ip, General::kListenIP)
+ GET_CONFIG(std::string, s_local_ip, General::kListenIP)
+ auto local_ip = s_local_ip;
if (!allArgs["local_ip"].empty()) {
local_ip = allArgs["local_ip"];
}
diff --git a/src/Common/config.cpp b/src/Common/config.cpp
index 309dae69..8924a05b 100644
--- a/src/Common/config.cpp
+++ b/src/Common/config.cpp
@@ -191,6 +191,7 @@ namespace Http {
#define HTTP_FIELD "http."
const string kSendBufSize = HTTP_FIELD "sendBufSize";
const string kMaxReqSize = HTTP_FIELD "maxReqSize";
+const string kMaxUploadSize = HTTP_FIELD "maxUploadSize";
const string kKeepAliveSecond = HTTP_FIELD "keepAliveSecond";
const string kCharSet = HTTP_FIELD "charSet";
const string kRootPath = HTTP_FIELD "rootPath";
@@ -205,6 +206,7 @@ const string kAllowIPRange = HTTP_FIELD "allow_ip_range";
static onceToken token([]() {
mINI::Instance()[kSendBufSize] = 64 * 1024;
mINI::Instance()[kMaxReqSize] = 4 * 10240;
+ mINI::Instance()[kMaxUploadSize] = 1024ULL * 1024 * 1024;
mINI::Instance()[kKeepAliveSecond] = 15;
mINI::Instance()[kDirMenu] = true;
mINI::Instance()[kVirtualPath] = "";
diff --git a/src/Common/config.h b/src/Common/config.h
index 2e2ebeb6..8220e144 100644
--- a/src/Common/config.h
+++ b/src/Common/config.h
@@ -194,19 +194,21 @@ extern const std::string kBroadcastCreateMuxer;
} while (0)
#define GET_CONFIG(type, arg, key) \
- static type arg = ::toolkit::mINI::Instance()[key]; \
- LISTEN_RELOAD_KEY(arg, key, { RELOAD_KEY(arg, key); });
+ static type arg##_storage_ = ::toolkit::mINI::Instance()[key]; \
+ static const type &arg = arg##_storage_; \
+ LISTEN_RELOAD_KEY(arg##_storage_, key, { RELOAD_KEY(arg##_storage_, key); });
#define GET_CONFIG_FUNC(type, arg, key, ...) \
- static type arg; \
+ static type arg##_storage_; \
+ static const type &arg = arg##_storage_; \
do { \
static ::toolkit::onceToken s_token_set([]() { \
static auto lam = __VA_ARGS__; \
static auto arg##_str = ::toolkit::mINI::Instance()[key]; \
- arg = lam(arg##_str); \
- LISTEN_RELOAD_KEY(arg, key, { \
+ arg##_storage_ = lam(arg##_str); \
+ LISTEN_RELOAD_KEY(arg##_str, key, { \
RELOAD_KEY(arg##_str, key); \
- arg = lam(arg##_str); \
+ arg##_storage_ = lam(arg##_str); \
}); \
}); \
} while (0)
@@ -356,6 +358,9 @@ extern const std::string kSendBufSize;
// http 最大请求字节数 [AUTO-TRANSLATED:8239eb9c]
// HTTP maximum request byte size
extern const std::string kMaxReqSize;
+// 自定义HttpBody流式接收请求体的最大字节数
+// Maximum request body size streamed into a custom HttpBody
+extern const std::string kMaxUploadSize;
// http keep-alive秒数 [AUTO-TRANSLATED:d4930c66]
// HTTP keep-alive seconds
extern const std::string kKeepAliveSecond;
diff --git a/src/Http/HttpBody.cpp b/src/Http/HttpBody.cpp
index fd57e742..c46af49a 100644
--- a/src/Http/HttpBody.cpp
+++ b/src/Http/HttpBody.cpp
@@ -405,7 +405,7 @@ Buffer::Ptr HttpBufferBody::readData(size_t size) {
// ─────────────────────────────────────────────────────────────────────────────
HttpFileStorage::HttpFileStorage(std::string file_path) {
- _fp = fopen(file_path.data(), "ab");
+ _fp = fopen(file_path.data(), "wb");
if (!_fp) {
throw std::runtime_error("HttpFileStorage: failed to open file: " + file_path + ", err: " + toolkit::get_uv_errmsg());
}
@@ -418,9 +418,14 @@ HttpFileStorage::~HttpFileStorage() {
fclose(_fp);
_fp = nullptr;
}
+ if (_written != _content_size || !_written) {
+ // 删除不完整的文件
+ File::delete_file(_path);
+ }
}
-void HttpFileStorage::writeData(const char *data, size_t size) {
+void HttpFileStorage::writeData(const char *data, size_t size, uint64_t content_size) {
+ _content_size = content_size;
if (!_fp) {
throw std::runtime_error("HttpFileStorage: file not open");
}
diff --git a/src/Http/HttpBody.h b/src/Http/HttpBody.h
index deea1d22..0d46e4e1 100644
--- a/src/Http/HttpBody.h
+++ b/src/Http/HttpBody.h
@@ -35,7 +35,11 @@ public:
using Ptr = std::shared_ptr;
virtual ~HttpBody() {
if (_on_completed) {
- _on_completed();
+ try {
+ _on_completed();
+ } catch (std::exception &ex) {
+ ErrorL << ex.what();
+ }
}
}
@@ -97,7 +101,7 @@ public:
* 写入数据,默认抛出异常表示不支持写入
* Write data, default throws exception indicating write not supported
*/
- virtual void writeData(const char *data, size_t size) {
+ virtual void writeData(const char *data, size_t size, uint64_t content_size) {
throw std::runtime_error("HttpBody::writeData not supported");
}
@@ -207,20 +211,21 @@ public:
using Ptr = std::shared_ptr;
/**
- * @param file_path 文件路径,文件以二进制追加模式打开
- * @param file_path File path, opened in binary append mode
+ * @param file_path 文件路径,文件以二进制写入模式打开(覆盖已有内容)
+ * @param file_path File path, opened in binary write mode (truncates existing content)
*/
HttpFileStorage(std::string file_path);
~HttpFileStorage() override;
- void writeData(const char *data, size_t size) override;
+ void writeData(const char *data, size_t size, uint64_t content_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;
+ uint64_t _written = 0;
+ uint64_t _content_size = 0;
std::string _path;
};
diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp
index 0323e521..2ef9b747 100644
--- a/src/Http/HttpSession.cpp
+++ b/src/Http/HttpSession.cpp
@@ -82,7 +82,7 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
return 0;
}
- size_t content_len;
+ uint64_t content_len;
auto &content_len_str = _parser["Content-Length"];
if (content_len_str.empty()) {
if (it->first == "POST") {
@@ -114,23 +114,75 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
NOTICE_EMIT(BroadcastBeforeHttpRequestArgs, Broadcast::kBroadcastBeforeHttpRequest, _parser, body, *this);
}
- if (content_len > _max_req_size || body) {
+ // 自定义HttpBody直接消费网络分片,不经过HttpRequestSplitter的内存缓存;因此maxReqSize只负责选择缓存策略,
+ // 不能作为这里的上传上限,否则未携带Content-Length的正常流式上传会在默认40KB左右被错误拒绝。
+ // A custom HttpBody consumes network fragments directly without HttpRequestSplitter buffering. Therefore maxReqSize
+ // only selects the buffering strategy and must not be used as the upload limit, or normal unknown-length uploads
+ // would be rejected at roughly the default 40KB threshold.
+ if (body) {
+ GET_CONFIG(uint64_t, max_upload_size_config, Http::kMaxUploadSize);
+ // 已知长度可以在接收body前判断是否超限,避免先向HttpBody写入数据再拒绝请求;
+ // 如果上传文件时不指定content-len,也直接拒绝,因为后续没法触发文件上传完毕事件
+ if (content_len > max_upload_size_config) {
+ WarnL << "Http upload size is too huge or no content-len provided: " << content_len << " > " << max_upload_size_config
+ << ", please set " << Http::kMaxUploadSize << " in config.ini file.";
+ sendResponse(413, true);
+ _parser.clear();
+ // 仍返回不定长body模式并丢弃连接关闭前可能到达的数据,防止splitter把body误当成下一条请求头。
+ // Keep the splitter in variable-body mode and discard data arriving before close, so body bytes are not
+ // interpreted as another request header.
+ _on_recv_body = [](const char *, size_t) { return true; };
+ return -1;
+ }
+
+ size_t received = 0;
+ _on_recv_body = [this, received, content_len, body, it](const char *data, size_t len) mutable {
+ auto remain = content_len - received;
+ if (len > remain) {
+ // 告知HttpFileStorage,写入的数据长度超过content_len,触发文件删除操作
+ body->writeData(data, len, content_len);
+ // 上传的数据超过声明的content-len, 直接拒绝
+ sendResponse(413, true);
+ WarnL << "Upload file size larger than content_len: " << received + len << " > " << content_len;
+ return false;
+ }
+
+ received += len;
+ body->writeData(data, len, content_len);
+ if (received < content_len) {
+ // 还没收满 [AUTO-TRANSLATED:cecc867e]
+ // Not yet received
+ return true;
+ }
+ // 收满了 [AUTO-TRANSLATED:0c9cebd7]
+ // Received full
+ setContentLen(0);
+ _parser.setBody(std::move(body));
+ (this->*(it->second))();
+ _parser.clear();
+ return false;
+ };
+ // 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存 [AUTO-TRANSLATED:0012b6c1]
+ // Declare that the following is all body; Http body is buffered in this object, not saved through HttpRequestSplitter
+ return -1;
+ }
+
+ // 未提供自定义HttpBody时保持原有语义:maxReqSize仅决定是否放弃内存整包缓存并改为分片回调,
+ // 不在本分支中把它扩展为普通请求体的硬上限。
+ // Without a custom HttpBody, preserve the original behavior: maxReqSize only switches from whole-body buffering
+ // to fragment callbacks; it is not extended into a hard limit for ordinary request bodies in this branch.
+ if (content_len > _max_req_size) {
// // 不定长body或超大body //// [AUTO-TRANSLATED:8d66ee77]
// // Indefinite length body or oversized body ////
- if (content_len != SIZE_MAX && !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.";
}
size_t received = 0;
- _on_recv_body = [this, received, content_len, body, it](const char *data, size_t len) mutable {
+ _on_recv_body = [this, received, content_len](const char *data, size_t len) mutable {
received += len;
- if (body) {
- body->writeData(data, len);
- } else {
- onRecvUnlimitedContent(_parser, data, len, content_len, received);
- }
-
+ onRecvUnlimitedContent(_parser, data, len, content_len, received);
if (received < content_len) {
// 还没收满 [AUTO-TRANSLATED:cecc867e]
// Not yet received
@@ -140,13 +192,7 @@ 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]