diff --git a/include/http/http.h b/include/http/http.h index 895fb9d..c523376 100644 --- a/include/http/http.h +++ b/include/http/http.h @@ -9,17 +9,18 @@ #define CWS_HTTP_HEADER_CONTENT_MAX 1024 typedef enum cws_http_method { - CWS_HTTP_GET, - CWS_HTTP_POST, - CWS_HTTP_PUT, - CWS_HTTP_DELETE, - CWS_HTTP_HEAD, + HTTP_GET, + HTTP_POST, + HTTP_PUT, + HTTP_DELETE, + HTTP_HEAD, + HTTP_UNKNOWN, } cws_http_method_e; typedef enum cws_http_status { - CWS_HTTP_OK, - CWS_HTTP_NOT_FOUND, - CWS_HTTP_NOT_IMPLEMENTED, + HTTP_OK, + HTTP_NOT_FOUND, + HTTP_NOT_IMPLEMENTED, } cws_http_status_e; typedef struct cws_http { @@ -31,18 +32,11 @@ typedef struct cws_http { hashmap_s *headers; } cws_http_s; -cws_http_s *cws_http_parse(string_s *request_str, int sockfd); - -int cws_http_get_content_type(cws_http_s *request, char *content_type); - -size_t cws_http_response_builder(char **response, char *http_version, cws_http_status_e status, char *content_type, char *connection, char *body, - size_t body_len_bytes); +cws_http_s *cws_http_parse(string_s *request_str); void cws_http_send_response(cws_http_s *request, cws_http_status_e status); -int cws_http_send_resource(cws_http_s *request); - -void cws_http_send_simple_html(cws_http_s *request, cws_http_status_e status, char *title, char *description); +size_t http_response_builder(char **response, char *http_version, cws_http_status_e status, char *content_type, char *body, size_t body_len_bytes); void cws_http_free(cws_http_s *request); diff --git a/src/http/http.c b/src/http/http.c index a2eb10d..2ee457c 100644 --- a/src/http/http.c +++ b/src/http/http.c @@ -2,7 +2,6 @@ #include "http/http.h" -#include #include #include #include @@ -12,42 +11,173 @@ #include "utils/debug.h" #include "utils/utils.h" -static void cws_http_init(cws_http_s **request) { - *request = calloc(1, sizeof(cws_http_s)); - if (!*request) { - return; +static cws_http_s *http_new() { + cws_http_s *request = malloc(sizeof(cws_http_s)); + if (!request) { + return NULL; } - (*request)->http_version = string_new("", 16); - (*request)->location = string_new("", 128); - (*request)->location_path = string_new("", 512); + request->http_version = string_new("", 16); + request->location = string_new("", 128); + request->location_path = string_new("", 512); + + return request; } -static int cws_http_parse_method(cws_http_s *request, const char *method) { +static cws_http_method_e http_parse_method(const char *method) { if (strcmp(method, "GET") == 0) { - request->method = CWS_HTTP_GET; - return 0; + return HTTP_GET; } if (strcmp(method, "POST") == 0) { - request->method = CWS_HTTP_POST; + return HTTP_POST; + } + + return HTTP_UNKNOWN; +} + +static int http_get_content_type(char *location_path, char *content_type) { + /* Find last occurrence of a string */ + char *ptr = strrchr(location_path, '.'); + if (ptr == NULL) { + return -1; + } + ptr += 1; + + char ct[32] = {0}; + + if (strcmp(ptr, "html") == 0 || strcmp(ptr, "css") == 0 || strcmp(ptr, "javascript") == 0) { + strncpy(ct, "text", sizeof ct); + } + + if (strcmp(ptr, "jpg") == 0 || strcmp(ptr, "png") == 0) { + strncpy(ct, "image", sizeof ct); + } + + snprintf(content_type, 1024, "%s/%s", ct, ptr); + + return 0; +} + +static char *http_status_string(cws_http_status_e status) { + switch (status) { + case HTTP_OK: { + return "200 OK"; + break; + } + case HTTP_NOT_FOUND: { + return "404 Not Found"; + break; + } + case HTTP_NOT_IMPLEMENTED: { + return "501 Not Implemented"; + break; + } + } + + return "?"; +} + +static size_t file_data(const char *path, char **data) { + FILE *file = fopen(path, "rb"); + if (!file) { + return NULL; + } + + /* Retrieve file size */ + fseek(file, 0, SEEK_END); + const size_t content_length = ftell(file); + rewind(file); + + /* Retrieve file data */ + *data = malloc(content_length); + if (!data) { + fclose(file); + CWS_LOG_ERROR("Unable to allocate file data"); + + return NULL; + } + + /* Read file data */ + size_t read_bytes = fread(data, 1, content_length, file); + fclose(file); + + if (read_bytes != content_length) { + free(data); + CWS_LOG_ERROR("Partial read from file"); + return 0; } - return -1; + return content_length; } -cws_http_s *cws_http_parse(string_s *request_str, int sockfd) { +static cws_server_ret http_send_resource(cws_http_s *request) { + /* Retrieve correct Content-Type */ + char content_type[1024]; + http_get_content_type(request->location_path->data, content_type); + + /* TODO: Check for keep-alive */ + + char *data; + char *response; + size_t content_length = file_data(request->location_path->data, &data); + + size_t response_len = http_response_builder(&response, "HTTP/1.1", HTTP_OK, content_type, data, content_length); + + ssize_t sent = send(request->sockfd, response, response_len, MSG_NOSIGNAL); + CWS_LOG_DEBUG("Sent %zu bytes", sent); + + free(response); + free(data); + + return CWS_SERVER_OK; +} + +static void http_send_simple_html(cws_http_s *request, cws_http_status_e status, char *title, char *description) { + char body[512]; + memset(body, 0, sizeof(body)); + + snprintf(body, sizeof(body), + "\n" + "\n" + " %s\n" + "\n" + "\n" + "

%s

\n" + "" + "", + title, description); + size_t body_len = strlen(body); + + char conn[32] = "keep-alive"; + bucket_s *connection = hm_get(request->headers, "Connection"); + if (connection) { + strncpy(conn, (char *)connection->value, sizeof(conn) - 1); + conn[sizeof(conn) - 1] = '\0'; + } + hm_free_bucket(connection); + + char *response = NULL; + size_t response_len = http_response_builder(&response, "HTTP/1.1", status, "text/html", body, body_len); + + ssize_t sent = send(request->sockfd, response, response_len, MSG_NOSIGNAL); + if (sent < 0) { + CWS_LOG_ERROR("Failed to send response"); + } + + free(response); +} + +cws_http_s *cws_http_parse(string_s *request_str) { if (!request_str) { return NULL; } - cws_http_s *request; - cws_http_init(&request); + cws_http_s *request = http_new(); if (request == NULL) { return NULL; } - request->sockfd = sockfd; char *request_str_cpy = strdup(string_cstr(request_str)); if (!request_str_cpy) { @@ -60,7 +190,6 @@ cws_http_s *cws_http_parse(string_s *request_str, int sockfd) { char *pch = NULL; /* Parse HTTP method */ - // TODO: fix strtok_r pch = strtok_r(request_str_cpy, " ", &saveptr); if (pch == NULL) { cws_http_free(request); @@ -70,13 +199,14 @@ cws_http_s *cws_http_parse(string_s *request_str, int sockfd) { } // CWS_LOG_DEBUG("method: %s", pch); - int ret = cws_http_parse_method(request, pch); + request->method = http_parse_method(pch); + // TODO: fix here if (ret < 0) { /* Not implemented */ cws_http_free(request); free(request_str_cpy); - cws_http_send_response(request, CWS_HTTP_NOT_IMPLEMENTED); + cws_http_send_response(request, HTTP_NOT_IMPLEMENTED); return NULL; } @@ -150,36 +280,15 @@ cws_http_s *cws_http_parse(string_s *request_str, int sockfd) { return request; } -static char *cws_http_status_string(cws_http_status_e status) { - switch (status) { - case CWS_HTTP_OK: { - return "200 OK"; - break; - } - case CWS_HTTP_NOT_FOUND: { - return "404 Not Found"; - break; - } - case CWS_HTTP_NOT_IMPLEMENTED: { - return "501 Not Implemented"; - break; - } - } - - return "?"; -} - -size_t cws_http_response_builder(char **response, char *http_version, cws_http_status_e status, char *content_type, char *connection, char *body, - size_t body_len_bytes) { - char *status_code = cws_http_status_string(status); +size_t http_response_builder(char **response, char *http_version, cws_http_status_e status, char *content_type, char *body, size_t body_len_bytes) { + char *status_code = http_status_string(status); size_t header_len = snprintf(NULL, 0, "%s %s\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" - "Connection: %s\r\n" "\r\n", - http_version, status_code, content_type, body_len_bytes, connection); + http_version, status_code, content_type, body_len_bytes); size_t total_len = header_len + body_len_bytes; @@ -188,8 +297,7 @@ size_t cws_http_response_builder(char **response, char *http_version, cws_http_s return 0; } - snprintf(*response, header_len + 1, "%s %s\r\nContent-Type: %s\r\nContent-Length: %ld\r\nConnection: %s\r\n\r\n", http_version, status_code, content_type, - body_len_bytes, connection); + snprintf(*response, header_len + 1, "%s %s\r\nContent-Type: %s\r\nContent-Length: %ld\r\n\r\n", http_version, status_code, content_type, body_len_bytes); /* Only append body if we have it */ if (body && body_len_bytes > 0) { @@ -201,157 +309,20 @@ size_t cws_http_response_builder(char **response, char *http_version, cws_http_s void cws_http_send_response(cws_http_s *request, cws_http_status_e status) { switch (status) { - case CWS_HTTP_OK: + case HTTP_OK: + http_send_resource(request); break; - case CWS_HTTP_NOT_FOUND: { - cws_http_send_simple_html(request, CWS_HTTP_NOT_FOUND, "404 Not Found", "Resource not found, 404."); + case HTTP_NOT_FOUND: { + http_send_simple_html(request, HTTP_NOT_FOUND, "404 Not Found", "Resource not found, 404."); break; } - case CWS_HTTP_NOT_IMPLEMENTED: { - cws_http_send_simple_html(request, CWS_HTTP_NOT_IMPLEMENTED, "501 Not Implemented", "Method not implemented, 501."); + case HTTP_NOT_IMPLEMENTED: { + http_send_simple_html(request, HTTP_NOT_IMPLEMENTED, "501 Not Implemented", "Method not implemented, 501."); break; } } } -int cws_http_send_resource(cws_http_s *request) { - /* keep-alive by default */ - int keepalive = 1; - - FILE *file = fopen(string_cstr(request->location_path), "rb"); - if (file == NULL) { - cws_http_send_response(request, CWS_HTTP_NOT_FOUND); - return 0; - } - - /* Retrieve correct Content-Type */ - char content_type[1024]; - int ret = cws_http_get_content_type(request, content_type); - if (ret < 0) { - fclose(file); - cws_http_send_response(request, CWS_HTTP_NOT_FOUND); - return 0; - } - - /* Retrieve file size */ - fseek(file, 0, SEEK_END); - const size_t content_length = ftell(file); - rewind(file); - - /* Retrieve file data */ - char *file_data = malloc(content_length); - if (file_data == NULL) { - fclose(file); - CWS_LOG_ERROR("Unable to allocate file data"); - return 0; - } - - /* Read file data */ - size_t read_bytes = fread(file_data, 1, content_length, file); - fclose(file); - - if (read_bytes != content_length) { - free(file_data); - CWS_LOG_ERROR("Partial read from file"); - - return 0; - } - - /* Check for keep-alive */ - char conn[32] = "keep-alive"; - bucket_s *connection = hm_get(request->headers, "Connection"); - if (connection && strcmp((char *)connection->value, "keep-alive") == 0) { - strcpy(conn, "keep-alive"); - keepalive = 0; - } - hm_free_bucket(connection); - - char *response = NULL; - size_t response_len = cws_http_response_builder(&response, "HTTP/1.1", CWS_HTTP_OK, content_type, conn, file_data, content_length); - - /* Send response in chunks to avoid blocking */ - size_t bytes_sent = 0; - ssize_t sent; - const size_t chunk_size = 8192; - - while (bytes_sent < response_len) { - size_t to_send = (response_len - bytes_sent > chunk_size) ? chunk_size : (response_len - bytes_sent); - sent = send(request->sockfd, response + bytes_sent, to_send, MSG_NOSIGNAL); - - if (sent <= 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - continue; - } - break; - } - bytes_sent += sent; - } - - free(response); - free(file_data); - - return keepalive; -} - -int cws_http_get_content_type(cws_http_s *request, char *content_type) { - char *ptr = strrchr(string_cstr(request->location_path), '.'); - if (ptr == NULL) { - return -1; - } - ptr += 1; - - char ct[32] = {0}; - - /* TODO: Improve content_type (used to test) */ - if (strcmp(ptr, "html") == 0 || strcmp(ptr, "css") == 0 || strcmp(ptr, "javascript") == 0) { - strncpy(ct, "text", sizeof ct); - } - - if (strcmp(ptr, "jpg") == 0 || strcmp(ptr, "png") == 0) { - strncpy(ct, "image", sizeof ct); - } - - snprintf(content_type, 1024, "%s/%s", ct, ptr); - - return 0; -} - -void cws_http_send_simple_html(cws_http_s *request, cws_http_status_e status, char *title, char *description) { - char body[512]; - memset(body, 0, sizeof(body)); - - snprintf(body, sizeof(body), - "\n" - "\n" - " %s\n" - "\n" - "\n" - "

%s

\n" - "" - "", - title, description); - size_t body_len = strlen(body); - - char conn[32] = "keep-alive"; - bucket_s *connection = hm_get(request->headers, "Connection"); - if (connection) { - strncpy(conn, (char *)connection->value, sizeof(conn) - 1); - conn[sizeof(conn) - 1] = '\0'; - } - hm_free_bucket(connection); - - char *response = NULL; - size_t response_len = cws_http_response_builder(&response, "HTTP/1.1", status, "text/html", conn, body, body_len); - - /* Send response with error handling */ - ssize_t sent = send(request->sockfd, response, response_len, MSG_NOSIGNAL); - if (sent < 0) { - CWS_LOG_ERROR("Failed to send response: %s", strerror(errno)); - } - - free(response); -} - void cws_http_free(cws_http_s *request) { hm_free(request->headers); string_free(request->http_version); diff --git a/src/server/worker.c b/src/server/worker.c index e797804..32cc638 100644 --- a/src/server/worker.c +++ b/src/server/worker.c @@ -34,14 +34,10 @@ static int cws_worker_setup_epoll(cws_worker_s *worker) { return 0; } -static size_t cws_read_data(int sockfd, string_s **str) { +static size_t cws_read_data(int sockfd, string_s *str) { size_t total_bytes = 0; ssize_t bytes_read; - if (*str == NULL) { - *str = string_new("", 4096); - } - char tmp[4096]; memset(tmp, 0, sizeof(tmp)); @@ -63,7 +59,7 @@ static size_t cws_read_data(int sockfd, string_s **str) { } total_bytes += bytes_read; - string_append(*str, tmp); + string_append(str, tmp); } return total_bytes; @@ -191,8 +187,8 @@ cws_server_ret cws_epoll_del(int epfd, int sockfd) { } cws_server_ret cws_server_handle_client_data(int epfd, int client_fd) { - string_s *data; - size_t total_bytes = cws_read_data(client_fd, &data); + string_s *data = string_new("", 4096); + size_t total_bytes = cws_read_data(client_fd, data); if (total_bytes <= 0) { if (data) { string_free(data); @@ -202,7 +198,8 @@ cws_server_ret cws_server_handle_client_data(int epfd, int client_fd) { return CWS_SERVER_CLIENT_DISCONNECTED_ERROR; } - cws_http_s *request = cws_http_parse(data, client_fd); + /* TODO: set sockfd to request */ + cws_http_s *request = cws_http_parse(data); string_free(data); if (request == NULL) { @@ -211,11 +208,11 @@ cws_server_ret cws_server_handle_client_data(int epfd, int client_fd) { return CWS_SERVER_HTTP_PARSE_ERROR; } - int keepalive = cws_http_send_resource(request); + // cws_http_send_resource(request); cws_http_free(request); - if (!keepalive) { - cws_server_close_client(epfd, client_fd); - } + // if (!keepalive) { + // cws_server_close_client(epfd, client_fd); + //} return CWS_SERVER_OK; }