refactor(req/res): new request/response structure

This commit is contained in:
2026-01-14 02:19:08 +01:00
parent b083c264b8
commit f195bf4202
9 changed files with 380 additions and 166 deletions

20
include/http/handler.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef CWS_HANDLER_H
#define CWS_HANDLER_H
#include "http/request.h"
#include "http/response.h"
/* Configuration for static file serving */
typedef struct cws_handler_config {
const char *root_dir; /*< "www" */
const char *index_file; /*< "index.html" */
} cws_handler_config_s;
/* Static file handler */
cws_response_s *cws_handler_static_file(cws_request_s *request, cws_handler_config_s *config);
/* Error handlers */
cws_response_s *cws_handler_not_found(cws_request_s *request);
cws_response_s *cws_handler_not_implemented(cws_request_s *request);
#endif

View File

@@ -1,6 +1,7 @@
#ifndef CWS_REQUEST_H #ifndef CWS_REQUEST_H
#define CWS_REQUEST_H #define CWS_REQUEST_H
#include "http/types.h"
#include <myclib/myhashmap.h> #include <myclib/myhashmap.h>
#include <myclib/mystring.h> #include <myclib/mystring.h>
#include <stddef.h> #include <stddef.h>
@@ -9,28 +10,13 @@
#define CWS_HTTP_HEADER_MAX 512 #define CWS_HTTP_HEADER_MAX 512
#define CWS_HTTP_HEADER_CONTENT_MAX 1024 #define CWS_HTTP_HEADER_CONTENT_MAX 1024
typedef enum cws_http_method {
HTTP_GET,
HTTP_POST,
HTTP_PUT,
HTTP_DELETE,
HTTP_HEAD,
HTTP_UNKNOWN,
} cws_http_method_e;
typedef enum cws_http_status {
HTTP_OK,
HTTP_NOT_FOUND,
HTTP_NOT_IMPLEMENTED,
} cws_http_status_e;
typedef struct cws_request { typedef struct cws_request {
int sockfd;
cws_http_method_e method; cws_http_method_e method;
string_s *location; string_s *path;
string_s *location_path; string_s *query_string;
string_s *http_version; string_s *http_version;
hashmap_s *headers; hashmap_s *headers;
string_s *body;
} cws_request_s; } cws_request_s;
cws_request_s *cws_http_parse(string_s *request_str); cws_request_s *cws_http_parse(string_s *request_str);

View File

@@ -1,8 +1,39 @@
#ifndef CWS_RESPONSE_H #ifndef CWS_RESPONSE_H
#define CWS_RESPONSE_H #define CWS_RESPONSE_H
#include "http/request.h" #include "http/types.h"
#include <myclib/myhashmap.h>
#include <myclib/mystring.h>
#include <stddef.h>
#include <stdio.h>
void cws_http_send_response(cws_request_s *request, cws_http_status_e status); typedef enum cws_response_body_type {
RESPONSE_BODY_NONE,
RESPONSE_BODY_STRING,
RESPONSE_BODY_FILE,
} cws_response_body_type_e;
typedef struct cws_response {
cws_http_status_e status;
hashmap_s *headers;
/* Body handling */
cws_response_body_type_e body_type;
string_s *body_string;
FILE *body_file;
size_t content_length;
} cws_response_s;
cws_response_s *cws_response_new(cws_http_status_e status);
void cws_response_free(cws_response_s *response);
void cws_response_set_header(cws_response_s *response, const char *key, const char *value);
void cws_response_set_body_string(cws_response_s *response, const char *body);
void cws_response_set_body_file(cws_response_s *response, const char *filepath);
cws_response_s *cws_response_html(cws_http_status_e status, const char *title, const char *body);
cws_response_s *cws_response_error(cws_http_status_e status, const char *message);
int cws_response_send(int sockfd, cws_response_s *response);
#endif #endif

27
include/http/types.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef CWS_HTTP_TYPES_H
#define CWS_HTTP_TYPES_H
#include <stddef.h>
/* HTTP Methods */
typedef enum cws_http_method {
HTTP_GET,
HTTP_POST,
HTTP_PUT,
HTTP_DELETE,
HTTP_HEAD,
HTTP_UNKNOWN,
} cws_http_method_e;
/* HTTP Status Codes */
typedef enum cws_http_status {
HTTP_OK = 200,
HTTP_BAD_REQUEST = 400,
HTTP_NOT_FOUND = 404,
HTTP_INTERNAL_ERROR = 500,
HTTP_NOT_IMPLEMENTED = 501,
} cws_http_status_e;
const char *cws_http_status_string(cws_http_status_e status);
#endif

View File

@@ -7,6 +7,7 @@
#include "core/epoll.h" #include "core/epoll.h"
#include "core/socket.h" #include "core/socket.h"
#include "http/handler.h"
#include "http/request.h" #include "http/request.h"
#include "http/response.h" #include "http/response.h"
#include "utils/error.h" #include "utils/error.h"
@@ -26,38 +27,26 @@ static void worker_close_client(int epfd, int client_fd) {
close(client_fd); close(client_fd);
} }
/* Read client request data; 0 = incomplete, <0 = disconnect */ static cws_return worker_handle_client_data(int epfd, int client_fd) {
static cws_return worker_read_data(int epfd, int client_fd, string_s *data) { string_s *data = string_new("", 4096);
ssize_t total_bytes = cws_read_data(client_fd, data);
/* Read data from socket */
ssize_t total_bytes = cws_socket_read(client_fd, data);
if (total_bytes == 0) { if (total_bytes == 0) {
/* Partial request; wait for more data */ /* Partial request; wait for more data */
/* string_free(data);
* TODO: do not return CWS_OK
* instead free data and continue
*/
return CWS_OK; return CWS_OK;
} }
if (total_bytes < 0) { if (total_bytes < 0) {
/* Client closed or read error */
worker_close_client(epfd, client_fd); worker_close_client(epfd, client_fd);
string_free(data);
return CWS_CLIENT_DISCONNECTED_ERROR; return CWS_CLIENT_DISCONNECTED_ERROR;
} }
return CWS_OK; /* Parse HTTP request */
}
static cws_return worker_handle_client_data(int epfd, int client_fd) {
string_s *data = string_new("", 4096);
cws_return ret = worker_read_data(epfd, client_fd, data);
if (ret != CWS_OK) {
string_free(data);
return ret;
}
/* Parse full HTTP request */
cws_request_s *request = cws_http_parse(data); cws_request_s *request = cws_http_parse(data);
string_free(data); string_free(data);
if (request == NULL) { if (request == NULL) {
@@ -65,11 +54,19 @@ static cws_return worker_handle_client_data(int epfd, int client_fd) {
return CWS_HTTP_PARSE_ERROR; return CWS_HTTP_PARSE_ERROR;
} }
request->sockfd = client_fd; /* Configure handler */
cws_handler_config_s config = {.root_dir = "www", .index_file = "index.html"};
/* TODO: do not send HTTP_OK */ /* Handle request and generate response */
cws_http_send_response(request, HTTP_OK); cws_response_s *response = cws_handler_static_file(request, &config);
/* Send response */
if (response) {
cws_response_send(client_fd, response);
cws_response_free(response);
}
/* Cleanup */
cws_http_free(request); cws_http_free(request);
worker_close_client(epfd, client_fd); worker_close_client(epfd, client_fd);

68
src/http/handler.c Normal file
View File

@@ -0,0 +1,68 @@
#include "http/handler.h"
#include "utils/debug.h"
#include <myclib/mystring.h>
#include <string.h>
#include <sys/stat.h>
/* Sanitize and resolve file path */
static string_s *resolve_file_path(const char *url_path, cws_handler_config_s *config) {
string_s *full_path = string_new(config->root_dir, 256);
if (strcmp(url_path, "/") == 0) {
string_append(full_path, "/");
string_append(full_path, config->index_file);
return full_path;
}
string_append(full_path, url_path);
return full_path;
}
static bool file_exists(const char *filepath) {
struct stat st;
return stat(filepath, &st) == 0 && S_ISREG(st.st_mode);
}
cws_response_s *cws_handler_static_file(cws_request_s *request, cws_handler_config_s *config) {
if (!request || !config) {
return cws_response_error(HTTP_INTERNAL_ERROR, "Invalid request or configuration");
}
if (request->method != HTTP_GET) {
return cws_handler_not_implemented(request);
}
string_s *filepath = resolve_file_path(string_cstr(request->path), config);
const char *path = string_cstr(filepath);
CWS_LOG_DEBUG("Resolved path: %s", path);
if (!file_exists(path)) {
string_free(filepath);
return cws_handler_not_found(request);
}
cws_response_s *response = cws_response_new(HTTP_OK);
if (!response) {
string_free(filepath);
return cws_response_error(HTTP_INTERNAL_ERROR, "Failed to create response");
}
cws_response_set_body_file(response, path);
string_free(filepath);
CWS_LOG_DEBUG("Serving file: %s (%zu bytes)", path, response->content_length);
return response;
}
cws_response_s *cws_handler_not_found(cws_request_s *request) {
(void)request;
return cws_response_error(HTTP_NOT_FOUND, "The requested resource was not found.");
}
cws_response_s *cws_handler_not_implemented(cws_request_s *request) {
(void)request;
return cws_response_error(HTTP_NOT_IMPLEMENTED, "Method not implemented.");
}

View File

@@ -1,5 +1,6 @@
#include "http/request.h" #include "http/request.h"
#include <myclib/myhashmap.h>
#include <myclib/mystring.h> #include <myclib/mystring.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -9,15 +10,16 @@
#include "utils/hash.h" #include "utils/hash.h"
static cws_request_s *http_new() { static cws_request_s *http_new() {
cws_request_s *request = malloc(sizeof *request); cws_request_s *request = malloc(sizeof(*request));
if (!request) { if (!request) {
return NULL; return NULL;
} }
memset(request, 0, sizeof *request); memset(request, 0, sizeof(*request));
request->http_version = string_new("", 16); request->http_version = string_new("", 16);
request->location = string_new("", 128); request->path = string_new("", 256);
request->location_path = string_new("", 128); request->query_string = NULL;
request->body = NULL;
return request; return request;
} }
@@ -26,10 +28,18 @@ static cws_http_method_e http_parse_method(const char *method) {
if (strcmp(method, "GET") == 0) { if (strcmp(method, "GET") == 0) {
return HTTP_GET; return HTTP_GET;
} }
if (strcmp(method, "POST") == 0) { if (strcmp(method, "POST") == 0) {
return HTTP_POST; return HTTP_POST;
} }
if (strcmp(method, "PUT") == 0) {
return HTTP_PUT;
}
if (strcmp(method, "DELETE") == 0) {
return HTTP_DELETE;
}
if (strcmp(method, "HEAD") == 0) {
return HTTP_HEAD;
}
return HTTP_UNKNOWN; return HTTP_UNKNOWN;
} }
@@ -58,7 +68,7 @@ static bool parse_location(cws_request_s *req, char **cursor) {
s[len] = '\0'; s[len] = '\0';
CWS_LOG_DEBUG("location: %s", s); CWS_LOG_DEBUG("location: %s", s);
string_append(req->location, s); string_append(req->path, s);
*cursor = s + len + 1; *cursor = s + len + 1;
return true; return true;
@@ -112,7 +122,6 @@ static bool parse_headers(cws_request_s *req, char **cursor) {
strncpy(hv, header_value, sizeof(hv) - 1); strncpy(hv, header_value, sizeof(hv) - 1);
hv[sizeof(hv) - 1] = '\0'; hv[sizeof(hv) - 1] = '\0';
// CWS_LOG_DEBUG("%s:%s", hk, hv);
hm_set(req->headers, hk, hv); hm_set(req->headers, hk, hv);
/* Move to the next line */ /* Move to the next line */
@@ -125,7 +134,7 @@ static bool parse_headers(cws_request_s *req, char **cursor) {
} }
cws_request_s *cws_http_parse(string_s *request_str) { cws_request_s *cws_http_parse(string_s *request_str) {
if (!request_str || !request_str->data) { if (!request_str || !string_cstr(request_str)) {
return NULL; return NULL;
} }
@@ -134,7 +143,7 @@ cws_request_s *cws_http_parse(string_s *request_str) {
return NULL; return NULL;
} }
char *str = strdup(request_str->data); char *str = string_copy(request_str);
if (!str) { if (!str) {
cws_http_free(request); cws_http_free(request);
return NULL; return NULL;
@@ -142,31 +151,33 @@ cws_request_s *cws_http_parse(string_s *request_str) {
char *orig = str; char *orig = str;
/* Parse HTTP method */ /* Parse HTTP method */
parse_method(request, &str); if (!parse_method(request, &str)) {
free(orig);
/* Parse location */ cws_http_free(request);
parse_location(request, &str); return NULL;
}
/* Adjust location path */
/* @TODO: fix path traversal */ /* Parse location (URL path) */
string_append(request->location_path, "www"); if (!parse_location(request, &str)) {
if (strcmp(request->location->data, "/") == 0) { free(orig);
string_append(request->location_path, "/index.html"); cws_http_free(request);
} else { return NULL;
string_append(request->location_path, request->location->data);
} }
CWS_LOG_DEBUG("location path: %s", request->location_path->data);
/* Parse HTTP version */ /* Parse HTTP version */
parse_version(request, &str); if (!parse_version(request, &str)) {
free(orig);
cws_http_free(request);
return NULL;
}
/* Parse headers */ /* Parse headers */
parse_headers(request, &str); if (!parse_headers(request, &str)) {
free(orig);
cws_http_free(request);
return NULL;
}
/* TODO: Parse body */
/* orig is at the beginning of the body */
/* Free the original string */
free(orig); free(orig);
return request; return request;
@@ -185,12 +196,16 @@ void cws_http_free(cws_request_s *request) {
string_free(request->http_version); string_free(request->http_version);
} }
if (request->location) { if (request->path) {
string_free(request->location); string_free(request->path);
} }
if (request->location_path) { if (request->query_string) {
string_free(request->location_path); string_free(request->query_string);
}
if (request->body) {
string_free(request->body);
} }
free(request); free(request);

View File

@@ -1,122 +1,174 @@
#include "http/response.h" #include "http/response.h"
#include "http/mime.h" #include "http/mime.h"
#include "http/request.h"
#include "utils/debug.h" #include "utils/debug.h"
#include "utils/hash.h"
#include <core/socket.h> #include <core/socket.h>
#include <myclib/mystring.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
/* 8 KB reading buffer length */
#define CHUNK_SIZE 8192 #define CHUNK_SIZE 8192
#define HEADERS_LEN 512 #define HEADERS_BUFFER_SIZE 2048
static const char *http_status_string(cws_http_status_e status) { static hashmap_s *response_headers_new(void) {
switch (status) { return hm_new(my_str_hash_fn, my_str_equal_fn, my_str_free_fn, my_str_free_fn, sizeof(char) * 256,
case HTTP_OK: { sizeof(char) * 512);
return "200 OK";
}
case HTTP_NOT_FOUND: {
return "404 Not Found";
}
case HTTP_NOT_IMPLEMENTED: {
return "501 Not Implemented";
}
} }
return "?"; cws_response_s *cws_response_new(cws_http_status_e status) {
cws_response_s *resp = malloc(sizeof(*resp));
if (!resp) {
return NULL;
} }
static void http_send_headers(int sockfd, const char *status_str, const char *content_type, size_t content_length) { resp->status = status;
char headers[HEADERS_LEN]; resp->headers = response_headers_new();
int len = snprintf(headers, sizeof headers, resp->body_type = RESPONSE_BODY_NONE;
"HTTP/1.1 %s\r\n" resp->body_string = NULL;
"Content-Type: %s\r\n" resp->body_file = NULL;
"Content-Length: %zu\r\n" resp->content_length = 0;
"Connection: close\r\n"
"\r\n",
status_str, content_type, content_length);
if (len > 0) { cws_response_set_header(resp, "Connection", "close");
cws_send_data(sockfd, headers, len, 0);
} return resp;
} }
static void http_send_file(cws_request_s *request) { void cws_response_free(cws_response_s *response) {
const char *path = request->location_path->data; if (!response) {
FILE *fp = fopen(path, "rb"); return;
}
if (response->headers) {
hm_free(response->headers);
}
if (response->body_string) {
string_free(response->body_string);
}
if (response->body_file) {
fclose(response->body_file);
}
free(response);
}
void cws_response_set_header(cws_response_s *response, const char *key, const char *value) {
if (!response || !key || !value) {
return;
}
char k[256], v[512];
strncpy(k, key, sizeof(k) - 1);
k[sizeof(k) - 1] = '\0';
strncpy(v, value, sizeof(v) - 1);
v[sizeof(v) - 1] = '\0';
hm_set(response->headers, k, v);
}
void cws_response_set_body_string(cws_response_s *response, const char *body) {
if (!response || !body) {
return;
}
if (response->body_string) {
string_free(response->body_string);
}
response->body_type = RESPONSE_BODY_STRING;
response->body_string = string_new(body, strlen(body) + 1);
response->content_length = strlen(body);
}
void cws_response_set_body_file(cws_response_s *response, const char *filepath) {
if (!response || !filepath) {
return;
}
FILE *fp = fopen(filepath, "rb");
if (!fp) { if (!fp) {
CWS_LOG_ERROR("Cannot open file: %s", path); CWS_LOG_ERROR("Cannot open file: %s", filepath);
cws_http_send_response(request, HTTP_NOT_FOUND);
return; return;
} }
/* Get file size */
struct stat st; struct stat st;
if (stat(path, &st) != 0) { if (stat(filepath, &st) != 0) {
fclose(fp); fclose(fp);
cws_http_send_response(request, HTTP_NOT_FOUND);
return; return;
} }
size_t file_size = st.st_size;
char content_tye[CWS_HTTP_CONTENT_TYPE]; char content_type[64];
mime_get_content_type(path, content_tye); cws_mime_get_ct(filepath, content_type);
cws_response_set_header(response, "Content-Type", content_type);
/* Send headers */ response->body_type = RESPONSE_BODY_FILE;
http_send_headers(request->sockfd, http_status_string(HTTP_OK), content_tye, file_size); response->body_file = fp;
response->content_length = st.st_size;
/* Send the file in chunks */
char buffer[CHUNK_SIZE];
size_t bytes_read = 0;
while ((bytes_read = fread(buffer, 1, sizeof buffer, fp)) > 0) {
/* TODO: check return */
cws_send_data(request->sockfd, buffer, bytes_read, 0);
} }
fclose(fp); cws_response_s *cws_response_html(cws_http_status_e status, const char *title, const char *body) {
CWS_LOG_DEBUG("Served file: %s (%zu bytes)", path, file_size); cws_response_s *resp = cws_response_new(status);
if (!resp) {
return NULL;
} }
void http_send_simple_html(cws_request_s *request, cws_http_status_e status, const char *title, const char *desc) { char html[4096];
const char *fmt = "<html>\n" snprintf(html, sizeof(html),
"<html>\n"
"<head><title>%s</title></head>\n" "<head><title>%s</title></head>\n"
"<body><p>%s</p></body>\n" "<body><h1>%s</h1><p>%s</p></body>\n"
"</html>"; "</html>",
title, title, body);
int body_len = snprintf(NULL, 0, fmt, title, desc); cws_response_set_header(resp, "Content-Type", "text/html");
if (body_len < 0) cws_response_set_body_string(resp, html);
body_len = 0;
http_send_headers(request->sockfd, http_status_string(status), "text/html", body_len); return resp;
}
char *body = malloc(body_len + 1); cws_response_s *cws_response_error(cws_http_status_e status, const char *message) {
if (body) { const char *status_str = cws_http_status_string(status);
snprintf(body, body_len + 1, fmt, title, desc); return cws_response_html(status, status_str, message);
cws_send_data(request->sockfd, body, body_len, 0); }
free(body);
int cws_response_send(int sockfd, cws_response_s *response) {
if (!response) {
return -1;
}
char headers[HEADERS_BUFFER_SIZE];
int offset = snprintf(headers, sizeof(headers), "HTTP/1.1 %s\r\n", cws_http_status_string(response->status));
char content_length_str[32];
snprintf(content_length_str, sizeof(content_length_str), "%zu", response->content_length);
cws_response_set_header(response, "Content-Length", content_length_str);
offset += snprintf(headers + offset, sizeof(headers) - offset,
"Content-Length: %zu\r\n"
"Connection: close\r\n",
response->content_length);
offset += snprintf(headers + offset, sizeof(headers) - offset, "\r\n");
if (cws_socket_send(sockfd, headers, offset, 0) < 0) {
return -1;
}
if (response->body_type == RESPONSE_BODY_STRING && response->body_string) {
const char *body = string_cstr(response->body_string);
cws_socket_send(sockfd, body, response->content_length, 0);
} else if (response->body_type == RESPONSE_BODY_FILE && response->body_file) {
char buffer[CHUNK_SIZE];
size_t bytes_read;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), response->body_file)) > 0) {
if (cws_socket_send(sockfd, buffer, bytes_read, 0) < 0) {
return -1;
}
} }
} }
void cws_http_send_response(cws_request_s *request, cws_http_status_e status) { return 0;
switch (status) {
case HTTP_OK:
http_send_file(request);
break;
case HTTP_NOT_FOUND:
http_send_simple_html(request, HTTP_NOT_FOUND, "404 Not Found", "The requested resource was not found.");
break;
case HTTP_NOT_IMPLEMENTED:
http_send_simple_html(request, HTTP_NOT_IMPLEMENTED, "501 Not Implemented", "Method not implemented.");
break;
default:
http_send_simple_html(request, status, "Error", "An unexpected error occurred.");
break;
}
} }

18
src/http/types.c Normal file
View File

@@ -0,0 +1,18 @@
#include "http/types.h"
const char *cws_http_status_string(cws_http_status_e status) {
switch (status) {
case HTTP_OK:
return "200 OK";
case HTTP_BAD_REQUEST:
return "400 Bad Request";
case HTTP_NOT_FOUND:
return "404 Not Found";
case HTTP_INTERNAL_ERROR:
return "500 Internal Server Error";
case HTTP_NOT_IMPLEMENTED:
return "501 Not Implemented";
default:
return "500 Internal Server Error";
}
}