more work

This commit is contained in:
AlexandreRouma 2023-01-26 02:55:11 +01:00
parent 66bbc93535
commit e59d804b31
4 changed files with 1262 additions and 0 deletions

402
core/src/utils/net.cpp Normal file
View File

@ -0,0 +1,402 @@
#include "net.h"
#include <string.h>
#include <codecvt>
#ifdef _WIN32
#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK)
#else
#define WOULD_BLOCK (errno == EWOULDBLOCK)
#endif
namespace net {
bool _init = false;
// === Private functions ===
void init() {
if (_init) { return; }
#ifdef _WIN32
// Initialize WinSock2
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa)) {
throw std::runtime_error("Could not initialize WinSock2");
return;
}
#else
// Disable SIGPIPE to avoid closing when the remote host disconnects
signal(SIGPIPE, SIG_IGN);
#endif
_init = true;
}
bool queryHost(uint32_t* addr, std::string host) {
hostent* ent = gethostbyname(host.c_str());
if (!ent || !ent->h_addr_list[0]) { return false; }
*addr = *(uint32_t*)ent->h_addr_list[0];
return true;
}
void closeSocket(SockHandle_t sock) {
#ifdef _WIN32
shutdown(sock, SD_BOTH);
closesocket(sock);
#else
shutdown(sock, SHUT_RDWR);
close(sock);
#endif
}
void setNonblocking(SockHandle_t sock) {
#ifdef _WIN32
u_long enabled = 1;
ioctlsocket(sock, FIONBIO, &enabled);
#else
fcntl(sock, F_SETFL, O_NONBLOCK);
#endif
}
// === Address functions ===
Address::Address() {
memset(&addr, 0, sizeof(addr));
}
Address::Address(const std::string& host, int port) {
// Initialize WSA if needed
init();
// Lookup host
hostent* ent = gethostbyname(host.c_str());
if (!ent || !ent->h_addr_list[0]) {
throw std::runtime_error("Unknown host");
}
// Build address
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = *(uint32_t*)ent->h_addr_list[0];
addr.sin_port = htons(port);
}
Address::Address(IP_t ip, int port) {
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(ip);
addr.sin_port = htons(port);
}
std::string Address::getIPStr() {
char buf[128];
IP_t ip = getIP();
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
return buf;
}
IP_t Address::getIP() {
return htonl(addr.sin_addr.s_addr);
}
void Address::setIP(IP_t ip) {
addr.sin_addr.s_addr = htonl(ip);
}
int Address::getPort() {
return htons(addr.sin_port);
}
void Address::setPort(int port) {
addr.sin_port = htons(port);
}
// === Socket functions ===
Socket::Socket(SockHandle_t sock, const Address* raddr) {
this->sock = sock;
if (raddr) {
this->raddr = new Address(*raddr);
}
}
Socket::~Socket() {
close();
if (raddr) { delete raddr; }
}
void Socket::close() {
if (!open) { return; }
open = false;
closeSocket(sock);
}
bool Socket::isOpen() {
return open;
}
SocketType Socket::type() {
return raddr ? SOCKET_TYPE_UDP : SOCKET_TYPE_TCP;
}
int Socket::send(const uint8_t* data, size_t len, const Address* dest) {
return sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
}
int Socket::sendstr(const std::string& str, const Address* dest) {
return send((const uint8_t*)str.c_str(), str.length(), dest);
}
int Socket::recv(uint8_t* data, size_t maxLen, bool forceLen, int timeout, Address* dest) {
// Create FD set
fd_set set;
FD_ZERO(&set);
FD_SET(sock, &set);
// Define timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
int read = 0;
bool blocking = (timeout != NONBLOCKING);
do {
// Wait for data or error if
if (blocking) {
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
if (err <= 0) { return err; }
}
// Receive
int addrLen = sizeof(sockaddr_in);
int err = ::recvfrom(sock, (char*)&data[read], maxLen - read, 0,(sockaddr*)(dest ? &dest->addr : NULL), (socklen_t*)(dest ? &addrLen : NULL));
if (err <= 0 && !WOULD_BLOCK) {
close();
return err;
}
read += err;
}
while (blocking && forceLen && read < maxLen);
return read;
}
int Socket::recvline(std::string& str, int maxLen, int timeout, Address* dest) {
// Disallow nonblocking mode
if (!timeout) { return -1; }
str.clear();
int read = 0;
while (!maxLen || read < maxLen) {
char c;
int err = recv((uint8_t*)&c, 1, false, timeout, dest);
if (err <= 0) { return err; }
read++;
if (c == '\n') { break; }
str += c;
}
return read;
}
// === Listener functions ===
Listener::Listener(SockHandle_t sock) {
this->sock = sock;
}
Listener::~Listener() {
stop();
}
void Listener::stop() {
closeSocket(sock);
open = false;
}
bool Listener::listening() {
return open;
}
std::shared_ptr<Socket> Listener::accept(Address* dest, int timeout) {
// Create FD set
fd_set set;
FD_ZERO(&set);
FD_SET(sock, &set);
// Define timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
// Wait for data or error
if (timeout != NONBLOCKING) {
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
if (err <= 0) { return NULL; }
}
// Accept
int addrLen = sizeof(sockaddr_in);
SockHandle_t s = ::accept(sock, (sockaddr*)(dest ? &dest->addr : NULL), (socklen_t*)(dest ? &addrLen : NULL));
if ((int)s < 0) {
if (!WOULD_BLOCK) { stop(); }
return NULL;
}
// Enable nonblocking mode
setNonblocking(s);
return std::make_shared<Socket>(s);
}
// === Creation functions ===
std::map<std::string, InterfaceInfo> listInterfaces() {
// Init library if needed
init();
std::map<std::string, InterfaceInfo> ifaces;
#ifdef _WIN32
// Pre-allocate buffer
ULONG size = sizeof(IP_ADAPTER_ADDRESSES);
PIP_ADAPTER_ADDRESSES addresses = (PIP_ADAPTER_ADDRESSES)malloc(size);
// Reallocate to real size
if (GetAdaptersAddresses(AF_INET, 0, NULL, addresses, &size) == ERROR_BUFFER_OVERFLOW) {
addresses = (PIP_ADAPTER_ADDRESSES)realloc(addresses, size);
if (GetAdaptersAddresses(AF_INET, 0, NULL, addresses, &size)) {
throw std::exception("Could not list network interfaces");
}
}
// Save data
std::wstring_convert<std::codecvt_utf8<wchar_t>> utfConv;
for (auto iface = addresses; iface; iface = iface->Next) {
InterfaceInfo info;
auto ip = iface->FirstUnicastAddress;
if (!ip || ip->Address.lpSockaddr->sa_family != AF_INET) { continue; }
info.address = ntohl(*(uint32_t*)&ip->Address.lpSockaddr->sa_data[2]);
info.netmask = ~((1 << (32 - ip->OnLinkPrefixLength)) - 1);
info.broadcast = info.address | (~info.netmask);
ifaces[utfConv.to_bytes(iface->FriendlyName)] = info;
}
// Free tables
free(addresses);
#else
// Get iface list
struct ifaddrs* addresses = NULL;
getifaddrs(&addresses);
// Save data
for (auto iface = addresses; iface; iface = iface->ifa_next) {
if (iface->ifa_addr->sa_family != AF_INET) { continue; }
InterfaceInfo info;
info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]);
info.netmask = ntohl(*(uint32_t*)&iface->ifa_netmask->sa_data[2]);
info.broadcast = info.address | (~info.netmask);
ifaces[iface->ifa_name] = info;
}
// Free iface list
freeifaddrs(addresses);
#endif
return ifaces;
}
std::shared_ptr<Listener> listen(const Address& addr) {
// Init library if needed
init();
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// TODO: Support non-blockign mode
#ifndef _WIN32
// Allow port reusing if the app was killed or crashed
// and the socket is stuck in TIME_WAIT state.
// This option has a different meaning on Windows,
// so we use it only for non-Windows systems
int enable = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
closeSocket(s);
throw std::runtime_error("Could not configure socket");
return NULL;
}
#endif
// Bind socket to the port
if (bind(s, (sockaddr*)&addr.addr, sizeof(sockaddr_in))) {
closeSocket(s);
throw std::runtime_error("Could not bind socket");
return NULL;
}
// Enable listening
if (::listen(s, SOMAXCONN) != 0) {
throw std::runtime_error("Could start listening for connections");
return NULL;
}
// Enable nonblocking mode
setNonblocking(s);
// Return listener class
return std::make_shared<Listener>(s);
}
std::shared_ptr<Listener> listen(std::string host, int port) {
return listen(Address(host, port));
}
std::shared_ptr<Socket> connect(const Address& addr) {
// Init library if needed
init();
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Connect to server
if (::connect(s, (sockaddr*)&addr.addr, sizeof(sockaddr_in))) {
closeSocket(s);
throw std::runtime_error("Could not connect");
return NULL;
}
// Enable nonblocking mode
setNonblocking(s);
// Return socket class
return std::make_shared<Socket>(s);
}
std::shared_ptr<Socket> connect(std::string host, int port) {
return connect(Address(host, port));
}
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr) {
// Init library if needed
init();
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// Bind socket to local port
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
closeSocket(s);
throw std::runtime_error("Could not bind socket");
return NULL;
}
// Return socket class
return std::make_shared<Socket>(s, &raddr);
}
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr) {
return openudp(Address(rhost, rport), laddr);
}
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) {
return openudp(raddr, Address(lhost, lport));
}
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport) {
return openudp(Address(rhost, rport), Address(lhost, lport));
}
}

281
core/src/utils/net.h Normal file
View File

@ -0,0 +1,281 @@
#pragma once
#include <stdint.h>
#include <mutex>
#include <memory>
#include <map>
#ifdef _WIN32
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iphlpapi.h>
#else
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#include <poll.h>
#include <fcntl.h>
#include <ifaddrs.h>
#endif
namespace net {
#ifdef _WIN32
typedef SOCKET SockHandle_t;
typedef int socklen_t;
#else
typedef int SockHandle_t;
#endif
typedef uint32_t IP_t;
class Socket;
class Listener;
struct InterfaceInfo {
IP_t address;
IP_t netmask;
IP_t broadcast;
};
class Address {
friend Socket;
friend Listener;
public:
/**
* Default constructor. Corresponds to 0.0.0.0:0.
*/
Address();
/**
* Do not instantiate this class manually. Use the provided functions.
* @param host Hostname or IP.
* @param port TCP/UDP port.
*/
Address(const std::string& host, int port);
/**
* Do not instantiate this class manually. Use the provided functions.
* @param ip IP in host byte order.
* @param port TCP/UDP port.
*/
Address(IP_t ip, int port);
/**
* Get the IP address.
* @return IP address in standard string format.
*/
std::string getIPStr();
/**
* Get the IP address.
* @return IP address in host byte order.
*/
IP_t getIP();
/**
* Set the IP address.
* @param ip IP address in host byte order.
*/
void setIP(IP_t ip);
/**
* Get the TCP/UDP port.
* @return TCP/UDP port number.
*/
int getPort();
/**
* Set the TCP/UDP port.
* @param port TCP/UDP port number.
*/
void setPort(int port);
struct sockaddr_in addr;
};
enum {
NO_TIMEOUT = -1,
NONBLOCKING = 0
};
enum SocketType {
SOCKET_TYPE_TCP,
SOCKET_TYPE_UDP
};
class Socket {
public:
/**
* Do not instantiate this class manually. Use the provided functions.
*/
Socket(SockHandle_t sock, const Address* raddr = NULL);
~Socket();
/**
* Close socket. The socket can no longer be used after this.
*/
void close();
/**
* Check if the socket is open.
* @return True if open, false if closed.
*/
bool isOpen();
/**
* Get socket type. Either TCP or UDP.
* @return Socket type.
*/
SocketType type();
/**
* Send data on socket.
* @param data Data to be sent.
* @param len Number of bytes to be sent.
* @param dest Destination address. NULL to use the default remote address.
* @return Number of bytes sent.
*/
int send(const uint8_t* data, size_t len, const Address* dest = NULL);
/**
* Send string on socket. Terminating NULL byte is not sent, include one in the string if you need it.
* @param str String to be sent.
* @param dest Destination address. NULL to use the default remote address.
* @return Number of bytes sent.
*/
int sendstr(const std::string& str, const Address* dest = NULL);
/**
* Receive data from socket.
* @param data Buffer to read the data into.
* @param maxLen Maximum number of bytes to read.
* @param forceLen Read the maximum number of bytes even if it requires multiple receive operations.
* @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed.
* @param dest Destination address. If multiple packets, this will contain the address of the last one. NULL if not used.
* @return Number of bytes read. 0 means timed out or closed. -1 means would block or error.
*/
int recv(uint8_t* data, size_t maxLen, bool forceLen = false, int timeout = NO_TIMEOUT, Address* dest = NULL);
/**
* Receive line from socket.
* @param str String to read the data into.
* @param maxLen Maximum line length allowed, 0 for no limit.
* @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed.
* @param dest Destination address. If multiple packets, this will contain the address of the last one. NULL if not used.
* @return Length of the returned string. 0 means timed out or closed. -1 means would block or error.
*/
int recvline(std::string& str, int maxLen = 0, int timeout = NO_TIMEOUT, Address* dest = NULL);
private:
Address* raddr = NULL;
SockHandle_t sock;
bool open = true;
};
class Listener {
public:
/**
* Do not instantiate this class manually. Use the provided functions.
*/
Listener(SockHandle_t sock);
~Listener();
/**
* Stop listening. The listener can no longer be used after this.
*/
void stop();
/**
* CHeck if the listener is still listening.
* @return True if listening, false if not.
*/
bool listening();
/**
* Accept connection.
* @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed.
* @return Socket of the connection. NULL means timed out, would block or closed.
*/
std::shared_ptr<Socket> accept(Address* dest = NULL, int timeout = NO_TIMEOUT);
private:
SockHandle_t sock;
bool open = true;
};
/**
* Get a list of the network interface.
* @return List of network interfaces and their addresses.
*/
std::map<std::string, InterfaceInfo> listInterfaces();
/**
* Create TCP listener.
* @param addr Address to listen on.
* @return Listener instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Listener> listen(const Address& addr);
/**
* Create TCP listener.
* @param host Hostname or IP to listen on ("0.0.0.0" for Any).
* @param port Port to listen on.
* @return Listener instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Listener> listen(std::string host, int port);
/**
* Create TCP connection.
* @param addr Remote address.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> connect(const Address& addr);
/**
* Create TCP connection.
* @param host Remote hostname or IP address.
* @param port Remote port.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> connect(std::string host, int port);
/**
* Create UDP socket.
* @param raddr Remote address.
* @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr);
/**
* Create UDP socket.
* @param rhost Remote hostname or IP address.
* @param rport Remote port.
* @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr);
/**
* Create UDP socket.
* @param raddr Remote address.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0);
/**
* Create UDP socket.
* @param rhost Remote hostname or IP address.
* @param rport Remote port.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0);
}

View File

@ -0,0 +1,304 @@
#include "http.h"
#include <inttypes.h>
namespace net::http {
std::string MessageHeader::serialize() {
std::string data;
// Add start line
data += serializeStartLine() + "\r\n";
// Add fields
for (const auto& [key, value] : fields) {
data += key + ": " + value + "\r\n";
}
// Add terminator
data += "\r\n";
return data;
}
void MessageHeader::deserialize(const std::string& data) {
// Clear existing fields
fields.clear();
// Parse first line
std::string line;
int offset = readLine(data, line);
deserializeStartLine(line);
// Parse fields
while (offset < data.size()) {
// Read line
offset = readLine(data, line, offset);
// If empty line, the header is done
if (line.empty()) { break; }
// Read until first ':' for the key
int klen = 0;
for (; klen < line.size(); klen++) {
if (line[klen] == ':') { break; }
}
// Find offset of value
int voff = klen + 1;
for (; voff < line.size(); voff++) {
if (line[voff] != ' ' && line[voff] != '\t') { break; }
}
// Save field
fields[line.substr(0, klen)] = line.substr(voff);
}
}
std::map<std::string, std::string>& MessageHeader::getFields() {
return fields;
}
bool MessageHeader::hasField(const std::string& name) {
return fields.find(name) != fields.end();
}
std::string MessageHeader::getField(const std::string name) {
// TODO: Check if exists
return fields[name];
}
void MessageHeader::setField(const std::string& name, const std::string& value) {
fields[name] = value;
}
void MessageHeader::clearField(const std::string& name) {
// TODO: Check if exists (but maybe no error?)
fields.erase(name);
}
int MessageHeader::readLine(const std::string& str, std::string& line, int start) {
// Get line length
int len = 0;
bool cr = false;
for (int i = start; i < str.size(); i++) {
if (str[i] == '\n') {
if (len && str[i-1] == '\r') { cr = true; }
break;
}
len++;
}
// Copy line
line = str.substr(start, len - (cr ? 1:0));
return start + len + 1;
}
RequestHeader::RequestHeader(Method method, std::string uri, std::string host) {
this->method = method;
this->uri = uri;
setField("Host", host);
}
RequestHeader::RequestHeader(const std::string& data) {
deserialize(data);
}
Method RequestHeader::getMethod() {
return method;
}
void RequestHeader::setMethod(Method method) {
this->method = method;
}
std::string RequestHeader::getURI() {
return uri;
}
void RequestHeader::setURI(const std::string& uri) {
this->uri = uri;
}
void RequestHeader::deserializeStartLine(const std::string& data) {
// TODO
}
std::string RequestHeader::serializeStartLine() {
// TODO: Allow to specify version
return MethodStrings[method] + " " + uri + " HTTP/1.1";
}
ResponseHeader::ResponseHeader(StatusCode statusCode) {
this->statusCode = statusCode;
if (StatusCodeStrings.find(statusCode) != StatusCodeStrings.end()) {
this->statusString = StatusCodeStrings[statusCode];
}
else {
this->statusString = "UNKNOWN";
}
}
ResponseHeader::ResponseHeader(StatusCode statusCode, const std::string& statusString) {
this->statusCode = statusCode;
this->statusString = statusString;
}
ResponseHeader::ResponseHeader(const std::string& data) {
deserialize(data);
}
StatusCode ResponseHeader::getStatusCode() {
return statusCode;
}
void ResponseHeader::setStatusCode(StatusCode statusCode) {
this->statusCode = statusCode;
}
std::string ResponseHeader::getStatusString() {
return statusString;
}
void ResponseHeader::setStatusString(const std::string& statusString) {
this->statusString = statusString;
}
void ResponseHeader::deserializeStartLine(const std::string& data) {
// Parse version
int offset = 0;
for (; offset < data.size(); offset++) {
if (data[offset] == ' ') { break; }
}
// TODO: Error if null length
// TODO: Parse version
// Skip spaces
for (; offset < data.size(); offset++) {
if (data[offset] != ' ' && data[offset] != '\t') { break; }
}
// Parse status code
int codeOffset = offset;
for (; offset < data.size(); offset++) {
if (data[offset] == ' ') { break; }
}
// TODO: Error if null length
statusCode = (StatusCode)std::stoi(data.substr(codeOffset, codeOffset - offset));
// Skip spaces
for (; offset < data.size(); offset++) {
if (data[offset] != ' ' && data[offset] != '\t') { break; }
}
// Parse status string
int stringOffset = offset;
for (; offset < data.size(); offset++) {
if (data[offset] == ' ') { break; }
}
// TODO: Error if null length (maybe?)
statusString = data.substr(stringOffset, stringOffset - offset);
}
std::string ResponseHeader::serializeStartLine() {
char buf[1024];
sprintf(buf, "%d %s", (int)statusCode, statusString.c_str());
return buf;
}
ChunkHeader::ChunkHeader(size_t length) {
this->length = length;
}
ChunkHeader::ChunkHeader(const std::string& data) {
deserialize(data);
}
std::string ChunkHeader::serialize() {
char buf[64];
sprintf(buf, "%" PRIX64 "\r\n", length);
return buf;
}
void ChunkHeader::deserialize(const std::string& data) {
// Parse length
int offset = 0;
for (; offset < data.size(); offset++) {
if (data[offset] == ' ') { break; }
}
// TODO: Error if null length
length = (StatusCode)std::stoull(data.substr(0, offset), nullptr, 16);
// TODO: Parse rest
}
size_t ChunkHeader::getLength() {
return length;
}
void ChunkHeader::setLength(size_t length) {
this->length = length;
}
Client::Client(std::shared_ptr<Socket> sock) {
this->sock = sock;
}
int Client::sendRequestHeader(RequestHeader& req) {
return sock->sendstr(req.serialize());
}
int Client::recvRequestHeader(RequestHeader& req, int timeout) {
// Non-blocking mode not alloowed
if (!timeout) { return -1; }
// Read response
std::string respData;
int err = recvHeader(respData, timeout);
if (err) { return err; }
// Deserialize
req.deserialize(respData);
}
int Client::sendResponseHeader(ResponseHeader& resp) {
return sock->sendstr(resp.serialize());
}
int Client::recvResponseHeader(ResponseHeader& resp, int timeout) {
// Non-blocking mode not alloowed
if (!timeout) { return -1; }
// Read response
std::string respData;
int err = recvHeader(respData, timeout);
if (err) { return err; }
// Deserialize
resp.deserialize(respData);
}
int Client::sendChunkHeader(ChunkHeader& chdr) {
return sock->sendstr(chdr.serialize());
}
int Client::recvChunkHeader(ChunkHeader& chdr, int timeout) {
std::string respData;
int err = sock->recvline(respData, 0, timeout);
if (err <= 0) { return err; }
if (respData[respData.size()-1] == '\r') {
respData.pop_back();
}
chdr.deserialize(respData);
return 0;
}
int Client::recvHeader(std::string& data, int timeout) {
while (sock->isOpen()) {
std::string line;
int ret = sock->recvline(line);
if (line == "\r") { break; }
if (ret <= 0) { return ret; }
data += line + "\n";
}
return 0;
}
}

275
core/src/utils/proto/http.h Normal file
View File

@ -0,0 +1,275 @@
#pragma once
#include <map>
#include <string>
#include "../net.h"
namespace net::http {
enum Method {
METHOD_OPTIONS,
METHOD_GET,
METHOD_HEAD,
METHOD_POST,
METHOD_PUT,
METHOD_DELETE,
METHOD_TRACE,
METHOD_CONNECT
};
inline std::map<Method, std::string> MethodStrings {
{ METHOD_OPTIONS, "OPTIONS" },
{ METHOD_GET, "GET" },
{ METHOD_HEAD, "HEAD" },
{ METHOD_POST, "POST" },
{ METHOD_PUT, "PUT" },
{ METHOD_DELETE, "DELETE" },
{ METHOD_TRACE, "TRACE" },
{ METHOD_CONNECT, "CONNECT" }
};
enum StatusCode {
STATUS_CODE_CONTINUE = 100,
STATUS_CODE_SWITCH_PROTO = 101,
STATUS_CODE_OK = 200,
STATUS_CODE_CREATED = 201,
STATUS_CODE_ACCEPTED = 202,
STATUS_CODE_NON_AUTH_INFO = 203,
STATUS_CODE_NO_CONTENT = 204,
STATUS_CODE_RESET_CONTENT = 205,
STATUS_CODE_PARTIAL_CONTENT = 206,
STATUS_CODE_MULTIPLE_CHOICES = 300,
STATUS_CODE_MOVED_PERMANENTLY = 301,
STATUS_CODE_FOUND = 302,
STATUS_CODE_SEE_OTHER = 303,
STATUS_CODE_NOT_MODIFIED = 304,
STATUS_CODE_USE_PROXY = 305,
STATUS_CODE_TEMP_REDIRECT = 307,
STATUS_CODE_BAD_REQUEST = 400,
STATUS_CODE_UNAUTHORIZED = 401,
STATUS_CODE_PAYMENT_REQUIRED = 402,
STATUS_CODE_FORBIDDEN = 403,
STATUS_CODE_NOT_FOUND = 404,
STATUS_CODE_METHOD_NOT_ALLOWED = 405,
STATUS_CODE_NOT_ACCEPTABLE = 406,
STATUS_CODE_PROXY_AUTH_REQ = 407,
STATUS_CODE_REQUEST_TIEMOUT = 408,
STATUS_CODE_CONFLICT = 409,
STATUS_CODE_GONE = 410,
STATUS_CODE_LENGTH_REQUIRED = 411,
STATUS_CODE_PRECONDITION_FAILED = 412,
STATUS_CODE_REQ_ENTITY_TOO_LARGE = 413,
STATUS_CODE_REQ_URI_TOO_LONG = 414,
STATUS_CODE_UNSUPPORTED_MEDIA_TYPE = 415,
STATUS_CODE_REQ_RANGE_NOT_SATISFIABLE = 416,
STATUS_CODE_EXPECTATION_FAILED = 417,
STATUS_CODE_IM_A_TEAPOT = 418,
STATUS_CODE_ENHANCE_YOUR_CALM = 420,
STATUS_CODE_INTERNAL_SERVER_ERROR = 500,
STATUS_CODE_NOT_IMPLEMENTED = 501,
STATUS_CODE_BAD_GATEWAY = 502,
STATUS_CODE_SERVICE_UNAVAILABLE = 503,
STATUS_CODE_GATEWAY_TIMEOUT = 504,
STATUS_CODE_HTTP_VERSION_UNSUPPORTED = 505
};
inline std::map<StatusCode, std::string> StatusCodeStrings {
{ STATUS_CODE_CONTINUE , "CONTINUE" },
{ STATUS_CODE_SWITCH_PROTO , "SWITCH_PROTO" },
{ STATUS_CODE_OK , "OK" },
{ STATUS_CODE_CREATED , "CREATED" },
{ STATUS_CODE_ACCEPTED , "ACCEPTED" },
{ STATUS_CODE_NON_AUTH_INFO , "NON_AUTH_INFO" },
{ STATUS_CODE_NO_CONTENT , "NO_CONTENT" },
{ STATUS_CODE_RESET_CONTENT , "RESET_CONTENT" },
{ STATUS_CODE_PARTIAL_CONTENT , "PARTIAL_CONTENT" },
{ STATUS_CODE_MULTIPLE_CHOICES , "MULTIPLE_CHOICES" },
{ STATUS_CODE_MOVED_PERMANENTLY , "MOVED_PERMANENTLY" },
{ STATUS_CODE_FOUND , "FOUND" },
{ STATUS_CODE_SEE_OTHER , "SEE_OTHER" },
{ STATUS_CODE_NOT_MODIFIED , "NOT_MODIFIED" },
{ STATUS_CODE_USE_PROXY , "USE_PROXY" },
{ STATUS_CODE_TEMP_REDIRECT , "TEMP_REDIRECT" },
{ STATUS_CODE_BAD_REQUEST , "BAD_REQUEST" },
{ STATUS_CODE_UNAUTHORIZED , "UNAUTHORIZED" },
{ STATUS_CODE_PAYMENT_REQUIRED , "PAYMENT_REQUIRED" },
{ STATUS_CODE_FORBIDDEN , "FORBIDDEN" },
{ STATUS_CODE_NOT_FOUND , "NOT_FOUND" },
{ STATUS_CODE_METHOD_NOT_ALLOWED , "METHOD_NOT_ALLOWED" },
{ STATUS_CODE_NOT_ACCEPTABLE , "NOT_ACCEPTABLE" },
{ STATUS_CODE_PROXY_AUTH_REQ , "PROXY_AUTH_REQ" },
{ STATUS_CODE_REQUEST_TIEMOUT , "REQUEST_TIEMOUT" },
{ STATUS_CODE_CONFLICT , "CONFLICT" },
{ STATUS_CODE_GONE , "GONE" },
{ STATUS_CODE_LENGTH_REQUIRED , "LENGTH_REQUIRED" },
{ STATUS_CODE_PRECONDITION_FAILED , "PRECONDITION_FAILED" },
{ STATUS_CODE_REQ_ENTITY_TOO_LARGE , "REQ_ENTITY_TOO_LARGE" },
{ STATUS_CODE_REQ_URI_TOO_LONG , "REQ_URI_TOO_LONG" },
{ STATUS_CODE_UNSUPPORTED_MEDIA_TYPE , "UNSUPPORTED_MEDIA_TYPE" },
{ STATUS_CODE_REQ_RANGE_NOT_SATISFIABLE, "REQ_RANGE_NOT_SATISFIABLE"},
{ STATUS_CODE_EXPECTATION_FAILED , "EXPECTATION_FAILED" },
{ STATUS_CODE_IM_A_TEAPOT , "IM_A_TEAPOT" },
{ STATUS_CODE_ENHANCE_YOUR_CALM , "ENHANCE_YOUR_CALM" },
{ STATUS_CODE_INTERNAL_SERVER_ERROR , "INTERNAL_SERVER_ERROR" },
{ STATUS_CODE_NOT_IMPLEMENTED , "NOT_IMPLEMENTED" },
{ STATUS_CODE_BAD_GATEWAY , "BAD_GATEWAY" },
{ STATUS_CODE_SERVICE_UNAVAILABLE , "SERVICE_UNAVAILABLE" },
{ STATUS_CODE_GATEWAY_TIMEOUT , "GATEWAY_TIMEOUT" },
{ STATUS_CODE_HTTP_VERSION_UNSUPPORTED , "HTTP_VERSION_UNSUPPORTED" }
};
/**
* HTTP Message Header
*/
class MessageHeader {
public:
/**
* Serialize header to string.
* @return Header in string form.
*/
std::string serialize();
/**
* Deserialize header from string.
* @param data Header in string form.
*/
void deserialize(const std::string& data);
/**
* Get field list.
* @return Map from field name to field.
*/
std::map<std::string, std::string>& getFields();
/**
* Check if a field exists in the header.
* @return True if the field exists, false otherwise.
*/
bool hasField(const std::string& name);
/**
* Get field value.
* @param name Name of the field.
* @return Field value.
*/
std::string getField(const std::string name);
/**
* Set field.
* @param name Field name.
* @param value Field value.
*/
void setField(const std::string& name, const std::string& value);
/**
* Delete field.
* @param name Field name.
*/
void clearField(const std::string& name);
private:
int readLine(const std::string& str, std::string& line, int start = 0);
virtual std::string serializeStartLine() = 0;
virtual void deserializeStartLine(const std::string& data) = 0;
std::map<std::string, std::string> fields;
};
/**
* HTTP Request Header
*/
class RequestHeader : public MessageHeader {
public:
RequestHeader() {}
/**
* Create request header from the mandatory parameters.
* @param method HTTP Method.
* @param uri URI of request.
* @param host Server host passed in the 'Host' field.
*/
RequestHeader(Method method, std::string uri, std::string host);
/**
* Create request header from its serialized string form.
* @param data Request header in string form.
*/
RequestHeader(const std::string& data);
/**
* Get HTTP Method.
* @return HTTP Method.
*/
Method getMethod();
void setMethod(Method method);
std::string getURI();
void setURI(const std::string& uri);
private:
void deserializeStartLine(const std::string& data);
std::string serializeStartLine();
Method method;
std::string uri;
};
class ResponseHeader : public MessageHeader {
public:
ResponseHeader() {}
ResponseHeader(StatusCode statusCode);
ResponseHeader(StatusCode statusCode, const std::string& statusString);
ResponseHeader(const std::string& data);
StatusCode getStatusCode();
void setStatusCode(StatusCode statusCode);
std::string getStatusString();
void setStatusString(const std::string& statusString);
private:
void deserializeStartLine(const std::string& data);
std::string serializeStartLine();
StatusCode statusCode;
std::string statusString;
};
class ChunkHeader {
public:
ChunkHeader() {}
ChunkHeader(size_t length);
ChunkHeader(const std::string& data);
std::string serialize();
void deserialize(const std::string& data);
size_t getLength();
void setLength(size_t length);
private:
size_t length;
};
class Client {
public:
Client(std::shared_ptr<Socket> sock);
int sendRequestHeader(RequestHeader& req);
int recvRequestHeader(RequestHeader& req, int timeout = -1);
int sendResponseHeader(ResponseHeader& resp);
int recvResponseHeader(ResponseHeader& resp, int timeout = -1);
int sendChunkHeader(ChunkHeader& chdr);
int recvChunkHeader(ChunkHeader& chdr, int timeout = -1);
private:
int recvHeader(std::string& data, int timeout = -1);
std::shared_ptr<Socket> sock;
};
}