#include "HttpServer.hpp" #include #include #include "GUI_App.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/NetworkAgent.hpp" #include "sentry_wrapper/SentryWrapper.hpp" #ifdef _WIN32 #include #include #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> bufptr = std::make_shared>(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 str = std::make_shared(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(*this, std::move(socket)); start(ss); } do_accept(); }); } void HttpServer::IOServer::start(std::shared_ptr session) { sessions.insert(session); session->start(); } void HttpServer::IOServer::stop(std::shared_ptr 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(*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 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 lock(m_health_check_mutex); should_continue = m_health_check_enabled; current_interval = m_health_check_interval; } if (!should_continue) { break; } // 使用条件变量等待,可以响应间隔变化 { std::unique_lock 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 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 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 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 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 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 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 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 lock(m_health_check_mutex); return m_health_check_interval; } bool HttpServer::is_health_check_enabled() const { std::lock_guard lock(m_health_check_mutex); return m_health_check_enabled; } bool HttpServer::is_restart_requested() const { std::lock_guard 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(const std::string&)>& request_handler) { this->m_request_handler = request_handler; } std::shared_ptr 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(); if (user_j.contains("name")) user_name = user_j["name"].get(); if (user_j.contains("avatar")) user_avatar = user_j["avatar"].get(); if (user_j.contains("account")) user_account = user_j["account"].get(); } 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(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(location_str); } } else { return std::make_shared(); } } std::shared_ptr 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(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 = "

redirect to url

"; 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 = "

404 Not Found

There's nothing here.

"; 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