add:添加一个mini http服务端tss-http
This commit is contained in:
parent
8121a370ce
commit
8f54951a28
10497
3rd/httplib.h
Normal file
10497
3rd/httplib.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
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_REQUIRED ON)
|
||||
|
||||
@ -63,6 +63,7 @@ add_subdirectory(server)
|
||||
add_subdirectory(client)
|
||||
add_subdirectory(filecomplete)
|
||||
add_subdirectory(tinyaes)
|
||||
add_subdirectory(http-server)
|
||||
|
||||
if (DEFINED 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 tss DESTINATION bin)
|
||||
install(TARGETS tss-http DESTINATION bin)
|
||||
|
||||
if (DEFINED USE_BOOST)
|
||||
install(FILES ${MINGW32_DLLS} DESTINATION bin)
|
||||
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
@ -96,6 +96,10 @@ Data verification: PASSED
|
||||
*/
|
||||
```
|
||||
|
||||
- `v1.5.2`及其以后的代码版本:新增了`tss-http`服务端,简单用于某些时候,客户机上没有`tsc`和`tss`程序时,通过`http`协议传输文件。
|
||||
|
||||
> 示例启动:`tss-http 8080 D:/files`(参数为端口、根目录)。
|
||||
|
||||
# 三、编译
|
||||
|
||||
当前项目支持`cmake`构建工具。
|
||||
|
16
http-server/CMakeLists.txt
Normal file
16
http-server/CMakeLists.txt
Normal 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
265
http-server/main.cpp
Normal 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;
|
||||
}
|
@ -15,6 +15,9 @@
|
||||
#endif
|
||||
|
||||
#if defined(GRAB_CRASH)
|
||||
#include <crashelper.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_BOOST
|
||||
#include <boost/filesystem.hpp>
|
||||
namespace fs = boost::filesystem;
|
||||
@ -22,8 +25,6 @@ namespace fs = boost::filesystem;
|
||||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
#endif
|
||||
#include <crashelper.h>
|
||||
#endif
|
||||
|
||||
void msignal_handler(int signal)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user