From e123c6344dff90a82571ba5951106b611a41ef3a Mon Sep 17 00:00:00 2001 From: talksik Date: Mon, 21 Sep 2026 16:26:24 -0700 Subject: [PATCH] create simple server abstraction --- main.c | 130 ++++++++----------------------------- server.c | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ server.h | 47 ++++++++++++++ 3 files changed, 264 insertions(+), 104 deletions(-) create mode 100644 server.c create mode 100644 server.h diff --git a/main.c b/main.c index 1ae048a..9252752 100644 --- a/main.c +++ b/main.c @@ -1,115 +1,37 @@ #include #include -#include -#include -#include -#include +#include "server.h" +#include +void handle_bootstrap(request *req, response *resp) { + printf("received a bootstrap request\n"); + resp->http_code = 200; + resp->body = "bootstrap"; +} + +void handle_payment(request *req, response *resp) { + printf("received a payment request\n"); + resp->http_code = 200; + resp->body = "payment complete"; +} + int main(int argc, char **argv) { printf("hello world\n"); - // http is built on top of tcp - // http/1.0 creates a new tcp connection every request - - // for an http server implmentation, we listen for packets in a loop, and group request packets - // then we respond with a response broken down into separate packets (headers, body, etc) - int fd = socket(AF_INET, SOCK_STREAM, 0); - // ?? connect or bind the socket to the address? - struct sockaddr_in addr = { - .sin_family = AF_INET, - .sin_port = htons(8080), - .sin_addr = INADDR_ANY, - }; - - int yes = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - int r = bind(fd, (struct sockaddr *) &addr, sizeof(addr)); - if (r == -1) { - printf("ERROR: unable to bind to address\n"); - return r; + uint16_t port = 8080; + server* s = create_server("0.0.0.0", port); + if (!s) { + printf("ERROR: unable to create server\n"); + return -1; } - // go ahead and listen for incoming connections - r = listen(fd, 3); - if (r == -1) { - printf("ERROR: unable to use socket to listen for incoming connections\n"); - return r; + register_route(s, "/bootstrap", handle_bootstrap); + register_route(s, "/payment", handle_payment); + + int r = start_server(s); + if (r != 0) { + printf("ERROR: unable to start server\n"); + return -1; } - - printf("Listening on port 8080\n"); - fflush(stdout); - - // we loop and do blocking accept calls for incoming connections - while (true) { - // we pass in a sockaddr which is filled with the incoming client's information - struct sockaddr client_addr; - int addr_len = sizeof(client_addr); - int client_fd = accept(fd, &client_addr, (socklen_t *) &addr_len); - if (client_fd == -1) { - printf("ERROR: unable to accept incoming client connection\n"); - return -1; - } - - // new socket is created for the one client connection - // we can recv from this socket - size_t size = 1000; - char *buf = malloc(size); - // TCP is a byte stream so we need to keep calling recv until an end condition (\r\n). HTTP is the protocol which specifies this, not TCP streams - - ssize_t received_bytes = recv(client_fd, buf, size, 0); - if (received_bytes > 0) { - printf("received bytes: %d\n", (int) received_bytes); - printf("%.*s\n", received_bytes, buf); - } - - // parse the request - char current_line[1000]; - int current_line_char = 0; - for (int i = 0; i < received_bytes; i++) { - printf("%c", buf[i]); - current_line[current_line_char] = buf[i]; - current_line_char++; - - if (buf[i] == '\n') { - printf("line complete: %.*s\n", current_line_char, current_line); - current_line_char = 0; - } - - // TODO: find the path from the first header `GET /bootstrap HTTP/1.1` - // decode the request further - } - fflush(stdout); - - free(buf); - - const char *response = - "HTTP/1.1 200 OK\r\n" - "Content-Length: 5\r\n" - "Content-Type: text/plain\r\n" - "Connection: close\r\n" - "\r\n" - "hello"; - r = send(client_fd, response, strlen(response), 0); - if (r == -1) { - printf("ERROR: in sending response"); - fflush(stdout); - } - printf("sent %zd of %zu bytes\n", r, strlen(response)); - fflush(stdout); - - close(client_fd); - } - - - // TODO: scaffolding - // keep track of multiple requests, examine the request data - // decode the request data - // then respond with particular html page - - // close socket - close(fd); - - // for an http client library, we need to create a tcp connection, send headers, and then body and other parts of the request in a structure that follows the protocol RFC } diff --git a/server.c b/server.c new file mode 100644 index 0000000..e3df89c --- /dev/null +++ b/server.c @@ -0,0 +1,191 @@ +#include "server.h" + +#include +#include +#include +#include + +#include +#include +#include + +typedef struct { + char *path; + route_handler handler; +} handler_metadata; + +struct server_s { + uint16_t port; + char *addr; + int server_fd; + + handler_metadata **handlers; + size_t handlers_len; +}; + +server *create_server(const char *addr, uint16_t port_number) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + printf("ERROR: unable to create socket\n"); + return NULL; + } + + server *s = malloc(sizeof(server)); + s->server_fd = fd; + s->port = port_number; + s->addr = strdup(addr); + s->handlers = NULL; + s->handlers_len = 0; + + return s; +} + +void destroy_server(server *s) { + // TODO: go through all current and pending connections and close them + if (s->server_fd != -1) { + close(s->server_fd); + } + + free(s->addr); + + for (int i = 0; i < s->handlers_len; i++) { + free(s->handlers[i]); + } + free(s->handlers); + + free(s); +} + +int start_server(server *s) { + struct in_addr sin_addr; + int r = inet_aton(s->addr, &sin_addr); + if (r == 0) { + printf("ERROR: unable to get address from provided input\n"); + return -1; + } + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = htons(s->port), + .sin_addr = sin_addr, + }; + + int yes = 1; + setsockopt(s->server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + r = bind(s->server_fd, (struct sockaddr *) &addr, sizeof(addr)); + if (r == -1) { + printf("ERROR: unable to bind to address\n"); + return -1; + } + + r = listen(s->server_fd, SOMAXCONN); + if (r == -1) { + printf("ERROR: unable to use socket to listen for incoming connections\n"); + return -1; + } + + while (true) { + struct sockaddr_storage client_addr; + socklen_t client_addr_len = sizeof(struct sockaddr_storage); + int client_fd = accept(s->server_fd, (struct sockaddr *)&client_addr, &client_addr_len); + if (client_fd == -1) { + printf("WARNING: unable to accept incoming client communication\n"); + continue; + } + + if (client_addr.ss_family == AF_INET) { + struct sockaddr_in *v4_addr = (struct sockaddr_in *)&client_addr; + // use v4_addr->sin_port, etc. + } + else if (client_addr.ss_family == AF_INET6) { + struct sockaddr_in6 *v6_addr = (struct sockaddr_in6 *)&client_addr; + // use v6_addr->sin6_port, etc. + } + + // TODO: call recv until we have the end of the headers and request + // append to a buffer, resizing along the way? + + // TODO: helper method which takes the client_fd and creates a request + // we can call recv until we receive \r\n\r\n + bool processing = true; + char data[4096] = {0}; + size_t data_size = sizeof(data); + size_t total_bytes = 0; + + while (processing) { + // TODO: if data buffer full, realloc + + ssize_t received_bytes = recv(client_fd, data + total_bytes, data_size - total_bytes - 1, 0); + + if (received_bytes > 0) { + total_bytes += received_bytes; + } + + // check if the last of the data is \r\n\r\n + if (total_bytes >= 4 && data[total_bytes - 1] == '\n' && data[total_bytes - 2] == '\r' + && data[total_bytes - 3] == '\n' && data[total_bytes - 4] == '\r') { + printf("Data: %s\n", data); + printf("Found end of the request\n"); + processing = false; + } + } + + request *req = malloc(sizeof(request)); + req->contents = data; + response *resp = malloc(sizeof(response)); + resp->http_code = 404; // default + resp->body = "default"; + + // find the relevant route handler, call it + for (int i = 0; i < s->handlers_len; i++) { + printf("checking if route handler matches. path: %s\n", s->handlers[i]->path); + if (strstr(data, s->handlers[i]->path) != NULL) { + s->handlers[i]->handler(req, resp); + + printf("response: %d\n", resp->http_code); + char response_content[1000]; + snprintf(response_content, sizeof(response_content), + "HTTP/1.1 %d OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n" + "\r\n" + "%s", + resp->http_code, strlen(resp->body), resp->body); + int r = send(client_fd, response_content, strlen(response_content), 0); + if (r == -1) { + printf("ERROR: in sending response\n"); + } + + break; + } + } + free(req); + free(resp); + + close(client_fd); + } + + return 0; +} + +int register_route(server *s, const char *route, route_handler handler) { + handler_metadata *new_handler = malloc(sizeof(handler_metadata)); + // TODO: handle error from malloc + + new_handler->path = strdup(route); + new_handler->handler = handler; + + // relloc to make room for one more pointer + size_t new_len = s->handlers_len + 1; + handler_metadata **new_array = realloc(s->handlers, new_len * sizeof(handler_metadata *)); + // TODO: handle error of realloc + + s->handlers = new_array; + s->handlers[s->handlers_len] = new_handler; + s->handlers_len = new_len; + + printf("registered route: %s\n", route); + printf("total handlers registered: %d\n", s->handlers_len); +} diff --git a/server.h b/server.h new file mode 100644 index 0000000..8614a87 --- /dev/null +++ b/server.h @@ -0,0 +1,47 @@ +#ifndef SERVER_H +#define SERVER_H + +#include +#include + +typedef struct server_s server; + +typedef struct { + char *key; + char *value; +} header; + +typedef struct { + /* header *headers; */ + /* size_t headers_len; */ + /* char *path; */ + /* char *method; */ + /* char *body; */ + char *contents; +} request; + +typedef struct { + uint16_t http_code; + char *body; +} response; + +// TODO: handlers need a way to respond to requests, maybe return a response with http code and body response +// we will provide them an already-allocated resp struct which they may fill +typedef void (*route_handler)(request *req, response *resp); + +// addr should be in dot-separated notation +// returns NULL if error during creation +server *create_server(const char *addr, uint16_t port_number); + +void destroy_server(server *s); + +// blocking call which handles inbound connections and requests +// returns -1 on internal failure +int start_server(server *s); +int register_route(server *s, const char *route, route_handler handler); + +// TODO: implementation wise, we need the server to handle incoming connections, read the incoming bytes of the connection in another thread, and then route handlers from different threads...will this lead to various problems? + /* - what if a route handler uses some shared resource (e.g. service handler) to fulfill the request? and different calls of the handler from different threads calls that service handler? */ + /* - keep it single-threaded for now. process one request at a time */ + +#endif // SERVER_H