Parsing of simple requests. HashMap.

This commit is contained in:
2025-09-13 17:32:45 +03:00
parent 39db2830e2
commit eb5e420d41
7 changed files with 419 additions and 26 deletions

View File

@@ -9,6 +9,8 @@ add_library(
STATIC STATIC
src/httpserver.h src/httpserver.h
src/httpserver.c src/httpserver.c
src/utils/hashmap.c
src/utils/hashmap.h
) )
add_executable( add_executable(

View File

@@ -5,14 +5,15 @@
#include "main.h" #include "main.h"
#include <stdio.h> #include <stdio.h>
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; return http_response;
} }
int main() { int main() {
struct HttpServer http_server; http_server_t http_server;
http_server.handle_request = &handle_request;
printf("Starting the server...\n"); printf("Starting the server...\n");
start_http_server(&http_server, ADDRESS, PORT); start_http_server(&http_server, ADDRESS, PORT);
printf("Bye!\n"); printf("Bye!\n");

View File

@@ -9,8 +9,7 @@
#define ADDRESS "127.0.0.1" #define ADDRESS "127.0.0.1"
#define PORT 8080 #define PORT 8080
struct HttpResponse* handleRequest(struct HttpRequest* request); http_response_t* handle_request(http_request_t* request);
int main(); int main();
#endif //SIMPLEHTTPSERVER_MAIN_H #endif //SIMPLEHTTPSERVER_MAIN_H

View File

@@ -3,17 +3,36 @@
// //
#include "httpserver.h" #include "httpserver.h"
#include <stdio.h>
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); const int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) if (server_fd == -1)
{ {
printf("Failed to create new socket.\n"); perror("Failed to create new socket.\n");
return; 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; struct sockaddr_in address;
address.sin_family = AF_INET; address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(addr); 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)); const int bind_c = bind(server_fd, (struct sockaddr*)&address, sizeof(address));
if (bind_c == -1) if (bind_c == -1)
{ {
printf("Failed to bind address.\n"); perror("Failed to bind address.\n");
return; return;
} }
const int listen_c = listen(server_fd, 3); const int listen_c = listen(server_fd, 3);
if (listen_c == -1) if (listen_c == -1)
{ {
printf("Failed to begin listening.\n"); perror("Failed to begin listening.\n");
return; return;
} }
@@ -41,26 +60,194 @@ void start_http_server(struct HttpServer* http_server, const char *addr, const s
close(server_fd); 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); const int socket_d = accept(server_fd, address, addr_len);
if (socket_d == -1) if (socket_d == -1)
{ {
printf("Failed to accept connection.\n"); perror("Failed to accept connection.\n");
return; return;
} }
char buffer[1024] = {0}; char buffer[1024] = {0};
const ssize_t bytes_read = read(socket_d, buffer, 1024); 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); printf("Client message (%ld):\n", bytes_read);
for (int i = 0; i < bytes_read; i++) for (int i = 0; i < bytes_read; i++)
{ {
if (buffer[i] == '\r') if (buffer[i] != '\r')
continue;
putchar(buffer[i]); putchar(buffer[i]);
} }
putchar('\n'); putchar('\n');
fflush(stdout); 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); 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;
}

View File

@@ -7,30 +7,79 @@
#define SIMPLEHTTPSERVER_H #define SIMPLEHTTPSERVER_H
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#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); typedef struct http_response
void process_conn(struct HttpServer* http_server, int server_fd, struct sockaddr* address, socklen_t* addr_len); {
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 #endif //SIMPLEHTTPSERVER_H

125
src/utils/hashmap.c Normal file
View File

@@ -0,0 +1,125 @@
//
// Created by nazar on 13.09.2025.
//
#include "hashmap.h"
#include <string.h>
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);
}

30
src/utils/hashmap.h Normal file
View File

@@ -0,0 +1,30 @@
//
// Created by nazar on 13.09.2025.
//
#ifndef SIMPLEHTTPSERVER_HASHMAP_H
#define SIMPLEHTTPSERVER_HASHMAP_H
#include <stdlib.h>
#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