386 lines
13 KiB
C++
386 lines
13 KiB
C++
#ifndef C99FBF1C_5805_40A3_963F_5BC1CA998C5C
|
|
#define C99FBF1C_5805_40A3_963F_5BC1CA998C5C
|
|
|
|
#include <Poco/Exception.h>
|
|
#include <Poco/Net/HTMLForm.h>
|
|
#include <Poco/Net/HTTPClientSession.h>
|
|
#include <Poco/Net/HTTPRequest.h>
|
|
#include <Poco/Net/HTTPResponse.h>
|
|
|
|
#include <Poco/Net/MessageHeader.h>
|
|
#include <Poco/StreamCopier.h>
|
|
#include <Poco/URI.h>
|
|
#include <cctype>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "Logger.h"
|
|
|
|
namespace Util
|
|
{
|
|
class HTTPClient
|
|
{
|
|
public:
|
|
struct HttpResponse
|
|
{
|
|
int statusCode;
|
|
std::string statusMessage;
|
|
std::string content;
|
|
std::string contentType;
|
|
std::map<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string>& 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<std::string, std::string>& 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<unsigned char>(c));
|
|
}
|
|
}
|
|
|
|
return escaped.str();
|
|
}
|
|
|
|
static std::string buildFormData(const std::map<std::string, std::string>& 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<Poco::Net::HTTPClientSession> createSession(const Poco::URI& uri, const ProxyConfig& proxy)
|
|
{
|
|
std::unique_ptr<Poco::Net::HTTPClientSession> session;
|
|
logger().debug("HTTPClient url: %s", uri.toString());
|
|
if (proxy.enabled)
|
|
{
|
|
session = std::make_unique<Poco::Net::HTTPClientSession>(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<Poco::Net::HTTPClientSession>(uri.getHost(), uri.getPort());
|
|
}
|
|
else
|
|
{
|
|
session = std::make_unique<Poco::Net::HTTPClientSession>(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
|