422 lines
11 KiB
C
422 lines
11 KiB
C
|
|
#include "base.h"
|
|
#include "utils.h"
|
|
#include "plugin_core.h"
|
|
|
|
struct server_closing_socket;
|
|
typedef struct server_closing_socket server_closing_socket;
|
|
|
|
struct server_closing_socket {
|
|
server *srv;
|
|
GList *link;
|
|
int fd;
|
|
};
|
|
|
|
static void server_closing_socket_cb(int revents, void* arg) {
|
|
server_closing_socket *scs = (server_closing_socket*) arg;
|
|
UNUSED(revents);
|
|
|
|
/* Whatever happend: we just close the socket */
|
|
shutdown(scs->fd, SHUT_RD);
|
|
close(scs->fd);
|
|
g_queue_delete_link(&scs->srv->closing_sockets, scs->link);
|
|
g_slice_free(server_closing_socket, scs);
|
|
}
|
|
|
|
void server_add_closing_socket(server *srv, int fd) {
|
|
server_closing_socket *scs = g_slice_new0(server_closing_socket);
|
|
|
|
shutdown(fd, SHUT_WR);
|
|
|
|
scs->srv = srv;
|
|
scs->fd = fd;
|
|
g_queue_push_tail(&srv->closing_sockets, scs);
|
|
scs->link = g_queue_peek_tail_link(&srv->closing_sockets);
|
|
|
|
ev_once(srv->loop, fd, EV_READ, 10.0, server_closing_socket_cb, scs);
|
|
}
|
|
|
|
/* Kill it - frees fd */
|
|
static void server_rem_closing_socket(server *srv, server_closing_socket *scs) {
|
|
ev_feed_fd_event(srv->loop, scs->fd, EV_READ);
|
|
}
|
|
|
|
void con_put(server *srv, connection *con);
|
|
|
|
static void server_option_free(gpointer _so) {
|
|
g_slice_free(server_option, _so);
|
|
}
|
|
|
|
static void server_action_free(gpointer _sa) {
|
|
g_slice_free(server_action, _sa);
|
|
}
|
|
|
|
static void server_setup_free(gpointer _ss) {
|
|
g_slice_free(server_setup, _ss);
|
|
}
|
|
|
|
static struct ev_signal
|
|
sig_w_INT,
|
|
sig_w_TERM,
|
|
sig_w_PIPE;
|
|
|
|
static void sigint_cb(struct ev_loop *loop, struct ev_signal *w, int revents) {
|
|
server *srv = (server*) w->data;
|
|
UNUSED(revents);
|
|
|
|
if (!srv->exiting) {
|
|
INFO(srv, "Got signal, shutdown");
|
|
server_exit(srv);
|
|
} else {
|
|
INFO(srv, "Got second signal, force shutdown");
|
|
ev_unloop (loop, EVUNLOOP_ALL);
|
|
}
|
|
}
|
|
|
|
static void sigpipe_cb(struct ev_loop *loop, struct ev_signal *w, int revents) {
|
|
/* ignore */
|
|
UNUSED(loop); UNUSED(w); UNUSED(revents);
|
|
}
|
|
|
|
#define CATCH_SIGNAL(loop, cb, n) do {\
|
|
my_ev_init(&sig_w_##n, cb); \
|
|
ev_signal_set(&sig_w_##n, SIG##n); \
|
|
ev_signal_start(loop, &sig_w_##n); \
|
|
sig_w_##n.data = srv; \
|
|
ev_unref(loop); /* Signal watchers shouldn't keep loop alive */ \
|
|
} while (0)
|
|
|
|
void server_check_keepalive(server *srv) {
|
|
ev_tstamp now = ev_now((srv)->loop);
|
|
|
|
if (0 == srv->keep_alive_queue.length) {
|
|
ev_timer_stop(srv->loop, &srv->keep_alive_timer);
|
|
} else {
|
|
srv->keep_alive_timer.repeat = ((connection*)g_queue_peek_head(&srv->keep_alive_queue))->keep_alive_data.timeout - now + 1;
|
|
ev_timer_again(srv->loop, &srv->keep_alive_timer);
|
|
}
|
|
}
|
|
|
|
static void server_keepalive_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|
server *srv = (server*) w->data;
|
|
ev_tstamp now = ev_now((srv)->loop);
|
|
GQueue *q = &srv->keep_alive_queue;
|
|
GList *l;
|
|
connection *con;
|
|
|
|
UNUSED(loop);
|
|
UNUSED(revents);
|
|
|
|
while ( NULL != (l = g_queue_peek_head_link(q)) &&
|
|
(con = (connection*) l->data)->keep_alive_data.timeout <= now ) {
|
|
guint timeout = GPOINTER_TO_INT(CORE_OPTION(CORE_OPTION_MAX_KEEP_ALIVE_IDLE));
|
|
ev_tstamp remaining = timeout - srv->keep_alive_queue_timeout - (now - con->keep_alive_data.timeout);
|
|
if (remaining > 0) {
|
|
ev_timer_set(&con->keep_alive_data.watcher, remaining, 0);
|
|
ev_timer_start(srv->loop, &con->keep_alive_data.watcher);
|
|
} else {
|
|
/* close it */
|
|
con_put(srv, con);
|
|
}
|
|
}
|
|
|
|
if (NULL == l) {
|
|
ev_timer_stop(srv->loop, &srv->keep_alive_timer);
|
|
} else {
|
|
srv->keep_alive_timer.repeat = con->keep_alive_data.timeout - now + 1;
|
|
ev_timer_again(srv->loop, &srv->keep_alive_timer);
|
|
}
|
|
}
|
|
|
|
server* server_new() {
|
|
server* srv = g_slice_new0(server);
|
|
|
|
srv->magic = LIGHTTPD_SERVER_MAGIC;
|
|
srv->state = SERVER_STARTING;
|
|
|
|
srv->connections_active = 0;
|
|
srv->connections = g_array_new(FALSE, TRUE, sizeof(connection*));
|
|
srv->sockets = g_array_new(FALSE, TRUE, sizeof(server_socket*));
|
|
g_queue_init(&srv->closing_sockets);
|
|
|
|
srv->plugins = g_hash_table_new(g_str_hash, g_str_equal);
|
|
srv->options = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_option_free);
|
|
srv->actions = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_action_free);
|
|
srv->setups = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, server_setup_free);
|
|
|
|
srv->plugins_handle_close = g_array_new(FALSE, TRUE, sizeof(plugin*));
|
|
|
|
srv->mainaction = NULL;
|
|
|
|
srv->exiting = FALSE;
|
|
srv->tmp_str = g_string_sized_new(255);
|
|
|
|
srv->last_generated_date_ts = 0;
|
|
srv->ts_date_str = g_string_sized_new(255);
|
|
|
|
log_init(srv);
|
|
|
|
g_queue_init(&srv->keep_alive_queue);
|
|
my_ev_init(&srv->keep_alive_timer, server_keepalive_cb);
|
|
srv->keep_alive_timer.data = srv;
|
|
|
|
return srv;
|
|
}
|
|
|
|
void server_free(server* srv) {
|
|
if (!srv) return;
|
|
|
|
srv->exiting = TRUE;
|
|
server_stop(srv);
|
|
|
|
{ /* close connections */
|
|
guint i;
|
|
if (srv->connections_active > 0) {
|
|
ERROR(srv, "Server shutdown with unclosed connections: %u", srv->connections_active);
|
|
for (i = srv->connections_active; i-- > 0;) {
|
|
connection *con = g_array_index(srv->connections, connection*, i);
|
|
connection_set_state(srv, con, CON_STATE_ERROR);
|
|
connection_state_machine(srv, con); /* cleanup plugins */
|
|
con_put(srv, con);
|
|
}
|
|
}
|
|
for (i = 0; i < srv->connections->len; i++) {
|
|
connection_free(srv, g_array_index(srv->connections, connection*, i));
|
|
}
|
|
g_array_free(srv->connections, TRUE);
|
|
}
|
|
|
|
{
|
|
guint i; for (i = 0; i < srv->sockets->len; i++) {
|
|
server_socket *sock = g_array_index(srv->sockets, server_socket*, i);
|
|
close(sock->watcher.fd);
|
|
g_slice_free(server_socket, sock);
|
|
}
|
|
g_array_free(srv->sockets, TRUE);
|
|
}
|
|
{ /* force closing sockets */
|
|
GList *iter;
|
|
for (iter = g_queue_peek_head_link(&srv->closing_sockets); iter; iter = g_list_next(iter)) {
|
|
server_closing_socket_cb(EV_TIMEOUT, (server_closing_socket*) iter->data);
|
|
}
|
|
g_queue_clear(&srv->closing_sockets);
|
|
}
|
|
|
|
g_hash_table_destroy(srv->plugins);
|
|
g_hash_table_destroy(srv->options);
|
|
g_hash_table_destroy(srv->actions);
|
|
g_hash_table_destroy(srv->setups);
|
|
|
|
g_array_free(srv->plugins_handle_close, TRUE);
|
|
|
|
action_release(srv, srv->mainaction);
|
|
|
|
g_string_free(srv->tmp_str, TRUE);
|
|
g_string_free(srv->ts_date_str, TRUE);
|
|
|
|
/* free logs */
|
|
g_thread_join(srv->log_thread);
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer k, v;
|
|
g_hash_table_iter_init(&iter, srv->logs);
|
|
while (g_hash_table_iter_next(&iter, &k, &v)) {
|
|
log_free(srv, v);
|
|
}
|
|
g_hash_table_destroy(srv->logs);
|
|
}
|
|
|
|
g_mutex_free(srv->log_mutex);
|
|
g_async_queue_unref(srv->log_queue);
|
|
|
|
g_queue_clear(&srv->keep_alive_queue);
|
|
ev_timer_stop(srv->loop, &srv->keep_alive_timer);
|
|
|
|
g_slice_free(server, srv);
|
|
}
|
|
|
|
gboolean server_loop_init(server *srv) {
|
|
srv->loop = ev_default_loop(srv->loop_flags);
|
|
|
|
if (!srv->loop) {
|
|
fatal ("could not initialise libev, bad $LIBEV_FLAGS in environment?");
|
|
return FALSE;
|
|
}
|
|
|
|
CATCH_SIGNAL(srv->loop, sigint_cb, INT);
|
|
CATCH_SIGNAL(srv->loop, sigint_cb, TERM);
|
|
CATCH_SIGNAL(srv->loop, sigpipe_cb, PIPE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static connection* con_get(server *srv) {
|
|
connection *con;
|
|
if (srv->connections_active >= srv->connections->len) {
|
|
con = connection_new(srv);
|
|
con->idx = srv->connections_active++;
|
|
g_array_append_val(srv->connections, con);
|
|
} else {
|
|
con = g_array_index(srv->connections, connection*, srv->connections_active++);
|
|
}
|
|
return con;
|
|
}
|
|
|
|
void con_put(server *srv, connection *con) {
|
|
connection_reset(srv, con);
|
|
srv->connections_active--;
|
|
if (con->idx != srv->connections_active) {
|
|
/* Swap [con->idx] and [srv->connections_active] */
|
|
connection *tmp;
|
|
assert(con->idx < srv->connections_active); /* con must be an active connection) */
|
|
tmp = g_array_index(srv->connections, connection*, srv->connections_active);
|
|
tmp->idx = con->idx;
|
|
con->idx = srv->connections_active;
|
|
g_array_index(srv->connections, connection*, con->idx) = con;
|
|
g_array_index(srv->connections, connection*, tmp->idx) = tmp;
|
|
}
|
|
}
|
|
|
|
static void server_listen_cb(struct ev_loop *loop, ev_io *w, int revents) {
|
|
server_socket *sock = (server_socket*) w->data;
|
|
server *srv = sock->srv;
|
|
int s;
|
|
sock_addr remote_addr;
|
|
socklen_t l = sizeof(remote_addr);
|
|
UNUSED(loop);
|
|
UNUSED(revents);
|
|
|
|
while (-1 != (s = accept(w->fd, (struct sockaddr*) &remote_addr, &l))) {
|
|
connection *con = con_get(srv);
|
|
con->state = CON_STATE_REQUEST_START;
|
|
con->remote_addr = remote_addr;
|
|
ev_io_set(&con->sock.watcher, s, EV_READ);
|
|
ev_io_start(srv->loop, &con->sock.watcher);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
errno = WSAGetLastError();
|
|
#endif
|
|
|
|
switch (errno) {
|
|
case EAGAIN:
|
|
#if EWOULDBLOCK != EAGAIN
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
case EINTR:
|
|
/* we were stopped _before_ we had a connection */
|
|
case ECONNABORTED: /* this is a FreeBSD thingy */
|
|
/* we were stopped _after_ we had a connection */
|
|
break;
|
|
|
|
case EMFILE: /* we are out of FDs */
|
|
/* TODO: server_out_of_fds(srv, NULL); */
|
|
break;
|
|
default:
|
|
ERROR(srv, "accept failed on fd=%d with error: %s", w->fd, g_strerror(errno));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void server_listen(server *srv, int fd) {
|
|
server_socket *sock;
|
|
|
|
sock = g_slice_new0(server_socket);
|
|
sock->srv = srv;
|
|
sock->watcher.data = sock;
|
|
fd_init(fd);
|
|
my_ev_init(&sock->watcher, server_listen_cb);
|
|
ev_io_set(&sock->watcher, fd, EV_READ);
|
|
if (srv->state == SERVER_RUNNING) ev_io_start(srv->loop, &sock->watcher);
|
|
|
|
g_array_append_val(srv->sockets, sock);
|
|
}
|
|
|
|
void server_start(server *srv) {
|
|
guint i;
|
|
GHashTableIter iter;
|
|
gpointer k, v;
|
|
if (srv->state == SERVER_STOPPING || srv->state == SERVER_RUNNING) return; /* no restart after stop */
|
|
srv->state = SERVER_RUNNING;
|
|
|
|
if (!srv->mainaction) {
|
|
ERROR(srv, "%s", "No action handlers defined");
|
|
server_stop(srv);
|
|
return;
|
|
}
|
|
|
|
srv->keep_alive_queue_timeout = 5;
|
|
|
|
srv->option_count = g_hash_table_size(srv->options);
|
|
srv->option_def_values = g_slice_alloc0(srv->option_count * sizeof(*srv->option_def_values));
|
|
|
|
/* set default option values */
|
|
g_hash_table_iter_init(&iter, srv->options);
|
|
while (g_hash_table_iter_next(&iter, &k, &v)) {
|
|
server_option *so = v;
|
|
srv->option_def_values[so->index] = so->default_value;
|
|
}
|
|
|
|
plugins_prepare_callbacks(srv);
|
|
|
|
for (i = 0; i < srv->sockets->len; i++) {
|
|
server_socket *sock = g_array_index(srv->sockets, server_socket*, i);
|
|
ev_io_start(srv->loop, &sock->watcher);
|
|
}
|
|
|
|
srv->started = ev_now(srv->loop);
|
|
|
|
log_thread_start(srv);
|
|
|
|
ev_loop(srv->loop, 0);
|
|
}
|
|
|
|
void server_stop(server *srv) {
|
|
guint i;
|
|
if (srv->state == SERVER_STOPPING) return;
|
|
srv->state = SERVER_STOPPING;
|
|
|
|
for (i = 0; i < srv->sockets->len; i++) {
|
|
server_socket *sock = g_array_index(srv->sockets, server_socket*, i);
|
|
ev_io_stop(srv->loop, &sock->watcher);
|
|
}
|
|
|
|
for (i = srv->connections_active; i-- > 0;) {
|
|
connection *con = g_array_index(srv->connections, connection*, i);
|
|
if (con->state == CON_STATE_KEEP_ALIVE)
|
|
con_put(srv, con);
|
|
}
|
|
}
|
|
|
|
void server_exit(server *srv) {
|
|
g_atomic_int_set(&srv->exiting, TRUE);
|
|
server_stop(srv);
|
|
|
|
{ /* force closing sockets */
|
|
GList *iter;
|
|
for (iter = g_queue_peek_head_link(&srv->closing_sockets); iter; iter = g_list_next(iter)) {
|
|
server_rem_closing_socket(srv, (server_closing_socket*) iter->data);
|
|
}
|
|
}
|
|
|
|
log_thread_wakeup(srv);
|
|
}
|
|
|
|
void joblist_append(server *srv, connection *con) {
|
|
connection_state_machine(srv, con);
|
|
}
|
|
|
|
GString *server_current_timestamp(server *srv) {
|
|
time_t cur_ts = CUR_TS(srv);
|
|
if (cur_ts != srv->last_generated_date_ts) {
|
|
g_string_set_size(srv->ts_date_str, 255);
|
|
strftime(srv->ts_date_str->str, srv->ts_date_str->allocated_len,
|
|
"%a, %d %b %Y %H:%M:%S GMT", gmtime(&(cur_ts)));
|
|
|
|
g_string_set_size(srv->ts_date_str, strlen(srv->ts_date_str->str));
|
|
|
|
srv->last_generated_date_ts = cur_ts;
|
|
}
|
|
return srv->ts_date_str;
|
|
}
|