Compare commits
5 Commits
b2b1de6495
...
291596e30a
| Author | SHA1 | Date | |
|---|---|---|---|
| 291596e30a | |||
| e1d928c67e | |||
| 6ef810f511 | |||
| 3cb8fda44d | |||
| 0de6dfda58 |
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
ssize_t cws_socket_send(int sockfd, const char *buffer, size_t len, int flags) {
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 "";
|
||||
}
|
||||
|
||||
void cws_http_free(cws_request_s *request) {
|
||||
return (char *)bucket->value;
|
||||
}
|
||||
|
||||
void cws_request_free(cws_request_s *request) {
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
|
||||
+5
-9
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user