#ifndef C99FBF1C_5805_40A3_963F_5BC1CA998C5C #define C99FBF1C_5805_40A3_963F_5BC1CA998C5C #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Logger.h" namespace Util { class HTTPClient { public: struct HttpResponse { int statusCode; std::string statusMessage; std::string content; std::string contentType; std::map headers; bool success; HttpResponse(): statusCode(0), success(false) { } }; struct ProxyConfig { std::string host; int port; bool enabled; ProxyConfig(): port(0), enabled(false) { } ProxyConfig(const std::string& h, int p): host(h), port(p), enabled(true) { } }; struct RequestConfig { int timeout; std::string userAgent; std::map headers; bool followRedirects; ProxyConfig proxy; RequestConfig(): timeout(30), userAgent("HTTPClient/1.0"), followRedirects(true) { } }; static HttpResponse get(const std::string& url, const RequestConfig& config = RequestConfig()) { return executeRequest("GET", url, "", config); } static bool downloadFile(const std::string& url, const std::string& localFilePath, const RequestConfig& config = RequestConfig()) { try { Poco::URI uri(url); auto session = createSession(uri, config.proxy); session->setTimeout(Poco::Timespan(config.timeout, 0)); std::string path = getPathAndQuery(uri); Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, path); setupRequest(request, config); session->sendRequest(request); Poco::Net::HTTPResponse response; std::istream& responseStream = session->receiveResponse(response); if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK) { std::cerr << "Download failed: HTTP " << response.getStatus() << std::endl; return false; } std::ofstream file(localFilePath, std::ios::binary); if (!file.is_open()) { std::cerr << "Cannot open file: " << localFilePath << std::endl; return false; } Poco::StreamCopier::copyStream(responseStream, file); file.close(); return true; } catch (Poco::Exception& e) { std::cerr << "Download exception: " << e.displayText() << std::endl; return false; } } static HttpResponse postJson(const std::string& url, const std::string& jsonData, const RequestConfig& config = RequestConfig()) { RequestConfig jsonConfig = config; jsonConfig.headers["Content-Type"] = "application/json"; return executeRequest("POST", url, jsonData, jsonConfig); } static HttpResponse postForm(const std::string& url, const std::map& formData, const RequestConfig& config = RequestConfig()) { std::string formString = buildFormData(formData); RequestConfig formConfig = config; formConfig.headers["Content-Type"] = "application/x-www-form-urlencoded"; return executeRequest("POST", url, formString, formConfig); } static HttpResponse postFormWithPoco(const std::string& url, const std::map& formData, const RequestConfig& config = RequestConfig()) { try { Poco::URI uri(url); auto session = createSession(uri, config.proxy); session->setTimeout(Poco::Timespan(config.timeout, 0)); std::string path = getPathAndQuery(uri); Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, path); setupRequest(request, config); request.setContentType("application/x-www-form-urlencoded"); Poco::Net::HTMLForm form; for (const auto& field : formData) { form.set(field.first, field.second); } std::ostream& requestStream = session->sendRequest(request); form.write(requestStream); Poco::Net::HTTPResponse response; std::istream& responseStream = session->receiveResponse(response); HttpResponse result; result.statusCode = response.getStatus(); result.statusMessage = response.getReason(); result.contentType = response.getContentType(); result.success = (result.statusCode == Poco::Net::HTTPResponse::HTTP_OK); Poco::Net::NameValueCollection::ConstIterator it = response.begin(); while (it != response.end()) { result.headers[it->first] = it->second; ++it; } Poco::StreamCopier::copyToString(responseStream, result.content); return result; } catch (Poco::Exception& e) { std::cerr << "POST form exception: " << e.displayText() << std::endl; HttpResponse result; result.success = false; result.statusMessage = e.displayText(); return result; } } static HttpResponse post(const std::string& url, const std::string& data, const std::string& contentType = "text/plain", const RequestConfig& config = RequestConfig()) { RequestConfig postConfig = config; postConfig.headers["Content-Type"] = contentType; return executeRequest("POST", url, data, postConfig); } static HttpResponse put(const std::string& url, const std::string& data = "", const RequestConfig& config = RequestConfig()) { return executeRequest("PUT", url, data, config); } static HttpResponse deleteRequest(const std::string& url, const RequestConfig& config = RequestConfig()) { return executeRequest("DELETE", url, "", config); } static RequestConfig createProxyConfig(const std::string& proxyHost, int proxyPort, const RequestConfig& baseConfig = RequestConfig()) { RequestConfig config = baseConfig; config.proxy = ProxyConfig(proxyHost, proxyPort); return config; } static HttpResponse executeRequest(const std::string& method, const std::string& url, const std::string& data, const RequestConfig& config) { HttpResponse result; try { Poco::URI uri(url); auto session = createSession(uri, config.proxy); session->setTimeout(Poco::Timespan(config.timeout, 0)); std::string path = getPathAndQuery(uri); Poco::Net::HTTPRequest request(method, path); setupRequest(request, config); if (!data.empty()) { request.setContentLength(data.length()); std::ostream& requestStream = session->sendRequest(request); requestStream << data; } else { session->sendRequest(request); } Poco::Net::HTTPResponse response; std::istream& responseStream = session->receiveResponse(response); result.statusCode = response.getStatus(); result.statusMessage = response.getReason(); result.contentType = response.getContentType(); result.success = (result.statusCode == Poco::Net::HTTPResponse::HTTP_OK); Poco::Net::NameValueCollection::ConstIterator it = response.begin(); while (it != response.end()) { result.headers[it->first] = it->second; ++it; } Poco::StreamCopier::copyToString(responseStream, result.content); if (config.followRedirects && (result.statusCode == Poco::Net::HTTPResponse::HTTP_MOVED_PERMANENTLY || result.statusCode == Poco::Net::HTTPResponse::HTTP_FOUND || result.statusCode == Poco::Net::HTTPResponse::HTTP_SEE_OTHER)) { auto locationIt = result.headers.find("Location"); if (locationIt != result.headers.end()) { std::cout << "Redirecting to: " << locationIt->second << std::endl; return executeRequest(method, locationIt->second, data, config); } } } catch (Poco::TimeoutException& e) { std::cerr << "Request timeout: " << e.displayText() << std::endl; result.success = false; result.statusMessage = "Timeout"; } catch (Poco::Exception& e) { std::cerr << "HTTP exception: " << e.displayText() << std::endl; result.success = false; result.statusMessage = e.displayText(); } catch (std::exception& e) { std::cerr << "Standard exception: " << e.what() << std::endl; result.success = false; result.statusMessage = e.what(); } return result; } private: static std::string urlEncode(const std::string& value) { std::ostringstream escaped; escaped.fill('0'); escaped << std::hex; for (char c : value) { if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { escaped << c; } else { escaped << '%' << std::setw(2) << int(static_cast(c)); } } return escaped.str(); } static std::string buildFormData(const std::map& formData) { std::ostringstream formStream; bool first = true; for (const auto& field : formData) { if (!first) { formStream << "&"; } formStream << urlEncode(field.first) << "=" << urlEncode(field.second); first = false; } return formStream.str(); } static std::unique_ptr createSession(const Poco::URI& uri, const ProxyConfig& proxy) { std::unique_ptr session; logger().debug("HTTPClient url: %s", uri.toString()); if (proxy.enabled) { session = std::make_unique(proxy.host, proxy.port); session->setProxy(uri.getHost(), uri.getPort()); } else { if (uri.getScheme() == "https") { logger().warning("HTTPClient 暂时不支持https,自动调整为使用http连接: %s", uri.toString()); session = std::make_unique(uri.getHost(), uri.getPort()); } else { session = std::make_unique(uri.getHost(), uri.getPort()); } } return session; } static void setupRequest(Poco::Net::HTTPRequest& request, const RequestConfig& config) { request.setVersion(Poco::Net::HTTPMessage::HTTP_1_1); request.set("User-Agent", config.userAgent); request.set("Accept", "*/*"); request.set("Connection", "close"); for (const auto& header : config.headers) { request.set(header.first, header.second); } } static std::string getPathAndQuery(const Poco::URI& uri) { std::string path = uri.getPathAndQuery(); if (path.empty()) { path = "/"; } return path; } }; } #endif