Linux C++ 使用socket进行Http请求
HTTP包格式#
暴力解析代码及测试(记得加参数-std=c++2a)#
#include <iostream>
#include <map>
#include <utility>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <regex>
#include <fstream>
#include <sys/unistd.h>
// 接收缓冲区大小
const size_t MAX_BUFF = 4096;
// 请求封装
class HttpRequest;
// 响应封装
class HttpResponse;
/**
* 简单的封装下socket
*/
class Socket
{
private:
int client;
public:
/**
* 根据IP和port链接
* @param host IP
* @param port 端口号
* @return 套接字
*/
int connect(const std::string &host, int port);
/**
* 发送数据
* @param data 数据字符串
*/
void send(const std::string &data) const;
/**
* 接收数据
* @param max 接收数据最大长度
* @return 数据长度和数据字符串
*/
std::tuple<size_t, std::string> receive(size_t max) const;
/**
* 释放链接
*/
~Socket();
};
int Socket::connect(const std::string &host, int port)
{
//创建套接字
this->client = socket(AF_INET, SOCK_STREAM, 0);
//地质结构体
struct sockaddr_in serverAddr
{
};
//IP类型
serverAddr.sin_family = AF_INET;
//端口号
serverAddr.sin_port = htons(port);
//IP地址
serverAddr.sin_addr.s_addr = inet_addr(host.c_str());
//连接返回套接字
int temp = ::connect(this->client, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
//小于零失败
if (temp < 0)
{
throw std::exception();
}
return temp;
}
void Socket::send(const std::string &data) const
{
::send(client, data.c_str(), data.length(), 0);
}
std::tuple<size_t, std::string> Socket::receive(size_t max) const
{
char buff[max];
//置0
memset(buff, '\0', max);
size_t len = ::recv(client, buff, sizeof(buff), 0);
return std::make_tuple(len, std::string(buff));
}
Socket::~Socket()
{
::close(this->client);
}
/**
* 一个简单的工具类
*/
class Utils
{
public:
/**
* 根据域名通过系统API获取IP
* @param name 域名
* @return IP字符串
*/
static std::string getHostIpByName(const std::string &name);
/**
* 判断是否是合法的url的正则表达式对象
*/
const static std::regex isUrlReg;
/**
* 获取url各部分信息的正则表达式对象
*/
const static std::regex urlInfoReg;
/**
* 判断是否是合法的url
* @param url url
* @return 是否是合法的url
*/
static bool isUrl(const std::string &url);
/**
* 获取url个部分信息
* @param url url
* @return 各部分信息
*/
static std::tuple<std::string, std::string, std::string, std::string, int> getUrlInfo(const std::string &url);
/**
* 获取[IP/域名][:port]各部分信息
* @param host [IP/域名][:port]
* @return IP/域名和端口
*/
static std::tuple<std::string, int> getHostInfo(std::string &host);
};
std::string Utils::getHostIpByName(const std::string &name)
{
struct hostent *hptr;
hptr = gethostbyname(name.c_str());
if (hptr == nullptr)
{
hstrerror(h_errno);
throw std::exception();
}
if (hptr->h_addrtype != AF_INET)
{
throw std::exception();
}
char **pptr = hptr->h_addr_list;
if (*pptr == nullptr)
{
throw std::exception();
}
char str[INET_ADDRSTRLEN];
inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str));
return std::string{str};
}
bool Utils::isUrl(const std::string &url)
{
return std::regex_match(url.c_str(), isUrlReg);
}
const std::regex Utils::isUrlReg = std::regex(/* NOLINT */
R"(^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#%[\]@!\$&'\*\+,;=.]+$)");
const std::regex Utils::urlInfoReg = std::regex(/* NOLINT */
R"((https?):\/\/(\S+?)(\/.*|$))");
std::tuple<std::string, /*协议*/
std::string, /*host,域名/ip*/
std::string, /*path*/
std::string, /*ip*/
int /*port*/
>
Utils::getUrlInfo(const std::string &url)
{
if (!isUrl(url))
{
throw std::exception();
}
std::string protocol, targetHost, path, ip;
std::smatch urlInfo;
if (!regex_search(url, urlInfo, urlInfoReg))
{
throw std::exception();
}
protocol = ((urlInfo[1] != "") ? urlInfo[1] : std::string("http"));
if (urlInfo[2] == "")
{
throw std::exception();
}
targetHost = urlInfo[2];
path = ((urlInfo[3] != "") ? urlInfo[3] : std::string("/"));
auto hostInfo = Utils::getHostInfo(targetHost);
std::string host = std::get<0>(hostInfo);
ip = getHostIpByName(host);
int port = std::get<1>(hostInfo);
return {protocol, host, path, ip, port};
}
std::tuple<std::string, int> Utils::getHostInfo(std::string &host)
{
if (host.find(':') == -1)
{
return {host, 80};
}
else
{
int port = 80;
std::string portStr = host.substr(host.find(':') + 1);
if (!portStr.empty())
{
stoi(portStr);
}
return {host.substr(0, host.find(':') - 1), port};
}
}
class HttpBasePackage
{
public:
std::map<std::string, std::string> header{};
// 数据体
std::string body;
};
class HttpClient
{
private:
/**
* 解析头信息
* @param rspHeadStream 头信息流
* @param httpResponse 应答对象
*/
static void parseHead(std::stringstream &rspHeadStream, HttpResponse &httpResponse);
/**
* 接收
* @param sc Socket
* @param rspTailStream 头信息流
* @param httpResponse 应答对象
* @param allLength 已接收总长度
* @param revStatus 接收状态
*/
static void rxinfo(Socket &sc, std::stringstream &rspTailStream, HttpResponse &httpResponse, size_t &allLength,
bool &revStatus);
public:
/**
* 通过HttpRequest直接进行请求
* @param request 请求数据对象
* @return 服务端应答对象
*/
static HttpResponse baseReq(HttpRequest &request);
/**
* 构建基础请求对象
* @param url 请求连接
* @param header 请求头map
* @return HttpRequest
*/
static HttpRequest makeReq(std::string &url, std::map<std::string, std::string> &header);
/**
* 构建基础请求对象
* @param url 请求连接
* @return HttpRequest
*/
static HttpRequest makeReq(std::string &url);
/**
* 通过url直接进行请求
* @param url 请求连接
* @return 服务端应答对象
*/
static HttpResponse getReq(std::string &url);
/**
* 通过url和自定义请求头进行请求
* @param url 请求连接
* @param header 请求头
* @return 服务端应答对象
*/
static HttpResponse getReq(std::string &url, std::map<std::string, std::string> &header);
/**
* post表单请求
* @param url 请求链接
* @param header 请求头Map
* @param data 请求数据
* @return 服务端应答对象
*/
static HttpResponse postForm(std::string url, std::map<std::string, std::string> &header,
std::map<std::string, std::string> &data);
/**
* post表单请求
* @param url 请求链接
* @param data 请求数据
* @return 服务端应答对象
*/
static HttpResponse postForm(std::string &url, std::map<std::string, std::string> &data);
/**
* post表单请求
* @param url 请求链接
* @return 服务端应答对象
*/
static HttpResponse postForm(std::string url);
/**
* post JSON请求
* @param url 请求链接
* @param header 请求头Map
* @param data 请求数据
* @return 服务端应答对象
*/
static HttpResponse postJson(std::string url, std::map<std::string, std::string> header, std::string json);
/**
* post JSON请求
* @param url 请求链接
* @param data 请求数据
* @return 服务端应答对象
*/
static HttpResponse postJson(std::string url, std::string json);
};
class HttpResponse : public HttpBasePackage
{
public:
const static char *TRANSFER_ENCODING;
const static char *CHUNKED;
const static char *CONTENT_LENGTH;
const static char *CONTENT_TYPE;
const static char *APPLICATION_JSON;
// 协议
std::string protocol;
// 返回码
int statusCode{};
// 返回描述
std::string statusDesc;
};
const char *HttpResponse::TRANSFER_ENCODING = "transfer-encoding";
const char *HttpResponse::CHUNKED = "chunked";
const char *HttpResponse::CONTENT_LENGTH = "content-length";
const char *HttpResponse::CONTENT_TYPE = "content-type";
const char *HttpResponse::APPLICATION_JSON = "application/json";
class HttpRequest : public HttpBasePackage
{
private:
//请求方法
std::string method{};
//请求地址
std::string host{};
//请求路径
std::string path{};
//请求端口
int port = 0;
public:
//http结束标识符
const static char *HTTP_END;
const static char *GET;
const static char *POST;
void setHeader(const std::map<std::string, std::string> &reqHeader) { this->header = reqHeader; }
void setMethod(std::string reqMethod) { this->method = std::move(reqMethod); }
void setHost(const std::string &targetHost) { this->host = Utils::getHostIpByName(targetHost); }
void setPort(int targetPort) { this->port = targetPort; }
void setPath(std::string reqPath) { this->path = std::move(reqPath); }
friend HttpResponse HttpClient::baseReq(HttpRequest &request);
};
const char *HttpRequest::GET = "GET";
const char *HttpRequest::POST = "POST";
const char *HttpRequest::HTTP_END = "\r\n0\r\n\r\n";
void HttpClient::rxinfo(Socket &sc, std::stringstream &rspStream, HttpResponse &httpResponse, size_t &allLength,
bool &revStatus)
{
//是否是不定长度数据
// bool flag = httpResponse.header.contains(HttpResponse::TRANSFER_ENCODING);
//开始接收剩余所有数据
while (true)
{
auto data = sc.receive(MAX_BUFF);
allLength += std::get<0>(data);
rspStream << std::get<1>(data);
if (!std::get<0>(data))
{
break;
}
}
parseHead(rspStream, httpResponse);
if (rspStream.str().substr(rspStream.str().length() - 7, 7) == HttpRequest::HTTP_END)
{
rspStream.str().erase(rspStream.str().begin() + (int)rspStream.str().length() - 7,
rspStream.str().end());
}
httpResponse.body = rspStream.str().substr(rspStream.str().find("\r\n\r\n"));
}
void HttpClient::parseHead(std::stringstream &rspHeadStream, HttpResponse &httpResponse)
{
//头首行返回描述解析
std::string row;
char buff[MAX_BUFF] = {0};
rspHeadStream.seekg(0, rspHeadStream.beg);
rspHeadStream.getline(buff, sizeof buff);
row = buff;
size_t lastSpaceIndex = row.rfind(' ');
httpResponse.statusDesc = row.substr(lastSpaceIndex + 1,
row.rfind('\r') - lastSpaceIndex - 1);
size_t firstSpaceIndex = row.find(' ');
auto aa = row.substr(firstSpaceIndex + 1, lastSpaceIndex - firstSpaceIndex - 1);
httpResponse.statusCode = stoi(aa);
httpResponse.protocol = row.substr(0, row.find(' '));
//开始解析头数据
while (true)
{
rspHeadStream.getline(buff, sizeof buff);
row = buff;
if (row.length() == 1 && row[0] == '\r')
{
break;
}
size_t splitIndex = row.find(": ");
std::string &&key = row.substr(0, splitIndex);
transform(key.begin(), key.end(), key.begin(), ::tolower);
std::string &&value = row.substr(splitIndex + 2, row.length() - 3 - splitIndex);
httpResponse.header.insert(std::make_pair(key, value));
}
}
HttpResponse HttpClient::baseReq(HttpRequest &request)
{
std::stringstream headerStream;
headerStream << request.method << ' ' << request.path << ' ' << "HTTP/1.1\r\n";
for (auto &&i : request.header)
{
headerStream << i.first << ": " << i.second << "\r\n";
}
headerStream << "\r\n";
headerStream << request.body;
Socket sc{};
sc.connect(request.host, request.port);
sc.send(headerStream.str());
std::stringstream rspStream;
HttpResponse httpResponse;
size_t allLength = 0;
bool revStatus;
//接收并解析
rxinfo(sc, rspStream, httpResponse, allLength, revStatus);
return httpResponse;
}
HttpResponse HttpClient::getReq(std::string &url)
{
if (!Utils::isUrl(url))
{
throw std::exception();
}
std::map<std::string, std::string> header;
HttpRequest &&request = makeReq(url, header);
return baseReq(request);
}
HttpResponse
HttpClient::getReq(std::string &url, std::map<std::string, std::string> &header)
{
HttpRequest &&request = makeReq(url, header);
return baseReq(request);
}
HttpResponse HttpClient::postForm(std::string url, std::map<std::string, std::string> &header,
std::map<std::string, std::string> &data)
{
header[HttpResponse::CONTENT_TYPE] = "application/x-www-form-urlencoded";
HttpRequest &&request = makeReq(url, header);
if (!data.empty())
{
std::string body = "?";
for (auto &&i : data)
{
body.append(std::string(i.first) + "=" + i.second + "&");
}
body.erase(body.end() - 1);
request.body = body;
}
return baseReq(request);
}
HttpRequest HttpClient::makeReq(std::string &url, std::map<std::string, std::string> &header)
{
auto urlInfo = Utils::getUrlInfo(url);
HttpRequest httpRequest;
httpRequest.setMethod(HttpRequest::GET);
httpRequest.setPath(std::get<2>(urlInfo));
httpRequest.setHost(std::get<3>(urlInfo));
httpRequest.setPort(std::get<4>(urlInfo));
//不存在则默认请求头
if (!header.contains("host"))
{
std::string port;
if (std::get<4>(urlInfo) != 80)
{
port = port + ":" + std::to_string(std::get<4>(urlInfo));
}
header["host"] = std::get<1>(urlInfo) + port;
}
if (!header.contains("user-Agent"))
{
header["user-agent"] = "wurl/0.0.0";
}
if (!header.contains("accept"))
{
header["accept"] = "*/*";
}
httpRequest.setHeader(header);
return httpRequest;
}
HttpResponse HttpClient::postJson(std::string url, std::map<std::string, std::string> header, std::string json)
{
header[HttpResponse::CONTENT_TYPE] = HttpResponse::APPLICATION_JSON;
header[HttpResponse::CONTENT_LENGTH] = std::to_string(json.length());
HttpRequest &&request = makeReq(url, header);
request.body = std::move(json);
request.setMethod(HttpRequest::POST);
return baseReq(request);
}
HttpRequest HttpClient::makeReq(std::string &url)
{
std::map<std::string, std::string> header;
return makeReq(url, header);
}
HttpResponse HttpClient::postForm(std::string &url, std::map<std::string, std::string> &data)
{
std::map<std::string, std::string> header;
return postForm(url, header, data);
}
HttpResponse HttpClient::postForm(std::string url)
{
std::map<std::string, std::string> header, data;
return postForm(std::move(url), header, data);
}
HttpResponse HttpClient::postJson(std::string url, std::string json)
{
std::map<std::string, std::string> header;
header[HttpResponse::CONTENT_TYPE] = HttpResponse::APPLICATION_JSON;
HttpRequest &&request = makeReq(url, header);
request.body = std::move(json);
return baseReq(request);
}
int main()
{
std::string url = "http://zzut.co.cnki.net";
auto &&getRsp = HttpClient::getReq(url);
std::cout << getRsp.protocol << '\n'
<< getRsp.statusDesc << '\n'
<< getRsp.statusCode << std::endl;
for_each(getRsp.header.begin(), getRsp.header.end(), [&](const std::pair<std::string, std::string> &t)
{ std::cout << t.first << '\t' << t.second << std::endl; });
std::cout << getRsp.body << std::endl;
std::fstream fp("./index.html", std::ios::trunc | std::ios::out);
fp << getRsp.body;
fp.close();
auto &&postRsp = HttpClient::postJson("http://sjgl.zzut.edu.cn/gxxs/xsjbxx101/getByXsxx.json",
{std::make_pair("Cookie", "SESSION=a0925632-10d2-2563-10d2-10d29b6910d2")},
R"({"xhId": "201852290000"})");
std::cout << postRsp.protocol << '\n'
<< postRsp.statusDesc << '\n'
<< postRsp.statusCode << std::endl;
for_each(postRsp.header.begin(), postRsp.header.end(), [&](const std::pair<std::string, std::string> &t)
{ std::cout << t.first << '\t' << t.second << std::endl; });
std::cout << postRsp.body << '\n';
return 0;
}
运行结果#
作者:Esofar
出处:https://www.cnblogs.com/WindSnowLi/p/16998178.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术