summaryrefslogtreecommitdiff
path: root/server.c
diff options
context:
space:
mode:
authorMikhail Burakov <mburakov@mailbox.org>2021-10-27 09:45:46 +0200
committerMikhail Burakov <mburakov@mailbox.org>2021-10-27 09:45:46 +0200
commit851f78ba58463fe1c91d141172a15c971782dbe4 (patch)
tree2776d1980e6ec3bd52d738ef5f6ef2e97c580890 /server.c
parent2f666613e353f3cb58e531682a106f4627e453af (diff)
Import existing code to mqhttp
Diffstat (limited to 'server.c')
-rw-r--r--server.c237
1 files changed, 237 insertions, 0 deletions
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..de8cf1b
--- /dev/null
+++ b/server.c
@@ -0,0 +1,237 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "server.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <search.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#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;
+}