refactor(req/res): new request/response structure
This commit is contained in:
20
include/http/handler.h
Normal file
20
include/http/handler.h
Normal 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
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
27
include/http/types.h
Normal 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
|
||||||
@@ -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
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 "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);
|
||||||
|
|||||||
@@ -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 "?";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void http_send_headers(int sockfd, const char *status_str, const char *content_type, size_t content_length) {
|
cws_response_s *cws_response_new(cws_http_status_e status) {
|
||||||
char headers[HEADERS_LEN];
|
cws_response_s *resp = malloc(sizeof(*resp));
|
||||||
int len = snprintf(headers, sizeof headers,
|
if (!resp) {
|
||||||
"HTTP/1.1 %s\r\n"
|
return NULL;
|
||||||
"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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
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_LOG_DEBUG("Served file: %s (%zu bytes)", path, file_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_send_simple_html(cws_request_s *request, cws_http_status_e status, const char *title, const char *desc) {
|
cws_response_s *cws_response_html(cws_http_status_e status, const char *title, const char *body) {
|
||||||
const char *fmt = "<html>\n"
|
cws_response_s *resp = cws_response_new(status);
|
||||||
"<head><title>%s</title></head>\n"
|
if (!resp) {
|
||||||
"<body><p>%s</p></body>\n"
|
return NULL;
|
||||||
"</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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
cws_response_s *cws_response_error(cws_http_status_e status, const char *message) {
|
||||||
switch (status) {
|
const char *status_str = cws_http_status_string(status);
|
||||||
case HTTP_OK:
|
return cws_response_html(status, status_str, message);
|
||||||
http_send_file(request);
|
}
|
||||||
break;
|
|
||||||
|
int cws_response_send(int sockfd, cws_response_s *response) {
|
||||||
case HTTP_NOT_FOUND:
|
if (!response) {
|
||||||
http_send_simple_html(request, HTTP_NOT_FOUND, "404 Not Found", "The requested resource was not found.");
|
return -1;
|
||||||
break;
|
}
|
||||||
|
|
||||||
case HTTP_NOT_IMPLEMENTED:
|
char headers[HEADERS_BUFFER_SIZE];
|
||||||
http_send_simple_html(request, HTTP_NOT_IMPLEMENTED, "501 Not Implemented", "Method not implemented.");
|
int offset = snprintf(headers, sizeof(headers), "HTTP/1.1 %s\r\n", cws_http_status_string(response->status));
|
||||||
break;
|
|
||||||
|
char content_length_str[32];
|
||||||
default:
|
snprintf(content_length_str, sizeof(content_length_str), "%zu", response->content_length);
|
||||||
http_send_simple_html(request, status, "Error", "An unexpected error occurred.");
|
cws_response_set_header(response, "Content-Length", content_length_str);
|
||||||
break;
|
|
||||||
}
|
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