diff --git a/CMakeLists.txt b/CMakeLists.txt index d1580f4..2b4866c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ add_library( STATIC src/httpserver.h src/httpserver.c + src/utils/hashmap.c + src/utils/hashmap.h ) add_executable( diff --git a/app/main.c b/app/main.c index cb2a741..95c6ca8 100644 --- a/app/main.c +++ b/app/main.c @@ -5,14 +5,15 @@ #include "main.h" #include -struct HttpResponse* handleRequest(struct HttpRequest* request) +http_response_t* handle_request(http_request_t* request) { - struct HttpResponse* http_response = {}; + http_response_t* http_response = {}; return http_response; } int main() { - struct HttpServer http_server; + http_server_t http_server; + http_server.handle_request = &handle_request; printf("Starting the server...\n"); start_http_server(&http_server, ADDRESS, PORT); printf("Bye!\n"); diff --git a/app/main.h b/app/main.h index b3f11c7..fa96cc6 100644 --- a/app/main.h +++ b/app/main.h @@ -9,8 +9,7 @@ #define ADDRESS "127.0.0.1" #define PORT 8080 -struct HttpResponse* handleRequest(struct HttpRequest* request); - +http_response_t* handle_request(http_request_t* request); int main(); #endif //SIMPLEHTTPSERVER_MAIN_H \ No newline at end of file diff --git a/src/httpserver.c b/src/httpserver.c index 886c87e..5139904 100644 --- a/src/httpserver.c +++ b/src/httpserver.c @@ -3,17 +3,36 @@ // #include "httpserver.h" -#include -void start_http_server(struct HttpServer* http_server, const char *addr, const short port) +struct { + const char* name; + enum http_method method; +} method_map[] = { + { "GET", GET }, + { "HEAD", HEAD }, + { "POST", POST }, + { "PUT", PUT }, + { "DELETE", DELETE }, + { "CONNECT", CONNECT }, + { "OPTIONS", OPTIONS }, + { "TRACE", TRACE }, + { "PATCH", PATCH } +}; + +void start_http_server(http_server_t* http_server, const char *addr, const short port) { const int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { - printf("Failed to create new socket.\n"); + perror("Failed to create new socket.\n"); return; } + int opt = 1; + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + perror("setsockopt(SO_REUSEADDR) failed"); + } + struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = inet_addr(addr); @@ -22,14 +41,14 @@ void start_http_server(struct HttpServer* http_server, const char *addr, const s const int bind_c = bind(server_fd, (struct sockaddr*)&address, sizeof(address)); if (bind_c == -1) { - printf("Failed to bind address.\n"); + perror("Failed to bind address.\n"); return; } const int listen_c = listen(server_fd, 3); if (listen_c == -1) { - printf("Failed to begin listening.\n"); + perror("Failed to begin listening.\n"); return; } @@ -41,26 +60,194 @@ void start_http_server(struct HttpServer* http_server, const char *addr, const s close(server_fd); } -void process_conn(struct HttpServer* http_server, const int server_fd, struct sockaddr* address, socklen_t* addr_len) +void process_conn(http_server_t* http_server, const int server_fd, struct sockaddr* address, socklen_t* addr_len) { const int socket_d = accept(server_fd, address, addr_len); if (socket_d == -1) { - printf("Failed to accept connection.\n"); + perror("Failed to accept connection.\n"); return; } char buffer[1024] = {0}; + const ssize_t bytes_read = read(socket_d, buffer, 1024); + if (bytes_read == -1) + { + perror("Failed white reading socket."); + return; + } + + // Debug output printf("Client message (%ld):\n", bytes_read); for (int i = 0; i < bytes_read; i++) { - if (buffer[i] == '\r') - continue; - putchar(buffer[i]); + if (buffer[i] != '\r') + putchar(buffer[i]); } putchar('\n'); fflush(stdout); + + enum http_request_parsing_stage request_parsing_stage = START_LINE_METHOD; + http_request_t http_request = {}; + + char method_str[8]; + size_t method_str_len = 0; + + char http_path[2048]; + size_t http_path_len = 0; + + char http_name[16]; + size_t http_name_len = 0; + + char header_key[256]; + size_t header_key_length = 0; + char header_val[1024]; + size_t header_val_length = 0; + + http_request.http_content = nullptr; + http_request.http_content_len = 0; + + for (int i = 0; i < bytes_read; i++) + { + bool drop = false; + switch (request_parsing_stage) + { + case START_LINE_METHOD: + if (buffer[i] == ' ') + { + method_str[method_str_len] = '\0'; + http_request.http_method = parse_method_str(method_str, method_str_len); + + if (http_request.http_method == UNKNOWN) + { + perror("Unknown HTTP method!\n"); + drop = true; + break; + } + request_parsing_stage = START_LINE_PATH; + break; + } + + method_str[method_str_len++] = buffer[i]; + break; + case START_LINE_PATH: + if (buffer[i] == ' ') + { + http_path[http_path_len] = '\0'; + http_request.http_path = http_path; + http_request.http_path_len = http_path_len; + request_parsing_stage = START_LINE_HTTP_VERSION_NAME; + break; + } + + http_path[http_path_len++] = buffer[i]; + break; + case START_LINE_HTTP_VERSION_NAME: + if (buffer[i] == '/') + { + http_request.http_version.http_name = http_name; + http_request.http_version.http_name_len = http_name_len; + + request_parsing_stage = START_LINE_HTTP_VERSION_MAJOR; + break; + } + + http_name[http_name_len++] = buffer[i]; + break; + case START_LINE_HTTP_VERSION_MAJOR: + if (buffer[i] == '.') + { + request_parsing_stage = START_LINE_HTTP_VERSION_MINOR; + break; + } + + http_request.http_version.major = buffer[i] - '0'; + break; + case START_LINE_HTTP_VERSION_MINOR: + if (i + 1 < bytes_read && buffer[i] == '\r' && buffer[i + 1] == '\n') + { + i++; + http_request.http_headers = new_hash_map(); + request_parsing_stage = HEADERS_KEY; + break; + } + + http_request.http_version.minor = buffer[i] - '0'; + break; + case HEADERS_KEY: + if (i + 1 < bytes_read && buffer[i] == ':' && buffer[i + 1] == ' ') + { + i++; + header_key[header_key_length] = '\0'; + request_parsing_stage = HEADERS_VALUE; + break; + } + + if (i + 1 < bytes_read && buffer[i] == '\r' && buffer[i + 1] == '\n') + { + request_parsing_stage = CONTENT; + break; + } + + header_key[header_key_length++] = buffer[i]; + break; + case HEADERS_VALUE: + if (i + 1 < bytes_read && buffer[i] == '\r' && buffer[i + 1] == '\n') + { + i++; + header_val[header_val_length] = '\0'; + char* key = calloc(header_key_length + 1, sizeof(char)); + strncpy(key, header_key, header_key_length); + char* val = calloc(header_val_length + 1, sizeof(char)); + strncpy(val, header_val, header_val_length); + + printf( + "Setting header '%s' (hash %d per cap %d): '%s'\n", + key, hashcode(http_request.http_headers, key), + http_request.http_headers->cap, val); + + set(http_request.http_headers, key, val); + header_key[0] = '\0'; + header_key_length = 0; + header_val[0] = '\0'; + header_val_length = 0; + request_parsing_stage = HEADERS_KEY; + break; + } + + header_val[header_val_length++] = buffer[i]; + break; + case CONTENT: + default: + drop = true; + break; + } + + if (drop) + break; + } + + http_response_t* http_response = http_server->handle_request(&http_request); + + // TODO: Send response + + free_hash_map(http_request.http_headers); + free(http_response); + close(socket_d); } + +enum http_method parse_method_str(const char* str, const size_t len) +{ + for (size_t i = 0; i < sizeof(method_map) / sizeof(method_map[0]); i++) { + if (strlen(method_map[i].name) == len && + strncmp(str, method_map[i].name, len) == 0) + { + return method_map[i].method; + } + } + + return UNKNOWN; +} \ No newline at end of file diff --git a/src/httpserver.h b/src/httpserver.h index 46d59bd..c61cd22 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -7,30 +7,79 @@ #define SIMPLEHTTPSERVER_H #include -#include +#include #include #include -#include -#include #include #include +#include +#include +#include +#include "utils/hashmap.h" -struct HttpRequest +typedef struct http_version { + char* http_name; // has to end with "HTTP" btw + size_t http_name_len; + unsigned short major; + unsigned short minor; +} http_version_t; +enum http_method +{ + UNKNOWN, + GET, + HEAD, + POST, + PUT, + DELETE, + CONNECT, + OPTIONS, + TRACE, + PATCH }; -struct HttpResponse +enum http_request_parsing_stage { - + START_LINE_METHOD, + START_LINE_PATH, + START_LINE_HTTP_VERSION_NAME, + START_LINE_HTTP_VERSION_MAJOR, + START_LINE_HTTP_VERSION_MINOR, + HEADERS_KEY, + HEADERS_VALUE, + CONTENT }; -struct HttpServer +typedef struct http_request { - struct HttpResponse* (*handleRequest)(struct HttpRequest* request); -}; + enum http_method http_method; + char* http_path; + size_t http_path_len; + http_version_t http_version; + hashmap_t* http_headers; + char* http_content; + size_t http_content_len; +} http_request_t; -void start_http_server(struct HttpServer* http_server, const char *addr, short port); -void process_conn(struct HttpServer* http_server, int server_fd, struct sockaddr* address, socklen_t* addr_len); +typedef struct http_response +{ + http_version_t http_version; + u_short http_status_code; + char* http_status_message; + size_t http_status_message_len; + hashmap_t* http_headers; + char* http_content; + size_t http_content_len; +} http_response_t; + +typedef struct http_server +{ + http_response_t* (*handle_request)(http_request_t* request); +} http_server_t; + +void start_http_server(http_server_t* http_server, const char *addr, short port); +void process_conn(http_server_t* http_server, int server_fd, struct sockaddr* address, socklen_t* addr_len); +enum http_method parse_method_str(const char* str, size_t len); #endif //SIMPLEHTTPSERVER_H \ No newline at end of file diff --git a/src/utils/hashmap.c b/src/utils/hashmap.c new file mode 100644 index 0000000..6d8ab38 --- /dev/null +++ b/src/utils/hashmap.c @@ -0,0 +1,125 @@ +// +// Created by nazar on 13.09.2025. +// + +#include "hashmap.h" + +#include + +hashmap_t* new_hash_map() { + hashmap_t* this = malloc(sizeof *this); + this->cap = DEFAULT_CAPACITY; + this->len = 0; + this->list = calloc(this->cap, sizeof(pair_t*)); + return this; +} + +unsigned int hashcode(const hashmap_t* this, const char* key) +{ + unsigned int code; + for (code = 0; *key != '\0'; key++) { + code = *key + 31 * code; + } + return code % this->cap; +} + +char* get(const hashmap_t* this, const char* key) +{ + const unsigned hash = hashcode(this, key); + + const pair_t* existing_pair = this->list[hash]; + if (existing_pair == nullptr) + return nullptr; + + while (strcmp(existing_pair->key, key) != 0) + { + if (existing_pair->next == nullptr) + return nullptr; + existing_pair = existing_pair->next; + } + + return existing_pair->val; +} + +void set(hashmap_t* this, char* key, char* val) +{ + const unsigned hash = hashcode(this, key); + + pair_t* new_pair = malloc(sizeof *new_pair); + new_pair->key = key; + new_pair->val = val; + new_pair->next = nullptr; + + pair_t* existing_pair = this->list[hash]; + + if (existing_pair == nullptr) + { + this->list[hash] = new_pair; + this->len++; + + if (this->len >= this->cap) + expand_hash_map(this); + + return; + } + + while (existing_pair->next != nullptr) + { + if (strcmp(existing_pair->key, key) == 0) + { + existing_pair->val = val; + free(new_pair); + return; + } + + existing_pair = existing_pair->next; + } + + existing_pair->next = new_pair; + this->len++; + + if (this->len >= this->cap) + expand_hash_map(this); +} + +void expand_hash_map(hashmap_t* this) +{ + const unsigned int old_cap = this->cap; + pair_t** old_list = this->list; + + this->cap *= 2; + this->len = 0; + this->list = calloc(this->cap, sizeof(pair_t*)); + + for (int i = 0; i < old_cap; i++) + { + pair_t* iter = old_list[i]; + while (iter != nullptr) + { + set(this, iter->key, iter->val); + pair_t* prev_iter = iter; + iter = iter->next; + free(prev_iter); + } + } + + free(old_list); +} + +void free_hash_map(const hashmap_t* this) +{ + for (int i = 0; i < this->cap; i++) + { + pair_t* iter = this->list[i]; + while (iter != nullptr) + { + pair_t* prev_iter = iter; + iter = iter->next; + free(prev_iter->key); + free(prev_iter->val); + free(prev_iter); + } + } + + free(this->list); +} \ No newline at end of file diff --git a/src/utils/hashmap.h b/src/utils/hashmap.h new file mode 100644 index 0000000..ca4fde2 --- /dev/null +++ b/src/utils/hashmap.h @@ -0,0 +1,30 @@ +// +// Created by nazar on 13.09.2025. +// + +#ifndef SIMPLEHTTPSERVER_HASHMAP_H +#define SIMPLEHTTPSERVER_HASHMAP_H + +#include +#define DEFAULT_CAPACITY 8 + +typedef struct pair { + char* key; + char* val; + struct pair* next; +} pair_t; + +typedef struct hashmap { + pair_t** list; + unsigned int cap; + unsigned int len; +} hashmap_t; + +hashmap_t* new_hash_map(); +unsigned int hashcode(const hashmap_t* this, const char* key); +char* get(const hashmap_t* this, const char* key); +void set(hashmap_t* this, char* key, char* val); +void expand_hash_map(hashmap_t* this); +void free_hash_map(const hashmap_t* this); + +#endif //SIMPLEHTTPSERVER_HASHMAP_H \ No newline at end of file