2
0
Fork 0

Merge virtual requests

personal/stbuehler/wip
Stefan Bühler 2008-10-25 15:25:02 +02:00
commit 73fff068c3
32 changed files with 960 additions and 606 deletions

View File

@ -294,6 +294,7 @@ SET(COMMON_SRC
url_parser.c
utils.c
value.c
virtualrequest.c
worker.c
plugin_core.c

View File

@ -121,10 +121,10 @@ void action_stack_clear(server *srv, action_stack *as) {
}
/** handle sublist now, remember current position (stack) */
void action_enter(connection *con, action *a) {
void action_enter(vrequest *vr, action *a) {
action_acquire(a);
action_stack_element ase = { a, 0 };
g_array_append_val(con->action_stack.stack, ase);
g_array_append_val(vr->action_stack.stack, ase);
}
static action_stack_element *action_stack_top(action_stack* as) {
@ -145,51 +145,66 @@ static action* action_stack_element_action(action_stack_element *ase) {
}
}
action_result action_execute(connection *con) {
handler_t action_execute(vrequest *vr) {
action *a;
action_stack *as = &con->action_stack;
action_stack *as = &vr->action_stack;
action_stack_element *ase;
action_result res;
handler_t res;
gboolean condres;
while (NULL != (ase = action_stack_top(as))) {
a = action_stack_element_action(ase);
if (!a) {
action_stack_pop(con->srv, as);
action_stack_pop(vr->con->srv, as);
continue;
}
con->wrk->stats.actions_executed++;
vr->con->wrk->stats.actions_executed++;
switch (a->type) {
case ACTION_TSETTING:
con->options[a->data.setting.ndx] = a->data.setting.value;
vr->con->options[a->data.setting.ndx] = a->data.setting.value;
break;
case ACTION_TFUNCTION:
res = a->data.function.func(con, a->data.function.param);
res = a->data.function.func(vr, a->data.function.param);
switch (res) {
case ACTION_GO_ON:
case ACTION_FINISHED:
case HANDLER_GO_ON:
case HANDLER_FINISHED:
break;
case ACTION_ERROR:
action_stack_reset(con->srv, as);
case HANDLER_ERROR:
action_stack_reset(vr->con->srv, as);
case HANDLER_COMEBACK:
case HANDLER_WAIT_FOR_EVENT:
case HANDLER_WAIT_FOR_FD:
return res;
case ACTION_WAIT_FOR_EVENT:
return ACTION_WAIT_FOR_EVENT;
}
break;
case ACTION_TCONDITION:
if (condition_check(con, a->data.condition.cond)) {
action_enter(con, a->data.condition.target);
}
else if (a->data.condition.target_else) {
action_enter(con, a->data.condition.target_else);
condres = FALSE;
res = condition_check(vr, a->data.condition.cond, &condres);
switch (res) {
case HANDLER_GO_ON:
case HANDLER_FINISHED:
if (condres) {
action_enter(vr, a->data.condition.target);
}
else if (a->data.condition.target_else) {
action_enter(vr, a->data.condition.target_else);
}
break;
case HANDLER_ERROR:
action_stack_reset(vr->con->srv, as);
case HANDLER_COMEBACK:
case HANDLER_WAIT_FOR_EVENT:
case HANDLER_WAIT_FOR_FD:
return res;
}
break;
case ACTION_TLIST:
action_enter(con, a);
action_enter(vr, a);
break;
}
ase->pos++;
}
return ACTION_FINISHED;
return HANDLER_FINISHED;
}

View File

@ -3,12 +3,12 @@
#include "settings.h"
typedef enum {
ACTION_GO_ON,
ACTION_FINISHED,
ACTION_ERROR,
ACTION_WAIT_FOR_EVENT
} action_result;
// typedef enum {
// ACTION_GO_ON,
// ACTION_FINISHED,
// ACTION_ERROR,
// ACTION_WAIT_FOR_EVENT
// } action_result;
// action type
typedef enum {
@ -28,8 +28,8 @@ struct action_stack {
GArray* stack;
};
struct server; struct connection;
typedef action_result (*ActionFunc)(struct connection *con, gpointer param);
struct server; struct connection; struct vrequest;
typedef handler_t (*ActionFunc)(struct vrequest *vr, gpointer param);
typedef void (*ActionFree)(struct server *srv, gpointer param);
struct action_func {
@ -68,8 +68,8 @@ LI_API void action_stack_reset(server *srv, action_stack *as);
LI_API void action_stack_clear(server *srv, action_stack *as);
/** handle sublist now, remember current position (stack) */
LI_API void action_enter(connection *con, action *a);
LI_API action_result action_execute(connection *con);
LI_API void action_enter(struct vrequest *vr, action *a);
LI_API handler_t action_execute(struct vrequest *vr);
LI_API void action_release(server *srv, action *a);

View File

@ -44,6 +44,7 @@ typedef struct connection connection;
#include "plugin.h"
#include "request.h"
#include "response.h"
#include "virtualrequest.h"
#include "log.h"
#include "connection.h"

View File

@ -45,16 +45,16 @@ static void chunkfile_release(chunkfile *cf) {
/* open the file cf->name if it is not already opened for reading
* may return HANDLER_GO_ON, HANDLER_ERROR, HANDLER_WAIT_FOR_FD
*/
handler_t chunkfile_open(connection *con, chunkfile *cf) {
handler_t chunkfile_open(vrequest *vr, chunkfile *cf) {
if (!cf) return HANDLER_ERROR;
if (-1 != cf->fd) return HANDLER_GO_ON;
if (!cf->name) {
CON_ERROR(con, "%s", "Missing filename for FILE_CHUNK");
VR_ERROR(vr, "%s", "Missing filename for FILE_CHUNK");
return HANDLER_ERROR;
}
if (-1 == (cf->fd = open(cf->name->str, O_RDONLY))) {
if (EMFILE == errno) return HANDLER_WAIT_FOR_FD;
CON_ERROR(con, "Couldn't open file '%s': %s", GSTR_SAFE_STR(cf->name), g_strerror(errno));
VR_ERROR(vr, "Couldn't open file '%s': %s", GSTR_SAFE_STR(cf->name), g_strerror(errno));
return HANDLER_ERROR;
}
#ifdef FD_CLOEXEC
@ -64,7 +64,7 @@ handler_t chunkfile_open(connection *con, chunkfile *cf) {
/* tell the kernel that we want to stream the file */
if (-1 == posix_fadvise(cf->fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
if (ENOSYS != errno) {
CON_ERROR(con, "posix_fadvise failed for '%s': %s (%i)", GSTR_SAFE_STR(cf->name), g_strerror(errno), cf->fd);
VR_ERROR(vr, "posix_fadvise failed for '%s': %s (%i)", GSTR_SAFE_STR(cf->name), g_strerror(errno), cf->fd);
}
}
#endif
@ -85,7 +85,7 @@ handler_t chunkfile_open(connection *con, chunkfile *cf) {
* the data is _not_ marked as "done"
* may return HANDLER_GO_ON, HANDLER_ERROR, HANDLER_WAIT_FOR_FD
*/
handler_t chunkiter_read(connection *con, chunkiter iter, off_t start, off_t length, char **data_start, off_t *data_len) {
handler_t chunkiter_read(vrequest *vr, chunkiter iter, off_t start, off_t length, char **data_start, off_t *data_len) {
chunk *c = chunkiter_chunk(iter);
off_t we_want, we_have, our_start, our_offset;
handler_t res = HANDLER_GO_ON;
@ -105,7 +105,7 @@ handler_t chunkiter_read(connection *con, chunkiter iter, off_t start, off_t len
*data_len = length;
break;
case FILE_CHUNK:
if (HANDLER_GO_ON != (res = chunkfile_open(con, c->file.file))) return res;
if (HANDLER_GO_ON != (res = chunkfile_open(vr, c->file.file))) return res;
if (length > MAX_MMAP_CHUNK) length = MAX_MMAP_CHUNK;
@ -142,11 +142,11 @@ handler_t chunkiter_read(connection *con, chunkiter iter, off_t start, off_t len
if (-1 == lseek(c->file.file->fd, our_start, SEEK_SET)) {
/* prefer the error of the first syscall */
if (0 != mmap_errno) {
CON_ERROR(con, "mmap failed for '%s' (fd = %i): %s",
VR_ERROR(vr, "mmap failed for '%s' (fd = %i): %s",
GSTR_SAFE_STR(c->file.file->name), c->file.file->fd,
g_strerror(mmap_errno));
} else {
CON_ERROR(con, "lseek failed for '%s' (fd = %i): %s",
VR_ERROR(vr, "lseek failed for '%s' (fd = %i): %s",
GSTR_SAFE_STR(c->file.file->name), c->file.file->fd,
g_strerror(errno));
}
@ -159,11 +159,11 @@ read_chunk:
if (EINTR == errno) goto read_chunk;
/* prefer the error of the first syscall */
if (0 != mmap_errno) {
CON_ERROR(con, "mmap failed for '%s' (fd = %i): %s",
VR_ERROR(vr, "mmap failed for '%s' (fd = %i): %s",
GSTR_SAFE_STR(c->file.file->name), c->file.file->fd,
g_strerror(mmap_errno));
} else {
CON_ERROR(con, "read failed for '%s' (fd = %i): %s",
VR_ERROR(vr, "read failed for '%s' (fd = %i): %s",
GSTR_SAFE_STR(c->file.file->name), c->file.file->fd,
g_strerror(errno));
}
@ -183,7 +183,7 @@ read_chunk:
/* don't advise files < 64Kb */
if (c->file.mmap.length > (64*1024) &&
0 != madvise(c->file.mmap.data, c->file.mmap.length, MADV_WILLNEED)) {
CON_ERROR(con, "madvise failed for '%s' (fd = %i): %s",
VR_ERROR(vr, "madvise failed for '%s' (fd = %i): %s",
GSTR_SAFE_STR(c->file.file->name), c->file.file->fd,
g_strerror(errno));
}

View File

@ -16,7 +16,7 @@ struct chunkiter;
typedef struct chunkiter chunkiter;
struct server;
struct connection;
struct vrequest;
#include "settings.h"
@ -76,7 +76,7 @@ struct chunkiter {
/* open the file cf->name if it is not already opened for reading
* may return HANDLER_GO_ON, HANDLER_ERROR, HANDLER_WAIT_FOR_FD
*/
LI_API handler_t chunkfile_open(struct connection *con, chunkfile *cf);
LI_API handler_t chunkfile_open(struct vrequest *vr, chunkfile *cf);
/******************
* chunk iterator *
@ -92,7 +92,7 @@ INLINE goffset chunkiter_length(chunkiter iter);
* the data is _not_ marked as "done"
* may return HANDLER_GO_ON, HANDLER_ERROR, HANDLER_WAIT_FOR_FD
*/
LI_API handler_t chunkiter_read(struct connection *con, chunkiter iter, off_t start, off_t length, char **data_start, off_t *data_len);
LI_API handler_t chunkiter_read(struct vrequest *vr, chunkiter iter, off_t start, off_t length, char **data_start, off_t *data_len);
/******************
* chunk *

View File

@ -23,7 +23,7 @@ handler_t chunk_parser_prepare(chunk_parser_ctx *ctx) {
return HANDLER_GO_ON;
}
handler_t chunk_parser_next(connection *con, chunk_parser_ctx *ctx, char **p, char **pe) {
handler_t chunk_parser_next(vrequest *vr, chunk_parser_ctx *ctx, char **p, char **pe) {
off_t l;
handler_t res;
@ -39,7 +39,7 @@ handler_t chunk_parser_next(connection *con, chunk_parser_ctx *ctx, char **p, ch
if (NULL == ctx->curi.element) return HANDLER_WAIT_FOR_EVENT;
if (HANDLER_GO_ON != (res = chunkiter_read(con, ctx->curi, ctx->start, l - ctx->start, &ctx->buf, &ctx->length))) {
if (HANDLER_GO_ON != (res = chunkiter_read(vr, ctx->curi, ctx->start, l - ctx->start, &ctx->buf, &ctx->length))) {
return res;
}
@ -53,7 +53,7 @@ void chunk_parser_done(chunk_parser_ctx *ctx, goffset len) {
ctx->start += len;
}
gboolean chunk_extract_to(connection *con, chunk_parser_mark from, chunk_parser_mark to, GString *dest) {
gboolean chunk_extract_to(vrequest *vr, chunk_parser_mark from, chunk_parser_mark to, GString *dest) {
g_string_set_size(dest, 0);
chunk_parser_mark i;
@ -62,7 +62,7 @@ gboolean chunk_extract_to(connection *con, chunk_parser_mark from, chunk_parser_
while (i.pos < len) {
char *buf;
off_t we_have;
if (HANDLER_GO_ON != chunkiter_read(con, i.ci, i.pos, len - i.pos, &buf, &we_have)) goto error;
if (HANDLER_GO_ON != chunkiter_read(vr, i.ci, i.pos, len - i.pos, &buf, &we_have)) goto error;
g_string_append_len(dest, buf, we_have);
i.pos += we_have;
}
@ -71,7 +71,7 @@ gboolean chunk_extract_to(connection *con, chunk_parser_mark from, chunk_parser_
while (i.pos < to.pos) {
char *buf;
off_t we_have;
if (HANDLER_GO_ON != chunkiter_read(con, i.ci, i.pos, to.pos - i.pos, &buf, &we_have)) goto error;
if (HANDLER_GO_ON != chunkiter_read(vr, i.ci, i.pos, to.pos - i.pos, &buf, &we_have)) goto error;
g_string_append_len(dest, buf, we_have);
i.pos += we_have;
}
@ -83,9 +83,9 @@ error:
return FALSE;
}
GString* chunk_extract(connection *con, chunk_parser_mark from, chunk_parser_mark to) {
GString* chunk_extract(vrequest *vr, chunk_parser_mark from, chunk_parser_mark to) {
GString *str = g_string_sized_new(0);
if (chunk_extract_to(con, from, to, str)) return str;
if (chunk_extract_to(vr, from, to, str)) return str;
g_string_free(str, TRUE);
return NULL;
}

View File

@ -32,11 +32,11 @@ struct chunk_parser_mark {
LI_API void chunk_parser_init(chunk_parser_ctx *ctx, chunkqueue *cq);
LI_API void chunk_parser_reset(chunk_parser_ctx *ctx);
LI_API handler_t chunk_parser_prepare(chunk_parser_ctx *ctx);
LI_API handler_t chunk_parser_next(struct connection *con, chunk_parser_ctx *ctx, char **p, char **pe);
LI_API handler_t chunk_parser_next(struct vrequest *vr, chunk_parser_ctx *ctx, char **p, char **pe);
LI_API void chunk_parser_done(chunk_parser_ctx *ctx, goffset len);
LI_API gboolean chunk_extract_to(struct connection *con, chunk_parser_mark from, chunk_parser_mark to, GString *dest);
LI_API GString* chunk_extract(struct connection *con, chunk_parser_mark from, chunk_parser_mark to);
LI_API gboolean chunk_extract_to(struct vrequest *vr, chunk_parser_mark from, chunk_parser_mark to, GString *dest);
LI_API GString* chunk_extract(struct vrequest *vr, chunk_parser_mark from, chunk_parser_mark to);
INLINE chunk_parser_mark chunk_parser_getmark(chunk_parser_ctx *ctx, const char *fpc);

View File

@ -249,9 +249,10 @@ const char* cond_lvalue_to_string(cond_lvalue_t t) {
}
/* COND_VALUE_STRING and COND_VALUE_REGEXP only */
static gboolean condition_check_eval_string(connection *con, condition *cond) {
static handler_t condition_check_eval_string(vrequest *vr, condition *cond, gboolean *res) {
connection *con = vr->con;
const char *val = "";
gboolean result = FALSE;
*res = FALSE;
switch (cond->lvalue->type) {
case COMP_REQUEST_LOCALIP:
@ -261,28 +262,28 @@ static gboolean condition_check_eval_string(connection *con, condition *cond) {
val = con->remote_addr_str->str;
break;
case COMP_REQUEST_PATH:
val = con->request.uri.path->str;
val = vr->request.uri.path->str;
break;
case COMP_REQUEST_HOST:
val = con->request.uri.host->str;
val = vr->request.uri.host->str;
break;
case COMP_REQUEST_SCHEME:
val = con->is_ssl ? "https" : "http";
break;
case COMP_REQUEST_QUERY_STRING:
val = con->request.uri.query->str;
val = vr->request.uri.query->str;
break;
case COMP_REQUEST_METHOD:
val = con->request.http_method_str->str;
val = vr->request.http_method_str->str;
break;
case COMP_PHYSICAL_PATH:
val = con->physical.path->str;
val = vr->physical.path->str;
break;
case COMP_PHYSICAL_PATH_EXISTS:
/* TODO: physical path exists */
break;
case COMP_REQUEST_HEADER:
http_header_get_fast(con->wrk->tmp_str, con->request.headers, GSTR_LEN(cond->lvalue->key));
http_header_get_fast(con->wrk->tmp_str, vr->request.headers, GSTR_LEN(cond->lvalue->key));
val = con->wrk->tmp_str->str;
break;
case COMP_PHYSICAL_SIZE:
@ -291,37 +292,39 @@ static gboolean condition_check_eval_string(connection *con, condition *cond) {
val = con->wrk->tmp_str->str;
break;
case COMP_REQUEST_CONTENT_LENGTH:
g_string_printf(con->wrk->tmp_str, "%"L_GOFFSET_FORMAT, con->request.content_length);
g_string_printf(con->wrk->tmp_str, "%"L_GOFFSET_FORMAT, vr->request.content_length);
val = con->wrk->tmp_str->str;
break;
}
switch (cond->op) {
case CONFIG_COND_EQ:
result = g_str_equal(val, cond->rvalue.string->str);
*res = g_str_equal(val, cond->rvalue.string->str);
break;
case CONFIG_COND_NE:
result = g_str_equal(val, cond->rvalue.string->str);
*res = g_str_equal(val, cond->rvalue.string->str);
break;
case CONFIG_COND_PREFIX:
result = g_str_has_prefix(val, cond->rvalue.string->str);
*res = g_str_has_prefix(val, cond->rvalue.string->str);
break;
case CONFIG_COND_NOPREFIX:
result = !g_str_has_prefix(val, cond->rvalue.string->str);
*res = !g_str_has_prefix(val, cond->rvalue.string->str);
break;
case CONFIG_COND_SUFFIX:
result = g_str_has_suffix(val, cond->rvalue.string->str);
*res = g_str_has_suffix(val, cond->rvalue.string->str);
break;
case CONFIG_COND_NOSUFFIX:
result = !g_str_has_suffix(val, cond->rvalue.string->str);
*res = !g_str_has_suffix(val, cond->rvalue.string->str);
break;
case CONFIG_COND_MATCH:
case CONFIG_COND_NOMATCH:
#ifdef HAVE_PCRE_H
/* TODO: pcre */
CON_ERROR(con, "%s", "regexp match not supported yet");
VR_ERROR(vr, "%s", "regexp match not supported yet");
return HANDLER_ERROR;
#else
CON_ERROR(con, "compiled without pcre, cannot use '%s'", comp_op_to_string(cond->op));
VR_ERROR(vr, "compiled without pcre, cannot use '%s'", comp_op_to_string(cond->op));
return HANDLER_ERROR;
#endif
break;
case CONFIG_COND_IP:
@ -330,41 +333,49 @@ static gboolean condition_check_eval_string(connection *con, condition *cond) {
case CONFIG_COND_GT:
case CONFIG_COND_LE:
case CONFIG_COND_LT:
CON_ERROR(con, "cannot compare string/regexp with '%s'", comp_op_to_string(cond->op));
break;
VR_ERROR(vr, "cannot compare string/regexp with '%s'", comp_op_to_string(cond->op));
return HANDLER_ERROR;
}
return result;
return HANDLER_GO_ON;
}
static gboolean condition_check_eval_int(connection *con, condition *cond) {
static handler_t condition_check_eval_int(vrequest *vr, condition *cond, gboolean *res) {
gint64 val;
*res = FALSE;
switch (cond->lvalue->type) {
case COMP_REQUEST_CONTENT_LENGTH:
val = con->request.content_length;
val = vr->request.content_length;
case COMP_PHYSICAL_SIZE:
val = con->physical.size;
/* TODO: stat file */
val = vr->physical.size;
break;
default:
CON_ERROR(con, "couldn't get int value for '%s', using -1", cond_lvalue_to_string(cond->lvalue->type));
val = -1;
VR_ERROR(vr, "couldn't get int value for '%s'", cond_lvalue_to_string(cond->lvalue->type));
return HANDLER_ERROR;
}
if (val > 0) switch (cond->op) {
switch (cond->op) {
case CONFIG_COND_EQ: /** == */
return (val == cond->rvalue.i);
*res = (val == cond->rvalue.i);
break;
case CONFIG_COND_NE: /** != */
return (val != cond->rvalue.i);
*res = (val != cond->rvalue.i);
break;
case CONFIG_COND_LT: /** < */
return (val < cond->rvalue.i);
*res = (val < cond->rvalue.i);
break;
case CONFIG_COND_LE: /** <= */
return (val <= cond->rvalue.i);
*res = (val <= cond->rvalue.i);
break;
case CONFIG_COND_GT: /** > */
return (val > cond->rvalue.i);
*res = (val > cond->rvalue.i);
break;
case CONFIG_COND_GE: /** >= */
return (val >= cond->rvalue.i);
*res = (val >= cond->rvalue.i);
break;
case CONFIG_COND_PREFIX:
case CONFIG_COND_NOPREFIX:
case CONFIG_COND_SUFFIX:
@ -373,11 +384,11 @@ static gboolean condition_check_eval_int(connection *con, condition *cond) {
case CONFIG_COND_NOMATCH:
case CONFIG_COND_IP:
case CONFIG_COND_NOTIP:
CON_ERROR(con, "cannot compare int with '%s'", comp_op_to_string(cond->op));
return FALSE;
VR_ERROR(vr, "cannot compare int with '%s'", comp_op_to_string(cond->op));
return HANDLER_ERROR;
}
return FALSE;
return HANDLER_GO_ON;
}
static gboolean ipv4_in_ipv4_net(guint32 target, guint32 match, guint32 networkmask) {
@ -422,65 +433,65 @@ static gboolean ip_in_net(condition_rvalue *target, condition_rvalue *network) {
}
/* CONFIG_COND_IP and CONFIG_COND_NOTIP only */
static gboolean condition_check_eval_ip(connection *con, condition *cond) {
static handler_t condition_check_eval_ip(vrequest *vr, condition *cond, gboolean *res) {
connection *con = vr->con;
condition_rvalue ipval;
const char *val = NULL;
gboolean result = FALSE;
*res = (cond->op == CONFIG_COND_NOTIP);
ipval.type = COND_VALUE_NUMBER;
switch (cond->lvalue->type) {
case COMP_REQUEST_LOCALIP:
if (!condition_ip_from_socket(&ipval, &con->local_addr))
return (cond->op == CONFIG_COND_NOTIP);
return HANDLER_GO_ON;
break;
case COMP_REQUEST_REMOTEIP:
if (!condition_ip_from_socket(&ipval, &con->remote_addr))
return (cond->op == CONFIG_COND_NOTIP);
return HANDLER_GO_ON;
break;
case COMP_REQUEST_PATH:
CON_ERROR(con, "%s", "Cannot parse request.path as ip");
return (cond->op == CONFIG_COND_NOTIP);
break;
VR_ERROR(vr, "%s", "Cannot parse request.path as ip");
return HANDLER_ERROR;
case COMP_REQUEST_HOST:
val = con->request.uri.host->str;
val = vr->request.uri.host->str;
break;
case COMP_REQUEST_SCHEME:
CON_ERROR(con, "%s", "Cannot parse request.scheme as ip");
return (cond->op == CONFIG_COND_NOTIP);
VR_ERROR(vr, "%s", "Cannot parse request.scheme as ip");
return HANDLER_ERROR;
case COMP_REQUEST_QUERY_STRING:
val = con->request.uri.query->str;
val = vr->request.uri.query->str;
break;
case COMP_REQUEST_METHOD:
CON_ERROR(con, "%s", "Cannot request.method as ip");
return (cond->op == CONFIG_COND_NOTIP);
VR_ERROR(vr, "%s", "Cannot parse request.method as ip");
return HANDLER_ERROR;
break;
case COMP_PHYSICAL_PATH:
case COMP_PHYSICAL_PATH_EXISTS:
CON_ERROR(con, "%s", "Cannot physical.path(-exists) as ip");
return (cond->op == CONFIG_COND_NOTIP);
VR_ERROR(vr, "%s", "Cannot parse physical.path(-exists) as ip");
return HANDLER_ERROR;
break;
case COMP_REQUEST_HEADER:
http_header_get_fast(con->wrk->tmp_str, con->request.headers, GSTR_LEN(cond->lvalue->key));
http_header_get_fast(con->wrk->tmp_str, vr->request.headers, GSTR_LEN(cond->lvalue->key));
val = con->wrk->tmp_str->str;
break;
case COMP_PHYSICAL_SIZE:
case COMP_REQUEST_CONTENT_LENGTH:
CON_ERROR(con, "%s", "Cannot parse integers as ip");
return (cond->op == CONFIG_COND_NOTIP);
VR_ERROR(vr, "%s", "Cannot parse integers as ip");
return HANDLER_ERROR;
break;
}
if (ipval.type == COND_VALUE_NUMBER) {
if (!val || !condition_parse_ip(&ipval, val))
return (cond->op == CONFIG_COND_NOTIP);
return HANDLER_GO_ON;
}
switch (cond->op) {
case CONFIG_COND_IP:
return ip_in_net(&ipval, &cond->rvalue);
*res = ip_in_net(&ipval, &cond->rvalue);
case CONFIG_COND_NOTIP:
return !ip_in_net(&ipval, &cond->rvalue);
*res = !ip_in_net(&ipval, &cond->rvalue);
case CONFIG_COND_PREFIX:
case CONFIG_COND_NOPREFIX:
case CONFIG_COND_SUFFIX:
@ -493,25 +504,26 @@ static gboolean condition_check_eval_ip(connection *con, condition *cond) {
case CONFIG_COND_GT:
case CONFIG_COND_LE:
case CONFIG_COND_LT:
CON_ERROR(con, "cannot match ips with '%s'", comp_op_to_string(cond->op));
break;
VR_ERROR(vr, "cannot match ips with '%s'", comp_op_to_string(cond->op));
return HANDLER_ERROR;
}
return result;
return HANDLER_GO_ON;
}
gboolean condition_check(connection *con, condition *cond) {
handler_t condition_check(vrequest *vr, condition *cond, gboolean *res) {
switch (cond->rvalue.type) {
case COND_VALUE_STRING:
#ifdef HAVE_PCRE_H
case COND_VALUE_REGEXP:
#endif
return condition_check_eval_string(con, cond);
return condition_check_eval_string(vr, cond, res);
case COND_VALUE_NUMBER:
return condition_check_eval_int(con, cond);
return condition_check_eval_int(vr, cond, res);
case COND_VALUE_SOCKET_IPV4:
case COND_VALUE_SOCKET_IPV6:
return condition_check_eval_ip(con, cond);
return condition_check_eval_ip(vr, cond, res);
}
return FALSE;
VR_ERROR(vr, "Unsupported conditional type: %i", cond->rvalue.type);
return HANDLER_ERROR;
}

View File

@ -125,7 +125,8 @@ LI_API void condition_release(server *srv, condition* c);
LI_API const char* comp_op_to_string(comp_operator_t op);
LI_API const char* cond_lvalue_to_string(cond_lvalue_t t);
LI_API gboolean condition_check(connection *con, condition *cond);
struct vrequest;
LI_API handler_t condition_check(struct vrequest *vr, condition *cond, gboolean *result);
/* parser */
/** parse an IPv4 (if netmask is not NULL with cidr netmask) */

View File

@ -7,75 +7,188 @@
/* only call it from the worker context the con belongs to */
void worker_con_put(connection *con); /* worker.c */
void internal_error(connection *con) {
if (con->response_headers_sent) {
CON_ERROR(con, "%s", "Couldn't send '500 Internal Error': headers already sent");
connection_set_state(con, CON_STATE_ERROR);
} else {
http_headers_reset(con->response.headers);
con->response.http_status = 500;
con->content_handler = NULL;
connection_set_state(con, CON_STATE_WRITE_RESPONSE);
}
}
static void parse_request_body(connection *con) {
if ( con->state >= CON_STATE_READ_REQUEST_CONTENT
&& con->state <= CON_STATE_WRITE_RESPONSE
&& !con->in->is_closed) {
if (con->request.content_length == -1) {
if ((con->state > CON_STATE_HANDLE_MAINVR || con->mainvr->state >= VRS_READ_CONTENT) && !con->in->is_closed) {
ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_READ);
if (con->mainvr->request.content_length == -1) {
/* TODO: parse chunked encoded request body, filters */
chunkqueue_steal_all(con->in, con->raw_in);
} else {
if (con->in->bytes_in < con->request.content_length) {
chunkqueue_steal_len(con->in, con->raw_in, con->request.content_length - con->in->bytes_in);
if (con->in->bytes_in < con->mainvr->request.content_length) {
chunkqueue_steal_len(con->in, con->raw_in, con->mainvr->request.content_length - con->in->bytes_in);
}
if (con->in->bytes_in == con->mainvr->request.content_length) {
con->in->is_closed = TRUE;
ev_io_rem_events(con->wrk->loop, &con->sock_watcher, EV_READ);
}
if (con->in->bytes_in == con->request.content_length) con->in->is_closed = TRUE;
}
} else {
ev_io_rem_events(con->wrk->loop, &con->sock_watcher, EV_READ);
}
}
static void forward_response_body(connection *con) {
if (con->state == CON_STATE_WRITE_RESPONSE && !con->raw_out->is_closed) {
if (con->out->length > 0) {
/* TODO: chunked encoding, filters */
chunkqueue_steal_all(con->raw_out, con->out);
vrequest *vr = con->mainvr;
if (con->state >= CON_STATE_HANDLE_MAINVR) {
if (!con->response_headers_sent) {
con->response_headers_sent = TRUE;
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "write response headers");
}
response_send_headers(con);
}
if (con->in->is_closed && 0 == con->raw_out->length)
con->raw_out->is_closed = TRUE;
chunkqueue_steal_all(con->raw_out, con->out);
if (con->out->is_closed) con->raw_out->is_closed = TRUE;
if (con->raw_out->length > 0) {
ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
} else {
ev_io_rem_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
}
} else {
ev_io_rem_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
}
}
static void connection_request_done(connection *con) {
vrequest *vr = con->mainvr;
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "response end (keep_alive = %i)", con->keep_alive);
}
plugins_handle_close(con);
if (con->keep_alive) {
connection_reset_keep_alive(con);
} else {
worker_con_put(con);
}
}
static gboolean check_response_done(connection *con) {
if (con->in->is_closed && con->raw_out->is_closed && 0 == con->raw_out->length) {
connection_request_done(con);
return TRUE;
}
return FALSE;
}
static void connection_close(connection *con) {
vrequest *vr = con->mainvr;
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "connection closed");
}
plugins_handle_close(con);
worker_con_put(con);
}
void connection_error(connection *con) {
vrequest *vr = con->mainvr;
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "connection closed (error)");
}
plugins_handle_close(con);
worker_con_put(con);
}
void connection_internal_error(connection *con) {
vrequest *vr = con->mainvr;
if (con->response_headers_sent) {
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;
forward_response_body(con);
}
}
static gboolean connection_handle_read(connection *con) {
vrequest *vr = con->mainvr;
if (con->raw_in->length == 0) return TRUE;
if (con->state == CON_STATE_KEEP_ALIVE) {
/* stop keep alive timeout watchers */
if (con->keep_alive_data.link) {
g_queue_delete_link(&con->wrk->keep_alive_queue, con->keep_alive_data.link);
con->keep_alive_data.link = NULL;
}
con->keep_alive_data.timeout = 0;
ev_timer_stop(con->wrk->loop, &con->keep_alive_data.watcher);
con->state = CON_STATE_READ_REQUEST_HEADER;
} else if (con->state == CON_STATE_REQUEST_START) {
con->state = CON_STATE_READ_REQUEST_HEADER;
}
if (con->state == CON_STATE_READ_REQUEST_HEADER && con->mainvr->state == VRS_CLEAN) {
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "reading request header");
}
switch(http_request_parse(con->mainvr, &con->req_parser_ctx)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
break; /* go on */
case HANDLER_WAIT_FOR_EVENT:
return TRUE;
case HANDLER_ERROR:
case HANDLER_COMEBACK: /* unexpected */
case HANDLER_WAIT_FOR_FD: /* unexpected */
/* unparsable header */
connection_error(con);
return FALSE;
}
con->wrk->stats.requests++;
/* headers ready */
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "validating request header");
}
if (!request_validate_header(con)) {
/* skip mainvr handling */
con->state = CON_STATE_WRITE;
con->keep_alive = FALSE;
con->in->is_closed = TRUE;
forward_response_body(con);
} else {
con->state = CON_STATE_HANDLE_MAINVR;
action_enter(con->mainvr, con->srv->mainaction);
vrequest_handle_request_headers(con->mainvr);
}
}
return TRUE;
}
static void connection_cb(struct ev_loop *loop, ev_io *w, int revents) {
connection *con = (connection*) w->data;
gboolean dojoblist = FALSE;
UNUSED(loop);
if (revents & EV_READ) {
if (con->in->is_closed) {
/* don't read the next request before current one is done */
ev_io_rem_events(loop, w, EV_READ);
} else {
switch (network_read(con, w->fd, con->raw_in)) {
switch (network_read(con->mainvr, w->fd, con->raw_in)) {
case NETWORK_STATUS_SUCCESS:
dojoblist = TRUE;
if (!connection_handle_read(con)) return;
break;
case NETWORK_STATUS_FATAL_ERROR:
CON_ERROR(con, "%s", "network read fatal error");
connection_set_state(con, CON_STATE_ERROR);
dojoblist = TRUE;
break;
connection_error(con);
return;
case NETWORK_STATUS_CONNECTION_CLOSE:
con->raw_in->is_closed = TRUE;
shutdown(w->fd, SHUT_RD);
connection_set_state(con, CON_STATE_CLOSE);
dojoblist = TRUE;
break;
connection_close(con);
return;
case NETWORK_STATUS_WAIT_FOR_EVENT:
break;
case NETWORK_STATUS_WAIT_FOR_AIO_EVENT:
@ -91,40 +204,32 @@ static void connection_cb(struct ev_loop *loop, ev_io *w, int revents) {
}
if (revents & EV_WRITE) {
ev_io_rem_events(loop, w, EV_WRITE);
if (con->raw_out->length > 0) {
switch (network_write(con, w->fd, con->raw_out)) {
switch (network_write(con->mainvr, w->fd, con->raw_out)) {
case NETWORK_STATUS_SUCCESS:
dojoblist = TRUE;
vrequest_joblist_append(con->mainvr);
break;
case NETWORK_STATUS_FATAL_ERROR:
CON_ERROR(con, "%s", "network write fatal error");
connection_set_state(con, CON_STATE_ERROR);
dojoblist = TRUE;
connection_error(con);
break;
case NETWORK_STATUS_CONNECTION_CLOSE:
connection_set_state(con, CON_STATE_CLOSE);
dojoblist = TRUE;
break;
connection_close(con);
return;
case NETWORK_STATUS_WAIT_FOR_EVENT:
break;
case NETWORK_STATUS_WAIT_FOR_AIO_EVENT:
/* TODO: aio */
ev_io_rem_events(loop, w, EV_WRITE);
break;
case NETWORK_STATUS_WAIT_FOR_FD:
/* TODO: wait for fd */
ev_io_rem_events(loop, w, EV_WRITE);
break;
}
}
if (con->raw_out->length == 0) {
ev_io_rem_events(loop, w, EV_WRITE);
dojoblist = TRUE;
}
}
if (dojoblist)
joblist_append(con);
check_response_done(con);
}
static void connection_keepalive_cb(struct ev_loop *loop, ev_timer *w, int revents) {
@ -133,6 +238,51 @@ static void connection_keepalive_cb(struct ev_loop *loop, ev_timer *w, int reven
worker_con_put(con);
}
static handler_t mainvr_handle_response_headers(vrequest *vr) {
connection *con = vr->con;
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
VR_TRACE(vr, "%s", "read request/handle response header");
}
if (con->expect_100_cont) {
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
VR_TRACE(vr, "%s", "send 100 Continue");
}
chunkqueue_append_mem(con->raw_out, CONST_STR_LEN("HTTP/1.1 100 Continue\r\n\r\n"));
con->expect_100_cont = FALSE;
ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
}
parse_request_body(con);
return HANDLER_GO_ON;
}
static handler_t mainvr_handle_response_body(vrequest *vr) {
connection *con = vr->con;
if (check_response_done(con)) return HANDLER_FINISHED;
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "write response");
}
parse_request_body(con);
forward_response_body(con);
if (check_response_done(con)) return HANDLER_FINISHED;
return HANDLER_GO_ON;
}
static handler_t mainvr_handle_response_error(vrequest *vr) {
connection_internal_error(vr->con);
return HANDLER_FINISHED;
}
static handler_t mainvr_handle_request_headers(vrequest *vr) {
/* start reading input */
parse_request_body(vr->con);
return HANDLER_GO_ON;
}
connection* connection_new(worker *wrk) {
server *srv = wrk->srv;
connection *con = g_slice_new0(connection);
@ -152,17 +302,18 @@ connection* connection_new(worker *wrk) {
con->raw_in = chunkqueue_new();
con->raw_out = chunkqueue_new();
con->in = chunkqueue_new();
con->out = chunkqueue_new();
action_stack_init(&con->action_stack);
con->options = g_slice_copy(srv->option_def_values->len * sizeof(option_value), srv->option_def_values->data);
request_init(&con->request);
physical_init(&con->physical);
response_init(&con->response);
http_request_parser_init(&con->req_parser_ctx, &con->request, con->raw_in);
con->mainvr = vrequest_new(con,
mainvr_handle_response_headers,
mainvr_handle_response_body,
mainvr_handle_response_error,
mainvr_handle_request_headers);
http_request_parser_init(&con->req_parser_ctx, &con->mainvr->request, con->raw_in);
con->in = con->mainvr->vr_in;
con->out = con->mainvr->vr_out;
con->keep_alive_data.link = NULL;
con->keep_alive_data.timeout = 0;
@ -194,16 +345,10 @@ void connection_reset(connection *con) {
chunkqueue_reset(con->raw_in);
chunkqueue_reset(con->raw_out);
chunkqueue_reset(con->in);
chunkqueue_reset(con->out);
action_stack_reset(con->srv, &con->action_stack);
memcpy(con->options, con->srv->option_def_values->data, con->srv->option_def_values->len * sizeof(option_value));
request_reset(&con->request);
physical_reset(&con->physical);
response_reset(&con->response);
vrequest_reset(con->mainvr);
http_request_parser_reset(&con->req_parser_ctx);
if (con->keep_alive_data.link) {
@ -217,6 +362,7 @@ void connection_reset(connection *con) {
void server_check_keepalive(server *srv);
void connection_reset_keep_alive(connection *con) {
vrequest *vr = con->mainvr;
ev_timer_stop(con->wrk->loop, &con->keep_alive_data.watcher);
{
con->keep_alive_data.max_idle = CORE_OPTION(CORE_OPTION_MAX_KEEP_ALIVE_IDLE).number;
@ -248,16 +394,10 @@ void connection_reset_keep_alive(connection *con) {
con->keep_alive = TRUE;
con->raw_out->is_closed = FALSE;
chunkqueue_reset(con->in);
chunkqueue_reset(con->out);
action_stack_reset(con->srv, &con->action_stack);
memcpy(con->options, con->srv->option_def_values->data, con->srv->option_def_values->len * sizeof(option_value));
request_reset(&con->request);
physical_reset(&con->physical);
response_reset(&con->response);
vrequest_reset(con->mainvr);
http_request_parser_reset(&con->req_parser_ctx);
}
@ -280,16 +420,10 @@ void connection_free(connection *con) {
chunkqueue_free(con->raw_in);
chunkqueue_free(con->raw_out);
chunkqueue_free(con->in);
chunkqueue_free(con->out);
action_stack_clear(con->srv, &con->action_stack);
g_slice_free1(con->srv->option_def_values->len * sizeof(option_value), con->options);
request_clear(&con->request);
physical_clear(&con->physical);
response_clear(&con->response);
vrequest_free(con->mainvr);
http_request_parser_clear(&con->req_parser_ctx);
if (con->keep_alive_data.link && con->wrk) {
@ -303,215 +437,3 @@ void connection_free(connection *con) {
g_slice_free(connection, con);
}
void connection_set_state(connection *con, connection_state_t state) {
if (state < con->state) {
CON_ERROR(con, "Cannot move into requested state: %i => %i, move to error state", con->state, state);
state = CON_STATE_ERROR;
}
con->state = state;
}
void connection_state_machine(connection *con) {
gboolean done = FALSE;
do {
switch (con->state) {
case CON_STATE_DEAD:
done = TRUE;
break;
case CON_STATE_KEEP_ALIVE:
if (con->raw_in->length > 0) {
/* stop keep alive timeout watchers */
if (con->keep_alive_data.link) {
g_queue_delete_link(&con->wrk->keep_alive_queue, con->keep_alive_data.link);
con->keep_alive_data.link = NULL;
}
con->keep_alive_data.timeout = 0;
ev_timer_stop(con->wrk->loop, &con->keep_alive_data.watcher);
connection_set_state(con, CON_STATE_REQUEST_START);
} else
done = TRUE;
break;
case CON_STATE_REQUEST_START:
connection_set_state(con, CON_STATE_READ_REQUEST_HEADER);
action_enter(con, con->srv->mainaction);
break;
case CON_STATE_READ_REQUEST_HEADER:
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "reading request header");
}
switch(http_request_parse(con, &con->req_parser_ctx)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
connection_set_state(con, CON_STATE_VALIDATE_REQUEST_HEADER);
break;
case HANDLER_WAIT_FOR_FD:
/* TODO: wait for fd */
done = TRUE;
break;
case HANDLER_WAIT_FOR_EVENT:
done = TRUE;
break;
case HANDLER_ERROR:
case HANDLER_COMEBACK: /* unexpected */
/* unparsable header */
connection_set_state(con, CON_STATE_ERROR);
break;
}
break;
case CON_STATE_VALIDATE_REQUEST_HEADER:
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "validating request header");
}
connection_set_state(con, CON_STATE_HANDLE_REQUEST_HEADER);
request_validate_header(con);
con->wrk->stats.requests++;
break;
case CON_STATE_HANDLE_REQUEST_HEADER:
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "handle request header");
}
switch (action_execute(con)) {
case ACTION_WAIT_FOR_EVENT:
done = TRUE;
break;
case ACTION_GO_ON:
case ACTION_FINISHED:
if (con->state == CON_STATE_HANDLE_REQUEST_HEADER) {
internal_error(con);
}
connection_set_state(con, CON_STATE_WRITE_RESPONSE);
break;
case ACTION_ERROR:
internal_error(con);
break;
}
break;
case CON_STATE_READ_REQUEST_CONTENT:
case CON_STATE_HANDLE_RESPONSE_HEADER:
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "read request/handle response header");
}
if (con->expect_100_cont) {
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "send 100 Continue");
}
chunkqueue_append_mem(con->raw_out, CONST_STR_LEN("HTTP/1.1 100 Continue\r\n\r\n"));
con->expect_100_cont = FALSE;
ev_io_add_events(con->wrk->loop, &con->sock_watcher, EV_WRITE);
}
parse_request_body(con);
if (con->content_handler)
con->content_handler->handle_content(con, con->content_handler);
switch (action_execute(con)) {
case ACTION_WAIT_FOR_EVENT:
done = TRUE;
break;
case ACTION_GO_ON:
case ACTION_FINISHED:
connection_set_state(con, CON_STATE_WRITE_RESPONSE);
break;
case ACTION_ERROR:
internal_error(con);
break;
}
break;
case CON_STATE_WRITE_RESPONSE:
if (con->in->is_closed && con->raw_out->is_closed) {
connection_set_state(con, CON_STATE_RESPONSE_END);
break;
}
if (!con->response_headers_sent) {
con->response_headers_sent = TRUE;
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "write response headers");
}
response_send_headers(con);
}
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "write response");
}
parse_request_body(con);
if (con->content_handler)
con->content_handler->handle_content(con, con->content_handler);
forward_response_body(con);
if (con->in->is_closed && con->raw_out->is_closed) {
connection_set_state(con, CON_STATE_RESPONSE_END);
break;
}
if (con->state == CON_STATE_WRITE_RESPONSE) done = TRUE;
break;
case CON_STATE_RESPONSE_END:
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "response end (keep_alive = %i)", con->keep_alive);
}
plugins_handle_close(con);
if (con->keep_alive) {
connection_reset_keep_alive(con);
} else {
worker_con_put(con);
done = TRUE;
}
break;
case CON_STATE_CLOSE:
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "connection closed");
}
plugins_handle_close(con);
worker_con_put(con);
done = TRUE;
break;
case CON_STATE_ERROR:
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
CON_TRACE(con, "%s", "connection closed (error)");
}
plugins_handle_close(con);
worker_con_put(con);
done = TRUE;
break;
}
} while (!done);
}
void connection_handle_direct(connection *con) {
connection_set_state(con, CON_STATE_WRITE_RESPONSE);
con->out->is_closed = TRUE;
}
void connection_handle_indirect(connection *con, plugin *p) {
if (!p) {
connection_handle_direct(con);
} else if (p->handle_content) {
connection_set_state(con, CON_STATE_READ_REQUEST_CONTENT);
con->content_handler = p;
} else {
CON_ERROR(con, "Indirect plugin '%s' handler has no handle_content callback", p->name);
internal_error(con);
}
}

View File

@ -10,54 +10,17 @@ typedef enum {
/** waiting for new input after first request */
CON_STATE_KEEP_ALIVE,
/** after the connect, the request is initialized, keep-alive starts here again */
/** after the connect, the request is initialized */
CON_STATE_REQUEST_START,
/** loop in the read-request-header until the full header is received */
CON_STATE_READ_REQUEST_HEADER,
/** validate the request-header */
CON_STATE_VALIDATE_REQUEST_HEADER,
/** find a handler for the request; there are two ways to produce responses:
* - direct response: for things like errors/auth/redirect
* just set the status code, perhaps fill in some headers,