/*
* Copyright (C) 2021 Mikhail Burakov. This file is part of MQhTTp.
*
* MQhTTp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MQhTTp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MQhTTp. If not, see .
*/
#include "server.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "logging.h"
#include "uhttp.h"
struct Client {
int fd;
struct Uhttp* uhttp;
};
struct Server {
int epfd;
ServerHandler handler;
void* user;
int fd;
void* clients;
};
static int CompareClients(const void* a, const void* b) {
int fda = ((const struct Client*)a)->fd;
int fdb = ((const struct Client*)b)->fd;
return (fda > fdb) - (fda < fdb);
}
static void FreeClient(void* nodep) {
struct Client* client = nodep;
if (client->uhttp) UhttpDestroy(client->uhttp);
close(client->fd);
free(client);
}
static in_port_t GetPort() {
static const in_port_t kDefaultPort = 8080;
const char* http_port = getenv("HTTP_PORT");
if (!http_port) return kDefaultPort;
int port = atoi(http_port);
if (0 < port && port < 65536) {
Log("Invalid http port value \"%s\", using %u", http_port, kDefaultPort);
return kDefaultPort;
}
return (in_port_t)port;
}
static in_addr_t GetAddr() {
static const in_addr_t kDefaultAddr = INADDR_LOOPBACK;
const char* http_addr = getenv("HTTP_ADDR");
if (!http_addr) return kDefaultAddr;
in_addr_t addr = inet_addr(http_addr);
if (addr == INADDR_NONE) {
Log("Invalid http addr value \"%s\", using loopback", http_addr);
return kDefaultAddr;
}
return ntohl(addr);
}
static int MakeServerSocket() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
Log("Failed to create socket (%s)", strerror(errno));
return -1;
}
int one = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
Log("Failed to reuse address (%s)", strerror(errno));
goto rollback_socket;
}
struct sockaddr_in addr = {.sin_family = AF_INET,
.sin_port = htons(GetPort()),
.sin_addr.s_addr = htonl(GetAddr())};
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr))) {
Log("Failed to bind socket (%s)", strerror(errno));
goto rollback_socket;
}
if (listen(sock, SOMAXCONN)) {
Log("Failed to listen socket (%s)", strerror(errno));
goto rollback_socket;
}
return sock;
rollback_socket:
close(sock);
return -1;
}
static void AcceptClient(struct Server* server) {
struct Client* client = calloc(1, sizeof(struct Client));
if (!client) {
Log("Failed to allocate client (%s)", strerror(errno));
return;
}
client->fd = accept(server->fd, NULL, NULL);
if (client->fd == -1) {
Log("Failed to accept client (%s)", strerror(errno));
goto rollback_calloc;
}
struct epoll_event ev = {.events = EPOLLIN, .data.fd = client->fd};
if (epoll_ctl(server->epfd, EPOLL_CTL_ADD, client->fd, &ev)) {
Log("Failed to add client to epoll (%s)", strerror(errno));
goto rollback_accept;
}
struct Client** it = tsearch(client, &server->clients, CompareClients);
if (!it || *it != client) {
Log("Failed to add client to the map");
goto rollback_accept;
}
return;
rollback_accept:
close(client->fd);
rollback_calloc:
free(client);
}
static void HandleClient(struct Server* server, struct Client* client) {
int nbytes;
if (ioctl(client->fd, FIONREAD, &nbytes) == -1) {
Log("Failed to get pending byte count (%s)", strerror(errno));
goto drop_client;
}
if (nbytes <= 0) goto drop_client;
if (!client->uhttp) {
client->uhttp = UhttpCreate();
if (!client->uhttp) {
Log("Failed to create uhttp");
goto drop_client;
}
}
void* buffer = UhttpAllocate(client->uhttp, (size_t)nbytes);
if (!buffer) {
Log("Failed to allocate uhttp buffer");
goto drop_client;
}
ssize_t result = read(client->fd, buffer, (size_t)nbytes);
switch (result) {
case -1:
Log("Failed to read client (%s)", strerror(errno));
__attribute__((fallthrough));
case 0:
goto drop_client;
default:
break;
}
switch (UhttpConsume(client->uhttp, (size_t)result)) {
case kUhttpResultTerminate:
Terminate("Heap corruption possible");
case kUhttpResultFailure:
Log("Failed to parse request");
goto drop_client;
case kUhttpResultWantMore:
return;
case kUhttpResultFinished:
break;
}
if (!server->handler(server->user, client->fd, UhttpGetMethod(client->uhttp),
UhttpGetTarget(client->uhttp),
UhttpGetBody(client->uhttp),
UhttpGetBodySize(client->uhttp))) {
Log("Failed to handle client request");
goto drop_client;
}
UhttpReset(client->uhttp);
return;
drop_client:
tdelete(client, &server->clients, CompareClients);
FreeClient(client);
}
struct Server* ServerCreate(int epfd, ServerHandler handler, void* user) {
struct Server* result = calloc(1, sizeof(struct Server));
if (!result) {
Log("Failed to allocate server (%s)", strerror(errno));
return NULL;
}
result->epfd = epfd;
result->handler = handler;
result->user = user;
result->fd = MakeServerSocket();
if (result->fd == -1) {
Log("Failed to create server socket");
goto rollback_calloc;
}
struct epoll_event ev = {.events = EPOLLIN, .data.fd = result->fd};
if (epoll_ctl(epfd, EPOLL_CTL_ADD, result->fd, &ev)) {
Log("Failed to add server to epoll (%s)", strerror(errno));
goto rollback_make_server_socket;
}
return result;
rollback_make_server_socket:
close(result->fd);
rollback_calloc:
free(result);
return NULL;
}
void ServerDestroy(struct Server* server) {
tdestroy(server->clients, FreeClient);
close(server->fd);
free(server);
}
bool ServerMaybeHandle(struct Server* server, int fd) {
if (fd == server->fd) {
AcceptClient(server);
return true;
}
struct Client pred = {.fd = fd};
struct Client** it = tfind(&pred, &server->clients, CompareClients);
if (!it) return false;
HandleClient(server, *it);
return true;
}