Compare commits

...

4 Commits

9 changed files with 114 additions and 45 deletions
-6
View File
@@ -22,12 +22,6 @@ meson compile -C build
2. Run `./build/cws` 2. Run `./build/cws`
3. Open `http://localhost:3030` in your browser 3. Open `http://localhost:3030` in your browser
## Roadmap
- [ ] Virtual hosts support
- [ ] Minimal templating engine
- [ ] IPv6 compatibility
## Performance ## Performance
Tested with [goku](https://github.com/jcaromiq/goku) (`-c 400 -d 30`): Tested with [goku](https://github.com/jcaromiq/goku) (`-c 400 -d 30`):
+2
View File
@@ -29,4 +29,6 @@ cws_config_s *cws_config_init(void);
void cws_config_free(cws_config_s *config); void cws_config_free(cws_config_s *config);
cws_vhost_s *config_get_vhost(cws_config_s *config, char *host);
#endif #endif
+17 -6
View File
@@ -5,10 +5,6 @@ project(
default_options: ['c_std=gnu23', 'warning_level=3'], default_options: ['c_std=gnu23', 'warning_level=3'],
) )
add_global_arguments('-Wno-pedantic', language: 'c')
cc = meson.get_compiler('c')
incdir = include_directories('include') incdir = include_directories('include')
srcdir = include_directories('src') srcdir = include_directories('src')
@@ -17,7 +13,7 @@ include_dirs = [incdir, srcdir]
subdir('src') subdir('src')
libtomlc17 = dependency('libtomlc17', required: true) libtomlc17 = dependency('libtomlc17', required: true)
libmyclib = cc.find_library('myclib', required: true) libmyclib = dependency('myclib', required: true)
deps = [libtomlc17, libmyclib] deps = [libtomlc17, libmyclib]
@@ -27,10 +23,25 @@ add_global_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c')
exe = executable('cws', server, include_directories: include_dirs, dependencies: deps) exe = executable('cws', server, include_directories: include_dirs, dependencies: deps)
# Commands
clangformat = find_program('clang-format', required: false)
if clangformat.found()
run_target(
'format',
command: [
'ninja',
'-C', join_paths(meson.current_build_dir()),
'clang-format',
],
)
endif
# Test # Test
test_src = files('test/server.c') test_src = files('test/server.c')
test_curl_dep = dependency('libcurl', required: false) test_curl_dep = dependency('libcurl', required: false)
test_deps = [test_curl_dep] test_deps = [test_curl_dep]
test_exec = executable('test_http', test_src, dependencies: test_deps) test_exec = executable('test_http', test_src, dependencies: test_deps)
test('index get', test_exec, args: [exe.full_path()]) test('index get', test_exec, args: [exe.full_path()])
+12
View File
@@ -173,3 +173,15 @@ void cws_config_free(cws_config_s *config) {
free(config); free(config);
} }
} }
cws_vhost_s *config_get_vhost(cws_config_s *config, char *host) {
for (unsigned i = 0; i < config->virtual_hosts_count; ++i) {
cws_vhost_s *vh = config->virtual_hosts;
if (!strcmp(vh[i].domain, host)) {
return &vh[i];
}
}
/* Return default domain */
return config->default_vh;
}
+35 -12
View File
@@ -1,6 +1,7 @@
#include "core/server.h" #include "core/server.h"
#include <errno.h> #include <errno.h>
#include <netdb.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/epoll.h> #include <sys/epoll.h>
@@ -45,56 +46,74 @@ cws_return cws_server_setup(cws_server_s *server, cws_config_s *config) {
return CWS_CONFIG_ERROR; return CWS_CONFIG_ERROR;
} }
memset(server, 0, sizeof *server); cws_return returncode = CWS_OK;
struct addrinfo hints; struct addrinfo hints = {0};
struct addrinfo *res; struct addrinfo *res = {0};
cws_server_setup_hints(&hints, config->host); cws_server_setup_hints(&hints, config->host);
int status = getaddrinfo(config->host, config->port, &hints, &res); int status = getaddrinfo(config->host, config->port, &hints, &res);
if (status != 0) { if (status != 0) {
cws_log_error("getaddrinfo() error: %s", gai_strerror(status)); cws_log_error("getaddrinfo() error: %s", gai_strerror(status));
return CWS_GETADDRINFO_ERROR; returncode = CWS_GETADDRINFO_ERROR;
goto cleanup;
} }
server->sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); server->sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (server->sockfd < 0) { if (server->sockfd < 0) {
cws_log_error("socket(): %s", strerror(errno)); cws_log_error("socket(): %s", strerror(errno));
return CWS_SOCKET_ERROR; returncode = CWS_SOCKET_ERROR;
goto cleanup;
} }
const int opt = 1; const int opt = 1;
status = setsockopt(server->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); status = setsockopt(server->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
if (status != 0) { if (status != 0) {
cws_log_error("setsockopt(): %s", strerror(errno)); cws_log_error("setsockopt(): %s", strerror(errno));
return CWS_SETSOCKOPT_ERROR; returncode = CWS_SETSOCKOPT_ERROR;
goto cleanup;
} }
status = bind(server->sockfd, res->ai_addr, res->ai_addrlen); status = bind(server->sockfd, res->ai_addr, res->ai_addrlen);
if (status != 0) { if (status != 0) {
cws_log_error("bind(): %s", strerror(errno)); cws_log_error("bind(): %s", strerror(errno));
return CWS_BIND_ERROR; returncode = CWS_BIND_ERROR;
goto cleanup;
} }
status = listen(server->sockfd, CWS_SERVER_BACKLOG); status = listen(server->sockfd, CWS_SERVER_BACKLOG);
if (status != 0) { if (status != 0) {
cws_log_error("listen(): %s", strerror(errno)); cws_log_error("listen(): %s", strerror(errno));
return CWS_LISTEN_ERROR; returncode = CWS_LISTEN_ERROR;
goto cleanup;
} }
freeaddrinfo(res); freeaddrinfo(res);
cws_return ret = cws_server_setup_epoll(server->sockfd, &server->epfd); cws_return ret = cws_server_setup_epoll(server->sockfd, &server->epfd);
if (ret != CWS_OK) { if (ret != CWS_OK) {
return ret; returncode = ret;
goto cleanup;
} }
server->workers = cws_worker_new(config->workers, config); server->workers = cws_worker_new(config->workers, config);
if (server->workers == NULL) { if (server->workers == NULL) {
return CWS_WORKER_ERROR; returncode = CWS_WORKER_ERROR;
goto cleanup;
} }
return CWS_OK; return CWS_OK;
cleanup:
if (res) {
freeaddrinfo(res);
}
if (server->sockfd >= 0) {
close(server->sockfd);
}
return returncode;
} }
cws_return cws_server_start(cws_server_s *server) { cws_return cws_server_start(cws_server_s *server) {
@@ -115,6 +134,10 @@ cws_return cws_server_start(cws_server_s *server) {
} }
for (int i = 0; i < nfds; ++i) { for (int i = 0; i < nfds; ++i) {
if (events[i].data.fd != server->sockfd) {
continue;
}
int client_fd = cws_server_handle_new_client(server->sockfd); int client_fd = cws_server_handle_new_client(server->sockfd);
if (client_fd < 0) { if (client_fd < 0) {
continue; continue;
@@ -163,11 +186,11 @@ void cws_server_shutdown(cws_server_s *server) {
return; return;
} }
if (server->sockfd > 0) { if (server->sockfd >= 0) {
close(server->sockfd); close(server->sockfd);
} }
if (server->epfd > 0) { if (server->epfd >= 0) {
close(server->epfd); close(server->epfd);
} }
+3 -13
View File
@@ -28,18 +28,6 @@ static void worker_close_client(int epfd, int client_fd) {
close(client_fd); close(client_fd);
} }
static cws_vhost_s *get_vhost(cws_config_s *config, char *host) {
for (unsigned i = 0; i < config->virtual_hosts_count; ++i) {
cws_vhost_s *vh = config->virtual_hosts;
if (!strcmp(vh[i].domain, host)) {
return &vh[i];
}
}
/* Return default domain */
return config->default_vh;
}
static cws_return worker_handle_client_data(int epfd, int client_fd, cws_config_s *config) { static cws_return worker_handle_client_data(int epfd, int client_fd, cws_config_s *config) {
string_s *data = string_new("", 4096); string_s *data = string_new("", 4096);
@@ -78,7 +66,7 @@ static cws_return worker_handle_client_data(int epfd, int client_fd, cws_config_
/* Configure handler */ /* Configure handler */
char *host = cws_request_get_header(request, "host"); char *host = cws_request_get_header(request, "host");
cws_vhost_s *vh = get_vhost(config, host); cws_vhost_s *vh = config_get_vhost(config, host);
cws_handler_config_s conf = { cws_handler_config_s conf = {
.domain = vh->domain, .domain = vh->domain,
.root = vh->root, .root = vh->root,
@@ -151,6 +139,7 @@ cws_worker_s **cws_worker_new(size_t workers_num, cws_config_s *config) {
/* Create per-worker epoll instance */ /* Create per-worker epoll instance */
if (worker_setup_epoll(workers[i]) != CWS_OK) { if (worker_setup_epoll(workers[i]) != CWS_OK) {
for (size_t j = 0; j < i; ++j) { for (size_t j = 0; j < i; ++j) {
close(workers[j]->epfd);
free(workers[j]); free(workers[j]);
} }
free(workers); free(workers);
@@ -174,6 +163,7 @@ void cws_worker_free(cws_worker_s **workers, size_t workers_num) {
for (size_t i = 0; i < workers_num; ++i) { for (size_t i = 0; i < workers_num; ++i) {
pthread_join(workers[i]->thread, NULL); pthread_join(workers[i]->thread, NULL);
close(workers[i]->epfd);
free(workers[i]); free(workers[i]);
} }
+16 -2
View File
@@ -1,21 +1,33 @@
#include "http/handler.h" #include "http/handler.h"
#include "config/config.h"
#include "utils/debug.h" #include "utils/debug.h"
#include <myclib/mystring.h> #include <myclib/mystring.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
/* Sanitize and resolve file path */ /* Sanitize and resolve file path */
/* TODO: fix path traversal */
static string_s *resolve_file_path(const char *url_path, cws_handler_config_s *config) { static string_s *resolve_file_path(const char *url_path, cws_handler_config_s *config) {
string_s *full_path = string_new(config->root, 256); string_s *full_path = string_new(config->root, 256);
if (!full_path) {
return NULL;
}
if (strcmp(url_path, "/") == 0) { if (strcmp(url_path, "/") == 0) {
string_append(full_path, "/"); string_append(full_path, "/");
/* Use vhost index file */ /* @TODO: Use vhost index file */
string_append(full_path, "index.html"); string_append(full_path, "index.html");
return full_path; return full_path;
} }
string_s *url_path_string = string_new(url_path, 0);
if (!url_path_string) {
return NULL;
}
if (string_find(url_path_string, "..")) {
return full_path;
}
string_append(full_path, url_path); string_append(full_path, url_path);
return full_path; return full_path;
@@ -43,6 +55,8 @@ cws_response_s *cws_handler_static_file(cws_request_s *request, cws_handler_conf
return cws_handler_not_implemented(); return cws_handler_not_implemented();
} }
/* @TODO: use config_get_vhost */
// cws_vhost_s *vhost = config_get_vhost(, request->host);
string_s *filepath = resolve_file_path(string_cstr(request->path), config); string_s *filepath = resolve_file_path(string_cstr(request->path), config);
const char *path = string_cstr(filepath); const char *path = string_cstr(filepath);
+28 -5
View File
@@ -3,6 +3,7 @@
#include "utils/debug.h" #include "utils/debug.h"
#include "utils/hash.h" #include "utils/hash.h"
#include <core/socket.h> #include <core/socket.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>
@@ -16,6 +17,26 @@ static hashmap_s *response_headers_new(void) {
sizeof(char) * HEADER_VALUE_MAX); sizeof(char) * HEADER_VALUE_MAX);
} }
static int response_get_headers(hashmap_s *headers, char *out_headers, size_t len) {
size_t keys_len = 0;
char **keys = (char **)hm_get_keys(headers, &keys_len);
if (!keys) {
cws_log_debug("no headers??");
return -1;
}
size_t offset = 0;
for (size_t i = 0; i < keys_len; ++i) {
bucket_s *bucket = hm_get(headers, keys[i]);
offset += snprintf(out_headers, *out_headers + offset, "%s: %s", keys[i], (char *)bucket->value);
if ((size_t)(headers + offset) >= len) {
return -1;
}
}
return 0;
}
cws_response_s *cws_response_new(cws_http_status_e status) { cws_response_s *cws_response_new(cws_http_status_e status) {
cws_response_s *resp = malloc(sizeof(*resp)); cws_response_s *resp = malloc(sizeof(*resp));
if (!resp) { if (!resp) {
@@ -137,16 +158,18 @@ int cws_response_send(int sockfd, cws_response_s *response) {
} }
char headers[HEADERS_BUFFER_SIZE]; char headers[HEADERS_BUFFER_SIZE];
response_get_headers(response->headers, headers, sizeof headers);
int offset = snprintf(headers, sizeof(headers), "HTTP/1.1 %s\r\n", cws_http_status_string(response->status)); int offset = snprintf(headers, sizeof(headers), "HTTP/1.1 %s\r\n", cws_http_status_string(response->status));
char content_length_str[32]; char content_length_str[32];
snprintf(content_length_str, sizeof(content_length_str), "%zu", response->content_length); snprintf(content_length_str, sizeof(content_length_str), "%zu", response->content_length);
cws_response_set_header(response, "Content-Length", content_length_str);
offset += snprintf(headers + offset, sizeof(headers) - offset, /* @TODO: I can do this in the response_get_header() but I need to check
"Content-Length: %zu\r\n" * if I have space left for \r\n
"Connection: close\r\n", */
response->content_length); cws_response_set_header(response, "Content-Length", content_length_str);
offset += snprintf(headers + offset, sizeof(headers) - offset, "Content-Length: %zu\r\n", response->content_length);
offset += snprintf(headers + offset, sizeof(headers) - offset, "\r\n"); offset += snprintf(headers + offset, sizeof(headers) - offset, "\r\n");
+1 -1
View File
@@ -32,7 +32,7 @@ int main(void) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
cws_server_s server; cws_server_s server = {0};
cws_return ret; cws_return ret;
ret = cws_server_setup(&server, config); ret = cws_server_setup(&server, config);