diff --git a/include/http/mime.h b/include/http/mime.h index 799686c..fbfe008 100644 --- a/include/http/mime.h +++ b/include/http/mime.h @@ -8,6 +8,6 @@ typedef struct mimetype { const char *type; } mimetype; -int http_get_content_type(char *location_path, char *content_type); +int http_get_content_type(const char *location_path, char *content_type); #endif diff --git a/include/http/response.h b/include/http/response.h index beb6775..b2db6df 100644 --- a/include/http/response.h +++ b/include/http/response.h @@ -2,12 +2,6 @@ #define CWS_RESPONSE_H #include "http/request.h" -#include - -size_t http_simple_html(char **response, cws_http_status_e status, char *title, char *description); - -size_t http_response_builder(char **response, cws_http_status_e status, char *content_type, - char *body, size_t body_len_bytes); void cws_http_send_response(cws_request_s *request, cws_http_status_e status); diff --git a/src/http/mime.c b/src/http/mime.c index 6daa90e..bc52201 100644 --- a/src/http/mime.c +++ b/src/http/mime.c @@ -9,7 +9,7 @@ static mimetype mimetypes[] = { {"html", "text/html"}, {"css", "text/css"}, {"js", "application/javascript"}, {"jpg", "image/jpeg"}, {"png", "image/png"}, {"ico", "image/x-icon"}}; -int http_get_content_type(char *location_path, char *content_type) { +int http_get_content_type(const char *location_path, char *content_type) { /* Find last occurrence of a string */ char *ptr = strrchr(location_path, '.'); if (ptr == NULL) { diff --git a/src/http/response.c b/src/http/response.c index 6dd566f..959e2b3 100644 --- a/src/http/response.c +++ b/src/http/response.c @@ -4,32 +4,16 @@ #include "http/request.h" #include "utils/debug.h" #include -#include #include #include #include +#include -size_t http_simple_html(char **response, cws_http_status_e status, char *title, char *description) { - char body[512] = {0}; +/* 8 KB reading buffer length */ +#define CHUNK_SIZE 8192 +#define HEADERS_LEN 512 - snprintf(body, sizeof(body), - "\n" - "\n" - " %s\n" - "\n" - "\n" - "

%s

\n" - "" - "", - title, description); - size_t body_len = strlen(body); - - size_t response_len = http_response_builder(response, status, "text/html", body, body_len); - - return response_len; -} - -static size_t http_header_len(char *status_code, char *content_type, size_t body_len) { +static size_t http_header_len(const char *status_code, char *content_type, size_t body_len) { size_t len = snprintf(NULL, 0, "HTTP/1.1 %s\r\n" "Content-Type: %s\r\n" @@ -41,94 +25,97 @@ static size_t http_header_len(char *status_code, char *content_type, size_t body return len; } -static char *http_status_string(cws_http_status_e status) { +static const 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 0; +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); } - - /* Retrieve file size */ - fseek(file, 0, SEEK_END); - const size_t content_length = ftell(file); - errno = 0; - rewind(file); - if (errno != 0) { - fclose(file); - CWS_LOG_ERROR("Unable to rewind"); - - return 0; - } - - /* Retrieve file data */ - *data = malloc(content_length); - if (!*data) { - fclose(file); - CWS_LOG_ERROR("Unable to allocate file data"); - - return 0; - } - - /* Read file data */ - size_t read_bytes = fread(*data, 1, content_length, file); - fclose(file); - - if (read_bytes != content_length) { - CWS_LOG_ERROR("Partial read from file"); - - return 0; - } - - return content_length; } -static void http_send_resource(cws_request_s *request) { - char *data = NULL; - size_t content_length = file_data(request->location_path->data, &data); - if (content_length == 0) { - /* File not found */ +static void http_send_file(cws_request_s *request) { + const char *path = request->location_path->data; + FILE *fp = fopen(path, "rb"); + if (!fp) { + CWS_LOG_ERROR("Cannot open file: %s", path); cws_http_send_response(request, HTTP_NOT_FOUND); return; } - char content_type[CWS_HTTP_CONTENT_TYPE]; - http_get_content_type(request->location_path->data, content_type); + /* Get file size */ + struct stat st; + if (stat(path, &st) != 0) { + fclose(fp); + cws_http_send_response(request, HTTP_NOT_FOUND); + return; + } + size_t file_size = st.st_size; - /* TODO: Check for keep-alive */ + char content_tye[CWS_HTTP_CONTENT_TYPE]; + http_get_content_type(path, content_tye); - char *response = NULL; + /* Send headers */ + http_send_headers(request->sockfd, http_status_string(HTTP_OK), content_tye, file_size); - size_t response_len = - http_response_builder(&response, HTTP_OK, content_type, data, content_length); + /* Send the file in chunks */ + char buffer[CHUNK_SIZE]; + size_t bytes_read = 0; - ssize_t sent = cws_send_data(request->sockfd, response, response_len, 0); - CWS_LOG_DEBUG("Sent %zd bytes", sent); + while ((bytes_read = fread(buffer, 1, sizeof buffer, fp)) > 0) { + /* TODO: check return */ + cws_send_data(request->sockfd, buffer, bytes_read, 0); + } - free(response); - free(data); + fclose(fp); + CWS_LOG_DEBUG("Served file: %s (%zu bytes)", path, file_size); } -size_t http_response_builder(char **response, cws_http_status_e status, char *content_type, - char *body, size_t body_len_bytes) { - char *status_code = http_status_string(status); +void http_send_simple_html(cws_request_s *request, cws_http_status_e status, const char *title, const char *desc) { + const char *fmt = "\n" + "%s\n" + "

%s

\n" + ""; + + 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); + } +} + +size_t http_response_builder(char **response, cws_http_status_e status, char *content_type, char *body, + size_t body_len_bytes) { + const char *status_code = http_status_string(status); size_t header_len = http_header_len(status_code, content_type, body_len_bytes); size_t total_len = header_len + body_len_bytes; @@ -139,8 +126,8 @@ size_t http_response_builder(char **response, cws_http_status_e status, char *co } snprintf(*response, header_len + 1, - "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n", - status_code, content_type, body_len_bytes); + "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n", status_code, + content_type, body_len_bytes); /* Only append body if we have it */ if (body && body_len_bytes > 0) { @@ -153,27 +140,21 @@ size_t http_response_builder(char **response, cws_http_status_e status, char *co } void cws_http_send_response(cws_request_s *request, cws_http_status_e status) { - char *response = NULL; - switch (status) { case HTTP_OK: - http_send_resource(request); + http_send_file(request); break; - case HTTP_NOT_FOUND: { - size_t len = http_simple_html(&response, HTTP_NOT_FOUND, "404 Not Found", - "Resource not found, 404."); - cws_send_data(request->sockfd, response, len, 0); + 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: { - size_t len = http_simple_html(&response, HTTP_NOT_IMPLEMENTED, "501 Not Implemented", - "Method not implemented, 501."); - cws_send_data(request->sockfd, response, len, 0); + 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; - } } - - free(response); }