chore: add core server infrastructure with config, logging, and server modules

This commit is contained in:
2025-11-28 02:49:36 +00:00
parent 8f72c85561
commit f75e1e6d9a
32 changed files with 1074 additions and 0 deletions
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+320
View File
@@ -0,0 +1,320 @@
#include "config.h"
#include "logging.h"
#include "../platform/pal.h"
#include "../platform/file.h"
#include "../platform/memory.h"
#include <string.h>
#include <ctype.h>
#include <errno.h>
void config_set_defaults(config_t *config) {
memset(config, 0, sizeof(config_t));
strncpy(config->bind_address, "0.0.0.0", sizeof(config->bind_address) - 1);
config->port = NWEDAV_DEFAULT_PORT;
config->ssl_port = NWEDAV_DEFAULT_SSL_PORT;
config->max_connections = NWEDAV_MAX_CONNECTIONS;
config->connection_timeout = NWEDAV_DEFAULT_TIMEOUT;
config->keepalive_timeout = NWEDAV_DEFAULT_KEEPALIVE;
config->max_request_size = NWEDAV_MAX_REQUEST_SIZE;
config->worker_threads = 0;
config->ssl.enabled = 0;
strncpy(config->ssl.min_protocol, "TLSv1.2", sizeof(config->ssl.min_protocol) - 1);
strncpy(config->ssl.ciphers, "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384", sizeof(config->ssl.ciphers) - 1);
strncpy(config->storage.root_path, "./data", sizeof(config->storage.root_path) - 1);
strncpy(config->storage.temp_path, "./tmp", sizeof(config->storage.temp_path) - 1);
strncpy(config->storage.database_path, "./nwedav.db", sizeof(config->storage.database_path) - 1);
strncpy(config->storage.properties_path, "./properties.db", sizeof(config->storage.properties_path) - 1);
strncpy(config->storage.locks_path, "./locks.db", sizeof(config->storage.locks_path) - 1);
config->auth.method = AUTH_METHOD_BASIC;
strncpy(config->auth.realm, "WebDAV Server", sizeof(config->auth.realm) - 1);
config->auth.session_timeout = 3600;
config->auth.max_failed_attempts = 5;
config->auth.lockout_duration = 900;
config->quota.default_quota = 10ULL * 1024 * 1024 * 1024;
config->quota.enforce = 1;
config->quota.warning_threshold = 90;
config->logging.level = LOG_LEVEL_INFO;
config->logging.max_size = 100 * 1024 * 1024;
config->logging.rotate_count = 10;
config->scaling.enabled = 1;
config->scaling.min_threads = 4;
config->scaling.max_threads = 64;
config->scaling.scale_up_threshold = 10;
config->scaling.scale_down_threshold = 2;
config->scaling.scale_up_step = 2;
config->scaling.scale_down_step = 1;
config->scaling.idle_timeout_ms = 30000;
config->scaling.check_interval_ms = 1000;
}
static char *trim(char *str) {
while (isspace((unsigned char)*str)) str++;
if (*str == 0) return str;
char *end = str + strlen(str) - 1;
while (end > str && isspace((unsigned char)*end)) end--;
end[1] = '\0';
return str;
}
static int parse_bool(const char *value) {
if (str_casecmp(value, "true") == 0 ||
str_casecmp(value, "yes") == 0 ||
str_casecmp(value, "1") == 0 ||
str_casecmp(value, "on") == 0) {
return 1;
}
return 0;
}
static uint64_t parse_size(const char *value) {
char *end;
errno = 0;
uint64_t size = strtoull(value, &end, 10);
if (errno == ERANGE) return 0;
while (isspace((unsigned char)*end)) end++;
uint64_t multiplier = 1;
if (*end == 'K' || *end == 'k') multiplier = 1024ULL;
else if (*end == 'M' || *end == 'm') multiplier = 1024ULL * 1024;
else if (*end == 'G' || *end == 'g') multiplier = 1024ULL * 1024 * 1024;
else if (*end == 'T' || *end == 't') multiplier = 1024ULL * 1024 * 1024 * 1024;
if (size > UINT64_MAX / multiplier) return 0;
return size * multiplier;
}
static log_level_t parse_log_level(const char *value) {
if (str_casecmp(value, "trace") == 0) return LOG_LEVEL_TRACE;
if (str_casecmp(value, "debug") == 0) return LOG_LEVEL_DEBUG;
if (str_casecmp(value, "info") == 0) return LOG_LEVEL_INFO;
if (str_casecmp(value, "warn") == 0 || str_casecmp(value, "warning") == 0) return LOG_LEVEL_WARN;
if (str_casecmp(value, "error") == 0) return LOG_LEVEL_ERROR;
if (str_casecmp(value, "fatal") == 0) return LOG_LEVEL_FATAL;
return LOG_LEVEL_INFO;
}
static auth_method_t parse_auth_method(const char *value) {
if (str_casecmp(value, "none") == 0) return AUTH_METHOD_NONE;
if (str_casecmp(value, "basic") == 0) return AUTH_METHOD_BASIC;
if (str_casecmp(value, "digest") == 0) return AUTH_METHOD_DIGEST;
return AUTH_METHOD_BASIC;
}
int config_load(const char *path, config_t *config) {
config_set_defaults(config);
if (!path || !path[0]) {
return NWEDAV_OK;
}
FILE *f = fopen(path, "r");
if (!f) {
return NWEDAV_ERROR_CONFIG;
}
char line[1024];
char section[64] = "";
while (fgets(line, sizeof(line), f)) {
char *p = trim(line);
if (*p == '\0' || *p == '#' || *p == ';') {
continue;
}
if (*p == '[') {
char *end = strchr(p, ']');
if (end) {
*end = '\0';
strncpy(section, p + 1, sizeof(section) - 1);
section[sizeof(section) - 1] = '\0';
}
continue;
}
char *eq = strchr(p, '=');
if (!eq) continue;
*eq = '\0';
char *key = trim(p);
char *value = trim(eq + 1);
if (strcmp(section, "server") == 0) {
if (strcmp(key, "bind_address") == 0) {
strncpy(config->bind_address, value, sizeof(config->bind_address) - 1);
} else if (strcmp(key, "port") == 0) {
config->port = (uint16_t)atoi(value);
} else if (strcmp(key, "ssl_port") == 0) {
config->ssl_port = (uint16_t)atoi(value);
} else if (strcmp(key, "max_connections") == 0) {
config->max_connections = (size_t)atoi(value);
} else if (strcmp(key, "connection_timeout") == 0) {
config->connection_timeout = atoi(value);
} else if (strcmp(key, "keepalive_timeout") == 0) {
config->keepalive_timeout = atoi(value);
} else if (strcmp(key, "max_request_size") == 0) {
config->max_request_size = parse_size(value);
} else if (strcmp(key, "worker_threads") == 0) {
config->worker_threads = atoi(value);
}
} else if (strcmp(section, "ssl") == 0) {
if (strcmp(key, "enabled") == 0) {
config->ssl.enabled = parse_bool(value);
} else if (strcmp(key, "cert_file") == 0) {
strncpy(config->ssl.cert_file, value, sizeof(config->ssl.cert_file) - 1);
} else if (strcmp(key, "key_file") == 0) {
strncpy(config->ssl.key_file, value, sizeof(config->ssl.key_file) - 1);
} else if (strcmp(key, "ca_file") == 0) {
strncpy(config->ssl.ca_file, value, sizeof(config->ssl.ca_file) - 1);
} else if (strcmp(key, "verify_client") == 0) {
config->ssl.verify_client = parse_bool(value);
} else if (strcmp(key, "min_protocol") == 0) {
strncpy(config->ssl.min_protocol, value, sizeof(config->ssl.min_protocol) - 1);
} else if (strcmp(key, "ciphers") == 0) {
strncpy(config->ssl.ciphers, value, sizeof(config->ssl.ciphers) - 1);
}
} else if (strcmp(section, "storage") == 0) {
if (strcmp(key, "root_path") == 0) {
strncpy(config->storage.root_path, value, sizeof(config->storage.root_path) - 1);
} else if (strcmp(key, "temp_path") == 0) {
strncpy(config->storage.temp_path, value, sizeof(config->storage.temp_path) - 1);
} else if (strcmp(key, "database_path") == 0) {
strncpy(config->storage.database_path, value, sizeof(config->storage.database_path) - 1);
} else if (strcmp(key, "properties_path") == 0) {
strncpy(config->storage.properties_path, value, sizeof(config->storage.properties_path) - 1);
} else if (strcmp(key, "locks_path") == 0) {
strncpy(config->storage.locks_path, value, sizeof(config->storage.locks_path) - 1);
}
} else if (strcmp(section, "auth") == 0) {
if (strcmp(key, "method") == 0) {
config->auth.method = parse_auth_method(value);
} else if (strcmp(key, "realm") == 0) {
strncpy(config->auth.realm, value, sizeof(config->auth.realm) - 1);
} else if (strcmp(key, "session_timeout") == 0) {
config->auth.session_timeout = atoi(value);
} else if (strcmp(key, "max_failed_attempts") == 0) {
config->auth.max_failed_attempts = atoi(value);
} else if (strcmp(key, "lockout_duration") == 0) {
config->auth.lockout_duration = atoi(value);
}
} else if (strcmp(section, "quota") == 0) {
if (strcmp(key, "default_quota") == 0) {
config->quota.default_quota = parse_size(value);
} else if (strcmp(key, "enforce") == 0) {
config->quota.enforce = parse_bool(value);
} else if (strcmp(key, "warning_threshold") == 0) {
config->quota.warning_threshold = atoi(value);
}
} else if (strcmp(section, "logging") == 0) {
if (strcmp(key, "level") == 0) {
config->logging.level = parse_log_level(value);
} else if (strcmp(key, "file") == 0) {
strncpy(config->logging.file, value, sizeof(config->logging.file) - 1);
} else if (strcmp(key, "access_log") == 0) {
strncpy(config->logging.access_log, value, sizeof(config->logging.access_log) - 1);
} else if (strcmp(key, "max_size") == 0) {
config->logging.max_size = parse_size(value);
} else if (strcmp(key, "rotate_count") == 0) {
config->logging.rotate_count = atoi(value);
}
} else if (strcmp(section, "scaling") == 0) {
if (strcmp(key, "enabled") == 0) {
config->scaling.enabled = parse_bool(value);
} else if (strcmp(key, "min_threads") == 0) {
config->scaling.min_threads = (size_t)atoi(value);
} else if (strcmp(key, "max_threads") == 0) {
config->scaling.max_threads = (size_t)atoi(value);
} else if (strcmp(key, "scale_up_threshold") == 0) {
config->scaling.scale_up_threshold = (size_t)atoi(value);
} else if (strcmp(key, "scale_down_threshold") == 0) {
config->scaling.scale_down_threshold = (size_t)atoi(value);
} else if (strcmp(key, "scale_up_step") == 0) {
config->scaling.scale_up_step = atoi(value);
} else if (strcmp(key, "scale_down_step") == 0) {
config->scaling.scale_down_step = atoi(value);
} else if (strcmp(key, "idle_timeout_ms") == 0) {
config->scaling.idle_timeout_ms = atoi(value);
} else if (strcmp(key, "check_interval_ms") == 0) {
config->scaling.check_interval_ms = atoi(value);
}
}
}
fclose(f);
return NWEDAV_OK;
}
int config_validate(config_t *config) {
if (config->port == 0) {
return NWEDAV_ERROR_CONFIG;
}
if (config->max_connections == 0) {
config->max_connections = NWEDAV_MAX_CONNECTIONS;
}
if (config->worker_threads == 0) {
config->worker_threads = pal_get_cpu_count() * 2;
if (config->worker_threads < 4) config->worker_threads = 4;
if (config->worker_threads > 64) config->worker_threads = 64;
}
if (config->storage.root_path[0] == '\0') {
return NWEDAV_ERROR_CONFIG;
}
if (config->scaling.enabled) {
if (config->scaling.min_threads == 0) {
config->scaling.min_threads = 2;
}
if (config->scaling.max_threads == 0) {
config->scaling.max_threads = (size_t)config->worker_threads * 4;
}
if (config->scaling.max_threads < config->scaling.min_threads) {
config->scaling.max_threads = config->scaling.min_threads;
}
if (config->scaling.scale_up_step <= 0) {
config->scaling.scale_up_step = 2;
}
if (config->scaling.scale_down_step <= 0) {
config->scaling.scale_down_step = 1;
}
if (config->scaling.idle_timeout_ms <= 0) {
config->scaling.idle_timeout_ms = 30000;
}
if (config->scaling.check_interval_ms <= 0) {
config->scaling.check_interval_ms = 1000;
}
}
return NWEDAV_OK;
}
void config_dump(config_t *config) {
printf("Server Configuration:\n");
printf(" bind_address: %s\n", config->bind_address);
printf(" port: %u\n", config->port);
printf(" ssl_port: %u\n", config->ssl_port);
printf(" max_connections: %zu\n", config->max_connections);
printf(" worker_threads: %d\n", config->worker_threads);
printf(" storage.root_path: %s\n", config->storage.root_path);
printf(" storage.database_path: %s\n", config->storage.database_path);
printf(" auth.method: %d\n", config->auth.method);
printf(" auth.realm: %s\n", config->auth.realm);
printf(" quota.default_quota: %llu\n", (unsigned long long)config->quota.default_quota);
printf(" quota.enforce: %d\n", config->quota.enforce);
printf(" scaling.enabled: %d\n", config->scaling.enabled);
printf(" scaling.min_threads: %zu\n", config->scaling.min_threads);
printf(" scaling.max_threads: %zu\n", config->scaling.max_threads);
printf(" scaling.scale_up_threshold: %zu\n", config->scaling.scale_up_threshold);
printf(" scaling.scale_down_threshold: %zu\n", config->scaling.scale_down_threshold);
printf(" scaling.idle_timeout_ms: %d\n", config->scaling.idle_timeout_ms);
}
+74
View File
@@ -0,0 +1,74 @@
#ifndef CONFIG_H
#define CONFIG_H
#include "nwedav.h"
typedef struct {
char bind_address[64];
uint16_t port;
uint16_t ssl_port;
size_t max_connections;
int connection_timeout;
int keepalive_timeout;
size_t max_request_size;
int worker_threads;
struct {
int enabled;
char cert_file[PATH_MAX];
char key_file[PATH_MAX];
char ca_file[PATH_MAX];
int verify_client;
char min_protocol[16];
char ciphers[512];
} ssl;
struct {
char root_path[PATH_MAX];
char temp_path[PATH_MAX];
char database_path[PATH_MAX];
char properties_path[PATH_MAX];
char locks_path[PATH_MAX];
} storage;
struct {
auth_method_t method;
char realm[128];
int session_timeout;
int max_failed_attempts;
int lockout_duration;
} auth;
struct {
uint64_t default_quota;
int enforce;
int warning_threshold;
} quota;
struct {
log_level_t level;
char file[PATH_MAX];
char access_log[PATH_MAX];
size_t max_size;
int rotate_count;
} logging;
struct {
int enabled;
size_t min_threads;
size_t max_threads;
size_t scale_up_threshold;
size_t scale_down_threshold;
int scale_up_step;
int scale_down_step;
int idle_timeout_ms;
int check_interval_ms;
} scaling;
} config_t;
void config_set_defaults(config_t *config);
int config_load(const char *path, config_t *config);
int config_validate(config_t *config);
void config_dump(config_t *config);
#endif
BIN
View File
Binary file not shown.
+123
View File
@@ -0,0 +1,123 @@
#include "logging.h"
#include "../platform/memory.h"
#include <string.h>
logger_t *g_logger = NULL;
struct logger {
FILE *file;
FILE *access_file;
log_level_t level;
mutex_t mutex;
char file_path[PATH_MAX];
size_t max_size;
int rotate_count;
};
static const char *level_names[] = {
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};
logger_t *logger_create(const char *file_path, log_level_t level) {
logger_t *log = calloc(1, sizeof(logger_t));
if (!log) return NULL;
log->level = level;
log->max_size = 100 * 1024 * 1024;
log->rotate_count = 10;
if (pal_mutex_init(&log->mutex) != NWEDAV_OK) {
free(log);
return NULL;
}
if (file_path && file_path[0]) {
strncpy(log->file_path, file_path, sizeof(log->file_path) - 1);
log->file = fopen(file_path, "a");
}
if (!log->file) {
log->file = stderr;
}
return log;
}
void logger_destroy(logger_t *log) {
if (!log) return;
pal_mutex_lock(&log->mutex);
if (log->file && log->file != stderr && log->file != stdout) {
fclose(log->file);
}
if (log->access_file && log->access_file != stderr && log->access_file != stdout) {
fclose(log->access_file);
}
pal_mutex_unlock(&log->mutex);
pal_mutex_destroy(&log->mutex);
free(log);
}
void logger_set_level(logger_t *log, log_level_t level) {
if (log) {
log->level = level;
}
}
void logger_log(logger_t *log, log_level_t level, const char *file, int line, const char *fmt, ...) {
if (!log || level < log->level) return;
pal_mutex_lock(&log->mutex);
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);
const char *basename = file;
const char *p = strrchr(file, '/');
if (p) basename = p + 1;
#ifdef _WIN32
p = strrchr(basename, '\\');
if (p) basename = p + 1;
#endif
fprintf(log->file, "[%s] [%s] [%s:%d] ",
timestamp, level_names[level], basename, line);
va_list ap;
va_start(ap, fmt);
vfprintf(log->file, fmt, ap);
va_end(ap);
fprintf(log->file, "\n");
fflush(log->file);
pal_mutex_unlock(&log->mutex);
}
void logger_access(logger_t *log, const char *client_ip, const char *method,
const char *uri, int status, size_t bytes, const char *user_agent) {
if (!log) return;
FILE *out = log->access_file ? log->access_file : log->file;
pal_mutex_lock(&log->mutex);
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%d/%b/%Y:%H:%M:%S %z", tm);
fprintf(out, "%s - - [%s] \"%s %s HTTP/1.1\" %d %zu \"-\" \"%s\"\n",
client_ip ? client_ip : "-",
timestamp,
method ? method : "-",
uri ? uri : "-",
status,
bytes,
user_agent ? user_agent : "-");
fflush(out);
pal_mutex_unlock(&log->mutex);
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef LOGGING_H
#define LOGGING_H
#include "nwedav.h"
#include "../platform/pal.h"
typedef struct logger logger_t;
logger_t *logger_create(const char *file_path, log_level_t level);
void logger_destroy(logger_t *log);
void logger_set_level(logger_t *log, log_level_t level);
void logger_log(logger_t *log, log_level_t level, const char *file, int line, const char *fmt, ...);
void logger_access(logger_t *log, const char *client_ip, const char *method,
const char *uri, int status, size_t bytes, const char *user_agent);
extern logger_t *g_logger;
#define LOG_TRACE(...) logger_log(g_logger, LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_DEBUG(...) logger_log(g_logger, LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_INFO(...) logger_log(g_logger, LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_WARN(...) logger_log(g_logger, LOG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_ERROR(...) logger_log(g_logger, LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define LOG_FATAL(...) logger_log(g_logger, LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#endif
Binary file not shown.
+503
View File
@@ -0,0 +1,503 @@
#include "server.h"
#include "../http/parser.h"
#include "../http/response.h"
#include "../webdav/methods.h"
#include "../platform/memory.h"
#include <string.h>
#include <signal.h>
#include <errno.h>
struct server {
config_t *config;
socket_t listen_fd;
event_loop_t *event_loop;
thread_pool_t *thread_pool;
connection_pool_t *conn_pool;
storage_backend_t *storage;
property_store_t *properties;
lock_manager_t *locks;
auth_context_t *auth;
volatile int running;
volatile size_t total_requests;
};
static server_t *g_server = NULL;
static void signal_handler(int sig) {
(void)sig;
if (g_server) {
g_server->running = 0;
}
}
server_t *server_create(config_t *config) {
server_t *server = calloc(1, sizeof(server_t));
if (!server) return NULL;
server->config = config;
server->listen_fd = SOCKET_INVALID;
server->storage = storage_create(config->storage.root_path);
if (!server->storage) {
LOG_ERROR("Failed to create storage backend at '%s'", config->storage.root_path);
free(server);
return NULL;
}
server->properties = property_store_create(config->storage.properties_path);
if (!server->properties) {
LOG_ERROR("Failed to create property store at '%s'", config->storage.properties_path);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->locks = lock_manager_create(config->storage.locks_path);
if (!server->locks) {
LOG_ERROR("Failed to create lock manager at '%s'", config->storage.locks_path);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->auth = auth_create(config->storage.database_path,
config->auth.method, config->auth.realm);
if (!server->auth) {
LOG_ERROR("Failed to create auth context");
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
if (config->scaling.enabled) {
threadpool_config_t tp_config;
threadpool_config_init(&tp_config);
tp_config.min_threads = config->scaling.min_threads;
tp_config.max_threads = config->scaling.max_threads;
tp_config.initial_threads = (size_t)config->worker_threads;
tp_config.scale_up_threshold = config->scaling.scale_up_threshold;
tp_config.scale_down_threshold = config->scaling.scale_down_threshold;
tp_config.scale_up_step = config->scaling.scale_up_step;
tp_config.scale_down_step = config->scaling.scale_down_step;
tp_config.idle_timeout_ms = config->scaling.idle_timeout_ms;
tp_config.check_interval_ms = config->scaling.check_interval_ms;
server->thread_pool = threadpool_create_adaptive(&tp_config);
} else {
server->thread_pool = threadpool_create(config->worker_threads);
}
if (!server->thread_pool) {
LOG_ERROR("Failed to create thread pool with %d threads", config->worker_threads);
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->conn_pool = connpool_create(config->max_connections);
if (!server->conn_pool) {
LOG_ERROR("Failed to create connection pool");
threadpool_destroy(server->thread_pool);
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
server->event_loop = event_loop_create(config->max_connections);
if (!server->event_loop) {
LOG_ERROR("Failed to create event loop");
connpool_destroy(server->conn_pool);
threadpool_destroy(server->thread_pool);
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
return NULL;
}
return server;
}
void server_destroy(server_t *server) {
if (!server) return;
server_stop(server);
event_loop_destroy(server->event_loop);
connpool_destroy(server->conn_pool);
threadpool_destroy(server->thread_pool);
auth_destroy(server->auth);
lock_manager_destroy(server->locks);
property_store_destroy(server->properties);
storage_destroy(server->storage);
free(server);
}
static int create_listen_socket(server_t *server) {
server->listen_fd = pal_socket_create(AF_INET, SOCK_STREAM, 0);
if (server->listen_fd == SOCKET_INVALID) {
LOG_ERROR("Failed to create socket");
return NWEDAV_ERROR_NETWORK;
}
pal_socket_setreuseaddr(server->listen_fd, 1);
pal_socket_setreuseport(server->listen_fd, 1);
pal_socket_setnonblock(server->listen_fd, 1);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(server->config->port);
if (strcmp(server->config->bind_address, "0.0.0.0") == 0) {
addr.sin_addr.s_addr = INADDR_ANY;
} else {
addr.sin_addr.s_addr = inet_addr(server->config->bind_address);
}
if (pal_socket_bind(server->listen_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
LOG_ERROR("Failed to bind to %s:%d", server->config->bind_address, server->config->port);
pal_socket_close(server->listen_fd);
server->listen_fd = SOCKET_INVALID;
return NWEDAV_ERROR_NETWORK;
}
if (pal_socket_listen(server->listen_fd, 1024) != 0) {
LOG_ERROR("Failed to listen on socket");
pal_socket_close(server->listen_fd);
server->listen_fd = SOCKET_INVALID;
return NWEDAV_ERROR_NETWORK;
}
return NWEDAV_OK;
}
typedef struct {
server_t *server;
connection_t *conn;
} request_task_t;
static void handle_request(void *arg) {
request_task_t *task = (request_task_t *)arg;
server_t *server = task->server;
connection_t *conn = task->conn;
free(task);
size_t consumed = 0;
int ret = http_parse_request(conn->read_buf->data, conn->read_buf->size,
&conn->request, &consumed);
if (ret != NWEDAV_OK) {
http_response_error(&conn->response, HTTP_400_BAD_REQUEST, "Invalid request");
} else {
buffer_consume(conn->read_buf, consumed);
if (conn->request.content_length > 0) {
while (conn->read_buf->size < conn->request.content_length) {
char buf[8192];
ssize_t n = pal_socket_recv(conn->fd, buf, sizeof(buf), 0);
if (n > 0) {
buffer_append(conn->read_buf, buf, n);
} else if (n == 0) {
break;
} else {
int err = pal_socket_error();
if (err == EAGAIN || err == EWOULDBLOCK) {
pal_sleep_ms(1);
continue;
}
break;
}
}
size_t body_len = conn->read_buf->size;
if (body_len > conn->request.content_length) {
body_len = conn->request.content_length;
}
if (body_len > 0) {
conn->request.body = malloc(body_len + 1);
if (conn->request.body) {
memcpy(conn->request.body, conn->read_buf->data, body_len);
conn->request.body[body_len] = '\0';
conn->request.body_len = body_len;
}
}
}
auth_user_t user;
ret = auth_check(server->auth, &conn->request, &user);
if (ret != NWEDAV_OK) {
auth_build_challenge(server->auth, &conn->response);
} else {
webdav_context_t ctx = {
.storage = server->storage,
.properties = server->properties,
.locks = server->locks,
.user_id = user.id,
.quota_bytes = user.quota_bytes,
.used_bytes = user.used_bytes
};
strncpy(ctx.username, user.username, sizeof(ctx.username) - 1);
ctx.username[sizeof(ctx.username) - 1] = '\0';
webdav_dispatch(&ctx, &conn->request, &conn->response);
logger_access(g_logger, conn->client_ip,
http_method_to_string(conn->request.method),
conn->request.uri,
conn->response.status,
conn->response.body_len,
http_get_header(&conn->request, "User-Agent"));
}
}
buffer_t *response_buf = buffer_create(4096);
if (conn->response.sendfile_fd >= 0) {
http_response_build_headers_only(&conn->response, response_buf);
} else {
http_response_build(&conn->response, response_buf);
}
pal_socket_cork(conn->fd, 1);
ssize_t sent = 0;
while ((size_t)sent < response_buf->size) {
ssize_t n = pal_socket_send(conn->fd, response_buf->data + sent,
response_buf->size - sent, 0);
if (n <= 0) break;
sent += n;
}
buffer_destroy(response_buf);
if (conn->response.sendfile_fd >= 0) {
off_t offset = conn->response.sendfile_offset;
size_t remaining = conn->response.sendfile_len;
while (remaining > 0) {
ssize_t n = pal_sendfile(conn->fd, conn->response.sendfile_fd, &offset, remaining);
if (n <= 0) break;
remaining -= n;
}
close(conn->response.sendfile_fd);
conn->response.sendfile_fd = -1;
}
pal_socket_cork(conn->fd, 0);
server->total_requests++;
int keep_alive = conn->keep_alive &&
conn->request.version_major >= 1 &&
conn->request.version_minor >= 1 &&
conn->requests_served < 100;
if (keep_alive) {
pal_mutex_lock(&conn->mutex);
connection_reset(conn);
conn->requests_served++;
conn->state = CONN_STATE_READING_REQUEST;
conn->processing = 0;
pal_mutex_unlock(&conn->mutex);
event_loop_add(server->event_loop, conn->fd, EVENT_READ, conn);
} else {
pal_mutex_lock(&conn->mutex);
conn->processing = 0;
pal_mutex_unlock(&conn->mutex);
connection_close(conn);
connpool_release(server->conn_pool, conn);
}
}
static void accept_connections(server_t *server) {
while (1) {
struct sockaddr_storage addr;
socklen_t addr_len = sizeof(addr);
socket_t client_fd = pal_socket_accept(server->listen_fd,
(struct sockaddr *)&addr, &addr_len);
if (client_fd == SOCKET_INVALID) {
break;
}
connection_t *conn = connpool_acquire(server->conn_pool);
if (!conn) {
LOG_WARN("Connection pool exhausted, rejecting connection");
pal_socket_close(client_fd);
continue;
}
pal_socket_setnonblock(client_fd, 1);
pal_socket_setnodelay(client_fd, 1);
connection_init(conn, client_fd, (struct sockaddr *)&addr, addr_len);
event_loop_add(server->event_loop, client_fd, EVENT_READ, conn);
LOG_DEBUG("Accepted connection from %s", conn->client_ip);
}
}
static void handle_connection_event(server_t *server, connection_t *conn, event_flags_t events) {
pal_mutex_lock(&conn->mutex);
if (conn->processing) {
pal_mutex_unlock(&conn->mutex);
return;
}
pal_mutex_unlock(&conn->mutex);
if (events & (EVENT_ERROR | EVENT_HUP)) {
event_loop_remove(server->event_loop, conn->fd);
connection_close(conn);
connpool_release(server->conn_pool, conn);
return;
}
if (events & EVENT_READ) {
char buf[8192];
ssize_t n;
while ((n = pal_socket_recv(conn->fd, buf, sizeof(buf), 0)) > 0) {
buffer_append(conn->read_buf, buf, n);
}
if (n == 0) {
event_loop_remove(server->event_loop, conn->fd);
connection_close(conn);
connpool_release(server->conn_pool, conn);
return;
}
char *end = memmem(conn->read_buf->data, conn->read_buf->size, "\r\n\r\n", 4);
if (end) {
event_loop_remove(server->event_loop, conn->fd);
pal_mutex_lock(&conn->mutex);
conn->state = CONN_STATE_PROCESSING;
conn->processing = 1;
pal_mutex_unlock(&conn->mutex);
request_task_t *task = malloc(sizeof(request_task_t));
task->server = server;
task->conn = conn;
threadpool_submit(server->thread_pool, handle_request, task);
}
}
}
int server_start(server_t *server) {
if (!server) return NWEDAV_ERROR;
int ret = create_listen_socket(server);
if (ret != NWEDAV_OK) {
return ret;
}
server->running = 1;
g_server = server;
#ifndef _WIN32
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGPIPE, SIG_IGN);
#endif
event_loop_add(server->event_loop, server->listen_fd, EVENT_READ, NULL);
LOG_INFO("Server started on %s:%d", server->config->bind_address, server->config->port);
LOG_INFO("Worker threads: %d", server->config->worker_threads);
LOG_INFO("Max connections: %zu", server->config->max_connections);
LOG_INFO("Storage root: %s", server->config->storage.root_path);
if (server->config->scaling.enabled) {
LOG_INFO("Adaptive scaling: enabled (min=%zu, max=%zu)",
server->config->scaling.min_threads, server->config->scaling.max_threads);
}
return NWEDAV_OK;
}
int server_stop(server_t *server) {
if (!server) return NWEDAV_ERROR;
server->running = 0;
if (server->listen_fd != SOCKET_INVALID) {
pal_socket_close(server->listen_fd);
server->listen_fd = SOCKET_INVALID;
}
LOG_INFO("Server stopped");
return NWEDAV_OK;
}
int server_run(server_t *server) {
if (!server) return NWEDAV_ERROR;
event_t events[64];
while (server->running) {
int n = event_loop_wait(server->event_loop, events, 64, 1000);
if (n < 0) {
if (server->running) {
LOG_ERROR("Event loop error");
}
break;
}
for (int i = 0; i < n; i++) {
if (events[i].user_data == NULL) {
accept_connections(server);
} else {
connection_t *conn = (connection_t *)events[i].user_data;
handle_connection_event(server, conn, events[i].events);
}
}
static time_t last_cleanup = 0;
time_t now = time(NULL);
if (now - last_cleanup > 60) {
lock_manager_cleanup_expired(server->locks);
last_cleanup = now;
}
if (server->config->scaling.enabled) {
threadpool_scale_check(server->thread_pool);
}
}
return NWEDAV_OK;
}
int server_is_running(server_t *server) {
return server ? server->running : 0;
}
void server_stats(server_t *server, size_t *connections, size_t *requests) {
if (connections) {
*connections = connpool_active_count(server->conn_pool);
}
if (requests) {
*requests = server->total_requests;
}
}
void server_threadpool_stats(server_t *server, threadpool_stats_t *stats) {
if (server && server->thread_pool && stats) {
threadpool_get_stats(server->thread_pool, stats);
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef SERVER_H
#define SERVER_H
#include "nwedav.h"
#include "config.h"
#include "logging.h"
#include "../platform/pal.h"
#include "../concurrency/threadpool.h"
#include "../concurrency/eventloop.h"
#include "../concurrency/connpool.h"
#include "../storage/backend.h"
#include "../storage/properties.h"
#include "../storage/locks.h"
#include "../auth/auth.h"
typedef struct server server_t;
server_t *server_create(config_t *config);
void server_destroy(server_t *server);
int server_start(server_t *server);
int server_stop(server_t *server);
int server_run(server_t *server);
int server_is_running(server_t *server);
void server_stats(server_t *server, size_t *connections, size_t *requests);
void server_threadpool_stats(server_t *server, threadpool_stats_t *stats);
#endif
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.