Loading

Linux C++ 使用socket进行Http请求

链接

HTTP包格式

  1. 基本请求格式

  2. 基本响应格式

暴力解析代码及测试(记得加参数-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;
}

运行结果

在这里插入图片描述

posted @ 2021-12-03 19:48  WindSnowLi  阅读(242)  评论(0编辑  收藏  举报