refactor(req/res): new request/response structure
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "core/epoll.h"
|
||||
#include "core/socket.h"
|
||||
#include "http/handler.h"
|
||||
#include "http/request.h"
|
||||
#include "http/response.h"
|
||||
#include "utils/error.h"
|
||||
@@ -26,38 +27,26 @@ static void worker_close_client(int epfd, int client_fd) {
|
||||
close(client_fd);
|
||||
}
|
||||
|
||||
/* Read client request data; 0 = incomplete, <0 = disconnect */
|
||||
static cws_return worker_read_data(int epfd, int client_fd, string_s *data) {
|
||||
ssize_t total_bytes = cws_read_data(client_fd, data);
|
||||
static cws_return worker_handle_client_data(int epfd, int client_fd) {
|
||||
string_s *data = string_new("", 4096);
|
||||
|
||||
/* Read data from socket */
|
||||
ssize_t total_bytes = cws_socket_read(client_fd, data);
|
||||
|
||||
if (total_bytes == 0) {
|
||||
/* Partial request; wait for more data */
|
||||
/*
|
||||
* TODO: do not return CWS_OK
|
||||
* instead free data and continue
|
||||
*/
|
||||
string_free(data);
|
||||
|
||||
return CWS_OK;
|
||||
}
|
||||
|
||||
if (total_bytes < 0) {
|
||||
/* Client closed or read error */
|
||||
worker_close_client(epfd, client_fd);
|
||||
string_free(data);
|
||||
return CWS_CLIENT_DISCONNECTED_ERROR;
|
||||
}
|
||||
|
||||
return CWS_OK;
|
||||
}
|
||||
|
||||
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 */
|
||||
/* Parse HTTP request */
|
||||
cws_request_s *request = cws_http_parse(data);
|
||||
string_free(data);
|
||||
if (request == NULL) {
|
||||
@@ -65,11 +54,19 @@ static cws_return worker_handle_client_data(int epfd, int client_fd) {
|
||||
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 */
|
||||
cws_http_send_response(request, HTTP_OK);
|
||||
/* Handle request and generate response */
|
||||
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);
|
||||
worker_close_client(epfd, client_fd);
|
||||
|
||||
|
||||
68
src/http/handler.c
Normal file
68
src/http/handler.c
Normal 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.");
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "http/request.h"
|
||||
|
||||
#include <myclib/myhashmap.h>
|
||||
#include <myclib/mystring.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -9,15 +10,16 @@
|
||||
#include "utils/hash.h"
|
||||
|
||||
static cws_request_s *http_new() {
|
||||
cws_request_s *request = malloc(sizeof *request);
|
||||
cws_request_s *request = malloc(sizeof(*request));
|
||||
if (!request) {
|
||||
return NULL;
|
||||
}
|
||||
memset(request, 0, sizeof *request);
|
||||
memset(request, 0, sizeof(*request));
|
||||
|
||||
request->http_version = string_new("", 16);
|
||||
request->location = string_new("", 128);
|
||||
request->location_path = string_new("", 128);
|
||||
request->path = string_new("", 256);
|
||||
request->query_string = NULL;
|
||||
request->body = NULL;
|
||||
|
||||
return request;
|
||||
}
|
||||
@@ -26,10 +28,18 @@ static cws_http_method_e http_parse_method(const char *method) {
|
||||
if (strcmp(method, "GET") == 0) {
|
||||
return HTTP_GET;
|
||||
}
|
||||
|
||||
if (strcmp(method, "POST") == 0) {
|
||||
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;
|
||||
}
|
||||
@@ -58,7 +68,7 @@ static bool parse_location(cws_request_s *req, char **cursor) {
|
||||
|
||||
s[len] = '\0';
|
||||
CWS_LOG_DEBUG("location: %s", s);
|
||||
string_append(req->location, s);
|
||||
string_append(req->path, s);
|
||||
*cursor = s + len + 1;
|
||||
|
||||
return true;
|
||||
@@ -112,7 +122,6 @@ static bool parse_headers(cws_request_s *req, char **cursor) {
|
||||
strncpy(hv, header_value, sizeof(hv) - 1);
|
||||
hv[sizeof(hv) - 1] = '\0';
|
||||
|
||||
// CWS_LOG_DEBUG("%s:%s", hk, hv);
|
||||
hm_set(req->headers, hk, hv);
|
||||
|
||||
/* 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) {
|
||||
if (!request_str || !request_str->data) {
|
||||
if (!request_str || !string_cstr(request_str)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -134,7 +143,7 @@ cws_request_s *cws_http_parse(string_s *request_str) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *str = strdup(request_str->data);
|
||||
char *str = string_copy(request_str);
|
||||
if (!str) {
|
||||
cws_http_free(request);
|
||||
return NULL;
|
||||
@@ -142,31 +151,33 @@ cws_request_s *cws_http_parse(string_s *request_str) {
|
||||
char *orig = str;
|
||||
|
||||
/* Parse HTTP method */
|
||||
parse_method(request, &str);
|
||||
|
||||
/* Parse location */
|
||||
parse_location(request, &str);
|
||||
|
||||
/* Adjust location path */
|
||||
/* @TODO: fix path traversal */
|
||||
string_append(request->location_path, "www");
|
||||
if (strcmp(request->location->data, "/") == 0) {
|
||||
string_append(request->location_path, "/index.html");
|
||||
} else {
|
||||
string_append(request->location_path, request->location->data);
|
||||
if (!parse_method(request, &str)) {
|
||||
free(orig);
|
||||
cws_http_free(request);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parse location (URL path) */
|
||||
if (!parse_location(request, &str)) {
|
||||
free(orig);
|
||||
cws_http_free(request);
|
||||
return NULL;
|
||||
}
|
||||
CWS_LOG_DEBUG("location path: %s", request->location_path->data);
|
||||
|
||||
/* Parse HTTP version */
|
||||
parse_version(request, &str);
|
||||
if (!parse_version(request, &str)) {
|
||||
free(orig);
|
||||
cws_http_free(request);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
return request;
|
||||
@@ -185,12 +196,16 @@ void cws_http_free(cws_request_s *request) {
|
||||
string_free(request->http_version);
|
||||
}
|
||||
|
||||
if (request->location) {
|
||||
string_free(request->location);
|
||||
if (request->path) {
|
||||
string_free(request->path);
|
||||
}
|
||||
|
||||
if (request->location_path) {
|
||||
string_free(request->location_path);
|
||||
if (request->query_string) {
|
||||
string_free(request->query_string);
|
||||
}
|
||||
|
||||
if (request->body) {
|
||||
string_free(request->body);
|
||||
}
|
||||
|
||||
free(request);
|
||||
|
||||
@@ -1,122 +1,174 @@
|
||||
#include "http/response.h"
|
||||
|
||||
#include "http/mime.h"
|
||||
#include "http/request.h"
|
||||
#include "utils/debug.h"
|
||||
#include "utils/hash.h"
|
||||
#include <core/socket.h>
|
||||
#include <myclib/mystring.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* 8 KB reading buffer length */
|
||||
#define CHUNK_SIZE 8192
|
||||
#define HEADERS_LEN 512
|
||||
#define HEADERS_BUFFER_SIZE 2048
|
||||
|
||||
static const char *http_status_string(cws_http_status_e status) {
|
||||
switch (status) {
|
||||
case HTTP_OK: {
|
||||
return "200 OK";
|
||||
}
|
||||
case HTTP_NOT_FOUND: {
|
||||
return "404 Not Found";
|
||||
}
|
||||
case HTTP_NOT_IMPLEMENTED: {
|
||||
return "501 Not Implemented";
|
||||
}
|
||||
}
|
||||
|
||||
return "?";
|
||||
static hashmap_s *response_headers_new(void) {
|
||||
return hm_new(my_str_hash_fn, my_str_equal_fn, my_str_free_fn, my_str_free_fn, sizeof(char) * 256,
|
||||
sizeof(char) * 512);
|
||||
}
|
||||
|
||||
static void http_send_headers(int sockfd, const char *status_str, const char *content_type, size_t content_length) {
|
||||
char headers[HEADERS_LEN];
|
||||
int len = snprintf(headers, sizeof headers,
|
||||
"HTTP/1.1 %s\r\n"
|
||||
"Content-Type: %s\r\n"
|
||||
"Content-Length: %zu\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n",
|
||||
status_str, content_type, content_length);
|
||||
|
||||
if (len > 0) {
|
||||
cws_send_data(sockfd, headers, len, 0);
|
||||
cws_response_s *cws_response_new(cws_http_status_e status) {
|
||||
cws_response_s *resp = malloc(sizeof(*resp));
|
||||
if (!resp) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
resp->status = status;
|
||||
resp->headers = response_headers_new();
|
||||
resp->body_type = RESPONSE_BODY_NONE;
|
||||
resp->body_string = NULL;
|
||||
resp->body_file = NULL;
|
||||
resp->content_length = 0;
|
||||
|
||||
cws_response_set_header(resp, "Connection", "close");
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
static void http_send_file(cws_request_s *request) {
|
||||
const char *path = request->location_path->data;
|
||||
FILE *fp = fopen(path, "rb");
|
||||
void cws_response_free(cws_response_s *response) {
|
||||
if (!response) {
|
||||
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) {
|
||||
CWS_LOG_ERROR("Cannot open file: %s", path);
|
||||
cws_http_send_response(request, HTTP_NOT_FOUND);
|
||||
CWS_LOG_ERROR("Cannot open file: %s", filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get file size */
|
||||
struct stat st;
|
||||
if (stat(path, &st) != 0) {
|
||||
if (stat(filepath, &st) != 0) {
|
||||
fclose(fp);
|
||||
cws_http_send_response(request, HTTP_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
size_t file_size = st.st_size;
|
||||
|
||||
char content_tye[CWS_HTTP_CONTENT_TYPE];
|
||||
mime_get_content_type(path, content_tye);
|
||||
char content_type[64];
|
||||
cws_mime_get_ct(filepath, content_type);
|
||||
cws_response_set_header(response, "Content-Type", content_type);
|
||||
|
||||
/* Send headers */
|
||||
http_send_headers(request->sockfd, http_status_string(HTTP_OK), content_tye, file_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_LOG_DEBUG("Served file: %s (%zu bytes)", path, file_size);
|
||||
response->body_type = RESPONSE_BODY_FILE;
|
||||
response->body_file = fp;
|
||||
response->content_length = st.st_size;
|
||||
}
|
||||
|
||||
void http_send_simple_html(cws_request_s *request, cws_http_status_e status, const char *title, const char *desc) {
|
||||
const char *fmt = "<html>\n"
|
||||
"<head><title>%s</title></head>\n"
|
||||
"<body><p>%s</p></body>\n"
|
||||
"</html>";
|
||||
|
||||
int body_len = snprintf(NULL, 0, fmt, title, desc);
|
||||
if (body_len < 0)
|
||||
body_len = 0;
|
||||
|
||||
http_send_headers(request->sockfd, http_status_string(status), "text/html", body_len);
|
||||
|
||||
char *body = malloc(body_len + 1);
|
||||
if (body) {
|
||||
snprintf(body, body_len + 1, fmt, title, desc);
|
||||
cws_send_data(request->sockfd, body, body_len, 0);
|
||||
free(body);
|
||||
cws_response_s *cws_response_html(cws_http_status_e status, const char *title, const char *body) {
|
||||
cws_response_s *resp = cws_response_new(status);
|
||||
if (!resp) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char html[4096];
|
||||
snprintf(html, sizeof(html),
|
||||
"<html>\n"
|
||||
"<head><title>%s</title></head>\n"
|
||||
"<body><h1>%s</h1><p>%s</p></body>\n"
|
||||
"</html>",
|
||||
title, title, body);
|
||||
|
||||
cws_response_set_header(resp, "Content-Type", "text/html");
|
||||
cws_response_set_body_string(resp, html);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
void cws_http_send_response(cws_request_s *request, cws_http_status_e status) {
|
||||
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;
|
||||
}
|
||||
cws_response_s *cws_response_error(cws_http_status_e status, const char *message) {
|
||||
const char *status_str = cws_http_status_string(status);
|
||||
return cws_response_html(status, status_str, message);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
18
src/http/types.c
Normal file
18
src/http/types.c
Normal 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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user