CppUtils/Utils/HttpHelper1.cpp
2025-06-05 10:06:43 +08:00

327 lines
8.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <string>
#include <regex>
#include <sstream>
#include <iomanip>
#include "HttpHelper.h"
#pragma comment(lib, "Ws2_32.lib")
std::string HttpHelper::HttpRequest(const std::string& host, const std::string& port,
const std::string& path, const std::string& method,
const std::string& data)
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo* result = nullptr, hints;
int iResult;
std::string response;
// 初始化Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
{
std::cerr << "WSAStartup失败: " << iResult << std::endl;
return "";
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_UNSPEC; // 支持IPv4或IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP; // 使用TCP协议
// 解析服务器地址和端口
iResult = getaddrinfo(host.c_str(), port.c_str(), &hints, &result);
if (iResult != 0)
{
std::cerr << "getaddrinfo失败: " << iResult << std::endl;
WSACleanup();
return "";
}
// 尝试连接到第一个解析出的地址
struct addrinfo* ptr = result;
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET)
{
std::cerr << "socket创建失败: " << WSAGetLastError() << std::endl;
freeaddrinfo(result);
WSACleanup();
return "";
}
// 连接到服务器
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR)
{
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
std::cerr << "无法连接到服务器!" << std::endl;
freeaddrinfo(result);
WSACleanup();
return "";
}
freeaddrinfo(result);
// 准备HTTP请求
std::string request;
if (method == "GET")
{
request = method + " " + path + " HTTP/1.1\r\n";
request += "Host: " + host + "\r\n";
request += "Connection: close\r\n\r\n";
}
else if (method == "POST")
{
request = method + " " + path + " HTTP/1.1\r\n";
request += "Host: " + host + "\r\n";
request += "Content-Type: application/x-www-form-urlencoded\r\n";
request += "Content-Length: " + std::to_string(data.length()) + "\r\n";
request += "Connection: close\r\n\r\n";
request += data;
}
else
{
std::cerr << "无效的HTTP方法: " << method << std::endl;
closesocket(ConnectSocket);
WSACleanup();
return "";
}
// 发送HTTP请求
iResult = send(ConnectSocket, request.c_str(), (int)request.length(), 0);
if (iResult == SOCKET_ERROR)
{
std::cerr << "发送失败: " << WSAGetLastError() << std::endl;
closesocket(ConnectSocket);
WSACleanup();
return "";
}
// 接收HTTP响应
const int bufSize = 512;
char recvbuf[bufSize];
do
{
iResult = recv(ConnectSocket, recvbuf, bufSize, 0);
if (iResult > 0)
{
response.append(recvbuf, iResult);
}
else if (iResult == 0)
{
// 连接关闭
break;
}
else
{
std::cerr << "接收失败: " << WSAGetLastError() << std::endl;
break;
}
} while (iResult > 0);
// 清理资源
closesocket(ConnectSocket);
WSACleanup();
return response;
}
static void ParseURL(const std::string& url, std::string& host, std::string& port, std::string& path)
{
// 使用正则表达式解析URL
std::regex url_regex(R"(^(http://|https://)?([^:/\s]+)(:([0-9]+))?([^?\s]*)?.*$)",
std::regex::icase);
std::smatch url_match_result;
if (std::regex_match(url, url_match_result, url_regex))
{
std::string protocol = url_match_result[1].str();
host = url_match_result[2].str();
port = url_match_result[4].str();
path = url_match_result[5].str();
if (path.empty())
path = "/";
if (port.empty())
{
if (!protocol.empty() && protocol == "https://")
port = "443"; // HTTPS默认端口
else
port = "80"; // HTTP默认端口
}
// 检查是否为HTTPS协议
if (!protocol.empty() && protocol == "https://")
{
// 注意此示例不支持HTTPS协议
std::cerr << "当前示例不支持HTTPS协议。" << std::endl;
}
}
else
{
std::cerr << "URL解析失败。" << std::endl;
}
}
static void ParseHttpResponse(const std::string& response, std::string& header, std::string& body)
{
// 查找header和body之间的分隔符\r\n\r\n
size_t pos = response.find("\r\n\r\n");
if (pos != std::string::npos)
{
// 提取header
header = response.substr(0, pos);
// 提取body包括可能的chunked编码
std::string raw_body = response.substr(pos + 4);
// 检查是否为chunked编码
std::regex transfer_encoding_regex(R"(Transfer-Encoding:\s*chunked)", std::regex::icase);
if (std::regex_search(header, transfer_encoding_regex))
{
// 解析chunked编码的body
body = "";
std::istringstream stream(raw_body);
std::string line;
while (std::getline(stream, line))
{
// 移除可能的\r字符
if (!line.empty() && line.back() == '\r')
line.pop_back();
// 如果行为空,则跳过
if (line.empty())
continue;
// 解析chunk大小
std::istringstream size_stream(line);
size_t chunk_size;
size_stream >> std::hex >> chunk_size;
if (chunk_size == 0)
{
// 读取到最后一个chunk结束
break;
}
// 读取chunk数据
std::string chunk_data(chunk_size, '\0');
stream.read(&chunk_data[0], chunk_size);
body += chunk_data;
// 读取\r\n
stream.get();
if (stream.peek() == '\n')
stream.get();
}
}
else
{
// 非chunked编码直接将raw_body赋值给body
body = raw_body;
}
}
else
{
// 未找到分隔符视为没有header
header = "";
body = response;
}
}
static int GetStatusCode(const std::string& header)
{
std::regex status_line_regex(R"(HTTP/\d\.\d\s+(\d{3}))");
std::smatch match;
if (std::regex_search(header, match, status_line_regex))
{
return std::stoi(match[1]);
}
return -1;
}
static std::string GetLocation(const std::string& header)
{
std::regex location_regex(R"(Location:\s*(.+))", std::regex::icase);
std::smatch match;
if (std::regex_search(header, match, location_regex))
{
// 移除可能的\r字符
std::string location = match[1];
location.erase(std::remove(location.begin(), location.end(), '\r'), location.end());
return location;
}
return "";
}
#include "../Utils/Utils.h"
std::string HttpHelper::HttpRequestGet(const std::string& url)
{
const int redirect_limit = 10;
std::string current_url = url;
int redirects = 0;
while (redirects <= redirect_limit)
{
std::string host, port, path;
ParseURL(current_url, host, port, path);
auto response = HttpRequest(host, port, path, "GET", "");
std::string header, body;
ParseHttpResponse(response, header, body);
// 获取HTTP状态码
int status_code = GetStatusCode(header);
// 检查是否需要重定向
if (status_code >= 300 && status_code < 400)
{
std::string location = GetLocation(header);
if (location.empty())
{
std::cerr << "重定向时未找到Location头部停止。" << std::endl;
return "";
}
// 处理相对URL
if (location.find("http://") != 0 && location.find("https://") != 0)
{
// 如果Location是相对路径构建完整的URL
if (location[0] != '/')
{
location = "/" + location;
}
location = "http://" + host + location;
}
// 更新current_url准备下一次请求
current_url = location;
redirects++;
continue; // 继续下一个循环
}
else
{
// 非重定向状态码,处理响应内容
// 解析头部检查Content-Encoding
auto headers = StringHelper::Split(header, { '\r', '\n' });
for (const auto& h : headers)
{
if (h.find("Content-Encoding: gzip") != std::string::npos)
{
// 调用GDecompress解压缩
auto dec = GDecompress((uint8_t*)body.data(), body.size());
body = std::string((char*)dec.data(), dec.size());
break;
}
}
return body;
}
}
std::cerr << "重定向次数超过限制(" << redirect_limit << "次),停止。" << std::endl;
return "";
}
std::string HttpHelper::HttpRequestPost(const std::string& url, const std::string& data)
{
std::string host, port, path;
ParseURL(url, host, port, path);
return HttpRequest(host, port, path, "POST", data);
}