From 096812b7d224ee3a1d0784301ebd08eedb2c0ea8 Mon Sep 17 00:00:00 2001 From: sladro Date: Sat, 11 Oct 2025 17:19:35 +0800 Subject: [PATCH] feat: add HTTP/HTTPS URL input support for STP files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add capability to download STP files directly from HTTP/HTTPS URLs in both CLI and HTTP server modes. Changes: - Add http_downloader module for downloading files from URLs - Extend GlobalConfig to track downloaded files - Update CLI parameter processing to detect and handle URLs - Enhance HTTP server to accept URL parameter alongside file upload - Implement automatic cleanup of downloaded temporary files - Add comprehensive usage documentation (USAGE.md) Usage examples: CLI: STP2GLB.exe --stp https://example.com/model.stp --glb output.glb API: curl -X POST http://localhost:8080/convert -F "url=https://example.com/model.stp" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CMakeLists.txt | 2 + USAGE.md | 180 ++++++++++++++++++++++++++++++++++++++++ src/config_structs.h | 4 + src/config_utils.cpp | 48 ++++++++--- src/config_utils.h | 15 ++++ src/http_downloader.cpp | 120 +++++++++++++++++++++++++++ src/http_downloader.h | 13 +++ src/http_server.cpp | 41 +++++++-- src/main.cpp | 12 +++ 9 files changed, 416 insertions(+), 19 deletions(-) create mode 100644 USAGE.md create mode 100644 src/config_utils.h create mode 100644 src/http_downloader.cpp create mode 100644 src/http_downloader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c0a8b8e..584c6b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,7 @@ set(SOURCES src/main.cpp src/config_utils.cpp src/http_server.cpp + src/http_downloader.cpp src/geom/Color.cpp src/cadit/occt/step_tree.cpp src/cadit/occt/debug.cpp @@ -87,6 +88,7 @@ set(HEADERS src/config_utils.h src/config_structs.h src/http_server.h + src/http_downloader.h src/geom/Color.h src/cadit/occt/step_tree.h src/cadit/occt/convert.h diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..a0e07e8 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,180 @@ +# STP2GLB 使用说明 + +STP2GLB 支持两种运行模式:**CLI命令行模式** 和 **HTTP服务器模式** + +--- + +## CLI 命令行模式 + +### 基本用法 + +#### 1. 本地文件转换 +```bash +STP2GLB.exe --stp input.stp --glb output.glb +``` + +#### 2. HTTP URL转换(新功能) +```bash +STP2GLB.exe --stp https://example.com/model.stp --glb output.glb +``` + +### 主要参数 + +| 参数 | 说明 | 默认值 | 范围 | +|------|------|--------|------| +| `--stp` | STP文件路径或HTTP/HTTPS URL(必填) | - | 本地路径或URL | +| `--glb` | 输出GLB文件路径(必填) | - | 本地路径 | +| `--lin-defl` | 线性偏差 | 0.5 | 0.0-1.0 | +| `--ang-defl` | 角度偏差 | 0.8 | 0.0-1.0 | +| `--rel-defl` | 相对偏差模式 | false | - | +| `--debug` | 调试模式(更详细的错误信息) | false | - | +| `--solid-only` | 仅转换实体 | false | - | +| `--max-geometry-num` | 最大几何体数量(0=无限制) | 0 | ≥0 | +| `--tessellation-timeout` | 网格化超时时间(秒) | 30 | >0 | + +### 使用示例 + +```bash +# 本地文件转换,调整精度 +STP2GLB.exe --stp model.stp --glb output.glb --lin-defl 0.1 --ang-defl 0.5 + +# 从URL下载并转换 +STP2GLB.exe --stp http://example.com/files/part.step --glb result.glb + +# 调试模式转换 +STP2GLB.exe --stp model.stp --glb output.glb --debug + +# 限制几何体数量 +STP2GLB.exe --stp large.stp --glb output.glb --max-geometry-num 100 +``` + +--- + +## HTTP 服务器模式 + +### 启动服务器 + +```bash +STP2GLB.exe --server --port 8080 --host 0.0.0.0 +``` + +### 服务器参数 + +| 参数 | 说明 | 默认值 | +|------|------|--------| +| `--server` | 启动服务器模式 | - | +| `--port` | 服务器端口 | 8080 | +| `--host` | 监听地址 | 0.0.0.0 | +| `--max-file-size` | 最大文件大小(MB) | 500 | + +### API 端点 + +#### 1. 健康检查 +```bash +GET /health +``` + +**响应示例:** +```json +{ + "status": "ok", + "service": "STP2GLB" +} +``` + +#### 2. 文件转换 +```bash +POST /convert +``` + +**支持两种输入方式:** + +##### 方式一:文件上传 +```bash +curl -X POST http://localhost:8080/convert \ + -F "file=@model.stp" \ + -F "linearDeflection=0.1" \ + -F "angularDeflection=0.5" +``` + +##### 方式二:HTTP URL(新功能) +```bash +curl -X POST http://localhost:8080/convert \ + -F "url=https://example.com/model.stp" \ + -F "linearDeflection=0.1" +``` + +### 转换参数 + +| 参数名 | 说明 | 默认值 | +|--------|------|--------| +| `file` | STP文件(multipart上传) | - | +| `url` | STP文件HTTP/HTTPS URL | - | +| `linearDeflection` | 线性偏差 | 0.5 | +| `angularDeflection` | 角度偏差 | 0.8 | +| `relativeDeflection` | 相对偏差(true/false) | false | +| `debug` | 调试模式(true/false) | false | +| `solidOnly` | 仅实体(true/false) | false | +| `maxGeometryNum` | 最大几何体数量 | 0 | + +**注意:** `file` 和 `url` 二选一,`url` 优先级更高 + +### 响应示例 + +**成功响应:** +```json +{ + "success": true, + "output_file": "1234567890_5678.glb", + "output_path": "./output/1234567890_5678.glb", + "conversion_time": 2.35 +} +``` + +**错误响应:** +```json +{ + "success": false, + "error": "Failed to download from URL: HTTP error: 404 Not Found" +} +``` + +--- + +## 注意事项 + +1. **URL下载**: + - 仅支持HTTP和HTTPS协议 + - URL必须直接指向.stp或.step文件 + - 下载的临时文件会在转换完成后自动清理 + +2. **文件大小限制**: + - HTTP服务器模式默认限制500MB + - 可通过 `--max-file-size` 参数调整 + +3. **超时设置**: + - 默认网格化超时30秒 + - 大型复杂模型建议增加超时时间 + +4. **调试模式**: + - 提供详细的STEP实体转换失败信息 + - 会显著降低转换速度 + +--- + +## 快速测试 + +```bash +# CLI模式测试 +STP2GLB.exe --stp https://example.com/test.stp --glb test.glb + +# 服务器模式测试 +STP2GLB.exe --server --port 8080 + +# 测试健康检查 +curl http://localhost:8080/health + +# 测试URL转换 +curl -X POST http://localhost:8080/convert \ + -F "url=https://example.com/model.stp" +``` diff --git a/src/config_structs.h b/src/config_structs.h index b9ab5ad..5eaf45a 100644 --- a/src/config_structs.h +++ b/src/config_structs.h @@ -42,6 +42,10 @@ struct GlobalConfig { BuildConfig buildConfig; ServerConfig serverConfig; + + // HTTP download tracking + bool is_downloaded_from_url = false; + std::string original_url; }; diff --git a/src/config_utils.cpp b/src/config_utils.cpp index 066fce1..f01389d 100644 --- a/src/config_utils.cpp +++ b/src/config_utils.cpp @@ -12,6 +12,7 @@ #include "config_structs.h" #include "cadit/occt/helpers.h" #include "CLI/App.hpp" +#include "http_downloader.h" // Helper function to process filter names from input or file std::vector process_filter_names(const std::string& input, const std::string& file_name) @@ -79,19 +80,40 @@ GlobalConfig process_parameters(CLI::App& app) const auto filter_names_include = process_filter_names(filter_names_include_input, filter_names_file_include); const auto filter_names_exclude = process_filter_names(filter_names_exclude_input, filter_names_file_exclude); - const std::string stpFilename = app.get_option("--stp")->results()[0]; - const std::string glbFilename = app.get_option("--glb")->results()[0]; + std::string stpFilename = app.get_option("--stp")->results()[0]; + std::string glbFilename = app.get_option("--glb")->results()[0]; + + bool isDownloadedFromUrl = false; + std::string originalUrl; + std::filesystem::path stpFilePath; + std::filesystem::path glbFilePath; + + // Check if input is HTTP URL + if (is_http_url(stpFilename)) { + std::cout << "Detected HTTP URL, downloading..." << std::endl; + try { + std::string downloaded_path = download_file_from_url(stpFilename, "./temp"); + stpFilePath = std::filesystem::path(downloaded_path); + isDownloadedFromUrl = true; + originalUrl = stpFilename; + std::cout << "URL download completed: " << downloaded_path << std::endl; + } catch (const std::exception& ex) { + throw std::runtime_error("Failed to download from URL: " + std::string(ex.what())); + } + } else { + stpFilePath = std::filesystem::path(stpFilename); + // Check if local file exists + if (!exists(stpFilePath)) { + throw std::invalid_argument("Invalid --stp filename \"" + stpFilename + "\". File does not exist."); + } + } + + glbFilePath = std::filesystem::path(glbFilename); // Validate extensions - const bool isStpValid = endsWithCaseInsensitive(stpFilename, ".stp") || endsWithCaseInsensitive(stpFilename, ".step"); - const bool isGlbValid = endsWithCaseInsensitive(glbFilename, ".glb"); - auto stpFilePath = std::filesystem::path(stpFilename); - auto glbFilePath = std::filesystem::path(glbFilename); + bool isStpValid = endsWithCaseInsensitive(stpFilePath.string(), ".stp") || endsWithCaseInsensitive(stpFilePath.string(), ".step"); + bool isGlbValid = endsWithCaseInsensitive(glbFilename, ".glb"); - // check if file paths exists - if (!exists(stpFilePath)) { - throw std::invalid_argument("Invalid --stp filename \"" + stpFilename + "\". File does not exist."); - } if (exists(glbFilePath)) { std::cout << "Warning: --glb filename \"" << glbFilename << "\" already exists and will be overwritten.\n"; } @@ -106,7 +128,7 @@ GlobalConfig process_parameters(CLI::App& app) // Create configuration return { - .stpFile = stpFilename, + .stpFile = stpFilePath, .glbFile = glbFilename, .debug_mode = app.get_option("--debug")->as(), .linearDeflection = app.get_option("--lin-defl")->as(), @@ -126,6 +148,8 @@ GlobalConfig process_parameters(CLI::App& app) .host = app.get_option("--host")->as(), .max_file_size_mb = app.get_option("--max-file-size")->as(), .temp_dir = "./temp" - } + }, + .is_downloaded_from_url = isDownloadedFromUrl, + .original_url = originalUrl }; } diff --git a/src/config_utils.h b/src/config_utils.h new file mode 100644 index 0000000..69def62 --- /dev/null +++ b/src/config_utils.h @@ -0,0 +1,15 @@ +// +// Created by ofskrand on 13.01.2025. +// + +#ifndef CONFIG_UTILS_H +#define CONFIG_UTILS_H +#include + +#include "config_structs.h" + + +GlobalConfig process_parameters(CLI::App& app); + + +#endif //CONFIG_UTILS_H diff --git a/src/http_downloader.cpp b/src/http_downloader.cpp new file mode 100644 index 0000000..abcc929 --- /dev/null +++ b/src/http_downloader.cpp @@ -0,0 +1,120 @@ +#include "http_downloader.h" +#include "third_party/httplib.h" +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +bool is_http_url(const std::string& path) { + std::string lower_path = path; + std::transform(lower_path.begin(), lower_path.end(), lower_path.begin(), ::tolower); + return lower_path.substr(0, 7) == "http://" || lower_path.substr(0, 8) == "https://"; +} + +std::string generate_download_filename(const std::string& extension) { + auto now = std::chrono::system_clock::now(); + auto timestamp = std::chrono::duration_cast(now.time_since_epoch()).count(); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(1000, 9999); + + std::ostringstream oss; + oss << "download_" << timestamp << "_" << dis(gen) << extension; + return oss.str(); +} + +std::string download_file_from_url(const std::string& url, const std::string& temp_dir) { + std::cout << "Downloading file: " << url << std::endl; + + // Ensure temp directory exists + fs::create_directories(temp_dir); + + // Parse URL + size_t protocol_end = url.find("://"); + if (protocol_end == std::string::npos) { + throw std::runtime_error("Invalid URL format: " + url); + } + + std::string protocol = url.substr(0, protocol_end); + std::string remaining = url.substr(protocol_end + 3); + + size_t host_end = remaining.find('/'); + std::string host; + std::string path; + + if (host_end == std::string::npos) { + host = remaining; + path = "/"; + } else { + host = remaining.substr(0, host_end); + path = remaining.substr(host_end); + } + + // Extract file extension from URL path + std::string extension = ".stp"; + size_t last_dot = path.find_last_of('.'); + if (last_dot != std::string::npos) { + std::string url_ext = path.substr(last_dot); + // Ensure it's a STP-related extension + std::string lower_ext = url_ext; + std::transform(lower_ext.begin(), lower_ext.end(), lower_ext.begin(), ::tolower); + if (lower_ext == ".stp" || lower_ext == ".step") { + extension = lower_ext; + } + } + + // Generate local filename + std::string filename = generate_download_filename(extension); + fs::path local_path = fs::path(temp_dir) / filename; + + std::cout << "Target host: " << host << std::endl; + std::cout << "Request path: " << path << std::endl; + std::cout << "Saving to: " << local_path << std::endl; + + // Create HTTP client + std::unique_ptr cli; + + if (protocol == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli = std::make_unique(host); +#else + throw std::runtime_error("HTTPS not supported in this build"); +#endif + } else { + cli = std::make_unique(host); + } + + // Set timeout + cli->set_read_timeout(300, 0); // 300 seconds + cli->set_write_timeout(300, 0); + + // Execute GET request + auto res = cli->Get(path.c_str()); + + if (!res) { + throw std::runtime_error("HTTP request failed: " + httplib::to_string(res.error())); + } + + if (res->status != 200) { + throw std::runtime_error("HTTP error: " + std::to_string(res->status) + " " + res->reason); + } + + // Save to local file + std::ofstream ofs(local_path, std::ios::binary); + if (!ofs) { + throw std::runtime_error("Cannot create file: " + local_path.string()); + } + + ofs.write(res->body.c_str(), res->body.size()); + ofs.close(); + + std::cout << "Download completed, file size: " << res->body.size() << " bytes" << std::endl; + + return local_path.string(); +} diff --git a/src/http_downloader.h b/src/http_downloader.h new file mode 100644 index 0000000..1c83919 --- /dev/null +++ b/src/http_downloader.h @@ -0,0 +1,13 @@ +#ifndef HTTP_DOWNLOADER_H +#define HTTP_DOWNLOADER_H + +#include + +// 判断路径是否为HTTP/HTTPS URL +bool is_http_url(const std::string& path); + +// 从URL下载文件到指定目录,返回本地文件路径 +// 下载失败会抛出std::runtime_error异常 +std::string download_file_from_url(const std::string& url, const std::string& temp_dir); + +#endif // HTTP_DOWNLOADER_H diff --git a/src/http_server.cpp b/src/http_server.cpp index 49fc8fe..9fe203f 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -2,6 +2,7 @@ #include "third_party/httplib.h" #include "cadit/occt/convert.h" #include "cadit/occt/debug.h" +#include "http_downloader.h" #include #include #include @@ -48,13 +49,30 @@ void start_http_server(const GlobalConfig& base_config) { svr.Post("/convert", [&base_config](const httplib::Request& req, httplib::Response& res) { std::cout << "Received conversion request" << "\n"; - if (!req.is_multipart_form_data() || !req.form.has_file("file")) { + bool use_url = false; + std::string url_param; + + // Check if URL parameter is provided + if (req.form.has_field("url")) { + url_param = req.form.get_field("url"); + if (!url_param.empty() && is_http_url(url_param)) { + use_url = true; + std::cout << "Using URL parameter: " << url_param << "\n"; + } + } + + // If no URL, check file upload + if (!use_url && (!req.is_multipart_form_data() || !req.form.has_file("file"))) { res.status = 400; - res.set_content("{\"error\":\"No file uploaded. Use 'file' field.\"}", "application/json"); + res.set_content("{\"error\":\"No file uploaded or URL provided. Use 'file' field or 'url' parameter.\"}", "application/json"); return; } - const auto& file = req.form.get_file("file", 0); + std::string file_content; + if (!use_url) { + const auto& file = req.form.get_file("file", 0); + file_content = file.content; + } GlobalConfig config = base_config; @@ -81,13 +99,22 @@ void start_http_server(const GlobalConfig& base_config) { std::string stp_filename = base_filename + ".stp"; std::string glb_filename = base_filename + ".glb"; - fs::path stp_path = fs::path(config.serverConfig.temp_dir) / stp_filename; + fs::path stp_path; fs::path glb_path = fs::path(config.serverConfig.output_dir) / glb_filename; try { - std::ofstream ofs(stp_path, std::ios::binary); - ofs.write(file.content.c_str(), file.content.size()); - ofs.close(); + // If using URL, download file; otherwise save uploaded file + if (use_url) { + std::string downloaded_path = download_file_from_url(url_param, config.serverConfig.temp_dir); + stp_path = fs::path(downloaded_path); + config.is_downloaded_from_url = true; + config.original_url = url_param; + } else { + stp_path = fs::path(config.serverConfig.temp_dir) / stp_filename; + std::ofstream ofs(stp_path, std::ios::binary); + ofs.write(file_content.c_str(), file_content.size()); + ofs.close(); + } config.stpFile = stp_path; config.glbFile = glb_path; diff --git a/src/main.cpp b/src/main.cpp index 303539d..24e94b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -140,5 +140,17 @@ int main(int argc, char* argv[]) const double seconds = static_cast(duration.count()) / 1e6; std::cout << "STP converted in: " << std::fixed << std::setprecision(2) << seconds << " seconds" << "\n"; + // Clean up downloaded temporary file + if (config.is_downloaded_from_url) { + try { + if (std::filesystem::exists(config.stpFile)) { + std::filesystem::remove(config.stpFile); + std::cout << "Cleaned up downloaded file: " << config.stpFile << "\n"; + } + } catch (const std::exception& ex) { + std::cerr << "Warning: Failed to clean up downloaded file: " << ex.what() << "\n"; + } + } + return 0; }