#include "pch.h" #include "HttpServer.h" #include "JsonLite.h" #include "Shrinkwrap90Manager.h" #include #include #include #include #include #include #include #include namespace { std::string ToLower(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return s; } bool GetString(const jsonlite::Object& obj, const char* key, std::string& out) { auto it = obj.find(key); if (it == obj.end()) return false; if (it->second.type != jsonlite::Value::Type::String) return false; out = it->second.s; return true; } bool GetBool(const jsonlite::Object& obj, const char* key, bool& out) { auto it = obj.find(key); if (it == obj.end()) return false; if (it->second.type != jsonlite::Value::Type::Bool) return false; out = it->second.b; return true; } bool GetNumber(const jsonlite::Object& obj, const char* key, double& out) { auto it = obj.find(key); if (it == obj.end()) return false; if (it->second.type != jsonlite::Value::Type::Number) return false; out = it->second.n; return true; } std::string BuildHttpResponse(int status, const std::string& body) { const char* reason = "OK"; if (status == 400) reason = "Bad Request"; else if (status == 404) reason = "Not Found"; else if (status == 405) reason = "Method Not Allowed"; else if (status == 500) reason = "Internal Server Error"; std::ostringstream oss; oss << "HTTP/1.1 " << status << " " << reason << "\r\n"; oss << "Content-Type: application/json; charset=utf-8\r\n"; oss << "Connection: close\r\n"; oss << "Content-Length: " << body.size() << "\r\n"; oss << "\r\n"; oss << body; return oss.str(); } std::string JsonError(const std::string& message) { std::ostringstream oss; oss << "{\"success\":false,\"data\":null,\"error\":\"" << jsonlite::EscapeString(message) << "\"}"; return oss.str(); } std::string JsonSuccess(const ShrinkwrapShellResult& data) { std::ostringstream oss; oss << "{\"success\":true,\"data\":{"; oss << "\"output_path\":\"" << jsonlite::EscapeString(data.output_path) << "\","; oss << "\"file_size\":\"" << jsonlite::EscapeString(data.file_size) << "\","; oss << "\"execution_time\":\"" << jsonlite::EscapeString(data.execution_time) << "\""; oss << "},\"error\":null}"; return oss.str(); } } // namespace HttpServer::HttpServer() = default; HttpServer::~HttpServer() { Stop(); } bool HttpServer::Start(int port, std::string* err, TriggerCallback trigger) { if (thread_.joinable()) { if (err) *err = "already started"; return false; } port_ = port; stop_ = false; trigger_callback_ = trigger; WSADATA wsa{}; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { if (err) *err = "WSAStartup failed"; return false; } listen_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listen_ == INVALID_SOCKET) { if (err) *err = "socket failed"; WSACleanup(); return false; } sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = htons(static_cast(port_)); int opt = 1; setsockopt(listen_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&opt), sizeof(opt)); if (::bind(listen_, reinterpret_cast(&addr), sizeof(addr)) == SOCKET_ERROR) { if (err) *err = "bind failed"; closesocket(listen_); listen_ = INVALID_SOCKET; WSACleanup(); return false; } if (listen(listen_, SOMAXCONN) == SOCKET_ERROR) { if (err) *err = "listen failed"; closesocket(listen_); listen_ = INVALID_SOCKET; WSACleanup(); return false; } thread_ = std::thread([this] { Run(); }); return true; } void HttpServer::Stop() { stop_ = true; if (listen_ != INVALID_SOCKET) { closesocket(listen_); listen_ = INVALID_SOCKET; } task_cv_.notify_all(); if (thread_.joinable()) { thread_.join(); } WSACleanup(); } void HttpServer::Run() { while (!stop_) { SOCKET client = accept(listen_, nullptr, nullptr); if (client == INVALID_SOCKET) { if (stop_) break; continue; } HandleClient(client); closesocket(client); } } bool HttpServer::HasPendingTask() const { std::lock_guard lock(task_mutex_); return pending_task_ != nullptr && !pending_task_->completed; } void HttpServer::ProcessPendingTask() { std::lock_guard lock(task_mutex_); if (!pending_task_ || pending_task_->completed) return; try { auto start = std::chrono::steady_clock::now(); Shrinkwrap90Manager mgr; pending_task_->result = mgr.ExportShell(pending_task_->request); auto end = std::chrono::steady_clock::now(); auto secs = std::chrono::duration_cast(end - start).count(); pending_task_->result.execution_time = Shrinkwrap90Manager::FormatHhMmSs(secs); pending_task_->success = true; } catch (pfcXPFC& ex) { xstring msg = ex.GetMessage(); const char* msgPtr = msg; pending_task_->error = (msgPtr && msgPtr[0]) ? std::string(msgPtr) : "Creo OTK error"; pending_task_->success = false; } catch (xthrowable&) { pending_task_->error = "Creo OTK exception"; pending_task_->success = false; } catch (const std::exception& ex) { pending_task_->error = ex.what(); pending_task_->success = false; } catch (...) { pending_task_->error = "unknown error"; pending_task_->success = false; } pending_task_->completed = true; task_cv_.notify_all(); } static bool ReadExact(SOCKET s, std::string& buf, size_t bytes) { while (buf.size() < bytes) { char tmp[4096]; int want = static_cast(std::min(sizeof(tmp), bytes - buf.size())); int n = recv(s, tmp, want, 0); if (n <= 0) return false; buf.append(tmp, tmp + n); } return true; } void HttpServer::HandleClient(SOCKET client) { std::string raw; raw.reserve(8192); while (raw.find("\r\n\r\n") == std::string::npos) { char tmp[4096]; int n = recv(client, tmp, sizeof(tmp), 0); if (n <= 0) return; raw.append(tmp, tmp + n); if (raw.size() > 1024 * 1024) { auto resp = BuildHttpResponse(400, JsonError("request too large")); send(client, resp.data(), static_cast(resp.size()), 0); return; } } size_t headerEnd = raw.find("\r\n\r\n"); std::string header = raw.substr(0, headerEnd); std::string body = raw.substr(headerEnd + 4); std::istringstream hs(header); std::string requestLine; if (!std::getline(hs, requestLine)) return; if (!requestLine.empty() && requestLine.back() == '\r') requestLine.pop_back(); std::istringstream rl(requestLine); std::string method, path, version; rl >> method >> path >> version; std::string line; size_t contentLength = 0; while (std::getline(hs, line)) { if (!line.empty() && line.back() == '\r') line.pop_back(); auto pos = line.find(':'); if (pos == std::string::npos) continue; std::string k = ToLower(line.substr(0, pos)); std::string v = line.substr(pos + 1); while (!v.empty() && std::isspace(static_cast(v[0]))) v.erase(v.begin()); if (k == "content-length") { contentLength = static_cast(std::strtoull(v.c_str(), nullptr, 10)); } } if (contentLength > 0 && body.size() < contentLength) { std::string more; if (!ReadExact(client, body, contentLength)) return; } if (contentLength > 0 && body.size() > contentLength) { body.resize(contentLength); } if (method != "POST") { auto resp = BuildHttpResponse(405, JsonError("only POST is supported")); send(client, resp.data(), static_cast(resp.size()), 0); return; } if (path != "/api/creo/shrinkwrap/shell") { auto resp = BuildHttpResponse(404, JsonError("not found")); send(client, resp.data(), static_cast(resp.size()), 0); return; } jsonlite::Object obj; std::string parseErr; if (!jsonlite::ParseObject(body, obj, parseErr)) { auto resp = BuildHttpResponse(400, JsonError("invalid json: " + parseErr)); send(client, resp.data(), static_cast(resp.size()), 0); return; } ShrinkwrapShellRequest req; GetString(obj, "software_type", req.software_type); double num = 0.0; if (GetNumber(obj, "quality", num)) req.quality = static_cast(num); if (GetNumber(obj, "chord_height", num)) req.chord_height = num; GetBool(obj, "fill_holes", req.fill_holes); GetBool(obj, "ignore_small_surfaces", req.ignore_small_surfaces); if (GetNumber(obj, "small_surface_percentage", num)) req.small_surface_percentage = num; GetString(obj, "output_file_path", req.output_file_path); GetString(obj, "output_type", req.output_type); GetBool(obj, "ignore_quilts", req.ignore_quilts); GetBool(obj, "ignore_skeleton", req.ignore_skeleton); GetBool(obj, "assign_mass_properties", req.assign_mass_properties); if (GetNumber(obj, "timeout_seconds", num)) req.timeout_seconds = static_cast(num); if (req.output_file_path.empty()) { auto resp = BuildHttpResponse(400, JsonError("output_file_path is required")); send(client, resp.data(), static_cast(resp.size()), 0); return; } PendingTask task; task.request = req; { std::unique_lock lock(task_mutex_); if (pending_task_ != nullptr) { auto resp = BuildHttpResponse(503, JsonError("server busy, another task is pending")); send(client, resp.data(), static_cast(resp.size()), 0); return; } pending_task_ = &task; } if (trigger_callback_) { trigger_callback_(); } { std::unique_lock lock(task_mutex_); int timeout_ms = req.timeout_seconds * 1000; if (timeout_ms <= 0) timeout_ms = 60000; bool completed = task_cv_.wait_for(lock, std::chrono::milliseconds(timeout_ms), [&task] { return task.completed; }); pending_task_ = nullptr; if (!completed) { auto resp = BuildHttpResponse(504, JsonError("task timeout")); send(client, resp.data(), static_cast(resp.size()), 0); return; } } if (!task.success) { auto resp = BuildHttpResponse(500, JsonError(task.error)); send(client, resp.data(), static_cast(resp.size()), 0); return; } auto resp = BuildHttpResponse(200, JsonSuccess(task.result)); send(client, resp.data(), static_cast(resp.size()), 0); }