add:添加一个mini http服务端tss-http

This commit is contained in:
taynpg 2025-04-29 11:07:43 +08:00
parent 8121a370ce
commit 8f54951a28
7 changed files with 10788 additions and 66 deletions

10497
3rd/httplib.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(transm VERSION 1.5.1 LANGUAGES CXX) project(transm VERSION 1.5.2 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -63,6 +63,7 @@ add_subdirectory(server)
add_subdirectory(client) add_subdirectory(client)
add_subdirectory(filecomplete) add_subdirectory(filecomplete)
add_subdirectory(tinyaes) add_subdirectory(tinyaes)
add_subdirectory(http-server)
if (DEFINED USE_TRANSM_TEST) if (DEFINED USE_TRANSM_TEST)
message(STATUS "USE USE_TRANSM_TEST ${USE_TRANSM_TEST}") message(STATUS "USE USE_TRANSM_TEST ${USE_TRANSM_TEST}")
@ -91,6 +92,7 @@ message(STATUS "VERSION_GIT_HASH: ${VERSION_GIT_HASH}")
install(TARGETS tsc DESTINATION bin) install(TARGETS tsc DESTINATION bin)
install(TARGETS tss DESTINATION bin) install(TARGETS tss DESTINATION bin)
install(TARGETS tss-http DESTINATION bin)
if (DEFINED USE_BOOST) if (DEFINED USE_BOOST)
install(FILES ${MINGW32_DLLS} DESTINATION bin) install(FILES ${MINGW32_DLLS} DESTINATION bin)

View File

@ -1,63 +0,0 @@
{
"configurations": [
{
"name": "x64 Local Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": ""
},
{
"name": "x64 Local Release",
"generator": "Ninja",
"configurationType": "Release",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}"
},
{
"name": "x64 Linux Debug",
"generator": "Ninja",
"configurationType": "Debug",
"cmakeExecutable": "cmake",
"remoteCopySourcesExclusionList": [ ".vs", "out" ],
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "linux_x64" ],
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "$HOME/vs/${projectDirName}/${workspaceHash}/src",
"remoteBuildRoot": "$HOME/vs/${projectDirName}/${workspaceHash}/out/build/${name}",
"remoteInstallRoot": "$HOME/vs/${projectDirName}/${workspaceHash}/out/install/${name}",
"remoteCopySources": true,
"rsyncCommandArgs": "-t --delete",
"remoteCopyBuildOutput": false,
"remoteCopySourcesMethod": "rsync",
"variables": []
},
{
"name": "x64 Linux Release",
"generator": "Ninja",
"configurationType": "Release",
"cmakeExecutable": "cmake",
"remoteCopySourcesExclusionList": [ ".vs", "out" ],
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "linux_x64" ],
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "$HOME/vs/${projectDirName}/${workspaceHash}/src",
"remoteBuildRoot": "$HOME/vs/${projectDirName}/${workspaceHash}/out/build/${name}",
"remoteInstallRoot": "$HOME/vs/${projectDirName}/${workspaceHash}/out/install/${name}",
"remoteCopySources": true,
"rsyncCommandArgs": "-t --delete",
"remoteCopyBuildOutput": false,
"remoteCopySourcesMethod": "rsync",
"variables": []
}
]
}

View File

@ -96,6 +96,10 @@ Data verification: PASSED
*/ */
``` ```
- `v1.5.2`及其以后的代码版本:新增了`tss-http`服务端,简单用于某些时候,客户机上没有`tsc``tss`程序时,通过`http`协议传输文件。
> 示例启动:`tss-http 8080 D:/files`(参数为端口、根目录)。
# 三、编译 # 三、编译
当前项目支持`cmake`构建工具。 当前项目支持`cmake`构建工具。

View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.16)
project(tss-http LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (MSVC)
add_compile_options(/source-charset:utf-8)
endif()
add_executable(tss-http main.cpp)
if(DEFINED USE_BOOST)
target_link_directories(tss-http PRIVATE ${MBOOST_LIB_DIR})
target_link_libraries(tss-http PRIVATE ${MBOOST_LIBS})
endif()

265
http-server/main.cpp Normal file
View File

@ -0,0 +1,265 @@
#include <fstream>
#include <httplib.h>
#include <iostream>
#include <string>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef USE_BOOST
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#else
#include <filesystem>
namespace fs = std::filesystem;
#endif
#ifdef _WIN32
std::string u8_to_ansi(const std::string& str)
{
int wideCharLen = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
if (wideCharLen <= 0) {
return "";
}
std::wstring wideStr(wideCharLen, L'\0');
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wideStr[0], wideCharLen);
int gbkLen = WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (gbkLen <= 0) {
return "";
}
std::string gbkStr(gbkLen, '\0');
WideCharToMultiByte(CP_ACP, 0, wideStr.c_str(), -1, &gbkStr[0], gbkLen, nullptr, nullptr);
gbkStr.resize(gbkLen - 1);
return gbkStr;
}
std::string ansi_to_u8(const std::string& str)
{
int wideCharLen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, nullptr, 0);
if (wideCharLen <= 0) {
return "";
}
std::wstring wideStr(wideCharLen, L'\0');
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, &wideStr[0], wideCharLen);
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wideStr.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (utf8Len <= 0) {
return "";
}
std::string utf8Str(utf8Len, '\0');
WideCharToMultiByte(CP_UTF8, 0, wideStr.c_str(), -1, &utf8Str[0], utf8Len, nullptr, nullptr);
utf8Str.resize(utf8Len - 1);
return utf8Str;
}
#endif
// 生成文件列表的HTML页面
// 生成文件列表的HTML页面
std::string generate_file_list(const std::string& base_path, const std::string& current_path)
{
fs::path full_path = fs::path(base_path) / current_path;
std::string html = R"(
<!DOCTYPE html>
<html>
<head>
<title>File Browser</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.path { color: #666; margin-bottom: 20px; }
ul { list-style-type: none; padding: 0; }
li { margin: 8px 0; }
a { text-decoration: none; color: #0066cc; }
a:hover { text-decoration: underline; }
.file::before { content: "\1F4C4 "; }
.dir::before { content: "\1F4C1 "; }
.parent::before { content: "\2B06 "; }
.size { color: #888; font-size: 0.9em; margin-left: 10px; }
</style>
</head>
<body>
<h1>File Browser</h1>
<div class="path">Current path: /)" +
current_path + R"(</div>
<ul>
)";
// 添加返回上一级链接(如果不是根目录)
if (current_path != "") {
fs::path parent_path = fs::path(current_path).parent_path();
std::string parent_link = parent_path.empty() ? "" : parent_path.string();
html += R"(<li class="parent"><a href="/browse/)" + parent_link + R"(">..</a></li>)";
}
// 遍历目录下的文件和子目录
for (const auto& entry : fs::directory_iterator(full_path)) {
std::string filename = entry.path().filename().string();
std::string new_path = (fs::path(current_path) / filename).string();
if (entry.is_directory()) {
html += R"(<li class="dir"><a href="/browse/)" + new_path + R"(">)" + filename + R"(/</a></li>)";
} else {
// 获取文件大小并转换为MB
uintmax_t file_size = entry.file_size();
double size_mb = file_size / (1024.0 * 1024.0);
char size_str[20];
snprintf(size_str, sizeof(size_str), "%.2f MB", size_mb);
html += R"(<li class="file"><a href="/download/)" + new_path + R"(">)" + filename + R"(</a><span class="size">)" +
size_str + R"(</span></li>)";
}
}
html += R"(
</ul>
</body>
</html>
)";
return html;
}
int main(int argc, char** argv)
{
if (argc != 3) {
std::cout << "Usage: " << argv[0] << " <port> <base_dir>" << std::endl;
return -2;
}
const std::string base_dir(argv[2]); // 基础文件目录
if (!fs::exists(base_dir)) {
std::cout << "Base directory does not exist: " << base_dir << std::endl;
return -1;
}
int port = std::stoi(argv[1]);
httplib::Server server;
// 确保基础目录存在
if (!fs::exists(base_dir)) {
fs::create_directory(base_dir);
}
// 文件浏览路由
server.Get("/browse(.*)", [&base_dir](const httplib::Request& req, httplib::Response& res) {
std::string path = req.matches[1].str();
#ifdef _WIN32
path = u8_to_ansi(path);
#endif
// 移除开头的斜杠
if (!path.empty() && path[0] == '/') {
path = path.substr(1);
}
// 安全检查:防止目录遍历攻击
if (path.find("..") != std::string::npos) {
res.set_content("Invalid path", "text/plain");
res.status = 400;
return;
}
fs::path full_path = fs::path(base_dir) / path;
if (!fs::exists(full_path)) {
res.set_content("Path not found", "text/plain");
res.status = 404;
return;
}
if (!fs::is_directory(full_path)) {
res.set_content("Not a directory", "text/plain");
res.status = 400;
return;
}
res.set_header("Content-Type", "text/html; charset=utf-8");
res.set_content(generate_file_list(base_dir, path), "text/html");
});
// 文件下载路由
server.Get("/download/(.*)", [&base_dir](const httplib::Request& req, httplib::Response& res) {
std::string path = req.matches[1];
#ifdef _WIN32
path = u8_to_ansi(path);
#endif
// 安全检查:防止目录遍历攻击
if (path.find("..") != std::string::npos) {
res.set_content("Invalid path", "text/plain");
res.status = 400;
return;
}
fs::path file_path = fs::path(base_dir) / path;
if (!fs::exists(file_path)) {
res.set_content("File not found", "text/plain");
res.status = 404;
return;
}
if (fs::is_directory(file_path)) {
res.set_content("Cannot download directory", "text/plain");
res.status = 400;
return;
}
// 设置响应头,触发浏览器下载
res.set_header("Content-Type", "application/octet-stream");
res.set_header("Content-Disposition", "attachment; filename=" + file_path.filename().string());
auto file = std::make_shared<std::ifstream>(file_path, std::ios::binary);
if (!*file) {
res.status = 500;
res.set_content("Failed to open file", "text/plain");
return;
}
// 获取文件大小
const size_t file_size = fs::file_size(file_path);
// 定义分块回调(严格匹配 ContentProvider 签名)
auto provider = [file](size_t offset, size_t length, httplib::DataSink& sink) {
file->seekg(offset);
const size_t chunk_size = std::min<size_t>(65536, length); // 64KB或剩余长度
std::vector<char> buffer(chunk_size);
size_t remaining = length;
while (remaining > 0 && sink.is_writable() && *file) {
size_t read_size = std::min(buffer.size(), remaining);
file->read(buffer.data(), read_size);
size_t bytes_read = file->gcount();
if (bytes_read > 0) {
sink.write(buffer.data(), bytes_read);
remaining -= bytes_read;
} else {
break;
}
}
return true;
};
// 定义资源清理回调
auto releaser = [file](bool /*success*/) { file->close(); };
// 调用 set_content_provider
res.set_content_provider(file_size, // 文件总大小
"application/octet-stream", // MIME类型
provider, // 数据提供回调
releaser // 资源清理回调
);
});
// 根目录重定向到/browse
server.Get("/", [](const httplib::Request& req, httplib::Response& res) { res.set_redirect("/browse"); });
std::cout << "Server running at http://localhost:" << port << std::endl;
std::cout << "Access the root path to browse files.\n";
if (!server.listen("0.0.0.0", port)) {
std::cerr << "Failed to start server\n";
return -1;
}
return 0;
}

View File

@ -15,6 +15,9 @@
#endif #endif
#if defined(GRAB_CRASH) #if defined(GRAB_CRASH)
#include <crashelper.h>
#endif
#ifdef USE_BOOST #ifdef USE_BOOST
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
@ -22,8 +25,6 @@ namespace fs = boost::filesystem;
#include <filesystem> #include <filesystem>
namespace fs = std::filesystem; namespace fs = std::filesystem;
#endif #endif
#include <crashelper.h>
#endif
void msignal_handler(int signal) void msignal_handler(int signal)
{ {