Compare commits

...

5 Commits

12 changed files with 151 additions and 89 deletions
+1
View File
@@ -22,6 +22,7 @@ typedef struct cws_config {
int workers;
cws_vhost_s *virtual_hosts;
unsigned virtual_hosts_count;
cws_vhost_s *default_vh;
} cws_config_s;
cws_config_s *cws_config_init(void);
+2 -2
View File
@@ -4,8 +4,8 @@
#include <myclib/mystring.h>
#include <sys/types.h>
ssize_t cws_socket_read(int sockfd, string_s *str);
int cws_socket_read(int sockfd, string_s *str);
ssize_t cws_socket_send(int sockfd, const char *buffer, size_t len, int flags);
int cws_socket_send(int sockfd, const char *buffer, size_t len, int flags);
#endif
+3 -7
View File
@@ -6,10 +6,6 @@
#include <myclib/mystring.h>
#include <stddef.h>
#define CWS_HTTP_CONTENT_TYPE 64
#define CWS_HTTP_HEADER_MAX 512
#define CWS_HTTP_HEADER_CONTENT_MAX 1024
typedef struct cws_request {
cws_http_method_e method;
string_s *host;
@@ -20,10 +16,10 @@ typedef struct cws_request {
string_s *body;
} cws_request_s;
cws_request_s *cws_http_parse(string_s *request_str);
cws_request_s *cws_request_parse(string_s *request_str);
char *cws_http_get_host(cws_request_s *request);
char *cws_request_get_header(cws_request_s *request, const char *header);
void cws_http_free(cws_request_s *request);
void cws_request_free(cws_request_s *request);
#endif
+6 -3
View File
@@ -9,9 +9,12 @@ add_global_arguments('-Wno-pedantic', language: 'c')
cc = meson.get_compiler('c')
subdir('src')
incdir = include_directories('include')
srcdir = include_directories('src')
include_dirs = [incdir, srcdir]
subdir('src')
libtomlc17 = dependency('libtomlc17', required: true)
libmath = cc.find_library('m', required: true)
@@ -23,7 +26,7 @@ add_global_arguments('-DUSE_COLORS', language: 'c')
add_global_arguments('-DEVELOPER', language: 'c')
add_global_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c')
exe = executable('cws', server, include_directories: incdir, dependencies: deps)
exe = executable('cws', server, include_directories: include_dirs, dependencies: deps)
# Test
test_src = files('test/server.c')
+28 -15
View File
@@ -6,23 +6,44 @@
#include "utils/debug.h"
static bool is_default(const char *domain) {
if (!strcmp(domain, "default")) {
return true;
}
return false;
}
static bool parse_vhosts(cws_config_s *config, toml_result_t result) {
toml_datum_t vhosts = toml_seek(result.toptab, "virtual_hosts");
/* Retrieve virtual hosts counter */
config->virtual_hosts_count = vhosts.u.arr.size;
/* Allocate virtual hosts array */
config->virtual_hosts = malloc(sizeof *config->virtual_hosts * config->virtual_hosts_count);
if (!config->virtual_hosts) {
return false;
}
/* Iterate for each virtual host */
for (int i = 0; i < vhosts.u.arr.size; ++i) {
cws_vhost_s *vh = &config->virtual_hosts[i];
toml_datum_t elem = vhosts.u.arr.elem[i];
/* Retrieve vh's domain */
toml_datum_t domain = toml_seek(elem, "domain");
vh->domain = strdup(domain.u.str.ptr);
if (!vh->domain) {
return false;
}
/* Check if vh->domain is the default domain */
if (is_default(vh->domain)) {
config->default_vh = vh;
}
/* Retrieve vh's root folder */
toml_datum_t root = toml_seek(elem, "root");
vh->root = strdup(root.u.str.ptr);
if (!vh->root) {
@@ -32,13 +53,17 @@ static bool parse_vhosts(cws_config_s *config, toml_result_t result) {
/* Pages */
toml_datum_t pages = toml_seek(elem, "pages");
vh->error_pages_count = pages.u.arr.size;
/* Allocate error pages array */
vh->error_pages = malloc(sizeof *vh->error_pages * vh->error_pages_count);
if (!vh->error_pages) {
return false;
}
/* Iterate for each page */
for (int j = 0; j < pages.u.arr.size; ++j) {
toml_datum_t page = pages.u.arr.elem[i];
toml_datum_t status = toml_seek(page, "status");
vh->error_pages[j].status = strdup(status.u.str.ptr);
if (!vh->error_pages[i].status) {
@@ -56,17 +81,6 @@ static bool parse_vhosts(cws_config_s *config, toml_result_t result) {
return true;
}
static bool find_default(cws_config_s *config) {
for (unsigned i = 0; i < config->virtual_hosts_count; ++i) {
cws_vhost_s *vh = config->virtual_hosts;
if (!strcmp(vh[i].domain, "default")) {
return true;
}
}
return false;
}
static bool parse_toml(cws_config_s *config) {
const char *path = "config.toml";
@@ -98,15 +112,14 @@ static bool parse_toml(cws_config_s *config) {
toml_datum_t workers = toml_seek(result.toptab, "server.workers");
config->workers = workers.u.int64;
parse_vhosts(config, result);
bool ret = parse_vhosts(config, result);
toml_free(result);
return find_default(config);
return ret;
}
cws_config_s *cws_config_init(void) {
cws_config_s *config = malloc(sizeof *config);
cws_config_s *config = calloc(1, sizeof *config);
if (!config) {
return NULL;
}
+39 -21
View File
@@ -3,46 +3,64 @@
#include <errno.h>
#include <sys/socket.h>
ssize_t cws_socket_read(int sockfd, string_s *str) {
int cws_socket_read(int sockfd, string_s *str) {
char tmp[4096] = {0};
for (;;) {
ssize_t n = recv(sockfd, tmp, sizeof tmp, 0);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
return -1;
}
if (n == 0) {
return -1;
}
/* We have some data */
if (n > 0) {
tmp[n] = '\0';
string_append(str, tmp);
return n;
}
/* Client closed */
if (n == 0) {
return 0;
}
if (errno == EINTR) {
continue;
}
/* No data now */
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return -2;
}
/* Something happened */
return -1;
}
}
ssize_t cws_socket_send(int sockfd, const char *buffer, size_t len, int flags) {
int cws_socket_send(int sockfd, const char *buffer, size_t len, int flags) {
size_t total_sent = 0;
ssize_t n;
while (total_sent < len) {
n = send(sockfd, buffer + total_sent, len - total_sent, flags);
ssize_t n = send(sockfd, buffer + total_sent, len - total_sent, flags);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
return -1;
if (n > 0) {
total_sent += (size_t)n;
continue;
}
if (n == 0) {
break;
}
total_sent += n;
if (errno == EINTR) {
continue;
}
return total_sent;
/* Partial write */
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
return -1;
}
return (ssize_t)total_sent;
}
+25 -13
View File
@@ -10,6 +10,7 @@
#include "http/handler.h"
#include "http/request.h"
#include "http/response.h"
#include "utils/debug.h"
#include "utils/error.h"
/* Create epoll instance for a worker */
@@ -35,32 +36,40 @@ static cws_vhost_s *get_vhost(cws_config_s *config, char *host) {
}
}
/* Return first domain */
/* TODO: return default domain */
return &config->virtual_hosts[0];
/* Return default domain */
return config->default_vh;
}
static cws_return worker_handle_client_data(int epfd, int client_fd, cws_config_s *config) {
string_s *data = string_new("", 4096);
/* Read data from socket */
ssize_t total_bytes = cws_socket_read(client_fd, data);
int total_bytes = cws_socket_read(client_fd, data);
if (total_bytes == 0) {
/* Partial request; wait for more data */
/* Partial request, wait for more data */
if (total_bytes == -2) {
string_free(data);
return CWS_OK;
}
if (total_bytes < 0) {
/* Connection closed */
if (total_bytes == 0) {
cws_log_info("Client (fd: %d) disconnected", client_fd);
worker_close_client(epfd, client_fd);
string_free(data);
return CWS_CLIENT_DISCONNECTED_ERROR;
}
/* Client error */
if (total_bytes == -1) {
worker_close_client(epfd, client_fd);
string_free(data);
return CWS_CLIENT_DISCONNECTED_ERROR;
}
/* Parse HTTP request */
cws_request_s *request = cws_http_parse(data);
cws_request_s *request = cws_request_parse(data);
string_free(data);
if (request == NULL) {
worker_close_client(epfd, client_fd);
@@ -68,7 +77,7 @@ static cws_return worker_handle_client_data(int epfd, int client_fd, cws_config_
}
/* Configure handler */
char *host = cws_http_get_host(request);
char *host = cws_request_get_header(request, "host");
cws_vhost_s *vh = get_vhost(config, host);
cws_handler_config_s conf = {
.domain = vh->domain,
@@ -84,11 +93,14 @@ static cws_return worker_handle_client_data(int epfd, int client_fd, cws_config_
cws_response_free(response);
}
/* Cleanup */
cws_http_free(request);
/* TODO: check Connection: keep-alive */
/* Close connection if requested */
/* keep-alive by default (http/1.1) */
if (!strcmp(cws_request_get_header(request, "Connection"), "close")) {
worker_close_client(epfd, client_fd);
}
/* Cleanup */
cws_request_free(request);
return CWS_OK;
}
+6
View File
@@ -51,12 +51,18 @@ cws_response_s *cws_handler_static_file(cws_request_s *request, cws_handler_conf
return cws_handler_not_found();
}
/* Allocate a response object */
/* @TODO: do not use http 200 ok as default */
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");
}
/* Retrieve Connection header and set it in the response */
const char *conn = cws_request_get_header(request, "Connection");
cws_response_set_header(response, "Connection", conn);
cws_response_set_body_file(response, path);
cws_log_debug("Serving file: %s (%zu bytes)", path, response->content_length);
string_free(filepath);
+4 -3
View File
@@ -3,9 +3,10 @@
#include <stdio.h>
#include <string.h>
#include "http/request.h"
#include "utils/error.h"
#include "internal/common.h"
static mimetype mimetypes[] = {{"html", "text/html"}, {"css", "text/css"}, {"js", "application/javascript"},
{"jpg", "image/jpeg"}, {"png", "image/png"}, {"ico", "image/x-icon"}};
@@ -18,13 +19,13 @@ int cws_mime_get_ct(const char *location_path, char *content_type) {
for (size_t i = 0; i < ARR_SIZE(mimetypes); ++i) {
if (!strcmp(ptr, mimetypes[i].ext)) {
snprintf(content_type, CWS_HTTP_CONTENT_TYPE - 1, "%s", mimetypes[i].type);
snprintf(content_type, CONTENT_TYPE_MAX - 1, "%s", mimetypes[i].type);
return CWS_OK;
}
}
snprintf(content_type, CWS_HTTP_CONTENT_TYPE - 1, "%s", "Content-Type not supported");
snprintf(content_type, CONTENT_TYPE_MAX - 1, "%s", "Content-Type not supported");
return CWS_OK;
}
+21 -15
View File
@@ -9,6 +9,8 @@
#include "utils/debug.h"
#include "utils/hash.h"
#include "internal/common.h"
static cws_request_s *http_request_new(void) {
cws_request_s *request = malloc(sizeof(*request));
if (!request) {
@@ -91,7 +93,7 @@ static bool parse_version(cws_request_s *req, char **cursor) {
static bool parse_headers(cws_request_s *req, char **cursor) {
req->headers = hm_new(my_str_hash_fn, my_str_equal_fn, my_str_free_fn, my_str_free_fn,
sizeof(char) * CWS_HTTP_HEADER_MAX, sizeof(char) * CWS_HTTP_HEADER_CONTENT_MAX);
sizeof(char) * HEADER_KEY_MAX, sizeof(char) * HEADER_VALUE_MAX);
char *s = *cursor + strspn(*cursor, "\r\n");
while (*s != '\0' && *s != '\r') {
@@ -113,8 +115,8 @@ static bool parse_headers(cws_request_s *req, char **cursor) {
char *header_value = colon + 1;
header_value += strspn(header_value, " \t");
char hk[CWS_HTTP_HEADER_MAX];
char hv[CWS_HTTP_HEADER_CONTENT_MAX];
char hk[HEADER_KEY_MAX];
char hv[HEADER_VALUE_MAX];
strncpy(hk, header_key, sizeof(hk) - 1);
hk[sizeof(hk) - 1] = '\0';
@@ -133,7 +135,7 @@ static bool parse_headers(cws_request_s *req, char **cursor) {
return true;
}
cws_request_s *cws_http_parse(string_s *request_str) {
cws_request_s *cws_request_parse(string_s *request_str) {
if (!request_str || !string_cstr(request_str)) {
return NULL;
}
@@ -145,7 +147,7 @@ cws_request_s *cws_http_parse(string_s *request_str) {
char *str = string_copy(request_str);
if (!str) {
cws_http_free(request);
cws_request_free(request);
return NULL;
}
char *orig = str;
@@ -153,28 +155,28 @@ cws_request_s *cws_http_parse(string_s *request_str) {
/* Parse HTTP method */
if (!parse_method(request, &str)) {
free(orig);
cws_http_free(request);
cws_request_free(request);
return NULL;
}
/* Parse location (URL path) */
if (!parse_location(request, &str)) {
free(orig);
cws_http_free(request);
cws_request_free(request);
return NULL;
}
/* Parse HTTP version */
if (!parse_version(request, &str)) {
free(orig);
cws_http_free(request);
cws_request_free(request);
return NULL;
}
/* Parse headers */
if (!parse_headers(request, &str)) {
free(orig);
cws_http_free(request);
cws_request_free(request);
return NULL;
}
@@ -183,16 +185,20 @@ cws_request_s *cws_http_parse(string_s *request_str) {
return request;
}
char *cws_http_get_host(cws_request_s *request) {
bucket_s *host = hm_get(request->headers, "Host");
if (!host) {
return "default";
char *cws_request_get_header(cws_request_s *request, const char *header) {
if (!request || !header || !request->headers) {
return "";
}
return (char *)host->value;
bucket_s *bucket = hm_get(request->headers, (void *)header);
if (!bucket) {
return "";
}
return (char *)bucket->value;
}
void cws_http_free(cws_request_s *request) {
void cws_request_free(cws_request_s *request) {
if (!request) {
return;
}
+5 -9
View File
@@ -9,12 +9,11 @@
#include <string.h>
#include <sys/stat.h>
#define CHUNK_SIZE 8192
#define HEADERS_BUFFER_SIZE 2048
#include "internal/common.h"
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);
return hm_new(my_str_hash_fn, my_str_equal_fn, my_str_free_fn, my_str_free_fn, sizeof(char) * HEADER_KEY_MAX,
sizeof(char) * HEADER_VALUE_MAX);
}
cws_response_s *cws_response_new(cws_http_status_e status) {
@@ -30,9 +29,6 @@ cws_response_s *cws_response_new(cws_http_status_e status) {
resp->body_file = NULL;
resp->content_length = 0;
/* TODO: get the value from connection */
cws_response_set_header(resp, "Connection", "close");
return resp;
}
@@ -61,7 +57,7 @@ void cws_response_set_header(cws_response_s *response, const char *key, const ch
return;
}
char k[256], v[512];
char k[HEADER_KEY_MAX], v[HEADER_VALUE_MAX];
strncpy(k, key, sizeof(k) - 1);
k[sizeof(k) - 1] = '\0';
strncpy(v, value, sizeof(v) - 1);
@@ -101,7 +97,7 @@ void cws_response_set_body_file(cws_response_s *response, const char *filepath)
return;
}
char content_type[64];
char content_type[CONTENT_TYPE_MAX];
cws_mime_get_ct(filepath, content_type);
cws_response_set_header(response, "Content-Type", content_type);
+10
View File
@@ -0,0 +1,10 @@
#ifndef CWS_INTERNALS_COMMON_H
#define CWS_INTERNALS_COMMON_H
#define CONTENT_TYPE_MAX 64
#define HEADER_KEY_MAX 256
#define HEADER_VALUE_MAX 1024
#define CHUNK_SIZE 8192
#define HEADERS_BUFFER_SIZE 2048
#endif