From c458377d4a8b0dd06c1773aaa41121be253bcd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 1 Jan 2009 16:44:42 +0100 Subject: [PATCH] Added mod_fastcgi, some api functions for it, and some new optional compiler warnings (which led to many small changes) --- include/lighttpd/chunk.h | 4 +- include/lighttpd/connection.h | 1 + include/lighttpd/environment.h | 3 + include/lighttpd/plugin.h | 16 +- include/lighttpd/request.h | 2 +- include/lighttpd/server.h | 1 + include/lighttpd/sys-socket.h | 5 + include/lighttpd/utils.h | 3 + include/lighttpd/virtualrequest.h | 6 +- src/CMakeLists.txt | 14 +- src/actions.c | 8 +- src/chunk.c | 39 +- src/chunk_parser.c | 3 +- src/condition_lua.c | 15 +- src/config_parser.rl | 3 +- src/connection.c | 6 +- src/environment.c | 9 + src/http_headers.c | 2 +- src/lighttpd.c | 6 +- src/log.c | 7 +- src/modules/mod_balancer.c | 15 +- src/modules/mod_fastcgi.c | 681 ++++++++++++++++++++++++++++++ src/modules/mod_fortune.c | 11 +- src/modules/mod_status.c | 18 +- src/network_sendfile.c | 2 +- src/plugin.c | 17 +- src/plugin_core.c | 9 +- src/profiler.c | 15 +- src/request.c | 9 +- src/response.c | 7 +- src/server.c | 18 +- src/url_parser.rl | 1 + src/utils.c | 44 +- src/value.c | 2 +- src/virtualrequest.c | 38 +- src/worker.c | 9 +- 36 files changed, 953 insertions(+), 96 deletions(-) create mode 100644 src/modules/mod_fastcgi.c diff --git a/include/lighttpd/chunk.h b/include/lighttpd/chunk.h index 205d2dc..c0b3c18 100644 --- a/include/lighttpd/chunk.h +++ b/include/lighttpd/chunk.h @@ -99,7 +99,7 @@ LI_API void chunkqueue_free(chunkqueue *cq); LI_API void chunkqueue_append_string(chunkqueue *cq, GString *str); /* memory gets copied */ -LI_API void chunkqueue_append_mem(chunkqueue *cq, void *mem, gssize len); +LI_API void chunkqueue_append_mem(chunkqueue *cq, const void *mem, gssize len); /* pass ownership of filename, do not free it */ LI_API void chunkqueue_append_file(chunkqueue *cq, GString *filename, off_t start, off_t length); @@ -135,6 +135,8 @@ INLINE chunkiter chunkqueue_iter(chunkqueue *cq); INLINE chunk* chunkqueue_first_chunk(chunkqueue *cq); +LI_API gboolean chunkqueue_extract_to(vrequest *vr, chunkqueue *cq, goffset len, GString *dest); + /******************** * Inline functions * ********************/ diff --git a/include/lighttpd/connection.h b/include/lighttpd/connection.h index 8fcd278..f1c440f 100644 --- a/include/lighttpd/connection.h +++ b/include/lighttpd/connection.h @@ -82,6 +82,7 @@ LI_API void connection_reset_keep_alive(connection *con); LI_API void connection_free(connection *con); LI_API void connection_error(connection *con); +LI_API void connection_internal_error(connection *con); LI_API void connection_handle_direct(connection *con); LI_API void connection_handle_indirect(connection *con, plugin *p); diff --git a/include/lighttpd/environment.h b/include/lighttpd/environment.h index 207478f..d4df98e 100644 --- a/include/lighttpd/environment.h +++ b/include/lighttpd/environment.h @@ -13,7 +13,10 @@ LI_API void environment_init(environment *env); LI_API void environment_reset(environment *env); LI_API void environment_clear(environment *env); +/* overwrite previous value */ LI_API void environment_set(environment *env, const gchar *key, size_t keylen, const gchar *val, size_t valuelen); +/* do not overwrite */ +LI_API void environment_insert(environment *env, const gchar *key, size_t keylen, const gchar *val, size_t valuelen); LI_API void environment_remove(environment *env, const gchar *key, size_t keylen); LI_API GString* environment_get(environment *env, const gchar *key, size_t keylen); diff --git a/include/lighttpd/plugin.h b/include/lighttpd/plugin.h index f17fb92..9515f17 100644 --- a/include/lighttpd/plugin.h +++ b/include/lighttpd/plugin.h @@ -19,12 +19,14 @@ typedef void (*PluginFreeOption) (server *srv, plugin *p, size_t ndx, opt typedef action* (*PluginCreateAction) (server *srv, plugin *p, value *val); typedef gboolean (*PluginSetup) (server *srv, plugin *p, value *val); -typedef void (*PluginHandleContent) (connection *con, plugin *p); typedef void (*PluginHandleClose) (connection *con, plugin *p); +typedef handler_t(*PluginHandleVRequest)(vrequest *vr, plugin *p); +typedef void (*PluginHandleVRClose) (vrequest *vr, plugin *p); struct plugin { size_t version; const gchar *name; /**< name of the plugin */ + guint id; /**< index in some plugin arrays */ gpointer data; /**< private plugin data */ @@ -32,19 +34,16 @@ struct plugin { PluginFree free; /**< called before plugin is unloaded */ - /** called if plugin registered as indirect handler with connection_handle_indirect(srv, con, p) - * - after response headers are created: - * connection_set_state(con, CON_STATE_HANDLE_RESPONSE_HEADER) - * - after content is generated close output queue: - * con->out->is_closed = TRUE - */ - PluginHandleContent handle_content; + PluginHandleVRequest handle_request_body; /** called for every plugin after connection got closed (response end, reset by peer, error) * the plugins code must not depend on any order of plugins loaded */ PluginHandleClose handle_close; + /** called for every plugin after vrequest got reset */ + PluginHandleVRClose handle_vrclose; + const plugin_option *options; const plugin_action *actions; const plugin_setup *setups; @@ -123,6 +122,7 @@ LI_API void release_option(server *srv, option_set *mark); /**< Does not free th LI_API void plugins_prepare_callbacks(server *srv); LI_API void plugins_handle_close(connection *con); +LI_API void plugins_handle_vrclose(vrequest *vr); /* Needed for config frontends */ /** For parsing 'somemod.option = "somevalue"', free value after call */ diff --git a/include/lighttpd/request.h b/include/lighttpd/request.h index 9a474ee..db95212 100644 --- a/include/lighttpd/request.h +++ b/include/lighttpd/request.h @@ -10,7 +10,7 @@ struct request_uri { GString *scheme; GString *authority; - GString *path; + GString *path, *orig_path; GString *query; GString *host; /* without userinfo and port */ diff --git a/include/lighttpd/server.h b/include/lighttpd/server.h index 2ce41e6..458eb04 100644 --- a/include/lighttpd/server.h +++ b/include/lighttpd/server.h @@ -48,6 +48,7 @@ struct server { GHashTable *setups; /**< const gchar* => (server_setup*) */ GArray *plugins_handle_close; /** list of handle_close callbacks */ + GArray *plugins_handle_vrclose; /** list of handle_vrclose callbacks */ GArray *option_def_values;/** array of option_value */ struct action *mainaction; diff --git a/include/lighttpd/sys-socket.h b/include/lighttpd/sys-socket.h index 56b395a..e618894 100644 --- a/include/lighttpd/sys-socket.h +++ b/include/lighttpd/sys-socket.h @@ -77,4 +77,9 @@ typedef union { struct sockaddr plain; } sock_addr; +typedef struct { + socklen_t len; + sock_addr *addr; +} sockaddr; + #endif diff --git a/include/lighttpd/utils.h b/include/lighttpd/utils.h index f49a6a6..c9afb03 100644 --- a/include/lighttpd/utils.h +++ b/include/lighttpd/utils.h @@ -55,4 +55,7 @@ LI_API GString *mimetype_get(vrequest *vr, GString *filename); /* converts a sock_addr to a human readable string. ipv4 and ipv6 supported. if dest is NULL, a new string will be allocated */ LI_API GString *sockaddr_to_string(sock_addr *saddr, GString *dest); +LI_API sockaddr sockaddr_from_string(GString *str, guint tcp_default_port); +LI_API void sockaddr_clear(sockaddr *saddr); + #endif diff --git a/include/lighttpd/virtualrequest.h b/include/lighttpd/virtualrequest.h index 232a044..d34b4ed 100644 --- a/include/lighttpd/virtualrequest.h +++ b/include/lighttpd/virtualrequest.h @@ -35,6 +35,7 @@ typedef enum { typedef handler_t (*filter_handler)(vrequest *vr, filter *f, plugin *p); typedef handler_t (*vrequest_handler)(vrequest *vr); +typedef handler_t (*vrequest_plugin_handler)(vrequest *vr, plugin *p); struct filter { chunkqueue *in, *out; @@ -57,11 +58,12 @@ struct vrequest { vrequest_state state; vrequest_handler - handle_request_headers, handle_request_body, + handle_request_headers, handle_response_headers, handle_response_body, handle_response_error; /* this is _not_ for 500 - internal error */ GPtrArray *plugin_ctx; + plugin *backend; request request; physical physical; @@ -113,7 +115,7 @@ LI_API void vrequest_handle_response_body(vrequest *vr); /* response completely ready */ LI_API gboolean vrequest_handle_direct(vrequest *vr); /* handle request over time */ -LI_API gboolean vrequest_handle_indirect(vrequest *vr, vrequest_handler handle_request_body); +LI_API gboolean vrequest_handle_indirect(vrequest *vr, plugin *p); LI_API void vrequest_state_machine(vrequest *vr); LI_API void vrequest_joblist_append(vrequest *vr); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1424a7..8183fa6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,8 @@ OPTION(WITH_OPENSSL "with openssl-support [default: on]" ON) OPTION(WITH_PCRE "with regex support [default: on]" ON) OPTION(WITH_LUA "with lua 5.1 for lua-configfile [default: on]" ON) OPTION(BUILD_STATIC "build a static lighttpd with all modules added") +OPTION(BUILD_EXTRA_WARNINGS "extra warnings") + IF(BUILD_STATIC) SET(LIGHTTPD_STATIC 1) ELSE(BUILD_STATIC) @@ -332,10 +334,18 @@ ADD_EXECUTABLE(lighttpd ) SET(L_INSTALL_TARGETS ${L_INSTALL_TARGETS} lighttpd) -SET(COMMON_LDFLAGS "${LUA_LDFLAGS} ${EV_LDFLAGS} ${GTHREAD_LDFLAGS} ${GMODULE_LDFLAGS}") -SET(COMMON_CFLAGS "${LUA_CFLAGS} ${EV_CFLAGS} ${GTHREAD_CFLAGS} ${GMODULE_CFLAGS}") +IF(BUILD_EXTRA_WARNINGS) + SET(WARN_FLAGS "-g -O2 -g2 -Wall -Wmissing-prototypes -Wmissing-declarations -Wdeclaration-after-statement -Wno-pointer-sign -Wcast-align -Winline -Wsign-compare -Wnested-externs -Wpointer-arith -Wl,--as-needed") + # -Werror -Wbad-function-cast +ELSE(BUILD_EXTRA_WARNINGS) + SET(WARN_FLAGS "") +ENDIF(BUILD_EXTRA_WARNINGS) + +SET(COMMON_LDFLAGS "${LUA_LDFLAGS} ${EV_LDFLAGS} ${GTHREAD_LDFLAGS} ${GMODULE_LDFLAGS} ${WARN_FLAGS}") +SET(COMMON_CFLAGS "${LUA_CFLAGS} ${EV_CFLAGS} ${GTHREAD_CFLAGS} ${GMODULE_CFLAGS} ${WARN_FLAGS}") ADD_AND_INSTALL_LIBRARY(mod_balancer "modules/mod_balancer.c") +ADD_AND_INSTALL_LIBRARY(mod_fastcgi "modules/mod_fastcgi.c") ADD_AND_INSTALL_LIBRARY(mod_fortune "modules/mod_fortune.c") ADD_AND_INSTALL_LIBRARY(mod_status "modules/mod_status.c") diff --git a/src/actions.c b/src/actions.c index b12d16e..8397bc1 100644 --- a/src/actions.c +++ b/src/actions.c @@ -14,8 +14,8 @@ struct action_stack_element { }; void action_release(server *srv, action *a) { - if (!a) return; guint i; + if (!a) return; assert(g_atomic_int_get(&a->refcount) > 0); if (g_atomic_int_dec_and_test(&a->refcount)) { switch (a->type) { @@ -192,6 +192,9 @@ handler_t action_execute(vrequest *vr) { while (NULL != (ase = action_stack_top(as))) { if (as->backend_failed) { + vr->state = VRS_HANDLE_REQUEST_HEADERS; + vr->backend = NULL; + /* pop top action in every case (if the balancer itself failed we don't want to restart it) */ action_stack_pop(srv, vr, as); while (NULL != (ase = action_stack_top(as)) && (ase->act->type != ACTION_TBALANCER || !ase->act->data.balancer.provide_backlog)) { @@ -206,6 +209,7 @@ handler_t action_execute(vrequest *vr) { as->backend_failed = FALSE; ase->finished = FALSE; + a = ase->act; res = a->data.balancer.fallback(vr, ase->backlog_provided, a->data.balancer.param, &ase->data.context, as->backend_error); switch (res) { case HANDLER_GO_ON: @@ -232,8 +236,8 @@ handler_t action_execute(vrequest *vr) { continue; } - a = ase->act; vr->con->wrk->stats.actions_executed++; + a = ase->act; switch (a->type) { case ACTION_TSETTING: diff --git a/src/chunk.c b/src/chunk.c index a041ac6..6905423 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -280,7 +280,7 @@ void chunkqueue_append_string(chunkqueue *cq, GString *str) { } /* memory gets copied */ -void chunkqueue_append_mem(chunkqueue *cq, void *mem, gssize len) { +void chunkqueue_append_mem(chunkqueue *cq, const void *mem, gssize len) { chunk *c; if (!len) return; c = chunk_new(); @@ -383,6 +383,8 @@ goffset chunkqueue_steal_len(chunkqueue *out, chunkqueue *in, goffset length) { /* steal all chunks from in and put them into out, return number of bytes stolen */ goffset chunkqueue_steal_all(chunkqueue *out, chunkqueue *in) { + goffset len; + /* if in->queue is empty, do nothing */ if (!in->length) return 0; /* if out->queue is empty, just swap in->queue/out->queue */ @@ -399,7 +401,7 @@ goffset chunkqueue_steal_all(chunkqueue *out, chunkqueue *in) { g_queue_init(in->queue); } /* count bytes in chunkqueues */ - goffset len = in->length; + len = in->length; in->bytes_out += len; in->length = 0; out->bytes_in += len; @@ -458,3 +460,36 @@ goffset chunkqueue_skip_all(chunkqueue *cq) { return bytes; } + +gboolean chunkqueue_extract_to(vrequest *vr, chunkqueue *cq, goffset len, GString *dest) { + chunkiter ci; + goffset coff, clen; + g_string_set_size(dest, 0); + if (len > cq->length) return FALSE; + + ci = chunkqueue_iter(cq); + coff = 0; + clen = chunkiter_length(ci); + + while (len > 0) { + coff = 0; + clen = chunkiter_length(ci); + while (coff < clen) { + gchar *buf; + off_t we_have; + if (HANDLER_GO_ON != chunkiter_read(vr, ci, coff, len, &buf, &we_have)) goto error; + g_string_append_len(dest, buf, we_have); + coff += we_have; + len -= we_have; + if (len <= 0) return TRUE; + } + chunkiter_next(&ci); + } + + return TRUE; + +error: + g_string_assign(dest, ""); + return FALSE; +} + diff --git a/src/chunk_parser.c b/src/chunk_parser.c index 1a3d602..19d4e41 100644 --- a/src/chunk_parser.c +++ b/src/chunk_parser.c @@ -53,9 +53,10 @@ void chunk_parser_done(chunk_parser_ctx *ctx, goffset len) { } gboolean chunk_extract_to(vrequest *vr, chunk_parser_mark from, chunk_parser_mark to, GString *dest) { + chunk_parser_mark i; + g_string_set_size(dest, 0); - chunk_parser_mark i; for ( i = from; i.ci.element != to.ci.element; chunkiter_next(&i.ci) ) { goffset len = chunkiter_length(i.ci); while (i.pos < len) { diff --git a/src/condition_lua.c b/src/condition_lua.c index cdc8f9d..c5e3fcf 100644 --- a/src/condition_lua.c +++ b/src/condition_lua.c @@ -67,7 +67,7 @@ condition* lua_get_condition(lua_State *L, int ndx) { return *(condition**) lua_touserdata(L, ndx); } -condition_lvalue* lua_get_condition_lvalue(lua_State *L, int ndx) { +static condition_lvalue* lua_get_condition_lvalue(lua_State *L, int ndx) { if (!lua_isuserdata(L, ndx)) return NULL; if (!lua_getmetatable(L, ndx)) return NULL; luaL_getmetatable(L, LUA_COND_LVALUE); @@ -79,7 +79,7 @@ condition_lvalue* lua_get_condition_lvalue(lua_State *L, int ndx) { return *(condition_lvalue**) lua_touserdata(L, ndx); } -cond_lvalue_t lua_get_cond_lvalue_t(lua_State *L, int ndx) { +static cond_lvalue_t lua_get_cond_lvalue_t(lua_State *L, int ndx) { if (!lua_isuserdata(L, ndx)) return -1; if (!lua_getmetatable(L, ndx)) return -1; luaL_getmetatable(L, LUA_COND_LVALUE_T); @@ -152,9 +152,12 @@ static int lua_cond_lvalue_cmp(lua_State *L) { server *srv; GString *sval; condition *c; - condition_lvalue *lvalue = lua_get_condition_lvalue(L, 1); + condition_lvalue *lvalue; + comp_operator_t cmpop; + + lvalue = lua_get_condition_lvalue(L, 1); srv = (server*) lua_touserdata(L, lua_upvalueindex(1)); - comp_operator_t cmpop = (comp_operator_t) lua_tointeger(L, lua_upvalueindex(2)); + cmpop = (comp_operator_t) lua_tointeger(L, lua_upvalueindex(2)); if (NULL == (sval = lua_togstring(L, 2))) return 0; c = condition_new_string(srv, cmpop, lvalue, sval); @@ -191,7 +194,7 @@ static void lua_push_cond_lvalue_metatable(server *srv, lua_State *L) { } } -int lua_push_cond_lvalue(server *srv, lua_State *L, condition_lvalue *lvalue) { +static int lua_push_cond_lvalue(server *srv, lua_State *L, condition_lvalue *lvalue) { condition_lvalue **pv; pv = (condition_lvalue**) lua_newuserdata(L, sizeof(condition_lvalue*)); @@ -231,7 +234,7 @@ static void lua_push_cond_lvalue_t_metatable(server *srv, lua_State *L) { /* cond_lvalue_t */ -int lua_push_cond_lvalue_t(server *srv, lua_State *L, cond_lvalue_t t) { +static int lua_push_cond_lvalue_t(server *srv, lua_State *L, cond_lvalue_t t) { cond_lvalue_t *pt; pt = (cond_lvalue_t*) lua_newuserdata(L, sizeof(cond_lvalue_t)); diff --git a/src/config_parser.rl b/src/config_parser.rl index a974a79..5677fa1 100644 --- a/src/config_parser.rl +++ b/src/config_parser.rl @@ -1023,11 +1023,12 @@ void config_parser_finish(server *srv, GList *ctx_stack, gboolean free_all) { config_parser_context_t *ctx; GHashTableIter iter; gpointer key, val; + GList *l; _printf("ctx_stack size: %u\n", g_list_length(ctx_stack)); /* clear all contexts from the stack */ - GList *l = g_list_nth(ctx_stack, 1); + l = g_list_nth(ctx_stack, 1); while (l) { ctx = l->data; config_parser_context_free(srv, ctx, FALSE); diff --git a/src/connection.c b/src/connection.c index 7893378..445310a 100644 --- a/src/connection.c +++ b/src/connection.c @@ -99,11 +99,15 @@ void connection_internal_error(connection *con) { VR_ERROR(vr, "%s", "Couldn't send '500 Internal Error': headers already sent"); connection_error(con); } else { - vrequest_reset(con->mainvr); http_headers_reset(con->mainvr->response.headers); VR_ERROR(vr, "%s", "internal error"); con->mainvr->response.http_status = 500; con->state = CON_STATE_WRITE; + con->mainvr->state = VRS_WRITE_CONTENT; + chunkqueue_reset(con->mainvr->out); + chunkqueue_reset(con->out); + con->mainvr->out->is_closed = TRUE; + con->out->is_closed = TRUE; forward_response_body(con); } } diff --git a/src/environment.c b/src/environment.c index 685798e..55c2d0c 100644 --- a/src/environment.c +++ b/src/environment.c @@ -25,6 +25,15 @@ void environment_set(environment *env, const gchar *key, size_t keylen, const gc g_hash_table_insert(env->table, skey, sval); } +void environment_insert(environment *env, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) { + GString *sval = environment_get(env, key, keylen), *skey; + if (!sval) { + skey = g_string_new_len(key, keylen); + sval = g_string_new_len(val, valuelen); + g_hash_table_insert(env->table, skey, sval); + } +} + void environment_remove(environment *env, const gchar *key, size_t keylen) { const GString skey = { (gchar*) key, keylen, 0 }; /* fake a constant GString */ g_hash_table_remove(env->table, &skey); diff --git a/src/http_headers.c b/src/http_headers.c index bb23607..32f0aef 100644 --- a/src/http_headers.c +++ b/src/http_headers.c @@ -116,7 +116,7 @@ void http_header_remove_link(http_headers *headers, GList *l) { } gboolean http_header_remove(http_headers *headers, const gchar *key, size_t keylen) { - GList *l, *lp = NULL;; + GList *l, *lp = NULL; gboolean res = FALSE; for (l = http_header_find_first(headers, key, keylen); l; l = http_header_find_next(l, key, keylen)) { diff --git a/src/lighttpd.c b/src/lighttpd.c index fdc4b36..8129478 100644 --- a/src/lighttpd.c +++ b/src/lighttpd.c @@ -87,13 +87,15 @@ int main(int argc, char *argv[]) { if (!luaconfig) { GTimeVal start, end; gulong s, millis, micros; - g_get_current_time(&start); guint64 d; action *a; + config_parser_context_t *ctx; + + g_get_current_time(&start); /* standard config frontend */ ctx_stack = config_parser_init(srv); - config_parser_context_t *ctx = (config_parser_context_t*) ctx_stack->data; + ctx = (config_parser_context_t*) ctx_stack->data; if (!config_parser_file(srv, ctx_stack, config_path)) { config_parser_finish(srv, ctx_stack, TRUE); log_thread_start(srv); diff --git a/src/log.c b/src/log.c index 5aaca5b..a8092d2 100644 --- a/src/log.c +++ b/src/log.c @@ -415,6 +415,8 @@ void log_init(server *srv) { void log_cleanup(server *srv) { guint i; + log_timestamp_t *ts; + /* wait for logging thread to exit */ if (g_atomic_int_get(&srv->logs.thread_alive) == TRUE) { @@ -422,14 +424,12 @@ void log_cleanup(server *srv) { g_thread_join(srv->logs.thread); } - log_free(srv, srv->logs.stderr); g_hash_table_destroy(srv->logs.targets); g_mutex_free(srv->logs.mutex); g_async_queue_unref(srv->logs.queue); - log_timestamp_t *ts; for (i = 0; i < srv->logs.timestamps->len; i++) { ts = g_array_index(srv->logs.timestamps, log_timestamp_t*, i); g_print("ts #%d refcount: %d\n", i, ts->refcount); @@ -475,9 +475,10 @@ void log_thread_finish(server *srv) { } void log_thread_wakeup(server *srv) { + log_entry_t *e; + if (!g_atomic_int_get(&srv->logs.thread_alive)) log_thread_start(srv); - log_entry_t *e; e = g_slice_new0(log_entry_t); diff --git a/src/modules/mod_balancer.c b/src/modules/mod_balancer.c index d5d2863..62f49ea 100644 --- a/src/modules/mod_balancer.c +++ b/src/modules/mod_balancer.c @@ -1,6 +1,9 @@ #include +LI_API gboolean mod_balancer_init(modules *mods, module *mod); +LI_API gboolean mod_balancer_free(modules *mods, module *mod); + typedef enum { BE_ALIVE, BE_OVERLOADED, @@ -65,9 +68,11 @@ static gboolean balancer_fill_backends(balancer *b, server *srv, value *val) { return FALSE; } assert(srv == oa->data.val_action.srv); - backend be = { oa->data.val_action.action, 0, BE_ALIVE }; - action_acquire(be.act); - g_array_append_val(b->backends, be); + { + backend be = { oa->data.val_action.action, 0, BE_ALIVE }; + action_acquire(be.act); + g_array_append_val(b->backends, be); + } } return TRUE; } else { @@ -173,7 +178,7 @@ static void plugin_init(server *srv, plugin *p) { } -LI_API gboolean mod_balancer_init(modules *mods, module *mod) { +gboolean mod_balancer_init(modules *mods, module *mod) { MODULE_VERSION_CHECK(mods); mod->config = plugin_register(mods->main, "mod_balancer", plugin_init); @@ -181,7 +186,7 @@ LI_API gboolean mod_balancer_init(modules *mods, module *mod) { return mod->config != NULL; } -LI_API gboolean mod_balancer_free(modules *mods, module *mod) { +gboolean mod_balancer_free(modules *mods, module *mod) { if (mod->config) plugin_free(mods->main, mod->config); diff --git a/src/modules/mod_fastcgi.c b/src/modules/mod_fastcgi.c new file mode 100644 index 0000000..9a5b74a --- /dev/null +++ b/src/modules/mod_fastcgi.c @@ -0,0 +1,681 @@ + +#include +#include + +LI_API gboolean mod_fastcgi_init(modules *mods, module *mod); +LI_API gboolean mod_fastcgi_free(modules *mods, module *mod); + + +struct fastcgi_connection; +typedef struct fastcgi_connection fastcgi_connection; +struct fastcgi_context; +typedef struct fastcgi_context fastcgi_context; +struct FCGI_Record; +typedef struct FCGI_Record FCGI_Record; + + +typedef enum { + FS_WAIT_FOR_REQUEST, + FS_CONNECT, + FS_CONNECTING, + FS_CONNECTED, + FS_DONE +} fastcgi_state; + + +struct FCGI_Record { + guint8 version; + guint8 type; + guint16 requestID; + guint16 contentLength; + guint8 paddingLength; +}; + + +struct fastcgi_connection { + fastcgi_context *ctx; + vrequest *vr; + fastcgi_state state; + int fd; + ev_io fd_watcher; + chunkqueue *fcgi_in, *fcgi_out, *stdout; + + GString *buf_in_record; + FCGI_Record fcgi_in_record; + guint16 requestid; +}; + +struct fastcgi_context { + gint refcount; + sockaddr socket; + guint timeout; + plugin *plugin; +}; + +/* fastcgi types */ + +#define FCGI_VERSION_1 1 +#define FCGI_HEADER_LEN 8 + +enum FCGI_Type { + FCGI_BEGIN_REQUEST = 1, + FCGI_ABORT_REQUEST = 2, + FCGI_END_REQUEST = 3, + FCGI_PARAMS = 4, + FCGI_STDIN = 5, + FCGI_STDOUT = 6, + FCGI_STDERR = 7, + FCGI_DATA = 8, + FCGI_GET_VALUES = 9, + FCGI_GET_VALUES_RESULT = 10, + FCGI_UNKNOWN_TYPE = 11 +}; +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +enum FCGI_Flags { + FCGI_KEEP_CONN = 1 +}; + +enum FCGI_Role { + FCGI_RESPONDER = 1, + FCGI_AUTHORIZER = 2, + FCGI_FILTER = 3 +}; + +enum FCGI_ProtocolStatus { + FCGI_REQUEST_COMPLETE = 0, + FCGI_CANT_MPX_CONN = 1, + FCGI_OVERLOADED = 2, + FCGI_UNKNOWN_ROLE = 3 +}; + +/**********************************************************************************/ + +static fastcgi_context* fastcgi_context_new(server *srv, plugin *p, GString *dest_socket) { + sockaddr saddr; + fastcgi_context* ctx; + saddr = sockaddr_from_string(dest_socket, 0); + if (NULL == saddr.addr) { + ERROR(srv, "Invalid socket address '%s'", dest_socket->str); + return NULL; + } + ctx = g_slice_new0(fastcgi_context); + ctx->refcount = 1; + ctx->socket = saddr; + ctx->timeout = 5; + ctx->plugin = p; + return ctx; +} + +static void fastcgi_context_release(fastcgi_context *ctx) { + if (!ctx) return; + assert(g_atomic_int_get(&ctx->refcount) > 0); + if (g_atomic_int_dec_and_test(&ctx->refcount)) { + sockaddr_clear(&ctx->socket); + g_slice_free(fastcgi_context, ctx); + } +} + +static void fastcgi_context_acquire(fastcgi_context *ctx) { + assert(g_atomic_int_get(&ctx->refcount) > 0); + g_atomic_int_inc(&ctx->refcount); +} + +static void fastcgi_fd_cb(struct ev_loop *loop, ev_io *w, int revents); + +static fastcgi_connection* fastcgi_connection_new(vrequest *vr, fastcgi_context *ctx) { + fastcgi_connection* fcon = g_slice_new(fastcgi_connection); + + fastcgi_context_acquire(ctx); + fcon->ctx = ctx; + fcon->vr = vr; + fcon->fd = -1; + ev_init(&fcon->fd_watcher, fastcgi_fd_cb); + ev_io_set(&fcon->fd_watcher, -1, 0); + fcon->fd_watcher.data = fcon; + fcon->fcgi_in = chunkqueue_new(); + fcon->fcgi_out = chunkqueue_new(); + fcon->stdout = chunkqueue_new(); + fcon->buf_in_record = g_string_sized_new(FCGI_HEADER_LEN); + fcon->requestid = 1; + fcon->state = FS_WAIT_FOR_REQUEST; + return fcon; +} + +static void fastcgi_connection_free(fastcgi_connection *fcon) { + vrequest *vr; + if (!fcon) return; + + vr = fcon->vr; + ev_io_stop(vr->con->wrk->loop, &fcon->fd_watcher); + fastcgi_context_release(fcon->ctx); + if (fcon->fd != -1) close(fcon->fd); + + chunkqueue_free(fcon->fcgi_in); + chunkqueue_free(fcon->fcgi_out); + chunkqueue_free(fcon->stdout); + g_string_free(fcon->buf_in_record, TRUE); + + g_slice_free(fastcgi_connection, fcon); +} + +/**********************************************************************************/ +/* fastcgi stream helper */ + +static const gchar __padding[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +static void append_padding(GString *s, guint8 padlen) { + g_string_append_len(s, __padding, padlen); +} + +/* returns padding length */ +static guint8 stream_build_fcgi_record(GString *buf, guint8 type, guint16 requestid, guint16 datalen) { + guint16 w; + guint8 padlen = (8 - (datalen & 0x7)) % 8; /* padding must be < 8 */ + + g_string_set_size(buf, FCGI_HEADER_LEN); + g_string_truncate(buf, 0); + + g_string_append_c(buf, FCGI_VERSION_1); + g_string_append_c(buf, type); + w = htons(requestid); + g_string_append_len(buf, (const gchar*) &w, sizeof(w)); + w = htons(datalen); + g_string_append_len(buf, (const gchar*) &w, sizeof(w)); + g_string_append_c(buf, padlen); + g_string_append_c(buf, 0); + return padlen; +} + +/* returns padding length */ +static guint8 stream_send_fcgi_record(chunkqueue *out, guint8 type, guint16 requestid, guint16 datalen) { + GString *record = g_string_sized_new(FCGI_HEADER_LEN); + guint8 padlen = stream_build_fcgi_record(record, type, requestid, datalen); + chunkqueue_append_string(out, record); + return padlen; +} + +static void stream_send_data(chunkqueue *out, guint8 type, guint16 requestid, const gchar *data, size_t datalen) { + while (datalen > 0) { + guint16 tosend = (datalen > G_MAXUINT16) ? G_MAXUINT16 : datalen; + guint8 padlen = stream_send_fcgi_record(out, type, requestid, tosend); + GString *tmps = g_string_sized_new(tosend + padlen); + g_string_append_len(tmps, data, tosend); + append_padding(tmps, padlen); + chunkqueue_append_string(out, tmps); + data += tosend; + datalen -= tosend; + } +} + +/* kills string */ +static void stream_send_string(chunkqueue *out, guint8 type, guint16 requestid, GString *data) { + if (data->len > G_MAXUINT16) { + stream_send_data(out, type, requestid, GSTR_LEN(data)); + g_string_free(data, TRUE); + } else { + guint8 padlen = stream_send_fcgi_record(out, type, requestid, data->len); + append_padding(data, padlen); + chunkqueue_append_string(out, data); + } +} + +static void stream_send_chunks(chunkqueue *out, guint8 type, guint16 requestid, chunkqueue *in) { + while (in->length > 0) { + guint16 tosend = (in->length > G_MAXUINT16) ? G_MAXUINT16 : in->length; + guint8 padlen = stream_send_fcgi_record(out, type, requestid, tosend); + chunkqueue_steal_len(out, in, tosend); + chunkqueue_append_mem(out, __padding, padlen); + } + + if (in->is_closed) { + stream_send_fcgi_record(out, type, requestid, 0); + } +} + +static gboolean _append_str_len(GString *s, size_t len) { + if (len > G_MAXINT32) return FALSE; + if (len > 127) { + guint32 i = htonl(len | (1 << 31)); + g_string_append_len(s, (const gchar*) &i, sizeof(i)); + } else { + g_string_append_c(s, (char) len); + } + return TRUE; +} + +static gboolean append_key_value_pair(GString *s, const gchar *key, size_t keylen, const gchar *val, size_t valuelen) { + if (!_append_str_len(s, keylen) || !_append_str_len(s, valuelen)) return FALSE; + g_string_append_len(s, key, keylen); + g_string_append_len(s, val, valuelen); + return TRUE; +} + +/**********************************************************************************/ + +static void fastcgi_send_begin(fastcgi_connection *fcon) { + GString *buf = g_string_sized_new(16); + guint16 w; + + stream_build_fcgi_record(buf, FCGI_BEGIN_REQUEST, fcon->requestid, 8); + w = htons(FCGI_RESPONDER); + g_string_append_len(buf, (const char*) &w, sizeof(w)); + g_string_append_c(buf, 0); /* TODO: FCGI_KEEP_CONN */ + append_padding(buf, 5); + chunkqueue_append_string(fcon->fcgi_out, buf); +} + +static void fastcgi_env_setup(vrequest *vr) { + connection *con = vr->con; + GString *tmp = con->wrk->tmp_str; + environment_insert(&vr->env, CONST_STR_LEN("SERVER_SOFTWARE"), GSTR_LEN(CORE_OPTION(CORE_OPTION_SERVER_TAG).string)); + environment_insert(&vr->env, CONST_STR_LEN("SERVER_NAME"), GSTR_LEN(vr->request.uri.host)); + environment_insert(&vr->env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")); + { + guint port = 0; + switch (con->local_addr.plain.sa_family) { + case AF_INET: port = con->local_addr.ipv4.sin_port; break; +#ifdef HAVE_IPV6 + case AF_INET6: port = con->local_addr.ipv6.sin6_port; break; +#endif + } + if (port) { + g_string_printf(tmp, "%u", port); + environment_insert(&vr->env, CONST_STR_LEN("SERVER_PORT"), GSTR_LEN(tmp)); + } + } + { + sockaddr_to_string(&con->local_addr, tmp); + environment_insert(&vr->env, CONST_STR_LEN("SERVER_ADDR"), GSTR_LEN(tmp)); + } + + { + guint port = 0; + switch (con->remote_addr.plain.sa_family) { + case AF_INET: port = con->remote_addr.ipv4.sin_port; break; +#ifdef HAVE_IPV6 + case AF_INET6: port = con->remote_addr.ipv6.sin6_port; break; +#endif + } + if (port) { + g_string_printf(tmp, "%u", port); + environment_insert(&vr->env, CONST_STR_LEN("REMOTE_PORT"), GSTR_LEN(tmp)); + } + } + { + sockaddr_to_string(&con->remote_addr, tmp); + environment_insert(&vr->env, CONST_STR_LEN("REMOTE_ADDR"), GSTR_LEN(tmp)); + } + + /* TODO? auth vars; i think it would be easier if the auth mod sets them: + * REMOTE_USER, AUTH_TYPE + */ + { + g_string_printf(tmp, "%" L_GOFFSET_MODIFIER "i", vr->request.content_length); + environment_insert(&vr->env, CONST_STR_LEN("CONTENT_LENGTH"), GSTR_LEN(tmp)); + } + + environment_insert(&vr->env, CONST_STR_LEN("SCRIPT_NAME"), GSTR_LEN(vr->request.uri.path)); + + environment_insert(&vr->env, CONST_STR_LEN("PATH_INFO"), GSTR_LEN(vr->physical.pathinfo)); + if (vr->physical.pathinfo->len) { + g_string_truncate(tmp, 0); + g_string_append_len(tmp, GSTR_LEN(vr->physical.doc_root)); /* TODO: perhaps an option for alternative doc-root? */ + g_string_append_len(tmp, GSTR_LEN(vr->physical.pathinfo)); + environment_insert(&vr->env, CONST_STR_LEN("PATH_TRANSLATED"), GSTR_LEN(tmp)); + } + + environment_insert(&vr->env, CONST_STR_LEN("SCRIPT_FILENAME"), GSTR_LEN(vr->physical.path)); + environment_insert(&vr->env, CONST_STR_LEN("DOCUMENT_ROOT"), GSTR_LEN(vr->physical.doc_root)); + + environment_insert(&vr->env, CONST_STR_LEN("REQUEST_URI"), GSTR_LEN(vr->request.uri.orig_path)); + if (!g_string_equal(vr->request.uri.orig_path, vr->request.uri.path)) { + environment_insert(&vr->env, CONST_STR_LEN("REDIRECT_URI"), GSTR_LEN(vr->request.uri.path)); + } + environment_insert(&vr->env, CONST_STR_LEN("QUERY_STRING"), GSTR_LEN(vr->request.uri.query)); + + environment_insert(&vr->env, CONST_STR_LEN("REQUEST_METHOD"), GSTR_LEN(vr->request.http_method_str)); + environment_insert(&vr->env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); /* if php is compiled with --force-redirect */ + switch (vr->request.http_version) { + case HTTP_VERSION_1_1: + environment_insert(&vr->env, CONST_STR_LEN("SERVER_PROTOCOL"), CONST_STR_LEN("HTTP/1.1")); + break; + case HTTP_VERSION_1_0: + default: + environment_insert(&vr->env, CONST_STR_LEN("SERVER_PROTOCOL"), CONST_STR_LEN("HTTP/1.0")); + break; + } + + if (con->is_ssl) { + environment_insert(&vr->env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on")); + } +} + +static void fastcgi_send_env(vrequest *vr, fastcgi_connection *fcon) { + GHashTableIter i; + gpointer key, val; + GString *buf = g_string_sized_new(0); + + g_hash_table_iter_init(&i, vr->env.table); + while (g_hash_table_iter_next(&i, &key, &val)) { + append_key_value_pair(buf, GSTR_LEN((GString*) key), GSTR_LEN((GString*) val)); + } + + /* TODO: send headers */ + + stream_send_string(fcon->fcgi_out, FCGI_PARAMS, fcon->requestid, buf); + stream_send_fcgi_record(fcon->fcgi_out, FCGI_PARAMS, fcon->requestid, 0); +} + +static void fastcgi_forward_request(vrequest *vr, fastcgi_connection *fcon) { + stream_send_chunks(fcon->fcgi_out, FCGI_STDIN, fcon->requestid, vr->in); + if (fcon->fcgi_out->length > 0) + ev_io_add_events(vr->con->wrk->loop, &fcon->fd_watcher, EV_WRITE); +} + +static gboolean fastcgi_get_packet(fastcgi_connection *fcon) { + const gchar *data; + gint len; + if (!chunkqueue_extract_to(fcon->vr, fcon->fcgi_in, FCGI_HEADER_LEN, fcon->buf_in_record)) return FALSE; /* need more data */ + + data = fcon->buf_in_record->str; + fcon->fcgi_in_record.version = data[0]; + fcon->fcgi_in_record.type = data[1]; + fcon->fcgi_in_record.requestID = (data[2] << 8) | (data[3]); + fcon->fcgi_in_record.contentLength = (data[4] << 8) | (data[5]); + fcon->fcgi_in_record.paddingLength = data[6]; + + len = ((gint) fcon->fcgi_in_record.contentLength) + fcon->fcgi_in_record.paddingLength + FCGI_HEADER_LEN; + + if (len > fcon->fcgi_in->length) return FALSE; /* need more data */ + + return TRUE; +} + +static void fastcgi_parse_response(fastcgi_connection *fcon) { + while (fastcgi_get_packet(fcon)) { + if (fcon->fcgi_in_record.version != FCGI_VERSION_1) { + VR_ERROR(fcon->vr, "Unknown fastcgi protocol version %i", (gint) fcon->fcgi_in_record.version); + close(fcon->fd); + fcon->fd = -1; + vrequest_error(fcon->vr); + return; + } + chunkqueue_skip(fcon->fcgi_in, FCGI_HEADER_LEN); + switch (fcon->fcgi_in_record.type) { + case FCGI_END_REQUEST: + chunkqueue_skip(fcon->fcgi_in, fcon->fcgi_in_record.contentLength); + fcon->stdout->is_closed = TRUE; + break; + case FCGI_STDOUT: + if (0 == fcon->fcgi_in_record.contentLength) { + fcon->stdout->is_closed = TRUE; + } else { + chunkqueue_steal_len(fcon->stdout, fcon->fcgi_in, fcon->fcgi_in_record.contentLength); + } + break; + default: + VR_WARNING(fcon->vr, "Unhandled fastcgi record type %i", (gint) fcon->fcgi_in_record.type); + chunkqueue_skip(fcon->fcgi_in, fcon->fcgi_in_record.contentLength); + break; + } + chunkqueue_skip(fcon->fcgi_in, fcon->fcgi_in_record.paddingLength); + } +} + +/**********************************************************************************/ + +static handler_t fastcgi_statemachine(vrequest *vr, fastcgi_connection *fcon); + +static void fastcgi_fd_cb(struct ev_loop *loop, ev_io *w, int revents) { + fastcgi_connection *fcon = (fastcgi_connection*) w->data; + + if (fcon->state == FS_CONNECTING) { + if (HANDLER_GO_ON != fastcgi_statemachine(fcon->vr, fcon)) { + vrequest_error(fcon->vr); + } + return; + } + + if (revents & EV_READ) { + if (fcon->fcgi_in->is_closed) { + ev_io_rem_events(loop, w, EV_READ); + } else { + switch (network_read(fcon->vr, w->fd, fcon->fcgi_in)) { + case NETWORK_STATUS_SUCCESS: + break; + case NETWORK_STATUS_FATAL_ERROR: + VR_ERROR(fcon->vr, "%s", "network read fatal error"); + vrequest_error(fcon->vr); + return; + case NETWORK_STATUS_CONNECTION_CLOSE: + fcon->fcgi_in->is_closed = TRUE; + ev_io_stop(loop, w); + close(fcon->fd); + fcon->fd = -1; + break; + case NETWORK_STATUS_WAIT_FOR_EVENT: + break; + case NETWORK_STATUS_WAIT_FOR_AIO_EVENT: + /* TODO: aio */ + ev_io_rem_events(loop, w, EV_READ); + break; + case NETWORK_STATUS_WAIT_FOR_FD: + /* TODO: wait for fd */ + ev_io_rem_events(loop, w, EV_READ); + break; + } + } + } + + if (fcon->fd != -1 && (revents & EV_WRITE)) { + if (fcon->fcgi_out->length > 0) { + switch (network_write(fcon->vr, w->fd, fcon->fcgi_out)) { + case NETWORK_STATUS_SUCCESS: + break; + case NETWORK_STATUS_FATAL_ERROR: + VR_ERROR(fcon->vr, "%s", "network write fatal error"); + vrequest_error(fcon->vr); + return; + case NETWORK_STATUS_CONNECTION_CLOSE: + fcon->fcgi_in->is_closed = TRUE; + ev_io_stop(loop, w); + close(fcon->fd); + fcon->fd = -1; + break; + case NETWORK_STATUS_WAIT_FOR_EVENT: + break; + case NETWORK_STATUS_WAIT_FOR_AIO_EVENT: + ev_io_rem_events(loop, w, EV_WRITE); + /* TODO: aio */ + break; + case NETWORK_STATUS_WAIT_FOR_FD: + ev_io_rem_events(loop, w, EV_WRITE); + /* TODO: wait for fd */ + break; + } + } + if (fcon->fcgi_out->length == 0) { + ev_io_rem_events(loop, w, EV_WRITE); + } + } + + fastcgi_parse_response(fcon); + + /* TODO: parse stdout response */ + if (fcon->vr->out->bytes_in == 0 && fcon->stdout->length > 0) { + fcon->vr->response.http_status = 200; + vrequest_handle_response_headers(fcon->vr); + } + chunkqueue_steal_all(fcon->vr->out, fcon->stdout); + fcon->vr->out->is_closed = fcon->stdout->is_closed; + vrequest_handle_response_body(fcon->vr); + + if (fcon->fcgi_in->is_closed && !fcon->vr->out->is_closed) { + VR_ERROR(fcon->vr, "%s", "unexpected end-of-file (perhaps the fastcgi process died)"); + vrequest_error(fcon->vr); + } +} + +/**********************************************************************************/ +/* state machine */ + +static void fastcgi_close(vrequest *vr, plugin *p); + +static handler_t fastcgi_statemachine(vrequest *vr, fastcgi_connection *fcon) { + plugin *p = fcon->ctx->plugin; + + switch (fcon->state) { + case FS_WAIT_FOR_REQUEST: + if (-1 == vr->request.content_length || vr->request.content_length != vr->in->length) return HANDLER_GO_ON; + fcon->state = FS_CONNECT; + + /* fall through */ + case FS_CONNECT: + fcon->fd = socket(fcon->ctx->socket.addr->plain.sa_family, SOCK_STREAM, 0); + if (-1 == fcon->fd) { + if (errno == EMFILE || errno == EINTR) { + return HANDLER_WAIT_FOR_FD; + } + VR_ERROR(vr, "Couldn't open socket: %s", g_strerror(errno)); + return HANDLER_ERROR; + } + fd_init(fcon->fd); + ev_io_set(&fcon->fd_watcher, fcon->fd, EV_READ | EV_WRITE); + ev_io_start(vr->con->wrk->loop, &fcon->fd_watcher); + + /* fall through */ + case FS_CONNECTING: + if (-1 == connect(fcon->fd, &fcon->ctx->socket.addr->plain, fcon->ctx->socket.len)) { + switch (errno) { + case EINPROGRESS: + case EALREADY: + case EINTR: + fcon->state = FS_CONNECTING; + return HANDLER_GO_ON; + case EAGAIN: /* backend overloaded */ + fastcgi_close(vr, p); + vrequest_backend_overloaded(vr); + return HANDLER_GO_ON; + default: + VR_ERROR(vr, "Couldn't connect: %s", g_strerror(errno)); + fastcgi_close(vr, p); + vrequest_backend_dead(vr); + return HANDLER_GO_ON; + } + } + + fcon->state = FS_CONNECTED; + + /* prepare stream */ + fastcgi_send_begin(fcon); + fastcgi_env_setup(vr); + fastcgi_send_env(vr, fcon); + + /* fall through */ + case FS_CONNECTED: + fastcgi_forward_request(vr, fcon); + break; + + case FS_DONE: + break; + } + + return HANDLER_GO_ON; +} + + +/**********************************************************************************/ + +static handler_t fastcgi_handle(vrequest *vr, gpointer param, gpointer *context) { + fastcgi_context *ctx = (fastcgi_context*) param; + fastcgi_connection *fcon; + UNUSED(context); + if (!vrequest_handle_indirect(vr, ctx->plugin)) return HANDLER_GO_ON; + + fcon = fastcgi_connection_new(vr, ctx); + if (!fcon) { + return HANDLER_ERROR; + } + g_ptr_array_index(vr->plugin_ctx, ctx->plugin->id) = fcon; + + return fastcgi_statemachine(vr, fcon); +} + + +static handler_t fastcgi_handle_request_body(vrequest *vr, plugin *p) { + fastcgi_connection *fcon = (fastcgi_connection*) g_ptr_array_index(vr->plugin_ctx, p->id); + if (!fcon) return HANDLER_ERROR; + + return fastcgi_statemachine(vr, fcon); +} + +static void fastcgi_close(vrequest *vr, plugin *p) { + fastcgi_connection *fcon = (fastcgi_connection*) g_ptr_array_index(vr->plugin_ctx, p->id); + g_ptr_array_index(vr->plugin_ctx, p->id) = NULL; + + fastcgi_connection_free(fcon); +} + + +static void fastcgi_free(server *srv, gpointer param) { + fastcgi_context *ctx = (fastcgi_context*) param; + UNUSED(srv); + + fastcgi_context_release(ctx); +} + +static action* fastcgi_create(server *srv, plugin* p, value *val) { + fastcgi_context *ctx; + + if (val->type != VALUE_STRING) { + ERROR(srv, "%s", "fastcgi expects a string as parameter"); + return FALSE; + } + + ctx = fastcgi_context_new(srv, p, val->data.string); + if (!ctx) return NULL; + + return action_new_function(fastcgi_handle, NULL, fastcgi_free, ctx); +} + +static const plugin_option options[] = { + { NULL, 0, NULL, NULL, NULL } +}; + +static const plugin_action actions[] = { + { "fastcgi", fastcgi_create }, + { NULL, NULL } +}; + +static const plugin_setup setups[] = { + { NULL, NULL } +}; + + +static void plugin_init(server *srv, plugin *p) { + UNUSED(srv); + + p->options = options; + p->actions = actions; + p->setups = setups; + + p->handle_request_body = fastcgi_handle_request_body; + p->handle_vrclose = fastcgi_close; +} + + +gboolean mod_fastcgi_init(modules *mods, module *mod) { + MODULE_VERSION_CHECK(mods); + + mod->config = plugin_register(mods->main, "mod_fastcgi", plugin_init); + + return mod->config != NULL; +} + +gboolean mod_fastcgi_free(modules *mods, module *mod) { + if (mod->config) + plugin_free(mods->main, mod->config); + + return TRUE; +} diff --git a/src/modules/mod_fortune.c b/src/modules/mod_fortune.c index a6e0937..8d4587a 100644 --- a/src/modules/mod_fortune.c +++ b/src/modules/mod_fortune.c @@ -27,6 +27,9 @@ #include +LI_API gboolean mod_fortune_init(modules *mods, module *mod); +LI_API gboolean mod_fortune_free(modules *mods, module *mod); + /* globals */ struct fortune_data; typedef struct fortune_data fortune_data; @@ -153,8 +156,8 @@ static const plugin_setup setups[] = { static void plugin_fortune_free(server *srv, plugin *p) { - UNUSED(srv); fortune_data *fd = p->data; + UNUSED(srv); /* free the cookies! */ for (guint i = 0; i < fd->cookies->len; i++) @@ -167,8 +170,8 @@ static void plugin_fortune_free(server *srv, plugin *p) { } static void plugin_fortune_init(server *srv, plugin *p) { - UNUSED(srv); fortune_data *fd; + UNUSED(srv); p->options = options; p->actions = actions; @@ -182,7 +185,7 @@ static void plugin_fortune_init(server *srv, plugin *p) { } -LI_API gboolean mod_fortune_init(modules *mods, module *mod) { +gboolean mod_fortune_init(modules *mods, module *mod) { server *srv = mods->main; MODULE_VERSION_CHECK(mods); @@ -195,7 +198,7 @@ LI_API gboolean mod_fortune_init(modules *mods, module *mod) { return TRUE; } -LI_API gboolean mod_fortune_free(modules *mods, module *mod) { +gboolean mod_fortune_free(modules *mods, module *mod) { if (mod->config) plugin_free(mods->main, mod->config); diff --git a/src/modules/mod_status.c b/src/modules/mod_status.c index fef3371..c02abad 100644 --- a/src/modules/mod_status.c +++ b/src/modules/mod_status.c @@ -31,6 +31,8 @@ #include #include +LI_API gboolean mod_status_init(modules *mods, module *mod); +LI_API gboolean mod_status_free(modules *mods, module *mod); /* html snippet constants */ static const gchar header[] = @@ -162,8 +164,9 @@ struct mod_status_wrk_data { /* the CollectFunc */ static gpointer status_collect_func(worker *wrk, gpointer fdata) { - UNUSED(fdata); mod_status_wrk_data *sd = g_slice_new(mod_status_wrk_data); + UNUSED(fdata); + sd->stats = wrk->stats; sd->worker_ndx = wrk->ndx; /* gather connection info */ @@ -190,18 +193,14 @@ static gpointer status_collect_func(worker *wrk, gpointer fdata) { /* the CollectCallback */ static void status_collect_cb(gpointer cbdata, gpointer fdata, GPtrArray *result, gboolean complete) { - UNUSED(fdata); vrequest *vr = cbdata; - + UNUSED(fdata); if (complete) { GString *css; GString *tmpstr; guint total_connections = 0; - VR_DEBUG(vr, "finished collecting data: %s", complete ? "complete" : "not complete"); - vr->response.http_status = 200; - /* we got everything */ statistics_t totals = { G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), G_GUINT64_CONSTANT(0), @@ -211,6 +210,9 @@ static void status_collect_cb(gpointer cbdata, gpointer fdata, GPtrArray *result }; GString *html = g_string_sized_new(8 * 1024); + VR_DEBUG(vr, "finished collecting data: %s", complete ? "complete" : "not complete"); + vr->response.http_status = 200; + /* calculate total stats over all workers */ for (guint i = 0; i < result->len; i++) { mod_status_wrk_data *sd = g_ptr_array_index(result, i); @@ -510,7 +512,7 @@ static void plugin_status_init(server *srv, plugin *p) { } -LI_API gboolean mod_status_init(modules *mods, module *mod) { +gboolean mod_status_init(modules *mods, module *mod) { UNUSED(mod); MODULE_VERSION_CHECK(mods); @@ -520,7 +522,7 @@ LI_API gboolean mod_status_init(modules *mods, module *mod) { return mod->config != NULL; } -LI_API gboolean mod_status_free(modules *mods, module *mod) { +gboolean mod_status_free(modules *mods, module *mod) { UNUSED(mods); UNUSED(mod); if (mod->config) diff --git a/src/network_sendfile.c b/src/network_sendfile.c index e3f120d..7828e20 100644 --- a/src/network_sendfile.c +++ b/src/network_sendfile.c @@ -151,7 +151,7 @@ static network_sendfile_result lighty_sendfile(vrequest *vr, int fd, int filefd, /* first chunk must be a FILE_CHUNK ! */ -network_status_t network_backend_sendfile(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max) { +static network_status_t network_backend_sendfile(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max) { off_t file_offset, toSend; ssize_t r; gboolean did_write_something = FALSE; diff --git a/src/plugin.c b/src/plugin.c index e64bfcd..f20f8b6 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -69,13 +69,14 @@ void plugin_free(server *srv, plugin *p) { } void server_plugins_free(server *srv) { + gpointer key, val; + GHashTableIter i; + if (g_atomic_int_get(&srv->state) == SERVER_RUNNING) { ERROR(srv, "%s", "Cannot free plugins while server is running"); return; } - gpointer key, val; - GHashTableIter i; g_hash_table_iter_init(&i, srv->plugins); while (g_hash_table_iter_next(&i, &key, &val)) { plugin *p = (plugin*) val; @@ -111,6 +112,7 @@ plugin *plugin_register(server *srv, const gchar *name, PluginInit init) { } p = plugin_new(name); + p->id = g_hash_table_size(srv->plugins); g_hash_table_insert(srv->plugins, (gchar*) p->name, p); init(srv, p); @@ -321,6 +323,8 @@ void plugins_prepare_callbacks(server *srv) { p = (plugin*) v; if (p->handle_close) g_array_append_val(srv->plugins_handle_close, p); + if (p->handle_vrclose) + g_array_append_val(srv->plugins_handle_vrclose, p); } } @@ -333,6 +337,15 @@ void plugins_handle_close(connection *con) { } } +void plugins_handle_vrclose(vrequest *vr) { + GArray *a = vr->con->srv->plugins_handle_vrclose; + guint i, len = a->len; + for (i = 0; i < len; i++) { + plugin *p = g_array_index(a, plugin*, i); + p->handle_vrclose(vr, p); + } +} + gboolean plugin_set_default_option(server *srv, const gchar* name, value *val) { server_option *sopt; option_set setting; diff --git a/src/plugin_core.c b/src/plugin_core.c index 52df4db..02b67ad 100644 --- a/src/plugin_core.c +++ b/src/plugin_core.c @@ -140,9 +140,9 @@ static gboolean core_setup_set(server *srv, plugin* p, value *val) { } static handler_t core_handle_static(vrequest *vr, gpointer param, gpointer *context) { + int fd; UNUSED(param); UNUSED(context); - int fd; /* build physical path: docroot + uri.path */ g_string_truncate(vr->physical.path, 0); @@ -517,9 +517,11 @@ static gboolean core_option_log_parse(server *srv, plugin *p, size_t ndx, value if (g_str_equal(level_str->str, "*")) { for (guint i = 0; i < arr->len; i++) { + log_t *log; + if (NULL != g_array_index(arr, log_t*, i)) continue; - log_t *log = log_new(srv, log_type_from_path(path), path); + log = log_new(srv, log_type_from_path(path), path); g_array_index(arr, log_t*, i) = log; } } @@ -534,10 +536,10 @@ static gboolean core_option_log_parse(server *srv, plugin *p, size_t ndx, value } static void core_option_log_free(server *srv, plugin *p, size_t ndx, option_value oval) { + GArray *arr = oval.list; UNUSED(p); UNUSED(ndx); - GArray *arr = oval.list; if (!arr) return; for (guint i = 0; i < arr->len; i++) { @@ -783,6 +785,7 @@ static const plugin_setup setups[] = { { NULL, NULL } }; +void plugin_core_init(server *srv, plugin *p); void plugin_core_init(server *srv, plugin *p) { UNUSED(srv); diff --git a/src/profiler.c b/src/profiler.c index 1f8a3eb..3291697 100644 --- a/src/profiler.c +++ b/src/profiler.c @@ -53,13 +53,15 @@ static profiler_entry *profiler_hashtable_find(gpointer addr) { static void profiler_hashtable_insert(gpointer addr, gsize len) { profiler_entry *e = free_list; + guint h; + free_list = free_list->next ? free_list->next : calloc(1, sizeof(profiler_entry)); e->addr = addr; e->len = len; e->next = NULL; - guint h = profiler_hash_addr(addr); + h = profiler_hash_addr(addr); if (profiler_hashtable.nodes[h] == NULL) { profiler_hashtable.nodes[h] = e; @@ -139,9 +141,11 @@ static gpointer profiler_try_realloc(gpointer mem, gsize n_bytes) { l = 0; } else { + profiler_entry *e; + p = realloc(p, n_bytes); g_mutex_lock(profiler_mutex); - profiler_entry *e = profiler_hashtable_find(mem); + e = profiler_hashtable_find(mem); l = e->len; profiler_hashtable_remove(mem); g_mutex_unlock(profiler_mutex); @@ -191,10 +195,11 @@ static gpointer profiler_calloc(gsize n_blocks, gsize n_bytes) { static void profiler_free(gpointer mem) { gsize *p = mem; + profiler_entry *e; assert(p); g_mutex_lock(profiler_mutex); - profiler_entry *e = profiler_hashtable_find(mem); + e = profiler_hashtable_find(mem); stats_mem.free_times++; stats_mem.free_bytes += e->len; stats_mem.inuse_bytes -= e->len; @@ -244,11 +249,13 @@ void profiler_finish() { } void profiler_dump() { + profiler_mem s; + if (!profiler_enabled) return; g_mutex_lock(profiler_mutex); - profiler_mem s = stats_mem; + s = stats_mem; g_mutex_unlock(profiler_mutex); g_print("--- memory profiler stats ---\n"); diff --git a/src/request.c b/src/request.c index 5e152b7..10e33d8 100644 --- a/src/request.c +++ b/src/request.c @@ -11,6 +11,7 @@ void request_init(request *req) { req->uri.scheme = g_string_sized_new(0); req->uri.authority = g_string_sized_new(0); req->uri.path = g_string_sized_new(0); + req->uri.orig_path = g_string_sized_new(0); req->uri.query = g_string_sized_new(0); req->uri.host = g_string_sized_new(0); @@ -28,6 +29,7 @@ void request_reset(request *req) { g_string_truncate(req->uri.scheme, 0); g_string_truncate(req->uri.authority, 0); g_string_truncate(req->uri.path, 0); + g_string_truncate(req->uri.orig_path, 0); g_string_truncate(req->uri.query, 0); g_string_truncate(req->uri.host, 0); @@ -45,6 +47,7 @@ void request_clear(request *req) { g_string_free(req->uri.scheme, TRUE); g_string_free(req->uri.authority, TRUE); g_string_free(req->uri.path, TRUE); + g_string_free(req->uri.orig_path, TRUE); g_string_free(req->uri.query, TRUE); g_string_free(req->uri.host, TRUE); @@ -60,7 +63,7 @@ static void bad_request(connection *con, int status) { vrequest_handle_direct(con->mainvr); } -gboolean request_parse_url(vrequest *vr) { +static gboolean request_parse_url(vrequest *vr) { request *req = &vr->request; g_string_truncate(req->uri.query, 0); @@ -76,6 +79,10 @@ gboolean request_parse_url(vrequest *vr) { url_decode(req->uri.path); path_simplify(req->uri.path); + if (0 == req->uri.orig_path->len) { + g_string_append_len(req->uri.orig_path, GSTR_LEN(req->uri.path)); /* save orig path */ + } + return TRUE; } diff --git a/src/response.c b/src/response.c index c731479..30cda3d 100644 --- a/src/response.c +++ b/src/response.c @@ -26,10 +26,9 @@ void response_send_headers(connection *con) { if (vr->response.http_status < 100 || vr->response.http_status > 999) { VR_ERROR(vr, "wrong status: %i", vr->response.http_status); - vrequest_reset(con->mainvr); - http_headers_reset(con->mainvr->response.headers); - con->mainvr->response.http_status = 500; - con->state = CON_STATE_WRITE; + con->response_headers_sent = FALSE; + connection_internal_error(con); + return; } if (0 == con->out->length && con->mainvr->handle_response_body == NULL diff --git a/src/server.c b/src/server.c index 4db4252..af9cac5 100644 --- a/src/server.c +++ b/src/server.c @@ -67,6 +67,7 @@ server* server_new(const gchar *module_dir) { 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->plugins_handle_vrclose = g_array_new(FALSE, TRUE, sizeof(plugin*)); srv->option_def_values = g_array_new(FALSE, TRUE, sizeof(option_value)); srv->mainaction = NULL; @@ -99,13 +100,6 @@ void server_free(server* srv) { action_release(srv, srv->mainaction); - /* release modules */ - modules_cleanup(srv->modules); - - plugin_free(srv, srv->core_plugin); - - log_cleanup(srv); - /* free all workers */ { guint i; @@ -124,6 +118,13 @@ void server_free(server* srv) { g_array_free(srv->workers, TRUE); } + /* release modules */ + modules_cleanup(srv->modules); + + plugin_free(srv, srv->core_plugin); + + log_cleanup(srv); + { guint i; for (i = 0; i < srv->sockets->len; i++) { server_socket *sock = g_array_index(srv->sockets, server_socket*, i); @@ -135,7 +136,8 @@ void server_free(server* srv) { g_array_free(srv->option_def_values, TRUE); server_plugins_free(srv); - g_array_free(srv->plugins_handle_close, TRUE); /* TODO: */ + g_array_free(srv->plugins_handle_close, TRUE); + g_array_free(srv->plugins_handle_vrclose, TRUE); if (srv->started_str) g_string_free(srv->started_str, TRUE); diff --git a/src/url_parser.rl b/src/url_parser.rl index 2e11105..71dfa41 100644 --- a/src/url_parser.rl +++ b/src/url_parser.rl @@ -1,5 +1,6 @@ #include +#include #include diff --git a/src/utils.c b/src/utils.c index 4d3c5a9..b9f01cf 100644 --- a/src/utils.c +++ b/src/utils.c @@ -467,6 +467,7 @@ GString *mimetype_get(vrequest *vr, GString *filename) { arr = CORE_OPTION(CORE_OPTION_MIME_TYPES).list; for (guint i = 0; i < arr->len; i++) { + gint k, j; value *tuple = g_array_index(arr, value*, i); GString *ext = g_array_index(tuple->data.list, value*, 0)->data.string; if (ext->len > filename->len) @@ -476,8 +477,8 @@ GString *mimetype_get(vrequest *vr, GString *filename) { if (!ext->len) return g_array_index(tuple->data.list, value*, 1)->data.string; - gint k = filename->len - 1; - gint j = ext->len - 1; + k = filename->len - 1; + j = ext->len - 1; for (; j >= 0; j--) { if (ext->str[j] != filename->str[k]) break; @@ -536,16 +537,49 @@ GString *sockaddr_to_string(sock_addr *saddr, GString *dest) { /* ipv6 - not yet implemented with own function */ if (!dest) dest = g_string_sized_new(INET6_ADDRSTRLEN); - else - g_string_set_size(dest, INET6_ADDRSTRLEN); - inet_ntop(AF_INET6, saddr->ipv6.sin6_addr.s6_addr, dest->str, INET6_ADDRSTRLEN); + ipv6_tostring(dest, saddr->ipv6.sin6_addr.s6_addr); #endif + default: + if (dest) g_string_truncate(dest, 0); } return dest; } +sockaddr sockaddr_from_string(GString *str, guint tcp_default_port) { + guint32 ipv4; +#ifdef HAVE_IPV6 + guint8 ipv6[16]; +#endif + guint16 port = tcp_default_port; + sockaddr saddr = { 0, NULL }; + + if (parse_ipv4(str->str, &ipv4, NULL, &port)) { + if (!port) return saddr; + saddr.len = sizeof(struct sockaddr_in); + saddr.addr = (sock_addr*) g_slice_alloc0(saddr.len); + saddr.addr->ipv4.sin_family = AF_INET; + saddr.addr->ipv4.sin_addr.s_addr = ipv4; + saddr.addr->ipv4.sin_port = htons(port); +#ifdef HAVE_IPV6 + } else if (parse_ipv6(str->str, ipv6, NULL, &port)) { + if (!port) return saddr; + saddr.len = sizeof(struct sockaddr_in6); + saddr.addr = (sock_addr*) g_slice_alloc0(saddr.len); + saddr.addr->ipv6.sin6_family = AF_INET6; + memcpy(&saddr.addr->ipv6.sin6_addr, ipv6, 16); + saddr.addr->ipv6.sin6_port = htons(port); +#endif + } + return saddr; +} + +void sockaddr_clear(sockaddr *saddr) { + g_slice_free1(saddr->len, saddr->addr); +} + +/* unused */ void gstring_replace_char_with_str_len(GString *gstr, gchar c, gchar *str, guint len) { for (guint i = 0; i < gstr->len; i++) { if (gstr->str[i] == c) { diff --git a/src/value.c b/src/value.c index 6eaab13..a2aae94 100644 --- a/src/value.c +++ b/src/value.c @@ -194,12 +194,12 @@ GString *value_to_string(value *val) { break; case VALUE_HASH: { - str = g_string_new_len(CONST_STR_LEN("[")); GHashTableIter iter; gpointer k, v; GString *tmp; guint i = 0; + str = g_string_new_len(CONST_STR_LEN("[")); g_hash_table_iter_init(&iter, val->data.hash); while (g_hash_table_iter_next(&iter, &k, &v)) { diff --git a/src/virtualrequest.c b/src/virtualrequest.c index 479f129..f10954a 100644 --- a/src/virtualrequest.c +++ b/src/virtualrequest.c @@ -49,6 +49,8 @@ vrequest* vrequest_new(connection *con, vrequest_handler handle_response_headers vr->handle_response_error = handle_response_error; vr->handle_request_headers = handle_request_headers; + vr->plugin_ctx = g_ptr_array_new(); + g_ptr_array_set_size(vr->plugin_ctx, g_hash_table_size(srv->plugins)); vr->options = g_slice_copy(srv->option_def_values->len * sizeof(option_value), srv->option_def_values->data); request_init(&vr->request); @@ -70,6 +72,8 @@ vrequest* vrequest_new(connection *con, vrequest_handler handle_response_headers void vrequest_free(vrequest* vr) { action_stack_clear(vr, &vr->action_stack); + plugins_handle_vrclose(vr); + g_ptr_array_free(vr->plugin_ctx, TRUE); request_clear(&vr->request); physical_clear(&vr->physical); @@ -91,10 +95,16 @@ void vrequest_free(vrequest* vr) { void vrequest_reset(vrequest *vr) { action_stack_reset(vr, &vr->action_stack); + plugins_handle_vrclose(vr); + { + gint len = vr->plugin_ctx->len; + g_ptr_array_set_size(vr->plugin_ctx, 0); + g_ptr_array_set_size(vr->plugin_ctx, len); + } vr->state = VRS_CLEAN; - vr->handle_request_body = NULL; + vr->backend = NULL; request_reset(&vr->request); physical_reset(&vr->physical); @@ -114,24 +124,26 @@ void vrequest_reset(vrequest *vr) { void vrequest_error(vrequest *vr) { vr->state = VRS_ERROR; + vr->out->is_closed = TRUE; vrequest_joblist_append(vr); } -void vrequest_backend_overloaded(vrequest *vr) { - vr->action_stack.backend_failed = TRUE; - vr->action_stack.backend_error = BACKEND_OVERLOAD; -} - void vrequest_backend_error(vrequest *vr, backend_error berror) { vr->action_stack.backend_failed = TRUE; vr->action_stack.backend_error = berror; + vr->state = VRS_HANDLE_REQUEST_HEADERS; + vr->backend = NULL; + vrequest_joblist_append(vr); } +void vrequest_backend_overloaded(vrequest *vr) { + vrequest_backend_error(vr, BACKEND_OVERLOAD); +} void vrequest_backend_dead(vrequest *vr) { - vr->action_stack.backend_failed = TRUE; - vr->action_stack.backend_error = BACKEND_DEAD; + vrequest_backend_error(vr, BACKEND_DEAD); } + /* received all request headers */ void vrequest_handle_request_headers(vrequest *vr) { if (VRS_CLEAN == vr->state) { @@ -167,7 +179,7 @@ gboolean vrequest_handle_direct(vrequest *vr) { if (vr->state < VRS_READ_CONTENT) { vr->state = VRS_HANDLE_RESPONSE_HEADERS; vr->out->is_closed = TRUE; - vr->handle_request_body = NULL; + vr->backend = NULL; return TRUE; } else { return FALSE; @@ -175,10 +187,10 @@ gboolean vrequest_handle_direct(vrequest *vr) { } /* handle request over time */ -gboolean vrequest_handle_indirect(vrequest *vr, vrequest_handler handle_request_body) { +gboolean vrequest_handle_indirect(vrequest *vr, plugin *p) { if (vr->state < VRS_READ_CONTENT) { vr->state = VRS_READ_CONTENT; - vr->handle_request_body = handle_request_body; + vr->backend = p; return TRUE; } else { return FALSE; @@ -213,10 +225,10 @@ static gboolean vrequest_do_handle_actions(vrequest *vr) { static gboolean vrequest_do_handle_read(vrequest *vr) { handler_t res; if (vr->in->is_closed && vr->in->bytes_in == vr->in->bytes_out) return TRUE; - if (vr->handle_request_body) { + if (vr->backend && vr->backend->handle_request_body) { chunkqueue_steal_all(vr->in, vr->vr_in); /* TODO: filters */ if (vr->vr_in->is_closed) vr->in->is_closed = TRUE; - res = vr->handle_request_body(vr); + res = vr->backend->handle_request_body(vr, vr->backend); switch (res) { case HANDLER_GO_ON: break; diff --git a/src/worker.c b/src/worker.c index f251eed..4044ec7 100644 --- a/src/worker.c +++ b/src/worker.c @@ -168,17 +168,19 @@ GString *worker_current_timestamp(worker *wrk) { /* stop worker watcher */ static void worker_stop_cb(struct ev_loop *loop, ev_async *w, int revents) { + worker *wrk = (worker*) w->data; UNUSED(loop); UNUSED(revents); - worker *wrk = (worker*) w->data; + worker_stop(wrk, wrk); } /* exit worker watcher */ static void worker_exit_cb(struct ev_loop *loop, ev_async *w, int revents) { + worker *wrk = (worker*) w->data; UNUSED(loop); UNUSED(revents); - worker *wrk = (worker*) w->data; + worker_exit(wrk, wrk); } @@ -225,11 +227,10 @@ static void worker_new_con_cb(struct ev_loop *loop, ev_async *w, int revents) { /* stats watcher */ static void worker_stats_watcher_cb(struct ev_loop *loop, ev_timer *w, int revents) { worker *wrk = (worker*) w->data; + ev_tstamp now = ev_now(wrk->loop); UNUSED(loop); UNUSED(revents); - ev_tstamp now = ev_now(wrk->loop); - if (wrk->stats.last_update && now != wrk->stats.last_update) { wrk->stats.requests_per_sec = (wrk->stats.requests - wrk->stats.last_requests) / (now - wrk->stats.last_update);