Parsing of simple requests. HashMap.
This commit is contained in:
@@ -9,6 +9,8 @@ add_library(
|
||||
STATIC
|
||||
src/httpserver.h
|
||||
src/httpserver.c
|
||||
src/utils/hashmap.c
|
||||
src/utils/hashmap.h
|
||||
)
|
||||
|
||||
add_executable(
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
#include "main.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;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
@@ -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
|
||||
207
src/httpserver.c
207
src/httpserver.c
@@ -3,17 +3,36 @@
|
||||
//
|
||||
|
||||
#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);
|
||||
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;
|
||||
}
|
||||
@@ -7,30 +7,79 @@
|
||||
#define SIMPLEHTTPSERVER_H
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.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);
|
||||
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
|
||||
125
src/utils/hashmap.c
Normal file
125
src/utils/hashmap.c
Normal 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
30
src/utils/hashmap.h
Normal 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
|
||||
Reference in New Issue
Block a user