|
|
|
|
|
|
|
#include <lighttpd/base.h>
|
|
|
|
|
|
|
|
typedef struct action_stack_element action_stack_element;
|
|
|
|
|
|
|
|
struct action_stack_element {
|
|
|
|
liAction *act;
|
|
|
|
union {
|
|
|
|
gpointer context;
|
|
|
|
guint pos;
|
|
|
|
} data;
|
|
|
|
gboolean finished, backlog_provided;
|
|
|
|
};
|
|
|
|
|
|
|
|
void li_action_release(liServer *srv, liAction *a) {
|
|
|
|
guint i;
|
|
|
|
if (!a) return;
|
|
|
|
LI_FORCE_ASSERT(g_atomic_int_get(&a->refcount) > 0);
|
|
|
|
if (g_atomic_int_dec_and_test(&a->refcount)) {
|
|
|
|
switch (a->type) {
|
|
|
|
case LI_ACTION_TNOTHING:
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TSETTING:
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TSETTINGPTR:
|
|
|
|
li_release_optionptr(srv, a->data.settingptr.value);
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TFUNCTION:
|
|
|
|
if (a->data.function.free) {
|
|
|
|
a->data.function.free(srv, a->data.function.param);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TCONDITION:
|
|
|
|
li_condition_release(srv, a->data.condition.cond);
|
|
|
|
li_action_release(srv, a->data.condition.target);
|
|
|
|
li_action_release(srv, a->data.condition.target_else);
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TLIST:
|
|
|
|
for (i = a->data.list->len; i-- > 0; ) {
|
|
|
|
li_action_release(srv, g_array_index(a->data.list, liAction*, i));
|
|
|
|
}
|
|
|
|
g_array_free(a->data.list, TRUE);
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TBALANCER:
|
|
|
|
if (a->data.balancer.free) {
|
|
|
|
a->data.balancer.free(srv, a->data.balancer.param);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
g_slice_free(liAction, a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void li_action_acquire(liAction *a) {
|
|
|
|
LI_FORCE_ASSERT(g_atomic_int_get(&a->refcount) > 0);
|
|
|
|
g_atomic_int_inc(&a->refcount);
|
|
|
|
}
|
|
|
|
|
|
|
|
liAction* li_action_new(void) {
|
|
|
|
liAction *a = g_slice_new(liAction);
|
|
|
|
|
|
|
|
a->refcount = 1;
|
|
|
|
a->type = LI_ACTION_TNOTHING;
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
liAction *li_action_new_setting(liOptionSet setting) {
|
|
|
|
liAction *a = g_slice_new(liAction);
|
|
|
|
|
|
|
|
a->refcount = 1;
|
|
|
|
a->type = LI_ACTION_TSETTING;
|
|
|
|
a->data.setting = setting;
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
liAction *li_action_new_settingptr(liOptionPtrSet setting) {
|
|
|
|
liAction *a = g_slice_new(liAction);
|
|
|
|
|
|
|
|
a->refcount = 1;
|
|
|
|
a->type = LI_ACTION_TSETTINGPTR;
|
|
|
|
a->data.settingptr = setting;
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
liAction *li_action_new_function(liActionFuncCB func, liActionCleanupCB fcleanup, liActionFreeCB ffree, gpointer param) {
|
|
|
|
liAction *a;
|
|
|
|
|
|
|
|
a = g_slice_new(liAction);
|
|
|
|
a->refcount = 1;
|
|
|
|
a->type = LI_ACTION_TFUNCTION;
|
|
|
|
a->data.function.func = func;
|
|
|
|
a->data.function.cleanup = fcleanup;
|
|
|
|
a->data.function.free = ffree;
|
|
|
|
a->data.function.param = param;
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
liAction *li_action_new_list(void) {
|
|
|
|
liAction *a;
|
|
|
|
|
|
|
|
a = g_slice_new(liAction);
|
|
|
|
a->refcount = 1;
|
|
|
|
a->type = LI_ACTION_TLIST;
|
|
|
|
a->data.list = g_array_new(FALSE, TRUE, sizeof(liAction *));
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
liAction *li_action_new_condition(liCondition *cond, liAction *target, liAction *target_else) {
|
|
|
|
liAction *a;
|
|
|
|
|
|
|
|
a = g_slice_new(liAction);
|
|
|
|
a->refcount = 1;
|
|
|
|
a->type = LI_ACTION_TCONDITION;
|
|
|
|
a->data.condition.cond = cond;
|
|
|
|
a->data.condition.target = target;
|
|
|
|
a->data.condition.target_else = target_else;
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
liAction *li_action_new_balancer(liBackendSelectCB bselect, liBackendFallbackCB bfallback, liBackendFinishedCB bfinished, liBalancerFreeCB bfree, gpointer param, gboolean provide_backlog) {
|
|
|
|
liAction *a;
|
|
|
|
|
|
|
|
a = g_slice_new(liAction);
|
|
|
|
a->refcount = 1;
|
|
|
|
a->type = LI_ACTION_TBALANCER;
|
|
|
|
a->data.balancer.select = bselect;
|
|
|
|
a->data.balancer.fallback = bfallback;
|
|
|
|
a->data.balancer.finished = bfinished;
|
|
|
|
a->data.balancer.free = bfree;
|
|
|
|
a->data.balancer.param = param;
|
|
|
|
a->data.balancer.provide_backlog = provide_backlog;
|
|
|
|
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
|
|
|
void li_action_append_inplace(liAction *list, liAction *element) {
|
|
|
|
LI_FORCE_ASSERT(NULL != list && NULL != element);
|
|
|
|
LI_FORCE_ASSERT(1 == g_atomic_int_get(&list->refcount));
|
|
|
|
|
|
|
|
if (LI_ACTION_TLIST != list->type) {
|
|
|
|
liAction *wrapped = NULL;
|
|
|
|
if (LI_ACTION_TNOTHING != list->type) {
|
|
|
|
wrapped = li_action_new();
|
|
|
|
*wrapped = *list;
|
|
|
|
}
|
|
|
|
memset(list, 0, sizeof(*list));
|
|
|
|
list->refcount = 1;
|
|
|
|
list->type = LI_ACTION_TLIST;
|
|
|
|
list->data.list = g_array_new(FALSE, TRUE, sizeof(liAction *));
|
|
|
|
if (NULL != wrapped) g_array_append_val(list->data.list, wrapped);
|
|
|
|
}
|
|
|
|
if (LI_ACTION_TNOTHING != element->type) {
|
|
|
|
li_action_acquire(element);
|
|
|
|
g_array_append_val(list->data.list, element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void action_stack_element_release(liServer *srv, liVRequest *vr, action_stack_element *ase) {
|
|
|
|
liAction *a;
|
|
|
|
|
|
|
|
if (NULL == ase || NULL == (a = ase->act)) return;
|
|
|
|
|
|
|
|
switch (a->type) {
|
|
|
|
case LI_ACTION_TNOTHING:
|
|
|
|
case LI_ACTION_TSETTING:
|
|
|
|
case LI_ACTION_TSETTINGPTR:
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TFUNCTION:
|
|
|
|
if (ase->data.context && a->data.function.cleanup) {
|
|
|
|
a->data.function.cleanup(vr, a->data.function.param, ase->data.context);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TCONDITION:
|
|
|
|
if (a->data.condition.cond->rvalue.type == LI_COND_VALUE_REGEXP) {
|
|
|
|
/* pop regex stack */
|
|
|
|
GArray *rs = vr->action_stack.regex_stack;
|
|
|
|
/* cheap check to prevent segfault if condition errored without pushing onto stack; whole stack gets cleaned anyways */
|
|
|
|
if (rs->len) {
|
|
|
|
liActionRegexStackElement *arse = &g_array_index(rs, liActionRegexStackElement, rs->len - 1);
|
|
|
|
if (arse->string)
|
|
|
|
g_string_free(arse->string, TRUE);
|
|
|
|
g_match_info_free(arse->match_info);
|
|
|
|
g_array_set_size(rs, rs->len - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TLIST:
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TBALANCER:
|
|
|
|
a->data.balancer.finished(vr, a->data.balancer.param, ase->data.context);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
li_action_release(srv, ase->act);
|
|
|
|
ase->act = NULL;
|
|
|
|
ase->data.context = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void li_action_stack_init(liActionStack *as) {
|
|
|
|
as->stack = g_array_sized_new(FALSE, TRUE, sizeof(action_stack_element), 16);
|
|
|
|
as->regex_stack = g_array_sized_new(FALSE, FALSE, sizeof(liActionRegexStackElement), 16);
|
|
|
|
as->backend_stack = g_array_sized_new(FALSE, TRUE, sizeof(action_stack_element), 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void li_action_backend_stack_reset(liVRequest *vr, liActionStack *as) {
|
|
|
|
liServer *srv = vr->wrk->srv;
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
if (NULL == as->backend_stack) return;
|
|
|
|
|
|
|
|
/* index 0 is the "deepest" backend - release it first */
|
|
|
|
for (i = 0; i < as->backend_stack->len; i++ ) {
|
|
|
|
action_stack_element_release(srv, vr, &g_array_index(as->backend_stack, action_stack_element, i));
|
|
|
|
}
|
|
|
|
g_array_set_size(as->backend_stack, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void li_action_stack_reset(liVRequest *vr, liActionStack *as) {
|
|
|
|
liServer *srv = vr->wrk->srv;
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
for (i = as->stack->len; i-- > 0; ) {
|
|
|
|
action_stack_element_release(srv, vr, &g_array_index(as->stack, action_stack_element, i));
|
|
|
|
}
|
|
|
|
g_array_set_size(as->stack, 0);
|
|
|
|
|
|
|
|
li_action_backend_stack_reset(vr, as);
|
|
|
|
|
|
|
|
as->backend_failed = FALSE;
|
|
|
|
as->backend_finished = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void li_action_stack_clear(liVRequest *vr, liActionStack *as) {
|
|
|
|
liServer *srv = vr->wrk->srv;
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
for (i = as->stack->len; i-- > 0; ) {
|
|
|
|
action_stack_element_release(srv, vr, &g_array_index(as->stack, action_stack_element, i));
|
|
|
|
}
|
|
|
|
g_array_free(as->stack, TRUE);
|
|
|
|
|
|
|
|
li_action_backend_stack_reset(vr, as);
|
|
|
|
g_array_free(as->backend_stack, TRUE);
|
|
|
|
|
|
|
|
g_array_free(as->regex_stack, TRUE);
|
|
|
|
|
|
|
|
as->stack = as->backend_stack = as->regex_stack = NULL;
|
|
|
|
as->backend_failed = FALSE;
|
|
|
|
as->backend_finished = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static action_stack_element *action_stack_top(liActionStack* as) {
|
|
|
|
return as->stack->len > 0 ? &g_array_index(as->stack, action_stack_element, as->stack->len - 1) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** handle sublist now, remember current position (stack) */
|
|
|
|
void li_action_enter(liVRequest *vr, liAction *a) {
|
|
|
|
liActionStack *as = &vr->action_stack;
|
|
|
|
action_stack_element *top_ase = action_stack_top(as);
|
|
|
|
action_stack_element ase = { a, { 0 }, FALSE,
|
|
|
|
(top_ase ? top_ase->backlog_provided || (top_ase->act->type == LI_ACTION_TBALANCER && top_ase->act->data.balancer.provide_backlog) : FALSE) };
|
|
|
|
li_action_acquire(a);
|
|
|
|
g_array_append_val(as->stack, ase);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void action_stack_pop(liServer *srv, liVRequest *vr, liActionStack *as) {
|
|
|
|
action_stack_element *ase;
|
|
|
|
|
|
|
|
if (as->stack->len == 0) return;
|
|
|
|
|
|
|
|
ase = &g_array_index(as->stack, action_stack_element, as->stack->len - 1);
|
|
|
|
|
|
|
|
if (ase->act->type == LI_ACTION_TBALANCER && !as->backend_finished) {
|
|
|
|
/* release later if backend is finished (i.e. "disconnected") */
|
|
|
|
g_array_append_val(as->backend_stack, *ase);
|
|
|
|
} else {
|
|
|
|
action_stack_element_release(srv, vr, ase);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_array_set_size(as->stack, as->stack->len - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
liHandlerResult li_action_execute(liVRequest *vr) {
|
|
|
|
liAction *a;
|
|
|
|
liActionStack *as = &vr->action_stack;
|
|
|
|
action_stack_element *ase;
|
|
|
|
guint ase_ndx;
|
|
|
|
liHandlerResult res;
|
|
|
|
gboolean condres;
|
|
|
|
liServer *srv = vr->wrk->srv;
|
|
|
|
|
|
|
|
while (NULL != (ase = action_stack_top(as))) {
|
|
|
|
if (as->backend_failed) {
|
|
|
|
/* set by li_vrequest_backend_error */
|
|
|
|
vr->state = LI_VRS_HANDLE_REQUEST_HEADERS;
|
|
|
|
vr->backend = NULL;
|
|
|
|
|
|
|
|
while (NULL != (ase = action_stack_top(as)) && (ase->act->type != LI_ACTION_TBALANCER || !ase->act->data.balancer.provide_backlog)) {
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
}
|
|
|
|
if (!ase) { /* no backlogging balancer found */
|
|
|
|
if (li_vrequest_handle_direct(vr))
|
|
|
|
vr->response.http_status = 503;
|
|
|
|
return LI_HANDLER_GO_ON;
|
|
|
|
}
|
|
|
|
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 LI_HANDLER_GO_ON:
|
|
|
|
ase->finished = TRUE;
|
|
|
|
break;
|
|
|
|
case LI_HANDLER_ERROR:
|
|
|
|
li_action_stack_reset(vr, as);
|
|
|
|
return res;
|
|
|
|
case LI_HANDLER_COMEBACK:
|
|
|
|
case LI_HANDLER_WAIT_FOR_EVENT:
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
if (as->backend_failed && ase == action_stack_top(as)) {
|
|
|
|
/* when backend selection failed and balancer i still the top action, we remove the balancer itself so it doesn't loop forever */
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (ase->finished) {
|
|
|
|
/* a TFUNCTION may enter sub actions _and_ return GO_ON, so we cannot pop the last element
|
|
|
|
* but we have to remember we already executed it
|
|
|
|
*/
|
|
|
|
if (ase->act->type == LI_ACTION_TBALANCER) {
|
|
|
|
/* wait until we found a backend */
|
|
|
|
LI_VREQUEST_WAIT_FOR_RESPONSE_HEADERS(vr);
|
|
|
|
}
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
vr->wrk->stats.actions_executed++;
|
|
|
|
a = ase->act;
|
|
|
|
ase_ndx = as->stack->len - 1; /* sometimes the stack gets modified - reread "ase" after that */
|
|
|
|
|
|
|
|
switch (a->type) {
|
|
|
|
case LI_ACTION_TNOTHING:
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TSETTING:
|
|
|
|
vr->options[a->data.setting.ndx] = a->data.setting.value;
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TSETTINGPTR:
|
|
|
|
if (vr->optionptrs[a->data.settingptr.ndx] != a->data.settingptr.value) {
|
|
|
|
g_atomic_int_inc(&a->data.settingptr.value->refcount);
|
|
|
|
li_release_optionptr(srv, vr->optionptrs[a->data.settingptr.ndx]);
|
|
|
|
vr->optionptrs[a->data.settingptr.ndx] = a->data.settingptr.value;
|
|
|
|
}
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TFUNCTION:
|
|
|
|
res = a->data.function.func(vr, a->data.function.param, &ase->data.context);
|
|
|
|
ase = &g_array_index(as->stack, action_stack_element, ase_ndx);
|
|
|
|
|
|
|
|
switch (res) {
|
|
|
|
case LI_HANDLER_GO_ON:
|
|
|
|
ase->finished = TRUE;
|
|
|
|
break;
|
|
|
|
case LI_HANDLER_ERROR:
|
|
|
|
li_action_stack_reset(vr, as);
|
|
|
|
return res;
|
|
|
|
case LI_HANDLER_COMEBACK:
|
|
|
|
return res;
|
|
|
|
case LI_HANDLER_WAIT_FOR_EVENT:
|
|
|
|
if (ase != action_stack_top(as)) break; /* allow an action to push another action and rerun after it again */
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TCONDITION:
|
|
|
|
condres = FALSE;
|
|
|
|
res = li_condition_check(vr, a->data.condition.cond, &condres);
|
|
|
|
switch (res) {
|
|
|
|
case LI_HANDLER_GO_ON:
|
|
|
|
ase->finished = TRUE;
|
|
|
|
if (condres) {
|
|
|
|
if (a->data.condition.target) li_action_enter(vr, a->data.condition.target);
|
|
|
|
}
|
|
|
|
else if (a->data.condition.target_else) {
|
|
|
|
li_action_enter(vr, a->data.condition.target_else);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LI_HANDLER_ERROR:
|
|
|
|
li_action_stack_reset(vr, as);
|
|
|
|
return res;
|
|
|
|
case LI_HANDLER_COMEBACK:
|
|
|
|
case LI_HANDLER_WAIT_FOR_EVENT:
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TLIST:
|
|
|
|
if (ase->data.pos >= a->data.list->len) {
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
} else {
|
|
|
|
guint p = ase->data.pos++;
|
|
|
|
li_action_enter(vr, g_array_index(a->data.list, liAction*, p));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LI_ACTION_TBALANCER:
|
|
|
|
/* skip balancer if request is already handled */
|
|
|
|
if (li_vrequest_is_handled(vr)) {
|
|
|
|
ase->finished = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
res = a->data.balancer.select(vr, ase->backlog_provided, a->data.balancer.param, &ase->data.context);
|
|
|
|
ase = &g_array_index(as->stack, action_stack_element, ase_ndx);
|
|
|
|
switch (res) {
|
|
|
|
case LI_HANDLER_GO_ON:
|
|
|
|
ase->finished = TRUE;
|
|
|
|
break;
|
|
|
|
case LI_HANDLER_ERROR:
|
|
|
|
li_action_stack_reset(vr, as);
|
|
|
|
return res;
|
|
|
|
case LI_HANDLER_COMEBACK:
|
|
|
|
case LI_HANDLER_WAIT_FOR_EVENT:
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
if (as->backend_failed && ase == action_stack_top(as)) {
|
|
|
|
/* when backend selection failed and balancer is still the top action, we remove the balancer itself so it doesn't loop forever */
|
|
|
|
action_stack_pop(srv, vr, as);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (as->backend_failed) {
|
|
|
|
if (li_vrequest_handle_direct(vr))
|
|
|
|
vr->response.http_status = 503;
|
|
|
|
}
|
|
|
|
return LI_HANDLER_GO_ON;
|
|
|
|
}
|
|
|
|
|
|
|
|
void li_vrequest_backend_finished(liVRequest *vr) {
|
|
|
|
if (!li_vrequest_is_handled(vr)) return;
|
|
|
|
vr->action_stack.backend_finished = TRUE;
|
|
|
|
li_action_backend_stack_reset(vr, &vr->action_stack);
|
|
|
|
}
|