Compare commits
6 Commits
ca5adbea1a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a276e644a7 | |||
| 52f80fdbd7 | |||
| 406fe36977 | |||
| 14950e5937 | |||
| cfaae5529e | |||
| 3e936767ce |
@@ -6,8 +6,8 @@ A minimal HTTP web server written in C.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [libcyaml](https://github.com/tlsa/libcyaml)
|
||||
- myclib (on my profile)
|
||||
- [tomlc17](https://github.com/cktan/tomlc17)
|
||||
- [doxygen](https://www.doxygen.nl/) (optional, for documentation only - requires `dot`)
|
||||
|
||||
## Build
|
||||
|
||||
22
config.toml
Normal file
22
config.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
# Default server values
|
||||
[server]
|
||||
host = "localhost"
|
||||
port = "3030"
|
||||
# Root folder in case there are no virtual hosts
|
||||
root = "www"
|
||||
|
||||
[[virtual_hosts]]
|
||||
domain = "localhost"
|
||||
root = "www"
|
||||
|
||||
[[virtual_hosts.pages]]
|
||||
status = "404"
|
||||
path = "www/pages/404.html"
|
||||
|
||||
[[virtual_hosts.pages]]
|
||||
status = "505"
|
||||
path = "www/pages/505.html"
|
||||
|
||||
[[virtual_hosts]]
|
||||
domain = "example.com"
|
||||
root = "example_site"
|
||||
17
config.yaml
17
config.yaml
@@ -1,17 +0,0 @@
|
||||
# Default hostname
|
||||
hostname: localhost
|
||||
|
||||
# Running port
|
||||
port: 3030
|
||||
|
||||
# Virtual hosts setup
|
||||
virtual_hosts:
|
||||
- domain: localhost
|
||||
# Root folder
|
||||
root: www
|
||||
# Custom pages
|
||||
error_pages:
|
||||
- method: 404
|
||||
path: pages/404.html
|
||||
- method: 500
|
||||
path: pages/500.html
|
||||
@@ -3,21 +3,22 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct cws_error_page {
|
||||
unsigned method;
|
||||
const char *path;
|
||||
} cws_error_page;
|
||||
typedef struct cws_page {
|
||||
char *status;
|
||||
char *path;
|
||||
} cws_page_s;
|
||||
|
||||
typedef struct cws_vhost {
|
||||
char *domain;
|
||||
char *root;
|
||||
cws_error_page *error_pages;
|
||||
cws_page_s *error_pages;
|
||||
unsigned error_pages_count;
|
||||
} cws_vhost_s;
|
||||
|
||||
typedef struct cws_config {
|
||||
char *hostname;
|
||||
char *host;
|
||||
char *port;
|
||||
char *root;
|
||||
cws_vhost_s *virtual_hosts;
|
||||
unsigned virtual_hosts_count;
|
||||
} cws_config_s;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
/* Configuration for static file serving */
|
||||
typedef struct cws_handler_config {
|
||||
const char *root_dir; /*< "www" */
|
||||
const char *index_file; /*< "index.html" */
|
||||
const char *root_dir;
|
||||
const char *index_file;
|
||||
} cws_handler_config_s;
|
||||
|
||||
/* Static file handler */
|
||||
|
||||
@@ -21,11 +21,17 @@
|
||||
#define _DEBUG "[DEBUG]"
|
||||
#endif
|
||||
|
||||
void _cws_log_info_internal(const char *file, int line, const char *fmt, ...);
|
||||
void _cws_log_warning_internal(const char *file, int line, const char *fmt, ...);
|
||||
void _cws_log_error_internal(const char *file, int line, const char *fmt, ...);
|
||||
void _cws_log_debug_internal(const char *file, int line, const char *fmt, ...);
|
||||
|
||||
#define cws_log_info(fmt, ...) _cws_log_info_internal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define cws_log_warning(fmt, ...) _cws_log_warning_internal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define cws_log_error(fmt, ...) _cws_log_error_internal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
#define cws_log_debug(fmt, ...) _cws_log_debug_internal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
void cws_log_init(void);
|
||||
void cws_log_info(const char *fmt, ...);
|
||||
void cws_log_debug(const char *fmt, ...);
|
||||
void cws_log_warning(const char *fmt, ...);
|
||||
void cws_log_error(const char *fmt, ...);
|
||||
void cws_log_shutdown(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@ project(
|
||||
'cws',
|
||||
'c',
|
||||
version: '0.1.0',
|
||||
default_options: ['c_std=c18', 'warning_level=3'],
|
||||
default_options: ['c_std=c11', 'warning_level=3'],
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
@@ -11,12 +11,11 @@ subdir('src')
|
||||
|
||||
incdir = include_directories('include')
|
||||
|
||||
libyaml = dependency('yaml-0.1')
|
||||
libcyaml = dependency('libcyaml')
|
||||
libtomlc17 = dependency('libtomlc17', required: true)
|
||||
libmath = cc.find_library('m', required: true)
|
||||
libmyclib = cc.find_library('myclib', required: true)
|
||||
|
||||
deps = [libyaml, libcyaml, libmath, libmyclib]
|
||||
deps = [libtomlc17, libmath, libmyclib]
|
||||
|
||||
add_global_arguments('-DUSE_COLORS', language: 'c')
|
||||
add_global_arguments('-DEVELOPER', language: 'c')
|
||||
|
||||
@@ -1,85 +1,160 @@
|
||||
#include "config/config.h"
|
||||
|
||||
#include <cyaml/cyaml.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <tomlc17.h>
|
||||
|
||||
#include "utils/debug.h"
|
||||
|
||||
static const cyaml_config_t cyaml_config = {
|
||||
.log_fn = cyaml_log,
|
||||
.mem_fn = cyaml_mem,
|
||||
.log_level = CYAML_LOG_WARNING,
|
||||
};
|
||||
static char *cws_strdup(const char *str) {
|
||||
if (!str) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const cyaml_schema_field_t error_page_fields[] = {
|
||||
CYAML_FIELD_INT("method", CYAML_FLAG_DEFAULT, cws_error_page, method),
|
||||
CYAML_FIELD_STRING_PTR("path", CYAML_FLAG_POINTER, cws_error_page, path, 0, CYAML_UNLIMITED),
|
||||
CYAML_FIELD_END,
|
||||
};
|
||||
size_t len = strlen(str) + 1;
|
||||
char *copy = malloc(sizeof *copy * len);
|
||||
if (!copy) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static cyaml_schema_value_t error_page_schema = {
|
||||
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, cws_error_page, error_page_fields),
|
||||
};
|
||||
memcpy(copy, str, len);
|
||||
|
||||
static const cyaml_schema_field_t virtual_hosts_fields[] = {
|
||||
CYAML_FIELD_STRING_PTR("domain", CYAML_FLAG_POINTER, struct cws_vhost, domain, 0, CYAML_UNLIMITED),
|
||||
CYAML_FIELD_STRING_PTR("root", CYAML_FLAG_POINTER, struct cws_vhost, root, 0, CYAML_UNLIMITED),
|
||||
CYAML_FIELD_SEQUENCE("error_pages", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL, struct cws_vhost, error_pages,
|
||||
&error_page_schema, 0, CYAML_UNLIMITED),
|
||||
CYAML_FIELD_END,
|
||||
};
|
||||
return copy;
|
||||
}
|
||||
|
||||
static cyaml_schema_value_t virtual_hosts_schema = {
|
||||
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT, struct cws_vhost, virtual_hosts_fields),
|
||||
};
|
||||
static bool parse_vhosts(cws_config_s *config, toml_result_t result) {
|
||||
toml_datum_t vhosts = toml_seek(result.toptab, "virtual_hosts");
|
||||
config->virtual_hosts_count = vhosts.u.arr.size;
|
||||
config->virtual_hosts = malloc(sizeof *config->virtual_hosts * config->virtual_hosts_count);
|
||||
if (!config->virtual_hosts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const cyaml_schema_field_t top_schema_fields[] = {
|
||||
CYAML_FIELD_STRING_PTR("hostname", CYAML_FLAG_POINTER, struct cws_config, hostname, 0, CYAML_UNLIMITED),
|
||||
CYAML_FIELD_STRING_PTR("port", CYAML_FLAG_POINTER, struct cws_config, port, 0, CYAML_UNLIMITED),
|
||||
CYAML_FIELD_SEQUENCE("virtual_hosts", CYAML_FLAG_POINTER, struct cws_config, virtual_hosts, &virtual_hosts_schema,
|
||||
0, CYAML_UNLIMITED),
|
||||
CYAML_FIELD_END,
|
||||
};
|
||||
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];
|
||||
toml_datum_t domain = toml_seek(elem, "domain");
|
||||
vh->domain = cws_strdup(domain.u.str.ptr);
|
||||
if (!vh->domain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static cyaml_schema_value_t top_schema = {
|
||||
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, struct cws_config, top_schema_fields),
|
||||
};
|
||||
toml_datum_t root = toml_seek(elem, "root");
|
||||
vh->root = cws_strdup(root.u.str.ptr);
|
||||
if (!vh->root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
toml_datum_t pages = toml_seek(elem, "pages");
|
||||
vh->error_pages_count = pages.u.arr.size;
|
||||
vh->error_pages = malloc(sizeof *vh->error_pages * vh->error_pages_count);
|
||||
if (!vh->error_pages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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[i].status = cws_strdup(status.u.str.ptr);
|
||||
if (!vh->error_pages[i].status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
toml_datum_t path = toml_seek(page, "path");
|
||||
vh->error_pages[i].path = cws_strdup(path.u.str.ptr);
|
||||
if (!vh->error_pages[i].path) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool find_default_hostname(cws_config_s *config) {
|
||||
for (unsigned i = 0; i < config->virtual_hosts_count; ++i) {
|
||||
if (strcmp(config->hostname, config->virtual_hosts[i].domain) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool parse_toml(cws_config_s *config) {
|
||||
const char *path = "config.toml";
|
||||
|
||||
toml_result_t result = toml_parse_file_ex(path);
|
||||
if (!result.ok) {
|
||||
cws_log_error("Unable to parse config.toml");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
toml_datum_t host = toml_seek(result.toptab, "server.host");
|
||||
config->host = cws_strdup(host.u.str.ptr);
|
||||
if (!config->host) {
|
||||
return false;
|
||||
}
|
||||
|
||||
toml_datum_t port = toml_seek(result.toptab, "server.port");
|
||||
config->port = cws_strdup(port.u.str.ptr);
|
||||
if (!config->port) {
|
||||
return false;
|
||||
}
|
||||
|
||||
toml_datum_t root = toml_seek(result.toptab, "server.root");
|
||||
config->root = cws_strdup(root.u.str.ptr);
|
||||
if (!config->root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parse_vhosts(config, result);
|
||||
|
||||
toml_free(result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
cws_config_s *cws_config_init(void) {
|
||||
const char *path = "config.yaml";
|
||||
cws_config_s *config;
|
||||
|
||||
cyaml_err_t err = cyaml_load_file(path, &cyaml_config, &top_schema, (cyaml_data_t **)&config, NULL);
|
||||
if (err != CYAML_OK) {
|
||||
cws_log_error("%s", cyaml_strerror(err));
|
||||
|
||||
cws_config_s *config = malloc(sizeof *config);
|
||||
if (!config) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool found = find_default_hostname(config);
|
||||
if (!found) {
|
||||
cws_log_error("Default hostname not found in config.yaml");
|
||||
cws_config_free(config);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
parse_toml(config);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void cws_config_free(cws_config_s *config) {
|
||||
cyaml_err_t err = cyaml_free(&cyaml_config, &top_schema, config, 0);
|
||||
if (err != CYAML_OK) {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config->host) {
|
||||
free(config->host);
|
||||
}
|
||||
|
||||
if (config->port) {
|
||||
free(config->port);
|
||||
}
|
||||
|
||||
if (config->root) {
|
||||
free(config->root);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < config->virtual_hosts_count; ++i) {
|
||||
cws_vhost_s *vh = &config->virtual_hosts[i];
|
||||
if (vh->domain) {
|
||||
free(vh->domain);
|
||||
}
|
||||
|
||||
if (vh->root) {
|
||||
free(vh->root);
|
||||
}
|
||||
|
||||
for (unsigned j = 0; j < vh->error_pages_count; ++j) {
|
||||
if (vh->error_pages[i].path) {
|
||||
free(vh->error_pages[i].path);
|
||||
}
|
||||
|
||||
if (vh->error_pages[i].status) {
|
||||
free(vh->error_pages[i].status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(config);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ static cws_return cws_server_setup_epoll(int server_fd, int *epfd_out) {
|
||||
}
|
||||
|
||||
cws_return cws_server_setup(cws_server_s *server, cws_config_s *config) {
|
||||
if (!config || !config->hostname || !config->port) {
|
||||
if (!config || !config->host || !config->port) {
|
||||
return CWS_CONFIG_ERROR;
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ cws_return cws_server_setup(cws_server_s *server, cws_config_s *config) {
|
||||
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *res;
|
||||
cws_server_setup_hints(&hints, config->hostname);
|
||||
cws_server_setup_hints(&hints, config->host);
|
||||
|
||||
int status = getaddrinfo(config->hostname, config->port, &hints, &res);
|
||||
int status = getaddrinfo(config->host, config->port, &hints, &res);
|
||||
if (status != 0) {
|
||||
cws_log_error("getaddrinfo() error: %s", gai_strerror(status));
|
||||
return CWS_GETADDRINFO_ERROR;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "utils/debug.h"
|
||||
#include "utils/hash.h"
|
||||
|
||||
static cws_request_s *http_new(void) {
|
||||
static cws_request_s *http_request_new(void) {
|
||||
cws_request_s *request = malloc(sizeof(*request));
|
||||
if (!request) {
|
||||
return NULL;
|
||||
@@ -138,7 +138,7 @@ cws_request_s *cws_http_parse(string_s *request_str) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cws_request_s *request = http_new();
|
||||
cws_request_s *request = http_request_new();
|
||||
if (!request) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ 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;
|
||||
|
||||
@@ -14,7 +14,6 @@ void cws_signal_handler(int signo) {
|
||||
|
||||
int main(void) {
|
||||
cws_log_init();
|
||||
cws_log_debug("Starting cws");
|
||||
|
||||
if (signal(SIGINT, cws_signal_handler) == SIG_ERR) {
|
||||
cws_log_error("signal()");
|
||||
@@ -38,7 +37,7 @@ int main(void) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
cws_log_info("Running cws on http://%s:%s", config->hostname, config->port);
|
||||
cws_log_info("Running cws on http://%s:%s", config->host, config->port);
|
||||
ret = cws_server_start(&server);
|
||||
if (ret != CWS_OK) {
|
||||
cws_log_error("Unable to start web server: %s", cws_error_str(ret));
|
||||
|
||||
@@ -1,65 +1,73 @@
|
||||
#include "utils/debug.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/syslog.h>
|
||||
#include <syslog.h>
|
||||
|
||||
void cws_log_init(void) {
|
||||
openlog("cws", LOG_PID | LOG_CONS, LOG_DAEMON);
|
||||
}
|
||||
|
||||
void cws_log_info(const char *fmt, ...) {
|
||||
void _cws_log_info_internal(const char *file, int line, const char *fmt, ...) {
|
||||
va_list args;
|
||||
fprintf(stdout, _INFO " ");
|
||||
fprintf(stdout, _INFO " [%s:%d] ", file, line);
|
||||
va_start(args, fmt);
|
||||
|
||||
vfprintf(stdout, fmt, args);
|
||||
va_end(args);
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
va_start(args, fmt);
|
||||
syslog(LOG_INFO, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
fprintf(stdout, "\n");
|
||||
}
|
||||
|
||||
void cws_log_warning(const char *fmt, ...) {
|
||||
void _cws_log_warning_internal(const char *file, int line, const char *fmt, ...) {
|
||||
va_list args;
|
||||
fprintf(stdout, _WARNING " ");
|
||||
fprintf(stdout, _WARNING " [%s:%d] ", file, line);
|
||||
va_start(args, fmt);
|
||||
|
||||
vfprintf(stdout, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
va_start(args, fmt);
|
||||
syslog(LOG_WARNING, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void cws_log_error(const char *fmt, ...) {
|
||||
void _cws_log_error_internal(const char *file, int line, const char *fmt, ...) {
|
||||
va_list args;
|
||||
fprintf(stdout, _ERR " ");
|
||||
fprintf(stdout, _ERR " [%s:%d] ", file, line);
|
||||
va_start(args, fmt);
|
||||
|
||||
vfprintf(stdout, fmt, args);
|
||||
va_end(args);
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
va_start(args, fmt);
|
||||
syslog(LOG_ERR, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#ifdef EVELOPER
|
||||
void _cws_log_debug_internal(const char *file, int line, const char *fmt, ...) {
|
||||
va_list args;
|
||||
fprintf(stdout, _DEBUG " [%s:%d] ", file, line);
|
||||
va_start(args, fmt);
|
||||
vfprintf(stdout, fmt, args);
|
||||
va_end(args);
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
va_start(args, fmt);
|
||||
syslog(LOG_DEBUG, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
#else
|
||||
void _cws_log_debug_internal(const char *file, int line, const char *fmt, ...) {
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)fmt;
|
||||
/* Nothing */
|
||||
}
|
||||
#endif
|
||||
|
||||
void cws_log_shutdown(void) {
|
||||
closelog();
|
||||
}
|
||||
|
||||
#ifdef EVELOPER
|
||||
void cws_log_debug(const char *fmt, ...) {
|
||||
fprintf(stdout, _DEBUG " [%s:%d] ", __FILE__, __LINE__);
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
vfprintf(stdout, fmt, args);
|
||||
syslog(LOG_DEBUG, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
fprintf(stdout, "\n");
|
||||
}
|
||||
#else
|
||||
void cws_log_debug(const char *fmt, ...) {
|
||||
/* Nothing */
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user