This commit is contained in:
parent
14ebbbec3f
commit
378f64fc75
134
src/config.c
134
src/config.c
@ -29,7 +29,8 @@ static void compute_password_hash(const char *password, char *output, size_t out
|
||||
}
|
||||
}
|
||||
|
||||
app_config_t config;
|
||||
app_config_t *config = NULL;
|
||||
static app_config_t *stale_configs_head = NULL;
|
||||
|
||||
static int is_valid_hostname(const char *hostname) {
|
||||
if (!hostname || strlen(hostname) == 0 || strlen(hostname) > 253) return 0;
|
||||
@ -118,26 +119,37 @@ int config_load(const char *filename) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
config.port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
if (!new_config) {
|
||||
log_error("Failed to allocate memory for new config");
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
new_config->ref_count = 1; // Start with one reference for the global 'config' pointer
|
||||
|
||||
if (config.port < 1 || config.port > 65535) {
|
||||
fprintf(stderr, "Invalid port number: %d\n", config.port);
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
new_config->port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
|
||||
if (new_config->port < 1 || new_config->port > 65535) {
|
||||
fprintf(stderr, "Invalid port number: %d\n", new_config->port);
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *proxy_array = cJSON_GetObjectItem(root, "reverse_proxy");
|
||||
if (cJSON_IsArray(proxy_array)) {
|
||||
config.route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (config.route_count <= 0) {
|
||||
new_config->route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (new_config->route_count <= 0) {
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
config.routes = calloc(config.route_count, sizeof(route_config_t));
|
||||
if (!config.routes) {
|
||||
new_config->routes = calloc(new_config->route_count, sizeof(route_config_t));
|
||||
if (!new_config->routes) {
|
||||
log_error("Failed to allocate memory for routes");
|
||||
free(new_config);
|
||||
cJSON_Delete(root);
|
||||
return 0;
|
||||
}
|
||||
@ -145,7 +157,7 @@ int config_load(const char *filename) {
|
||||
int i = 0;
|
||||
cJSON *route_item;
|
||||
cJSON_ArrayForEach(route_item, proxy_array) {
|
||||
route_config_t *route = &config.routes[i];
|
||||
route_config_t *route = &new_config->routes[i];
|
||||
|
||||
cJSON *hostname = cJSON_GetObjectItem(route_item, "hostname");
|
||||
cJSON *upstream_host = cJSON_GetObjectItem(route_item, "upstream_host");
|
||||
@ -232,7 +244,7 @@ int config_load(const char *filename) {
|
||||
route->patches.rule_count++;
|
||||
}
|
||||
if (route->patches.rule_count > 0) {
|
||||
log_info("Loaded %d patch rules for %s", route->patches.rule_count, route->hostname);
|
||||
log_info("Loaded %d patch rules for %s", route->hostname);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,16 +256,47 @@ int config_load(const char *filename) {
|
||||
}
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
log_info("Loaded %d routes from %s", config.route_count, filename);
|
||||
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
}
|
||||
config = new_config;
|
||||
|
||||
log_info("Loaded %d routes from %s", config->route_count, filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void config_free(void) {
|
||||
if (config.routes) {
|
||||
free(config.routes);
|
||||
config.routes = NULL;
|
||||
void config_ref_inc(app_config_t *conf) {
|
||||
if (conf) {
|
||||
__sync_fetch_and_add(&conf->ref_count, 1);
|
||||
}
|
||||
config.route_count = 0;
|
||||
}
|
||||
|
||||
void config_ref_dec(app_config_t *conf) {
|
||||
if (!conf) return;
|
||||
|
||||
if (__sync_sub_and_fetch(&conf->ref_count, 1) == 0) {
|
||||
log_debug("Freeing configuration with port %d", conf->port);
|
||||
if (conf->routes) {
|
||||
free(conf->routes);
|
||||
}
|
||||
free(conf);
|
||||
}
|
||||
}
|
||||
|
||||
void config_free(void) {
|
||||
if (config) {
|
||||
config_ref_dec(config);
|
||||
config = NULL;
|
||||
}
|
||||
|
||||
app_config_t *current = stale_configs_head;
|
||||
while (current) {
|
||||
app_config_t *next = current->next;
|
||||
config_ref_dec(current);
|
||||
current = next;
|
||||
}
|
||||
stale_configs_head = NULL;
|
||||
}
|
||||
|
||||
void config_create_default(const char *filename) {
|
||||
@ -294,11 +337,18 @@ void config_create_default(const char *filename) {
|
||||
|
||||
route_config_t *config_find_route(const char *hostname) {
|
||||
if (!hostname) return NULL;
|
||||
|
||||
pthread_rwlock_rdlock(&config_lock);
|
||||
app_config_t *current_config = config;
|
||||
if (!current_config) {
|
||||
pthread_rwlock_unlock(&config_lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
route_config_t *result = NULL;
|
||||
for (int i = 0; i < config.route_count; i++) {
|
||||
if (strcasecmp(hostname, config.routes[i].hostname) == 0) {
|
||||
result = &config.routes[i];
|
||||
for (int i = 0; i < current_config->route_count; i++) {
|
||||
if (strcasecmp(hostname, current_config->routes[i].hostname) == 0) {
|
||||
result = ¤t_config->routes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -325,12 +375,17 @@ int config_check_file_changed(const char *filename) {
|
||||
int config_hot_reload(const char *filename) {
|
||||
log_info("Hot-reloading configuration from %s", filename);
|
||||
|
||||
app_config_t new_config;
|
||||
memset(&new_config, 0, sizeof(app_config_t));
|
||||
app_config_t *new_config = calloc(1, sizeof(app_config_t));
|
||||
if (!new_config) {
|
||||
log_error("Hot-reload: Failed to allocate memory for new config");
|
||||
return 0;
|
||||
}
|
||||
new_config->ref_count = 1;
|
||||
|
||||
char *json_string = read_file_to_string(filename);
|
||||
if (!json_string) {
|
||||
log_error("Hot-reload: Could not read config file");
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -338,37 +393,41 @@ int config_hot_reload(const char *filename) {
|
||||
free(json_string);
|
||||
if (!root) {
|
||||
log_error("Hot-reload: JSON parse error: %s", cJSON_GetErrorPtr());
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *port_item = cJSON_GetObjectItem(root, "port");
|
||||
new_config.port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
new_config->port = cJSON_IsNumber(port_item) ? port_item->valueint : 8080;
|
||||
|
||||
if (new_config.port < 1 || new_config.port > 65535) {
|
||||
log_error("Hot-reload: Invalid port number: %d", new_config.port);
|
||||
if (new_config->port < 1 || new_config->port > 65535) {
|
||||
log_error("Hot-reload: Invalid port number: %d", new_config->port);
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON *proxy_array = cJSON_GetObjectItem(root, "reverse_proxy");
|
||||
if (cJSON_IsArray(proxy_array)) {
|
||||
new_config.route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (new_config.route_count <= 0) {
|
||||
new_config->route_count = cJSON_GetArraySize(proxy_array);
|
||||
if (new_config->route_count <= 0) {
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
new_config.routes = calloc(new_config.route_count, sizeof(route_config_t));
|
||||
if (!new_config.routes) {
|
||||
new_config->routes = calloc(new_config->route_count, sizeof(route_config_t));
|
||||
if (!new_config->routes) {
|
||||
log_error("Hot-reload: Failed to allocate memory for routes");
|
||||
cJSON_Delete(root);
|
||||
free(new_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
cJSON *route_item;
|
||||
cJSON_ArrayForEach(route_item, proxy_array) {
|
||||
route_config_t *route = &new_config.routes[i];
|
||||
route_config_t *route = &new_config->routes[i];
|
||||
|
||||
cJSON *hostname = cJSON_GetObjectItem(route_item, "hostname");
|
||||
cJSON *upstream_host = cJSON_GetObjectItem(route_item, "upstream_host");
|
||||
@ -451,21 +510,22 @@ int config_hot_reload(const char *filename) {
|
||||
route->patches.rule_count);
|
||||
i++;
|
||||
}
|
||||
new_config.route_count = i;
|
||||
new_config->route_count = i;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
pthread_rwlock_wrlock(&config_lock);
|
||||
route_config_t *old_routes = config.routes;
|
||||
config.routes = new_config.routes;
|
||||
config.route_count = new_config.route_count;
|
||||
app_config_t *old_config = config;
|
||||
config = new_config;
|
||||
pthread_rwlock_unlock(&config_lock);
|
||||
|
||||
if (old_routes) {
|
||||
free(old_routes);
|
||||
if (old_config) {
|
||||
old_config->next = stale_configs_head;
|
||||
stale_configs_head = old_config;
|
||||
config_ref_dec(old_config);
|
||||
}
|
||||
|
||||
log_info("Hot-reload complete: %d routes loaded", new_config.route_count);
|
||||
log_info("Hot-reload complete: %d routes loaded", new_config->route_count);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
#define CONFIG_RELOAD_INTERVAL_SECONDS 3
|
||||
|
||||
extern app_config_t config;
|
||||
extern app_config_t *config;
|
||||
|
||||
int config_load(const char *filename);
|
||||
void config_free(void);
|
||||
@ -14,4 +14,7 @@ route_config_t *config_find_route(const char *hostname);
|
||||
int config_check_file_changed(const char *filename);
|
||||
int config_hot_reload(const char *filename);
|
||||
|
||||
void config_ref_inc(app_config_t *conf);
|
||||
void config_ref_dec(app_config_t *conf);
|
||||
|
||||
#endif
|
||||
|
||||
@ -199,6 +199,10 @@ void connection_close(int fd) {
|
||||
buffer_free(&conn->read_buf);
|
||||
buffer_free(&conn->write_buf);
|
||||
|
||||
if (conn->config) {
|
||||
config_ref_dec(conn->config);
|
||||
}
|
||||
|
||||
memset(conn, 0, sizeof(connection_t));
|
||||
conn->type = CONN_TYPE_UNUSED;
|
||||
conn->fd = -1;
|
||||
@ -375,12 +379,21 @@ static int try_upstream_connect(struct sockaddr_in *addr, int *out_fd) {
|
||||
void connection_connect_to_upstream(connection_t *client, const char *data, size_t data_len) {
|
||||
if (!client || !data) return;
|
||||
|
||||
app_config_t *current_config = config;
|
||||
if (!current_config) {
|
||||
connection_send_error_response(client, 503, "Service Unavailable", "Configuration not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
route_config_t *route = config_find_route(client->request.host);
|
||||
if (!route) {
|
||||
connection_send_error_response(client, 502, "Bad Gateway", "No route configured for this host");
|
||||
return;
|
||||
}
|
||||
|
||||
client->config = current_config;
|
||||
config_ref_inc(client->config);
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
@ -475,11 +488,19 @@ void connection_connect_to_upstream(connection_t *client, const char *data, size
|
||||
while(current < end) {
|
||||
const char* line_end = memchr(current, '\n', end - current);
|
||||
if (!line_end) break;
|
||||
if (strncasecmp(current, "Host:", 5) == 0) {
|
||||
old_host_header_start = current;
|
||||
old_host_header_end = line_end + 1;
|
||||
break;
|
||||
|
||||
if (strncasecmp(current, "Host", 4) == 0) {
|
||||
const char *colon = current + 4;
|
||||
while (colon < line_end && (*colon == ' ' || *colon == '\t')) {
|
||||
colon++;
|
||||
}
|
||||
if (colon < line_end && *colon == ':') {
|
||||
old_host_header_start = current;
|
||||
old_host_header_end = line_end + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
current = line_end + 1;
|
||||
}
|
||||
|
||||
|
||||
26
src/main.c
26
src/main.c
@ -32,23 +32,7 @@ static void signal_handler(int sig) {
|
||||
|
||||
static void reload_configuration(void) {
|
||||
if (!g_config_file) return;
|
||||
|
||||
log_info("Reloading configuration from %s", g_config_file);
|
||||
|
||||
app_config_t old_config = config;
|
||||
memset(&config, 0, sizeof(app_config_t));
|
||||
|
||||
if (!config_load(g_config_file)) {
|
||||
log_error("Failed to reload configuration, keeping old config");
|
||||
config = old_config;
|
||||
return;
|
||||
}
|
||||
|
||||
if (old_config.routes) {
|
||||
free(old_config.routes);
|
||||
}
|
||||
|
||||
log_info("Configuration reloaded successfully");
|
||||
config_hot_reload(g_config_file);
|
||||
}
|
||||
|
||||
static void graceful_shutdown(void) {
|
||||
@ -185,11 +169,11 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
connection_init_all();
|
||||
connection_setup_listener(config.port);
|
||||
connection_setup_listener(config->port);
|
||||
|
||||
log_info("Port %d", config.port);
|
||||
log_info("Dashboard: http://localhost:%d/rproxy/dashboard", config.port);
|
||||
log_info("Stats: http://localhost:%d/rproxy/api/stats", config.port);
|
||||
log_info("Port %d", config->port);
|
||||
log_info("Dashboard: http://localhost:%d/rproxy/dashboard", config->port);
|
||||
log_info("Stats: http://localhost:%d/rproxy/api/stats", config->port);
|
||||
log_info("Send SIGHUP to reload configuration");
|
||||
|
||||
atexit(cleanup);
|
||||
|
||||
@ -70,6 +70,7 @@ struct connection_s;
|
||||
struct vhost_stats_s;
|
||||
|
||||
struct route_config_s;
|
||||
struct app_config_s;
|
||||
|
||||
typedef struct connection_s {
|
||||
conn_type_t type;
|
||||
@ -87,6 +88,7 @@ typedef struct connection_s {
|
||||
int half_closed;
|
||||
int write_shutdown;
|
||||
struct route_config_s *route;
|
||||
struct app_config_s *config;
|
||||
int is_textual_content;
|
||||
int content_type_checked;
|
||||
int patch_blocked;
|
||||
@ -120,10 +122,12 @@ typedef struct route_config_s {
|
||||
patch_config_t patches;
|
||||
} route_config_t;
|
||||
|
||||
typedef struct {
|
||||
typedef struct app_config_s {
|
||||
int port;
|
||||
route_config_t *routes;
|
||||
int route_count;
|
||||
_Atomic int ref_count;
|
||||
struct app_config_s *next;
|
||||
} app_config_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user