Files
OrcaSlicer/src/slic3r/GUI/HttpServer.cpp

822 lines
30 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "HttpServer.hpp"
#include <boost/log/trivial.hpp>
#include <condition_variable>
#include "GUI_App.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "sentry_wrapper/SentryWrapper.hpp"
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#endif
namespace Slic3r { namespace GUI {
// 检测Windows系统是否支持UTF-8模式
bool is_windows_utf8_mode()
{
#ifdef _WIN32
static bool checked = false;
static bool utf8_mode = false;
if (!checked) {
// 检查系统是否启用了UTF-8模式
// 最可靠的方法检查ANSI代码页是否为UTF-8
if (GetACP() == CP_UTF8) {
utf8_mode = true;
} else {
// 对于现代Windows系统Windows 10 1903+
// 即使没有全局启用UTF-8模式文件系统API通常也能处理UTF-8路径
// 这里我们采用保守策略只有在明确启用UTF-8模式时才返回true
utf8_mode = false;
}
checked = true;
}
return utf8_mode;
#else
return true; // 非Windows系统通常支持UTF-8
#endif
}
// 辅助函数将UTF-8字符串转换为适合文件系统操作的编码
std::string utf8_to_filesystem_encoding(const std::string& utf8_str)
{
#ifdef _WIN32
if (utf8_str.empty()) return utf8_str;
// 策略1如果系统明确支持UTF-8模式直接返回UTF-8字符串
if (is_windows_utf8_mode()) {
return utf8_str;
}
// 策略2传统模式需要转换为系统编码GBK
// 虽然某些情况下UTF-8路径可能成功但为了确保兼容性还是进行编码转换
// 将UTF-8转换为宽字符UTF-16
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, nullptr, 0);
if (wlen <= 0) return utf8_str; // 转换失败,返回原字符串
std::wstring wstr(wlen - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, &wstr[0], wlen);
// 将宽字符转换为系统编码GBK
int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (len <= 0) return utf8_str; // 转换失败,返回原字符串
std::string result(len - 1, 0);
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, &result[0], len, nullptr, nullptr);
return result;
#else
// 在非Windows系统上UTF-8通常就是系统编码
return utf8_str;
#endif
}
std::string url_get_param(const std::string& url, const std::string& key)
{
size_t start = url.find(key);
if (start == std::string::npos)
return "";
size_t eq = url.find('=', start);
if (eq == std::string::npos)
return "";
std::string key_str = url.substr(start, eq - start);
if (key_str != key)
return "";
start += key.size() + 1;
size_t end = url.find('&', start);
if (end == std::string::npos)
end = url.length(); // Last param
std::string result = url.substr(start, end - start);
return result;
}
void session::start() { read_first_line(); }
void session::stop()
{
boost::system::error_code ignored_ec;
socket.shutdown(boost::asio::socket_base::shutdown_both, ignored_ec);
socket.close(ignored_ec);
}
void session::read_first_line()
{
auto self(shared_from_this());
async_read_until(socket, buff, '\r', [this, self](const boost::beast::error_code& e, std::size_t s) {
if (!e) {
std::string line, ignore;
std::istream stream{&buff};
std::getline(stream, line, '\r');
std::getline(stream, ignore, '\n');
headers.on_read_request_line(line);
read_next_line();
} else if (e != boost::asio::error::operation_aborted) {
server.stop(self);
}
});
}
void session::read_body()
{
auto self(shared_from_this());
int nbuffer = 1000;
std::shared_ptr<std::vector<char>> bufptr = std::make_shared<std::vector<char>>(nbuffer);
async_read(socket, boost::asio::buffer(*bufptr, nbuffer),
[this, self](const boost::beast::error_code& e, std::size_t s) { server.stop(self); });
}
void session::read_next_line()
{
auto self(shared_from_this());
if (headers.method == "OPTIONS") {
// 构造OPTIONS响应允许跨域
std::stringstream ssOut;
ssOut << "HTTP/1.1 200 OK\r\n";
ssOut << "Access-Control-Allow-Origin: *\r\n"; // 允许所有源
ssOut << "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"; // 允许的方法
ssOut << "Access-Control-Allow-Headers: Content-Type, Authorization\r\n"; // 允许的请求头
ssOut << "Content-Length: 0\r\n"; // 无响应体
ssOut << "\r\n"; // 头和主体之间的空行(必须)
// 异步发送响应
async_write(socket, boost::asio::buffer(ssOut.str()), [this, self](const boost::beast::error_code& e, std::size_t s) {
std::cout << "OPTIONS预检请求已处理" << std::endl;
server.stop(self); // 关闭连接
});
return; // 提前返回,避免后续逻辑
}
async_read_until(socket, buff, '\r', [this, self](const boost::beast::error_code& e, std::size_t s) {
if (!e) {
std::string line, ignore;
std::istream stream{&buff};
std::getline(stream, line, '\r');
std::getline(stream, ignore, '\n');
headers.on_read_header(line);
if (line.length() == 0) {
if (headers.content_length() == 0) {
std::cout << "Request received: " << headers.method << " " << headers.get_url();
if (headers.method == "OPTIONS") {
// Ignore http OPTIONS
server.stop(self);
return;
}
const std::string url_str = Http::url_decode(headers.get_url());
const auto resp = server.server.m_request_handler(url_str);
std::stringstream ssOut;
resp->write_response(ssOut);
std::shared_ptr<std::string> str = std::make_shared<std::string>(ssOut.str());
async_write(socket, boost::asio::buffer(str->c_str(), str->length()),
[this, self, str](const boost::beast::error_code& e, std::size_t s) {
std::cout << "done" << std::endl;
server.stop(self);
});
} else {
read_body();
}
} else {
read_next_line();
}
} else if (e != boost::asio::error::operation_aborted) {
server.stop(self);
}
});
}
void HttpServer::IOServer::do_accept()
{
acceptor.async_accept([this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
if (!acceptor.is_open()) {
return;
}
if (!ec) {
const auto ss = std::make_shared<session>(*this, std::move(socket));
start(ss);
}
do_accept();
});
}
void HttpServer::IOServer::start(std::shared_ptr<session> session)
{
sessions.insert(session);
session->start();
}
void HttpServer::IOServer::stop(std::shared_ptr<session> session)
{
sessions.erase(session);
session->stop();
}
void HttpServer::IOServer::stop_all()
{
for (auto s : sessions) {
s->stop();
}
sessions.clear();
}
HttpServer::IOServer::IOServer(HttpServer& server) : server(server), acceptor(io_service)
{
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), server.port);
acceptor.open(endpoint.protocol());
acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
}
HttpServer::HttpServer(boost::asio::ip::port_type port) : port(port) {}
HttpServer::~HttpServer()
{
BOOST_LOG_TRIVIAL(debug) << "HttpServer destructor called, cleaning up resources...";
stop();
BOOST_LOG_TRIVIAL(debug) << "HttpServer destructor completed";
}
bool HttpServer::is_port_available(boost::asio::ip::port_type port)
{
try {
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor acceptor(io_service);
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
acceptor.open(endpoint.protocol());
acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.close();
return true;
} catch (const boost::system::system_error&) {
return false;
}
}
boost::asio::ip::port_type HttpServer::find_available_port(boost::asio::ip::port_type start_port)
{
// 尝试从起始端口开始查找可用端口
for (boost::asio::ip::port_type p = start_port; p < start_port + 1000; ++p) {
if (is_port_available(p)) {
return p;
}
}
throw std::runtime_error("No available ports found");
}
void HttpServer::start()
{
BOOST_LOG_TRIVIAL(info) << "start_http_service...";
try {
// 如果指定端口不可用,查找下一个可用端口
if (!is_port_available(port)) {
auto new_port = find_available_port(port + 1);
BOOST_LOG_TRIVIAL(info) << "Original port " << port << " is in use, switching to port " << new_port;
port = new_port;
}
start_http_server = true;
m_http_server_thread = create_thread([this] {
try {
set_current_thread_name("http_server");
server_ = std::make_unique<IOServer>(*this);
server_->acceptor.listen();
server_->do_accept();
server_->io_service.run();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "HTTP server error: " << e.what();
Slic3r::sentryReportLog(Slic3r::SENTRY_LOG_FATAL,std::string("HttpServer::start ") + e.what(), BP_LOCAL_SERVER);
start_http_server = false;
}
});
// 等待服务器启动
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (!start_http_server) {
throw std::runtime_error("Failed to start HTTP server");
}
BOOST_LOG_TRIVIAL(info) << "HTTP server started successfully on port " << port;
// 启动健康检查
BOOST_LOG_TRIVIAL(debug) << "Starting health check for HTTP server...";
start_health_check();
// 重启检查已集成到健康检查中,无需单独线程
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Failed to start HTTP server: " << e.what();
std::string error_msg = "Failed to start HTTP server on port " + std::to_string(port) + ": " + e.what();
Slic3r::sentryReportLog(Slic3r::SENTRY_LOG_FATAL, error_msg.c_str(), BP_LOCAL_SERVER);
start_http_server = false;
throw;
}
}
void HttpServer::stop()
{
start_http_server = false;
// 停止健康检查
stop_health_check();
// 重启检查已集成到健康检查中,无需单独停止
if (server_) {
server_->acceptor.close();
server_->stop_all();
server_->io_service.stop();
}
if (m_http_server_thread.joinable())
m_http_server_thread.join();
server_.reset();
}
void HttpServer::restart()
{
BOOST_LOG_TRIVIAL(info) << "Restarting HTTP server on port " << port << "...";
BOOST_LOG_TRIVIAL(debug) << "Stopping current HTTP server...";
// 只停止HTTP服务器不停止健康检查和重启检查线程
start_http_server = false;
if (server_) {
server_->acceptor.close();
server_->stop_all();
server_->io_service.stop();
}
if (m_http_server_thread.joinable())
m_http_server_thread.join();
server_.reset();
BOOST_LOG_TRIVIAL(debug) << "Waiting for resources to be released...";
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 等待资源释放
BOOST_LOG_TRIVIAL(debug) << "Starting new HTTP server...";
start();
BOOST_LOG_TRIVIAL(info) << "HTTP server restart completed";
}
bool HttpServer::is_healthy()
{
if (!start_http_server || !server_) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed: server not started or server object is null";
return false;
}
try {
// 检查acceptor是否正常打开
if (!server_->acceptor.is_open()) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed: acceptor is not open";
return false;
}
// 检查io_service是否正在运行
if (server_->io_service.stopped()) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed: io_service is stopped";
return false;
}
// 尝试创建一个测试连接来验证服务器是否真正响应
boost::asio::io_service test_io_service;
boost::asio::ip::tcp::socket test_socket(test_io_service);
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string("127.0.0.1"), port);
boost::system::error_code ec;
test_socket.connect(endpoint, ec);
if (!ec) {
test_socket.close();
BOOST_LOG_TRIVIAL(debug) << "Health check passed: test connection successful on port " << port;
return true;
}
BOOST_LOG_TRIVIAL(debug) << "Health check failed: test connection failed with error: " << ec.message();
return false;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(debug) << "Health check failed with exception: " << e.what();
return false;
}
}
void HttpServer::start_health_check()
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
if (m_health_check_enabled) {
BOOST_LOG_TRIVIAL(info) << "Health check is already running";
return; // 已经在运行
}
BOOST_LOG_TRIVIAL(info) << "Starting HTTP server health check with interval: " << m_health_check_interval << "ms";
m_health_check_enabled = true;
m_health_check_thread = create_thread([this] {
set_current_thread_name("http_health_check");
while (true) {
// 检查是否应该继续运行
bool should_continue;
int current_interval;
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
should_continue = m_health_check_enabled;
current_interval = m_health_check_interval;
}
if (!should_continue) {
break;
}
// 使用条件变量等待,可以响应间隔变化
{
std::unique_lock<std::mutex> lock(m_health_check_mutex);
if (m_health_check_cv.wait_for(lock, std::chrono::milliseconds(current_interval),
[this] { return !m_health_check_enabled; })) {
// 如果等待被中断(健康检查被禁用),退出循环
break;
}
}
// 再次检查是否应该继续运行
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
if (!m_health_check_enabled) {
break;
}
}
// 检查服务器是否健康,或者服务器是否已经停止运行
if ((start_http_server && !is_healthy()) || !start_http_server) {
BOOST_LOG_TRIVIAL(warning) << "HTTP server health check failed or server stopped, performing restart...";
try {
// 在健康检查线程中直接执行重启,避免通过标志传递
restart();
BOOST_LOG_TRIVIAL(info) << "HTTP server restart completed by health check thread";
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Failed to restart HTTP server: " << e.what();
std::string error_msg = "HTTP server restart failed after health check on port " + std::to_string(port) + ": " + e.what();
Slic3r::sentryReportLog(Slic3r::SENTRY_LOG_ERROR, error_msg.c_str(), BP_LOCAL_SERVER);
}
} else if (start_http_server) {
BOOST_LOG_TRIVIAL(debug) << "HTTP server health check passed";
}
}
BOOST_LOG_TRIVIAL(info) << "Health check thread stopped";
});
}
void HttpServer::stop_health_check()
{
bool was_running = false;
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
if (!m_health_check_enabled) {
BOOST_LOG_TRIVIAL(debug) << "Health check is not running";
return;
}
BOOST_LOG_TRIVIAL(info) << "Stopping HTTP server health check...";
m_health_check_enabled = false;
was_running = true;
// 通知条件变量,确保健康检查线程能够立即响应停止信号
m_health_check_cv.notify_all();
} // 锁在这里自动释放
if (was_running && m_health_check_thread.joinable()) {
m_health_check_thread.join();
BOOST_LOG_TRIVIAL(info) << "Health check thread joined successfully";
}
}
void HttpServer::start_restart_check()
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
if (m_restart_check_enabled) {
BOOST_LOG_TRIVIAL(info) << "Restart check is already running";
return;
}
BOOST_LOG_TRIVIAL(info) << "Starting HTTP server restart check...";
m_restart_check_enabled = true;
m_restart_check_thread = create_thread([this] {
set_current_thread_name("http_restart_check");
while (true) {
// 检查是否应该继续运行
bool should_continue;
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
should_continue = m_restart_check_enabled;
}
if (!should_continue) {
break;
}
// 检查是否有重启请求
if (is_restart_requested()) {
BOOST_LOG_TRIVIAL(warning) << "HTTP server restart requested by health check, clearing restart flag...";
// 清除重启请求标志,避免重复处理
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
m_restart_requested = false;
}
// 在重启检查线程中我们只负责清理标志不直接调用restart()
// 实际的重启操作由主线程或其他机制来处理
BOOST_LOG_TRIVIAL(info) << "Restart flag cleared, restart should be handled externally";
}
// 等待一段时间再检查
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
BOOST_LOG_TRIVIAL(info) << "Restart check thread stopped";
});
}
void HttpServer::stop_restart_check()
{
bool was_running = false;
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
if (!m_restart_check_enabled) {
BOOST_LOG_TRIVIAL(debug) << "Restart check is not running";
return;
}
BOOST_LOG_TRIVIAL(info) << "Stopping HTTP server restart check...";
m_restart_check_enabled = false;
was_running = true;
}
if (was_running && m_restart_check_thread.joinable()) {
m_restart_check_thread.join();
BOOST_LOG_TRIVIAL(info) << "Restart check thread joined successfully";
}
}
void HttpServer::set_health_check_interval(int interval_ms)
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
if (interval_ms > 0) {
BOOST_LOG_TRIVIAL(info) << "Changing health check interval from " << m_health_check_interval << "ms to " << interval_ms << "ms";
m_health_check_interval = interval_ms;
// 通知条件变量,使新的间隔值立即生效
m_health_check_cv.notify_all();
} else {
BOOST_LOG_TRIVIAL(warning) << "Invalid health check interval: " << interval_ms << "ms, must be positive";
}
}
int HttpServer::get_health_check_interval() const
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
return m_health_check_interval;
}
bool HttpServer::is_health_check_enabled() const
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
return m_health_check_enabled;
}
bool HttpServer::is_restart_requested() const
{
std::lock_guard<std::mutex> lock(m_health_check_mutex);
return m_restart_requested;
}
void HttpServer::simulate_crash()
{
BOOST_LOG_TRIVIAL(warning) << "Simulating HTTP server crash for testing restart mechanism...";
try {
if (server_) {
// 关闭acceptor来模拟崩溃
server_->acceptor.close();
BOOST_LOG_TRIVIAL(info) << "Acceptor closed to simulate crash";
// 停止io_service来模拟崩溃
server_->io_service.stop();
BOOST_LOG_TRIVIAL(info) << "IO service stopped to simulate crash";
}
// 设置标志,让健康检查能够检测到崩溃
start_http_server = false;
BOOST_LOG_TRIVIAL(info) << "Server state set to crashed for health check detection";
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Error during crash simulation: " << e.what();
}
}
void HttpServer::set_request_handler(const std::function<std::shared_ptr<Response>(const std::string&)>& request_handler)
{
this->m_request_handler = request_handler;
}
std::shared_ptr<HttpServer::Response> HttpServer::bbl_auth_handle_request(const std::string& url)
{
BOOST_LOG_TRIVIAL(info) << "thirdparty_login: get_response";
if (boost::contains(url, "access_token")) {
std::string redirect_url = url_get_param(url, "redirect_url");
std::string access_token = url_get_param(url, "access_token");
std::string refresh_token = url_get_param(url, "refresh_token");
std::string expires_in_str = url_get_param(url, "expires_in");
std::string refresh_expires_in_str = url_get_param(url, "refresh_expires_in");
NetworkAgent* agent = wxGetApp().getAgent();
unsigned int http_code;
std::string http_body;
int result = agent->get_my_profile(access_token, &http_code, &http_body);
if (result == 0) {
std::string user_id;
std::string user_name;
std::string user_account;
std::string user_avatar;
try {
json user_j = json::parse(http_body);
if (user_j.contains("uidStr"))
user_id = user_j["uidStr"].get<std::string>();
if (user_j.contains("name"))
user_name = user_j["name"].get<std::string>();
if (user_j.contains("avatar"))
user_avatar = user_j["avatar"].get<std::string>();
if (user_j.contains("account"))
user_account = user_j["account"].get<std::string>();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Failed to parse user profile JSON: " << e.what();
std::string error_msg = "User profile JSON parse error: " + std::string(e.what());
Slic3r::sentryReportLog(Slic3r::SENTRY_LOG_ERROR, error_msg.c_str(), BP_LOCAL_SERVER);
}
json j;
j["data"]["refresh_token"] = refresh_token;
j["data"]["token"] = access_token;
j["data"]["expires_in"] = expires_in_str;
j["data"]["refresh_expires_in"] = refresh_expires_in_str;
j["data"]["user"]["uid"] = user_id;
j["data"]["user"]["name"] = user_name;
j["data"]["user"]["account"] = user_account;
j["data"]["user"]["avatar"] = user_avatar;
agent->change_user(j.dump());
if (agent->is_user_login()) {
wxGetApp().request_user_login(1);
}
GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); });
std::string location_str = (boost::format("%1%?result=success") % redirect_url).str();
return std::make_shared<ResponseRedirect>(location_str);
} else {
std::string error_str = "get_user_profile_error_" + std::to_string(result);
std::string location_str = (boost::format("%1%?result=fail&error=%2%") % redirect_url % error_str).str();
return std::make_shared<ResponseRedirect>(location_str);
}
} else {
return std::make_shared<ResponseNotFound>();
}
}
std::shared_ptr<HttpServer::Response> HttpServer::web_server_handle_request(const std::string& url)
{
BOOST_LOG_TRIVIAL(info) << "Handling file request for URL: " << url;
std::string file_path = map_url_to_file_path(url);
BOOST_LOG_TRIVIAL(info) << "Handling file_path request for URL: " << file_path;
return std::make_shared<ResponseFile>(file_path);
}
std::string HttpServer::map_url_to_file_path(const std::string& url)
{
if (url.find("..") != std::string::npos) {
return "";
}
wxString trimmed_url = wxString::FromUTF8(url);
size_t question_mark = trimmed_url.find('?');
if (question_mark != wxString::npos) {
trimmed_url = trimmed_url.substr(0, question_mark);
}
if (trimmed_url == "/") {
trimmed_url = "/flutter_web/index.html"; // 默认首页
} else if (trimmed_url.substr(0, 11) == "/localfile/") {
auto real_path = trimmed_url.substr(11);
return real_path.ToStdString(wxConvUTF8);
}
auto data_web_path = boost::filesystem::path(data_dir()) / "web";
if (!boost::filesystem::exists(data_web_path / "flutter_web")) {
auto source_path = boost::filesystem::path(resources_dir()) / "web" / "flutter_web";
auto target_path = data_web_path / "flutter_web";
copy_directory_recursively(source_path, target_path);
}
if (trimmed_url.find("flutter_web") == std::string::npos) {
wxString res = wxString::FromUTF8(resources_dir()) + trimmed_url;
return res.ToStdString(wxConvUTF8);
} else {
wxString res = wxString::FromUTF8(data_dir()) + trimmed_url;
return res.ToStdString(wxConvUTF8);
}
}
void HttpServer::ResponseRedirect::write_response(std::stringstream& ssOut)
{
const std::string sHTML = "<html><body><p>redirect to url </p></body></html>";
size_t content_length = sHTML.size(); // 字节长度(与字符数相同,因无多字节字符)
ssOut << "HTTP/1.1 302 Found\r\n";
ssOut << "Location: " << location_str << "\r\n";
ssOut << "Content-Type: text/html\r\n";
ssOut << "Content-Length: " << content_length << "\r\n"; // 正确计算长度
ssOut << "Access-Control-Allow-Origin: *\r\n"; // CORS头
ssOut << "\r\n"; // 头和主体之间的空行(必须)
ssOut << sHTML; // 响应体(长度必须匹配)
}
void HttpServer::ResponseNotFound::write_response(std::stringstream& ssOut)
{
const std::string sHTML = "<html><body><h1>404 Not Found</h1><p>There's nothing here.</p></body></html>";
size_t content_length = sHTML.size(); // 字节长度
ssOut << "HTTP/1.1 404 Not Found\r\n";
ssOut << "Content-Type: text/html\r\n";
ssOut << "Content-Length: " << content_length << "\r\n"; // 正确计算长度
ssOut << "Access-Control-Allow-Origin: *\r\n"; // CORS头
ssOut << "\r\n"; // 头和主体之间的空行(必须)
ssOut << sHTML; // 响应体(长度必须匹配)
}
void HttpServer::ResponseFile::write_response(std::stringstream& ssOut)
{
// 将UTF-8路径转换为适合文件系统操作的编码自动适配Windows的UTF-8模式
std::string system_file_path = utf8_to_filesystem_encoding(file_path);
std::ifstream file(system_file_path, std::ios::binary);
if (!file) {
ResponseNotFound notFoundResponse;
notFoundResponse.write_response(ssOut);
return;
}
// 读取文件内容并计算长度(关键:使用字节长度)
std::ostringstream fileStream;
fileStream << file.rdbuf();
std::string fileContent = fileStream.str();
size_t content_length = fileContent.size(); // 字节长度,非字符数
// 确定Content-Type保持原有逻辑
std::string content_type = "application/octet-stream";
if (ends_with(file_path, ".html"))
content_type = "text/html";
else if (ends_with(file_path, ".css"))
content_type = "text/css";
else if (ends_with(file_path, ".js"))
content_type = "text/javascript";
else if (ends_with(file_path, ".png"))
content_type = "image/png";
else if (ends_with(file_path, ".jpg"))
content_type = "image/jpeg";
else if (ends_with(file_path, ".gif"))
content_type = "image/gif";
else if (ends_with(file_path, ".svg"))
content_type = "image/svg+xml";
else if (ends_with(file_path, ".ttf"))
content_type = "application/x-font-ttf";
else if (ends_with(file_path, ".json"))
content_type = "application/json";
else if (ends_with(file_path, ".webp"))
content_type = "image/webp";
else if (ends_with(file_path, ".woff"))
content_type = "font/woff";
else if (ends_with(file_path, ".woff2"))
content_type = "font/woff2";
// 构造响应头(严格使用\r\n头结束后空行
ssOut << "HTTP/1.1 200 OK\r\n";
ssOut << "Content-Type: " << content_type << "\r\n";
ssOut << "Content-Length: " << content_length << "\r\n"; // 必须与实际内容长度一致
ssOut << "Access-Control-Allow-Origin: *\r\n"; // CORS头
ssOut << "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n";
ssOut << "Access-Control-Allow-Headers: Content-Type, Authorization\r\n";
ssOut << "\r\n"; // 头和主体之间的空行(必须)
ssOut << fileContent; // 响应体长度必须与Content-Length一致
}
}} // namespace Slic3r::GUI