前言

时至今日,HTTP已经是使用最广泛的传输协议,尽管市面上已经有了许多成熟的 HTTP 客户端库,但在实际应用中发现这些库往往过于复杂,使用起来不够简便。所以决定自己实现一个轻量级的 HTTP 客户端库。


实现

HTTP 协议头的解析可以简单的看作是字符串解析,我们要解决的是 HTTP 协议的流式接收解析和封装发送即可;

1、封装 socket,方便后续拓展;

socket.hpp

//
//
//  FileName:   socket.hpp
//  Author:     CortXu
//  E-mail:     [email protected]
//  Blog:       https://cortxu.com
//  Github:     https://github.com/hilive
//
//  Created on 2024/1/28.
//  Copyright © 2024 HiLive. All rights reserved.
//

#pragma once
#include "net_address.hpp"

namespace hilive {
namespace net {

class Socket {
 public:
  enum SelectMode { SELECT_READ = 1, SELECT_WRITE = 2, SELECT_ERROR = 4 };

 public:
  Socket();
  Socket(SOCKET_T sock);
  virtual ~Socket(void);

 public:
  int Connect(const NetAddress& addr);
  int Connect(const NetAddress& addr, uint32_t& timeout);
  int ConnectNB(const NetAddress& addr);
  int Send(const void* data, const int len, int flags = 0);
  int Recv(void* data, const int len, int flags = 0);
  int SendTo(const void* data, const int len, const NetAddress& addr, int flags = 0);
  int RecvFrom(void* data, const int len, NetAddress& addr, int flags = 0);
  int Close();
  int Bind(const NetAddress& addr, bool reUse = false);
  int Listen(int backlog = 64);
  SOCKET_T Accept(NetAddress& addr);

 public:
  NetAddress GetAddress();
  NetAddress GetPeerAddress();
  int GetSocketError();
  int GetLastError();
  int GetRecvTimeout();
  int GetSendTimeout();
  int ShutdownRecv();
  int ShutdownSend();
  int ShutdownRecvAndSend();
  int SetBlock(bool flag);
  int SetReuseAddr(bool flag);
  int SetReusePort(bool flag);
  int SetRecvTimeout(int value);
  int SetSendTimeout(int value);
  int SetTTL(int value);
  int GetTTL();
  int SetBrokenTimeout(bool flag);
  int SetSendBuffSize(int size);
  int GetSendBuffSize();
  int SetRecvBuffSize(int size);
  int GetRecvBuffSize();
  int SetNoDelay(bool flag);
  int SetBroadcast(bool flag);
  int GetFIONREAD();

  bool IsNoDelay();
  bool IsBlock();
  bool IsReuseAddr();
  bool IsReusePort();
  bool IsBroadcast();
  bool IsValid();

 public:
  SOCKET_T socket();

 protected:
  bool InitSocket(int af, int type, int proto = 0);

 private:
  int Poll(uint32_t timeout, int mode);
  int Ioctl(int r, void* arg);
  int SetOption(int level, int option, const void* value, int length);
  int GetOption(int level, int option, void* value, socklen_t& length);

 private:
  SOCKET_T socket_;
  bool enable_block_;
  bool enable_check_timeout_;
  uint32_t send_timeout_;
  uint32_t recv_timeout_;
};

}  // namespace net
}  // namespace hilive

socket.cpp

//
//
//  FileName:   socket.cpp
//  Author:     CortXu
//  E-mail:     [email protected]
//  Blog:       https://cortxu.com
//  Github:     https://github.com/hilive
//
//  Created on 2024/1/28.
//  Copyright © 2024 HiLive. All rights reserved.
//

#include "socket.hpp"

namespace hilive {
namespace net {

Socket::Socket()
    : socket_(INVALID_SOCKET_T), enable_block_(true), enable_check_timeout_(false), send_timeout_(0), recv_timeout_(0) {
#if defined(HILIVE_SYS_WINDOWS)
  WORD version = MAKEWORD(2, 2);
  WSADATA data;
  if (WSAStartup(version, &data) != 0) {
    printf("WSAStartup failed\r\n");
  }
#endif
}

Socket::Socket(SOCKET_T sock)
    : socket_(sock), enable_block_(true), enable_check_timeout_(false), send_timeout_(0), recv_timeout_(0) {
#if defined(HILIVE_SYS_WINDOWS)
  WORD version = MAKEWORD(2, 2);
  WSADATA data;
  if (WSAStartup(version, &data) != 0) {
    printf("WSAStartup failed\r\n");
  }
#endif
}

Socket::~Socket(void) {
  Close();
#if defined(HILIVE_SYS_WINDOWS)
  WSACleanup();
#endif
}

int Socket::Connect(const NetAddress& addr) {
  if (!IsValid()) {
    InitSocket(addr.af(), SOCK_STREAM, 0);
  }

  return ::connect(socket_, addr.addr(), NetAddress::ADDRESS_LENTH);
}

int Socket::Connect(const NetAddress& addr, uint32_t& timeout) {
  if (!IsValid()) {
    InitSocket(addr.af(), SOCK_STREAM, 0);
  }

  SetBlock(false);

  if (::connect(socket_, addr.addr(), NetAddress::ADDRESS_LENTH) != 0) {
    if (Poll(timeout, SELECT_READ | SELECT_WRITE | SELECT_ERROR) <= 0) {
      //  int err = getSocketError();
      return -1;
    }
  }

  SetBlock(true);

  return 0;
}

int Socket::ConnectNB(const NetAddress& addr) {
  if (!IsValid()) {
    InitSocket(addr.af(), SOCK_STREAM, 0);
  }

  SetBlock(false);

  int rc = ::connect(socket_, addr.addr(), NetAddress::ADDRESS_LENTH);
  if (rc != 0) {
    rc = GetLastError();
  }

  return rc;
}

int Socket::Send(const void* data, const int len, int flags) {
  if (!IsValid()) {
    return -2;
  }

  if (enable_check_timeout_ && send_timeout_ > 0 && Poll(send_timeout_, SELECT_WRITE) <= 0) {
    return -1;
  }

  return (int)::send(socket_, reinterpret_cast<const char*>(data), len, flags);
}

int Socket::Recv(void* data, const int len, int flags) {
  if (!IsValid()) {
    return -2;
  }

  if (enable_check_timeout_ && recv_timeout_ > 0 && Poll(recv_timeout_, SELECT_READ) <= 0) {
    return -1;
  }

  return (int)::recv(socket_, reinterpret_cast<char*>(data), len, flags);
}

int Socket::SendTo(const void* data, const int len, const NetAddress& addr, int flags) {
  if (!IsValid()) {
    return -2;
  }

  return (int)::sendto(socket_, reinterpret_cast<const char*>(data), len, flags, addr.addr(), addr.ADDRESS_LENTH);
}

int Socket::RecvFrom(void* data, const int len, NetAddress& addr, int flags) {
  if (!IsValid()) {
    return -2;
  }

  if (enable_check_timeout_ && recv_timeout_ > 0 && Poll(recv_timeout_, SELECT_READ) <= 0) {
    return -1;
  }

  sockaddr_in sa;
  socklen_t saLen = sizeof(sockaddr);
  int rc = (int)::recvfrom(socket_, reinterpret_cast<char*>(data), len, flags, (sockaddr*)&sa, &saLen);
  if (rc >= 0) {
    addr = NetAddress(sa.sin_addr.s_addr, sa.sin_port, sa.sin_family);
  }

  return rc;
}

int Socket::Close() {
  if (IsValid()) {
#if defined(HILIVE_SYS_WINDOWS)
    ::closesocket(socket_);
#else
    ::close(socket_);
#endif
  }
  socket_ = INVALID_SOCKET_T;

  return 0;
}

int Socket::Bind(const NetAddress& addr, bool reUse) {
  if (!IsValid()) {
    InitSocket(addr.af(), SOCK_STREAM, 0);
  }

  if (reUse) {
    SetReuseAddr(true);
    SetReusePort(true);
  }

  return ::bind(socket_, addr.addr(), NetAddress::ADDRESS_LENTH);
}

int Socket::Listen(int backlog) { return ::listen(socket_, backlog); }

SOCKET_T Socket::Accept(NetAddress& addr) {
  SOCKET_T sock = INVALID_SOCKET_T;
  struct sockaddr_in sa;
  socklen_t saLen = sizeof(sockaddr);
  sock = (int)::accept(socket_, (sockaddr*)&sa, &saLen);
  if (sock != INVALID_SOCKET_T) {
    addr = NetAddress(sa.sin_addr.s_addr, sa.sin_port, sa.sin_family);
  }

  return sock;
}

NetAddress Socket::GetAddress() {
  struct sockaddr_in sa;
  socklen_t saLen = sizeof(sockaddr);
  ::getsockname(socket_, (sockaddr*)&sa, &saLen);
  return NetAddress(sa.sin_addr.s_addr, sa.sin_port, sa.sin_family);
}

NetAddress Socket::GetPeerAddress() {
  struct sockaddr_in sa;
  socklen_t saLen = sizeof(sockaddr);
  ::getpeername(socket_, (sockaddr*)&sa, &saLen);
  return NetAddress(sa.sin_addr.s_addr, sa.sin_port, sa.sin_family);
}

int Socket::GetSocketError() {
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(SOL_SOCKET, SO_ERROR, &value, len);
  return value;
}

int Socket::GetLastError() {
#if defined(HILIVE_SYS_WINDOWS)
  return WSAGetLastError();
#else
  return errno;
#endif
}

int Socket::GetRecvTimeout() {
  int value(recv_timeout_);
  socklen_t len = sizeof(value);
  if (!enable_check_timeout_) {
    GetOption(SOL_SOCKET, SO_RCVTIMEO, &value, len);
  }

  return value;
}

int Socket::GetSendTimeout() {
  int value(send_timeout_);
  socklen_t len = sizeof(value);
  if (!enable_check_timeout_) {
    GetOption(SOL_SOCKET, SO_SNDTIMEO, &value, len);
  }

  return value;
}

int Socket::ShutdownRecv() { return ::shutdown(socket_, 0); }

int Socket::ShutdownSend() { return ::shutdown(socket_, 1); }

int Socket::ShutdownRecvAndSend() { return ::shutdown(socket_, 2); }

int Socket::SetBlock(bool flag) {
  enable_block_ = flag;
  int arg = flag ? 0 : 1;
  return Ioctl(FIONBIO, &arg);
}

int Socket::SetReuseAddr(bool flag) {
  int value = flag ? 1 : 0;
  return SetOption(SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
}

int Socket::SetReusePort(bool flag) {
#ifdef SO_REUSEPORT
  socklen_t value = flag ? 1 : 0;
  return SetOption(SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value));
#endif
  return 0;
}

int Socket::SetRecvTimeout(int value) {
  recv_timeout_ = value;
  if (!enable_check_timeout_) {
    return SetOption(SOL_SOCKET, SO_RCVTIMEO, &value, sizeof(value));
  }

  return 0;
}

int Socket::SetSendTimeout(int value) {
  send_timeout_ = value;
  if (!enable_check_timeout_) {
    return SetOption(SOL_SOCKET, SO_SNDTIMEO, &value, sizeof(value));
  }

  return 0;
}

int Socket::SetTTL(int value) { return SetOption(IPPROTO_IP, IP_TTL, &value, sizeof(value)); }

int Socket::GetTTL() {
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(IPPROTO_IP, IP_TTL, &value, len);

  return value;
}

int Socket::SetBrokenTimeout(bool flag) {
  enable_check_timeout_ = flag;
  return 0;
}

int Socket::GetFIONREAD() {
  int result;
  Ioctl(FIONREAD, &result);
  return result;
}

int Socket::SetSendBuffSize(int size) { return SetOption(SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); }

int Socket::GetSendBuffSize() {
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(SOL_SOCKET, SO_SNDBUF, &value, len);
  return value;
}

int Socket::SetRecvBuffSize(int size) { return SetOption(SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); }

int Socket::GetRecvBuffSize() {
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(SOL_SOCKET, SO_RCVBUF, &value, len);
  return value;
}

int Socket::SetNoDelay(bool flag) {
  int value = flag ? 1 : 0;
  return SetOption(IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
}

bool Socket::IsNoDelay() {
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(IPPROTO_TCP, TCP_NODELAY, &value, len);
  return value != 0;
}

int Socket::SetBroadcast(bool flag) {
  int value = flag ? 1 : 0;
  return SetOption(SOL_SOCKET, SO_BROADCAST, &value, sizeof(value));
}

bool Socket::IsBlock() { return enable_block_; }

bool Socket::IsReuseAddr() {
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(SOL_SOCKET, SO_REUSEADDR, &value, len);
  return value != 0;
}

bool Socket::IsReusePort() {
#ifdef SO_REUSEPORT
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(SOL_SOCKET, SO_REUSEPORT, &value, len);
  return value != 0;
#else
  return false;
#endif
}

bool Socket::IsBroadcast() {
  int value(0);
  socklen_t len = sizeof(value);
  GetOption(SOL_SOCKET, SO_BROADCAST, &value, len);
  return value != 0;
}

bool Socket::IsValid() { return socket_ != INVALID_SOCKET_T; }

SOCKET_T Socket::socket() { return socket_; }

bool Socket::InitSocket(int af, int type, int proto) {
  if (socket_ == INVALID_SOCKET_T) {
    socket_ = ::socket(af, type, proto);
  }

  return socket_ != INVALID_SOCKET_T;
}

int Socket::Poll(uint32_t timeout, int mode) {
  int rc = 0;
#ifndef HILIVE_SYS_LINUX
  fd_set fdRead;
  fd_set fdWrite;
  fd_set fdExcept;
  FD_ZERO(&fdRead);
  FD_ZERO(&fdWrite);
  FD_ZERO(&fdExcept);
  if (mode & SELECT_READ) {
    FD_SET(socket_, &fdRead);
  }
  if (mode & SELECT_WRITE) {
    FD_SET(socket_, &fdWrite);
  }
  if (mode & SELECT_ERROR) {
    FD_SET(socket_, &fdExcept);
  }

  struct timeval tv;
  tv.tv_sec = timeout / 1000;
  tv.tv_usec = timeout * 1000;

  rc = ::select(int(socket_) + 1, &fdRead, &fdWrite, &fdExcept, &tv);
#else
  int epollfd = epoll_create(1);
  if (epollfd < 0) {
    char buf[1024];
    strerror_r(errno, buf, sizeof(buf));
    error(std::string("Can't create epoll queue: ") + buf);
  }

  struct epoll_event evin;
  memset(&evin, 0, sizeof(evin));

  if (mode & SELECT_READ) evin.events |= EPOLLIN;
  if (mode & SELECT_WRITE) evin.events |= EPOLLOUT;
  if (mode & SELECT_ERROR) evin.events |= EPOLLERR;

  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, socket_, &evin) < 0) {
    char buf[1024];
    strerror_r(errno, buf, sizeof(buf));
    ::close(epollfd);
    error(std::string("Can't insert socket to epoll queue: ") + buf);
  }

  struct epoll_event evout;
  memset(&evout, 0, sizeof(evout));
  int ts = timeout;
  int rc = epoll_wait(epollfd, &evout, 1, ts);

  ::close(epollfd);
#endif
  return rc;
}

int Socket::Ioctl(int r, void* arg) {
#if defined(HILIVE_SYS_WINDOWS)
  return ioctlsocket(socket_, r, reinterpret_cast<u_long*>(arg));
#else
  return ::ioctl(socket_, r, arg);
#endif
}

int Socket::SetOption(int level, int option, const void* value, int length) {
  return ::setsockopt(socket_, level, option, reinterpret_cast<const char*>(value), length);
}

int Socket::GetOption(int level, int option, void* value, socklen_t& length) {
  return ::getsockopt(socket_, level, option, reinterpret_cast<char*>(value), &length);
}

}  // namespace net
}  // namespace hilive

2、封装 Http 连接;

http_conn.hpp

//
//
//  FileName:   http_conn.hpp
//  Author:     CortXu
//  E-mail:     [email protected]
//  Blog:       https://cortxu.com
//  Github:     https://github.com/hilive
//
//  Created on 2024/1/29.
//  Copyright © 2024 HiLive. All rights reserved.
//

#pragma once
#include "../base/error_code.hpp"
#include "../socket/stream_socket.hpp"
#include "http_header.hpp"

namespace hilive {
namespace net {

class HttpConn {
 public:
  HttpConn(bool no_delay = true, uint32_t timeout = 4000);
  virtual ~HttpConn();

 public:
  ErrorCode Connect(const std::string& host, const uint16_t port, const bool use_ssl = false);
  ErrorCode Send(const std::string& data);
  ErrorCode Send(const char* data, const int len);
  ErrorCode Recv(char* data, const int len, int& recv_ret);
  ErrorCode RecvLine(std::string& line, const int max_size);
  ErrorCode RecvLimit(std::string& limit, const int limit_size);
  void Close();

 private:
  bool connected_;
  bool no_delay_;
  uint32_t timeout_;
  StreamSocket* stream_socket_;
};

}  // namespace net
}  // namespace hilive

http_conn.cpp

//
//
//  FileName:   http_conn.cpp
//  Author:     CortXu
//  E-mail:     [email protected]
//  Blog:       https://cortxu.com
//  Github:     https://github.com/hilive
//
//  Created on 2024/1/29.
//  Copyright © 2024 HiLive. All rights reserved.
//

#include "http_conn.hpp"

namespace hilive {
namespace net {

HttpConn::HttpConn(bool no_delay, uint32_t timeout) : connected_(false), no_delay_(no_delay), timeout_(timeout) {
  stream_socket_ = new StreamSocket();
}

HttpConn::~HttpConn() {
  Close();

  delete stream_socket_;
  stream_socket_ = NULL;
}

ErrorCode HttpConn::Connect(const std::string& host, const uint16_t port, const bool use_ssl) {
  ErrorCode code = kErrorCodeOK;

  do {
    int ret = 0;
    NetAddress addr(host, port);
    if ((ret = stream_socket_->Connect(addr, timeout_)) < 0) {
      code = kErrorCodeIoError;
      break;
    }

    stream_socket_->SetNoDelay(no_delay_);
    stream_socket_->SetRecvTimeout(timeout_);
  } while (false);

  connected_ = (code == kErrorCodeOK ? true : false);
  return code;
}

ErrorCode HttpConn::Send(const std::string& data) {
  if (!connected_) {
    return kErrorCodeUnReady;
  }

  if (data.empty()) {
    return kErrorCodeParmasError;
  }

  int ret = stream_socket_->Send(data.c_str(), (int)data.length());
  return ret < data.length() ? kErrorCodeIoError : kErrorCodeOK;
}

ErrorCode HttpConn::Send(const char* data, const int len) { return Send(std::string(data, len)); }

ErrorCode HttpConn::Recv(char* data, const int len, int& recv_ret) {
  if (!connected_) {
    return kErrorCodeUnReady;
  }

  recv_ret = stream_socket_->Recv(data, len);
  return recv_ret > 0 ? kErrorCodeOK : kErrorCodeIoError;
}

ErrorCode HttpConn::RecvLine(std::string& line, const int max_size) {
  line.clear();
  line.reserve(HTTP_MAX_LINE_SIZE);

  ErrorCode code = kErrorCodeOK;
  char ch = 0;
  uint32_t read_size = 0;

  do {
    if (read_size >= max_size) {
      code = kErrorCodeNotFound;
      break;
    }

    int ret = 0;
    if ((code = Recv(&ch, 1, ret)) != kErrorCodeOK) {
      break;
    }

    if (ret == 1) {
      ++read_size;
      line.append(1, ch);
    } else {
      code = kErrorCodeIoError;
      break;
    }

    if (ch == '\n') {
      break;
    }
  } while (1);

  return code;
}

ErrorCode HttpConn::RecvLimit(std::string& limit, const int limit_size) {
  limit.clear();
  limit.reserve(limit_size);
  int size = limit_size;

  const int kBuffSize = 1024;
  char data[kBuffSize] = {0};

  int ret = 0;
  //  while (size > 0 && (ret = Recv(data, nRead)) > 0) {
  while (true) {
    int need_read = size > kBuffSize ? kBuffSize : size;
    Recv(data, need_read, ret);
    if (ret <= 0) {
      break;
    }

    limit.append(data, ret);
    size -= ret;
  }

  return limit.length() == limit_size ? kErrorCodeOK : kErrorCodeNotEnough;
}

void HttpConn::Close() {
  stream_socket_->ShutdownRecvAndSend();
  stream_socket_->Close();
  connected_ = false;
}

}  // namespace net
}  // namespace hilive

3、封装 Http Get/Post 请求;

http_client.hpp

//
//
//  FileName:   http_client.hpp
//  Author:     CortXu
//  E-mail:     [email protected]
//  Blog:       https://cortxu.com
//  Github:     https://github.com/hilive
//
//  Created on 2024/1/29.
//  Copyright © 2024 HiLive. All rights reserved.
//

#pragma once
#include "../base/error_code.hpp"
#include "http_request.hpp"
#include "http_response.hpp"

namespace hilive {
namespace net {

class HttpConn;

class HttpClient {
 public:
  HttpClient();
  virtual ~HttpClient();

 public:
  ErrorCode Connect(HttpRequest& request);
  ErrorCode Send(HttpRequest& request);
  ErrorCode Recv(HttpResponse& response);
  ErrorCode Send(HttpRequest& request, const std::string& data);
  ErrorCode Recv(HttpResponse& response, std::string& data);
  void Close();

 private:
  HttpConn* session_;
};

}  // namespace net
}  // namespace hilive

http_client.cpp

//
//
//  FileName:   http_client.cpp
//  Author:     CortXu
//  E-mail:     [email protected]
//  Blog:       https://cortxu.com
//  Github:     https://github.com/hilive
//
//  Created on 2024/1/29.
//  Copyright © 2024 HiLive. All rights reserved.
//

#include "http_client.hpp"

#include <iostream>

#include "http_conn.hpp"
#include "http_parser.hpp"

namespace hilive {
namespace net {

const int kBufferSize = 102400;

HttpClient::HttpClient() { session_ = new HttpConn(); }

HttpClient::~HttpClient() {
  Close();
  delete session_;
  session_ = NULL;
}

ErrorCode HttpClient::Connect(HttpRequest& request) {
  ErrorCode code = kErrorCodeOK;

  do {
    if ((code = HttpParser::ParseUrl(request.url, request)) != kErrorCodeOK) {
      break;
    }

    code = session_->Connect(request.host, request.port);
  } while (false);

  return code;
}

ErrorCode HttpClient::Send(HttpRequest& request) {
  ErrorCode code = kErrorCodeOK;

  do {
    std::string header;
    if ((code = HttpParser::FormatHeaders(request, header)) != kErrorCodeOK) {
      break;
    }

    if ((code = session_->Send(header)) != kErrorCodeOK) {
      break;
    }
  } while (false);
  return code;
}

ErrorCode HttpClient::Recv(HttpResponse& response) {
  ErrorCode code = kErrorCodeOK;
  for (;;) {
    std::string line;
    if ((code = session_->RecvLine(line, HTTP_MAX_LINE_SIZE)) != kErrorCodeOK) {
      break;
    }

    if ((code = HttpParser::ParseLine(line, response)) != kErrorCodeOK) {
      break;
    }
  }

  return response.IsOK() ? kErrorCodeOK : kErrorCodeIoError;
}

ErrorCode HttpClient::Send(HttpRequest& request, const std::string& data) { return session_->Send(data); }

ErrorCode HttpClient::Recv(HttpResponse& response, std::string& data) {
  ErrorCode code = kErrorCodeOK;

  do {
    if (!response.IsOK()) {
      code = kErrorCodeUnReady;
      break;
    }

    char buffer[kBufferSize] = {0};
    int ret = 0;
    if ((code = session_->Recv(buffer, kBufferSize, ret)) != kErrorCodeOK) {
      break;
    }

    response.recv_length += ret;
    data.assign(buffer, ret);
  } while (false);

  return code;
}

void HttpClient::Close() { session_->Close(); }

}  // namespace net
}  // namespace hilive

验证

1、用 nodejs 实现个 http server 供测试用;

const express = require('express');
var bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.get('/get', (req, res) => {
    console.log(req.url);
    res.send('Get OK, Hello, World!');
});

app.get('*', (req, res) => {
    res.sendStatus(404);
});

app.post('/post', function (req, res) {
    console.log(req.url);
    console.log(req.body);

    res.send('POST OK, Hello, World!');
});

// 监听端口  
app.listen(3000, () => {
    console.log('Server started on port 3000');
});

2、测试 Http Get 请求;

请求实现


void TestGet(const std::string& url) {
  HttpClient http;

  do {
    HttpRequest request;
    request.url = url;
    request.method = kHttpMethodGet;

    if (http.Connect(request) != kErrorCodeOK) {
      std::cout << "connect fail" << std::endl;
      break;
    }

    std::cout << std::endl << "request headers:" << std::endl;
    for (auto it = request.heads.begin(); it != request.heads.end(); ++it) {
      std::cout << it->first << ": " << it->second << std::endl;
    }

    if (http.Send(request) != kErrorCodeOK) {
      std::cout << "send request fail" << std::endl;
      break;
    }

    HttpResponse response;
    if (http.Recv(response) != kErrorCodeOK) {
      std::cout << "recv response fail" << std::endl;
      break;
    }

    std::cout << std::endl << "response headers:" << std::endl;
    for (auto it = response.heads.begin(); it != response.heads.end(); ++it) {
      std::cout << it->first << ": " << it->second << std::endl;
    }

    std::cout << std::endl << "body:" << std::endl;
    while (true) {
      std::string data;
      if (http.Recv(response, data) != kErrorCodeOK) {
        break;
      }

      std::cout << data;
    }

    std::cout << std::endl;
  } while (false);

  http.Close();
}

请求结果


request headers:
Accept: */*
Accept-Language: zh-CN
Connection: keep-alive
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36

response headers:
Connection: keep-alive
Content-Length: 21
Content-Type: text/html; charset=utf-8
Date: Mon, 29 Jan 2024 17:17:35 GMT
ETag: W/"15-NACmm4mekhlH2MWshCtV/Wfuyi8"
Keep-Alive: timeout=5
X-Powered-By: Express

body:
Get OK, Hello, World!

3、测试 Http Post 请求;

请求实现

void TestPost(const std::string& url) {
  HttpClient http;

  do {
    HttpRequest request;
    request.url = url;
    request.method = kHttpMethodPost;

    if (http.Connect(request) != kErrorCodeOK) {
      std::cout << "connect fail" << std::endl;
      break;
    }

    const uint32_t kMaxValSize = 128;
    char str_val[kMaxValSize] = {0};
    std::string body = "a=1&b=2&c=3";
    snprintf(str_val, kMaxValSize, "%u", (uint32_t)body.size());
    request.heads[HTTP_HEAD_CONTENT_LENGTH] = str_val;
    request.heads[HTTP_HEAD_CONTENT_TYPE] = "application/x-www-form-urlencoded";

    std::cout << std::endl << "request headers:" << std::endl;
    for (auto it = request.heads.begin(); it != request.heads.end(); ++it) {
      std::cout << it->first << ": " << it->second << std::endl;
    }

    if (http.Send(request) != kErrorCodeOK) {
      std::cout << "send request fail" << std::endl;
      break;
    }

    if (http.Send(request, body) != kErrorCodeOK) {
      break;
    }

    HttpResponse response;
    if (http.Recv(response) != kErrorCodeOK) {
      std::cout << "recv response fail" << std::endl;
      break;
    }

    std::cout << std::endl << "response headers:" << std::endl;
    for (auto it = response.heads.begin(); it != response.heads.end(); ++it) {
      std::cout << it->first << ": " << it->second << std::endl;
    }

    std::cout << std::endl << "body:" << std::endl;
    while (true) {
      std::string data;
      if (http.Recv(response, data) != kErrorCodeOK) {
        break;
      }

      std::cout << data;
    }

    std::cout << std::endl;
  } while (false);

  http.Close();
}

请求结果


request headers:
Accept: */*
Accept-Language: zh-CN
Connection: keep-alive
Content-Length: 11
Content-Type: application/x-www-form-urlencoded
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36

response headers:
Connection: keep-alive
Content-Length: 22
Content-Type: text/html; charset=utf-8
Date: Mon, 29 Jan 2024 17:21:45 GMT
ETag: W/"16-ysms68jYAtIazyo2+sjBvlojllI"
Keep-Alive: timeout=5
X-Powered-By: Express

body:
POST OK, Hello, World!

总结

至此一个简单的 Http Client 库的能力就实现了,可以满足基本的 Http Get/Post 请求;