mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2024-12-26 02:48:31 +01:00
more work
This commit is contained in:
parent
66bbc93535
commit
e59d804b31
402
core/src/utils/net.cpp
Normal file
402
core/src/utils/net.cpp
Normal 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
281
core/src/utils/net.h
Normal 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);
|
||||||
|
}
|
304
core/src/utils/proto/http.cpp
Normal file
304
core/src/utils/proto/http.cpp
Normal 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
275
core/src/utils/proto/http.h
Normal 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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user