From fb335e1e0c3f0dff311dc9bbdbfbe992cb2b67f4 Mon Sep 17 00:00:00 2001 From: Joris Vink Date: Sun, 2 May 2021 00:23:11 +0200 Subject: [PATCH] Major Python API improvements. 1) Add @kore.route as a decorator for Python. This decorator can be used on non-class methods to automatically declare their route and parameters. Takes the same arguments as the kore.domain.route function that exists today. Provides a nice clean way of setting up Kore if you dont want a whole class based approach. 2) Remove the requirement for the name for kore.server() and the kore.domain(attach=) keywords. Instead of no name was given, the name "default" is used in both places resulting in less boilerplating. 3) Allow multiple routes to be defined for the same URI as long as the methods are different. So you can have one method for GET / and another for POST /. All changes combined condense the initial experience of getting a Kore Python app up and running: eg: import kore kore.server(ip="127.0.0.1", port="8888", tls=False) kore.domain("*") @kore.route("/", methods=["get"]) async def index(req): req.response(200, b'get method') @kore.route("/", methods=["post"]) async def index_post(req) req.response(200, b'post method') --- Makefile | 2 + include/kore/kore.h | 4 +- include/kore/python_api.h | 1 + include/kore/python_methods.h | 36 +++- kodev/Makefile | 2 + src/cli.c | 1 + src/config.c | 5 +- src/http.c | 8 +- src/kore.c | 4 + src/module.c | 29 ++- src/python.c | 378 ++++++++++++++++++++++++---------- 11 files changed, 341 insertions(+), 129 deletions(-) diff --git a/Makefile b/Makefile index 6201d4c..d5ebd8c 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,8 @@ endif ifeq ("$(OSNAME)", "darwin") CFLAGS+=-I/opt/local/include/ -I/usr/local/opt/openssl/include LDFLAGS+=-L/opt/local/lib -L/usr/local/opt/openssl/lib + CFLAGS+=-I/opt/homebrew/opt/openssl/include + LDFLAGS+=-L/opt/homebrew/opt/openssl/lib S_SRC+=src/bsd.c else ifeq ("$(OSNAME)", "linux") CFLAGS+=-D_GNU_SOURCE=1 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 diff --git a/include/kore/kore.h b/include/kore/kore.h index c26553f..5554d10 100644 --- a/include/kore/kore.h +++ b/include/kore/kore.h @@ -952,8 +952,8 @@ void kore_domain_crl_add(struct kore_domain *, const void *, size_t); int kore_module_handler_new(struct kore_domain *, const char *, const char *, const char *, int); void kore_module_handler_free(struct kore_module_handle *); -struct kore_module_handle *kore_module_handler_find(struct http_request *, - struct kore_domain *); +int kore_module_handler_find(struct http_request *, + struct kore_domain *, int, struct kore_module_handle **); #endif struct kore_runtime_call *kore_runtime_getcall(const char *); diff --git a/include/kore/python_api.h b/include/kore/python_api.h index 21ce053..d8052aa 100644 --- a/include/kore/python_api.h +++ b/include/kore/python_api.h @@ -34,6 +34,7 @@ void kore_python_proc_reap(void); int kore_python_coro_pending(void); void kore_python_path(const char *); void kore_python_coro_delete(void *); +void kore_python_routes_resolve(void); void kore_python_log_error(const char *); PyObject *kore_python_callable(PyObject *, const char *); diff --git a/include/kore/python_methods.h b/include/kore/python_methods.h index e4ad14d..65895f7 100644 --- a/include/kore/python_methods.h +++ b/include/kore/python_methods.h @@ -54,6 +54,7 @@ static PyObject *python_kore_task_kill(PyObject *, PyObject *); static PyObject *python_kore_prerequest(PyObject *, PyObject *); static PyObject *python_kore_task_create(PyObject *, PyObject *); static PyObject *python_kore_socket_wrap(PyObject *, PyObject *); +static PyObject *python_kore_route(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_timer(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_domain(PyObject *, PyObject *, PyObject *); static PyObject *python_kore_gather(PyObject *, PyObject *, PyObject *); @@ -62,6 +63,7 @@ static PyObject *python_kore_sendobj(PyObject *, PyObject *, static PyObject *python_kore_server(PyObject *, PyObject *, PyObject *); + #if defined(KORE_USE_PGSQL) static PyObject *python_kore_pgsql_query(PyObject *, PyObject *, PyObject *); @@ -101,10 +103,11 @@ static struct PyMethodDef pykore_methods[] = { METHOD("prerequest", python_kore_prerequest, METH_VARARGS), METHOD("task_create", python_kore_task_create, METH_VARARGS), METHOD("socket_wrap", python_kore_socket_wrap, METH_VARARGS), + METHOD("route", python_kore_route, METH_VARARGS | METH_KEYWORDS), METHOD("timer", python_kore_timer, METH_VARARGS | METH_KEYWORDS), + METHOD("domain", python_kore_domain, METH_VARARGS | METH_KEYWORDS), METHOD("server", python_kore_server, METH_VARARGS | METH_KEYWORDS), METHOD("gather", python_kore_gather, METH_VARARGS | METH_KEYWORDS), - METHOD("domain", python_kore_domain, METH_VARARGS | METH_KEYWORDS), METHOD("sendobj", python_kore_sendobj, METH_VARARGS | METH_KEYWORDS), METHOD("websocket_broadcast", python_websocket_broadcast, METH_VARARGS), #if defined(KORE_USE_PGSQL) @@ -186,9 +189,38 @@ static PyTypeObject pyseccomp_type = { }; #endif +struct pyroute { + PyObject_HEAD + char *path; + PyObject *func; + PyObject *kwargs; + struct kore_domain *domain; + TAILQ_ENTRY(pyroute) list; +}; + +static PyObject *pyroute_inner(struct pyroute *, PyObject *); +static void pyroute_dealloc(struct pyroute *); + +static PyMethodDef pyroute_methods[] = { + METHOD("inner", pyroute_inner, METH_VARARGS), + METHOD(NULL, NULL, -1) +}; + +static PyTypeObject pyroute_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "kore.route", + .tp_doc = "kore route function", + .tp_methods = pyroute_methods, + .tp_basicsize = sizeof(struct pyroute), + .tp_dealloc = (destructor)pyroute_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, +}; + struct pydomain { PyObject_HEAD - struct kore_domain *config; + struct kore_domain *config; + struct kore_module_handle *next; + PyObject *kwargs; }; static PyObject *pydomain_filemaps(struct pydomain *, PyObject *); diff --git a/kodev/Makefile b/kodev/Makefile index c204d05..b21a6d6 100644 --- a/kodev/Makefile +++ b/kodev/Makefile @@ -30,6 +30,8 @@ OSNAME=$(shell uname -s | sed -e 's/[-_].*//g' | tr A-Z a-z) ifeq ("$(OSNAME)", "darwin") CFLAGS+=-I/opt/local/include/ -I/usr/local/opt/openssl/include LDFLAGS+=-L/opt/local/lib -L/usr/local/opt/openssl/lib + CFLAGS+=-I/opt/homebrew/opt/openssl/include + LDFLAGS+=-L/opt/homebrew/opt/openssl/lib else ifeq ("$(OSNAME)", "linux") CFLAGS+=-D_GNU_SOURCE=1 endif diff --git a/src/cli.c b/src/cli.c index fbd50ac..f102146 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1901,6 +1901,7 @@ cli_build_flags_common(struct buildopt *bopt, struct cli_buf *buf) /* Add default openssl include path from homebrew / ports under OSX. */ cli_buf_appendf(buf, "-I/opt/local/include "); cli_buf_appendf(buf, "-I/usr/local/opt/openssl/include "); + cli_buf_appendf(buf, "-I/opt/homebrew/opt/openssl/include "); #endif if (bopt->single_binary == 0) { cli_kore_features(bopt, &data, &len); diff --git a/src/config.c b/src/config.c index f7dc090..7f3466e 100644 --- a/src/config.c +++ b/src/config.c @@ -1876,13 +1876,10 @@ configure_deployment(char *value) skip_chroot = 0; } else { kore_log(LOG_NOTICE, - "pyko.config.deployment: bad value '%s'", value); + "kore.config.deployment: bad value '%s'", value); return (KORE_RESULT_ERROR); } - if (!kore_quiet) - kore_log(LOG_NOTICE, "deployment set to %s", value); - return (KORE_RESULT_OK); } diff --git a/src/http.c b/src/http.c index 9f9b214..fd46591 100644 --- a/src/http.c +++ b/src/http.c @@ -1594,7 +1594,7 @@ http_request_new(struct connection *c, const char *host, struct kore_domain *dom; struct http_request *req; char *p, *hp; - int m, flags; + int m, flags, exists; size_t hostlen, pathlen, qsoff; if (http_request_count >= http_request_limit) { @@ -1748,7 +1748,7 @@ http_request_new(struct connection *c, const char *host, } /* Checked further down below if we need to 404. */ - req->hdlr = kore_module_handler_find(req, dom); + exists = kore_module_handler_find(req, dom, m, &req->hdlr); TAILQ_INIT(&(req->resp_headers)); TAILQ_INIT(&(req->req_headers)); @@ -1774,13 +1774,13 @@ http_request_new(struct connection *c, const char *host, return (NULL); } - if (req->hdlr == NULL) { + if (exists == 0) { http_request_free(req); http_error_response(c, HTTP_STATUS_NOT_FOUND); return (NULL); } - if (!(req->hdlr->methods & m)) { + if (req->hdlr == NULL) { http_request_free(req); http_error_response(c, HTTP_STATUS_METHOD_NOT_ALLOWED); return (NULL); diff --git a/src/kore.c b/src/kore.c index 9963140..a500fbc 100644 --- a/src/kore.c +++ b/src/kore.c @@ -909,6 +909,10 @@ kore_server_start(int argc, char *argv[]) kore_call_parent_configure(argc, argv); #endif +#if defined(KORE_USE_PYTHON) + kore_python_routes_resolve(); +#endif + /* Check if keymgr will be active. */ LIST_FOREACH(srv, &kore_servers, list) { if (srv->tls) { diff --git a/src/module.c b/src/module.c index 6da2ad4..ff57a35 100644 --- a/src/module.c +++ b/src/module.c @@ -283,23 +283,38 @@ kore_module_handler_free(struct kore_module_handle *hdlr) kore_free(hdlr); } -struct kore_module_handle * -kore_module_handler_find(struct http_request *req, struct kore_domain *dom) +int +kore_module_handler_find(struct http_request *req, struct kore_domain *dom, + int method, struct kore_module_handle **out) { struct kore_module_handle *hdlr; + int exists; + + exists = 0; + *out = NULL; TAILQ_FOREACH(hdlr, &(dom->handlers), list) { if (hdlr->type == HANDLER_TYPE_STATIC) { - if (!strcmp(hdlr->path, req->path)) - return (hdlr); + if (!strcmp(hdlr->path, req->path)) { + if (hdlr->methods & method) { + *out = hdlr; + return (1); + } + exists++; + } } else { if (!regexec(&(hdlr->rctx), req->path, - HTTP_CAPTURE_GROUPS, req->cgroups, 0)) - return (hdlr); + HTTP_CAPTURE_GROUPS, req->cgroups, 0)) { + if (hdlr->methods & method) { + *out = hdlr; + return (1); + } + exists++; + } } } - return (NULL); + return (exists); } #endif /* !KORE_NO_HTTP */ diff --git a/src/python.c b/src/python.c index 8cafd27..8ae91f0 100644 --- a/src/python.c +++ b/src/python.c @@ -77,6 +77,15 @@ static PyObject *pyhttp_request_alloc(const struct http_request *); static struct python_coro *python_coro_create(PyObject *, struct http_request *); +static struct kore_domain *python_route_domain_resolve(struct pyroute *); + +static int python_route_install(struct pyroute *); +static int python_route_params(PyObject *, + struct kore_module_handle *, const char *, int); +static int python_route_methods(PyObject *, PyObject *, + struct kore_module_handle *); +static int python_route_auth(PyObject *, + struct kore_module_handle *); static int python_coro_run(struct python_coro *); static void python_coro_wakeup(struct python_coro *); @@ -108,10 +117,6 @@ static int pyhttp_iterobj_chunk_sent(struct netbuf *); static int pyhttp_iterobj_next(struct pyhttp_iterobj *); static void pyhttp_iterobj_disconnect(struct connection *); -static int pydomain_params(PyObject *, - struct kore_module_handle *, const char *, int); -static int pydomain_auth(PyObject *, struct kore_module_handle *); - #if defined(KORE_USE_PGSQL) static int pykore_pgsql_result(struct pykore_pgsql *); static void pykore_pgsql_callback(struct kore_pgsql *, void *); @@ -259,6 +264,7 @@ static struct pyseccomp *py_seccomp = NULL; #endif static TAILQ_HEAD(, pyproc) procs; +static TAILQ_HEAD(, pyroute) routes; static struct reqcall_list prereq; static struct kore_pool coro_pool; @@ -301,6 +307,7 @@ kore_python_init(void) TAILQ_INIT(&prereq); TAILQ_INIT(&procs); + TAILQ_INIT(&routes); TAILQ_INIT(&coro_runnable); TAILQ_INIT(&coro_suspended); @@ -351,6 +358,9 @@ kore_python_init(void) kore_seccomp_filter("python", filter_python, KORE_FILTER_LEN(filter_python)); #endif + + if (!kore_configure_setting("deployment", "dev")) + fatal("failed to set initial deployment"); } void @@ -449,6 +459,19 @@ kore_python_coro_pending(void) return (!TAILQ_EMPTY(&coro_runnable)); } +void +kore_python_routes_resolve(void) +{ + struct pyroute *route; + + while ((route = TAILQ_FIRST(&routes)) != NULL) { + TAILQ_REMOVE(&routes, route, list); + if (!python_route_install(route)) + fatalx("failed to install route for %s", route->path); + Py_DECREF((PyObject *)route); + } +} + void kore_python_log_error(const char *function) { @@ -1570,6 +1593,7 @@ python_module_init(void) python_push_type("pylock", pykore, &pylock_type); python_push_type("pytimer", pykore, &pytimer_type); python_push_type("pyqueue", pykore, &pyqueue_type); + python_push_type("pyroute", pykore, &pyroute_type); python_push_type("pysocket", pykore, &pysocket_type); python_push_type("pydomain", pykore, &pydomain_type); python_push_type("pyconnection", pykore, &pyconnection_type); @@ -1756,9 +1780,6 @@ python_kore_server(PyObject *self, PyObject *args, PyObject *kwargs) struct kore_server *srv; const char *name, *ip, *port, *path; - if (!PyArg_ParseTuple(args, "s", &name)) - return (NULL); - if (kwargs == NULL) { PyErr_SetString(PyExc_RuntimeError, "missing keyword args"); return (NULL); @@ -1778,6 +1799,16 @@ python_kore_server(PyObject *self, PyObject *args, PyObject *kwargs) return (NULL); } + name = python_string_from_dict(kwargs, "name"); + if (name == NULL) + name = "default"; + + if ((srv = kore_server_lookup(name)) != NULL) { + PyErr_Format(PyExc_RuntimeError, + "server '%s' already exist", name); + return (NULL); + } + srv = kore_server_create(name); python_bool_from_dict(kwargs, "tls", &srv->tls); @@ -2011,16 +2042,11 @@ python_kore_domain(PyObject *self, PyObject *args, PyObject *kwargs) if (!PyArg_ParseTuple(args, "s", &name)) return (NULL); - if (kwargs == NULL) { - PyErr_SetString(PyExc_RuntimeError, "missing keyword args"); - return (NULL); - } + if (kwargs != NULL) + attach = python_string_from_dict(kwargs, "attach"); - if ((attach = python_string_from_dict(kwargs, "attach")) == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "missing or invalid 'attach' keyword"); - return (NULL); - } + if (attach == NULL) + attach = "default"; if ((srv = kore_server_lookup(attach)) == NULL) { PyErr_Format(PyExc_RuntimeError, @@ -2029,6 +2055,11 @@ python_kore_domain(PyObject *self, PyObject *args, PyObject *kwargs) } if (srv->tls) { + if (kwargs == NULL) { + PyErr_Format(PyExc_RuntimeError, + "no keywords for TLS enabled domain %s", name); + return (NULL); + } key = python_string_from_dict(kwargs, "key"); cert = python_string_from_dict(kwargs, "cert"); @@ -2069,6 +2100,9 @@ python_kore_domain(PyObject *self, PyObject *args, PyObject *kwargs) if ((domain = PyObject_New(struct pydomain, &pydomain_type)) == NULL) return (NULL); + domain->next = NULL; + domain->kwargs = NULL; + if ((domain->config = kore_domain_new(name)) == NULL) fatal("failed to create new domain configuration"); @@ -2096,6 +2130,35 @@ python_kore_domain(PyObject *self, PyObject *args, PyObject *kwargs) return ((PyObject *)domain); } +static PyObject * +python_kore_route(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *path; + PyObject *inner; + struct pyroute *route; + + if ((route = PyObject_New(struct pyroute, &pyroute_type)) == NULL) + return (NULL); + + if (!PyArg_ParseTuple(args, "s", &path)) + return (NULL); + + route->domain = NULL; + route->kwargs = kwargs; + route->path = kore_strdup(path); + + Py_XINCREF(route->kwargs); + + inner = PyObject_GetAttrString((PyObject *)route, "inner"); + if (inner == NULL) { + Py_DECREF((PyObject *)route); + PyErr_SetString(PyExc_RuntimeError, "failed to find inner"); + return (NULL); + } + + return (inner); +} + static PyObject * python_kore_gather(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -4931,6 +4994,36 @@ pyhttp_file_get_filename(struct pyhttp_file *pyfile, void *closure) return (name); } +void +pyroute_dealloc(struct pyroute *route) +{ + kore_free(route->path); + + Py_XDECREF(route->func); + Py_XDECREF(route->kwargs); + + PyObject_Del((PyObject *)route); +} + +static PyObject * +pyroute_inner(struct pyroute *route, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O", &obj)) + return (NULL); + + if (!PyCallable_Check(obj)) + return (NULL); + + route->func = obj; + Py_INCREF(route->func); + + TAILQ_INSERT_TAIL(&routes, route, list); + + return (route->func); +} + void pydomain_dealloc(struct pydomain *domain) { @@ -5004,114 +5097,179 @@ pydomain_filemaps(struct pydomain *domain, PyObject *args) static PyObject * pydomain_route(struct pydomain *domain, PyObject *args, PyObject *kwargs) { - struct kore_module_handle *hdlr; - int method; - const char *path, *val; - Py_ssize_t list_len, idx; - PyObject *callable, *repr, *obj, *item; + PyObject *obj; + const char *path; + struct pyroute *route; - if (!PyArg_ParseTuple(args, "sO", &path, &callable)) + if (!PyArg_ParseTuple(args, "sO", &path, &obj)) return (NULL); - if (!PyCallable_Check(callable)) + if (!PyCallable_Check(obj)) return (NULL); - TAILQ_FOREACH(hdlr, &domain->config->handlers, list) { - if (!strcmp(hdlr->path, path)) { - PyErr_Format(PyExc_RuntimeError, - "route '%s' exists", path); - return (NULL); - } - } - - if ((repr = PyObject_Repr(callable)) == NULL) + if ((route = PyObject_New(struct pyroute, &pyroute_type)) == NULL) return (NULL); - val = PyUnicode_AsUTF8(repr); + route->kwargs = kwargs; + route->domain = domain->config; + route->path = kore_strdup(path); - hdlr = kore_calloc(1, sizeof(*hdlr)); - hdlr->dom = domain->config; - hdlr->func = kore_strdup(val); - hdlr->path = kore_strdup(path); - hdlr->methods = HTTP_METHOD_ALL; - TAILQ_INIT(&hdlr->params); + Py_XINCREF(route->kwargs); - Py_DECREF(repr); + route->func = obj; + Py_INCREF(route->func); - hdlr->rcall = kore_calloc(1, sizeof(struct kore_runtime_call)); - hdlr->rcall->addr = callable; - hdlr->rcall->runtime = &kore_python_runtime; - - if (kwargs != NULL) { - if ((obj = PyDict_GetItemString(kwargs, "methods")) != NULL) { - if (!PyList_CheckExact(obj)) { - kore_module_handler_free(hdlr); - return (NULL); - } - - hdlr->methods = 0; - list_len = PyList_Size(obj); - - for (idx = 0; idx < list_len; idx++) { - if ((item = PyList_GetItem(obj, idx)) == NULL) { - kore_module_handler_free(hdlr); - return (NULL); - } - - if ((val = PyUnicode_AsUTF8(item)) == NULL) { - kore_module_handler_free(hdlr); - return (NULL); - } - - method = http_method_value(val); - if (method == 0) { - PyErr_Format(PyExc_RuntimeError, - "unknown method '%s'", val); - kore_module_handler_free(hdlr); - return (NULL); - } - - hdlr->methods |= method; - if (method == HTTP_METHOD_GET) - hdlr->methods |= HTTP_METHOD_HEAD; - - if (!pydomain_params(kwargs, - hdlr, val, method)) { - kore_module_handler_free(hdlr); - return (NULL); - } - } - } - - if ((obj = PyDict_GetItemString(kwargs, "auth")) != NULL) { - if (!pydomain_auth(obj, hdlr)) { - kore_module_handler_free(hdlr); - return (NULL); - } - } - } - - if (path[0] == '/') { - hdlr->type = HANDLER_TYPE_STATIC; - } else { - hdlr->type = HANDLER_TYPE_DYNAMIC; - - if (regcomp(&hdlr->rctx, hdlr->path, REG_EXTENDED)) { - PyErr_SetString(PyExc_RuntimeError, - "failed to compile regex for path"); - kore_module_handler_free(hdlr); - return (NULL); - } - } - - Py_INCREF(callable); - TAILQ_INSERT_TAIL(&domain->config->handlers, hdlr, list); + TAILQ_INSERT_TAIL(&routes, route, list); Py_RETURN_NONE; } static int -pydomain_params(PyObject *kwargs, struct kore_module_handle *hdlr, +python_route_install(struct pyroute *route) +{ + const char *val; + struct kore_domain *domain; + struct kore_module_handle *hdlr, *entry; + PyObject *kwargs, *repr, *obj; + + if ((repr = PyObject_Repr(route->func)) == NULL) { + kore_python_log_error("python_route_install"); + return (KORE_RESULT_ERROR); + } + + domain = python_route_domain_resolve(route); + + hdlr = kore_calloc(1, sizeof(*hdlr)); + hdlr->dom = domain; + hdlr->methods = HTTP_METHOD_ALL; + hdlr->path = kore_strdup(route->path); + + TAILQ_INIT(&hdlr->params); + + val = PyUnicode_AsUTF8(repr); + hdlr->func = kore_strdup(val); + + kwargs = route->kwargs; + + hdlr->rcall = kore_calloc(1, sizeof(struct kore_runtime_call)); + hdlr->rcall->addr = route->func; + hdlr->rcall->runtime = &kore_python_runtime; + Py_INCREF(hdlr->rcall->addr); + + if (kwargs != NULL) { + if ((obj = PyDict_GetItemString(kwargs, "methods")) != NULL) { + if (!python_route_methods(obj, kwargs, hdlr)) { + kore_python_log_error("python_route_install"); + kore_module_handler_free(hdlr); + return (KORE_RESULT_ERROR); + } + } + + if ((obj = PyDict_GetItemString(kwargs, "auth")) != NULL) { + if (!python_route_auth(obj, hdlr)) { + kore_python_log_error("python_route_install"); + kore_module_handler_free(hdlr); + return (KORE_RESULT_ERROR); + } + } + } + + if (hdlr->path[0] == '/') { + hdlr->type = HANDLER_TYPE_STATIC; + } else { + hdlr->type = HANDLER_TYPE_DYNAMIC; + if (regcomp(&hdlr->rctx, hdlr->path, REG_EXTENDED)) + fatal("failed to compile regex for '%s'", hdlr->path); + } + + TAILQ_FOREACH(entry, &domain->handlers, list) { + if (!strcmp(entry->path, hdlr->path) && + (entry->methods & hdlr->methods)) + fatal("duplicate route for '%s'", route->path); + } + + TAILQ_INSERT_TAIL(&domain->handlers, hdlr, list); + + return (KORE_RESULT_OK); +} + +static struct kore_domain * +python_route_domain_resolve(struct pyroute *route) +{ + struct kore_server *srv; + const char *name; + struct kore_domain *domain; + + if (route->domain != NULL) + return (route->domain); + + if (route->kwargs != NULL) + name = python_string_from_dict(route->kwargs, "domain"); + else + name = NULL; + + if (name != NULL) { + domain = NULL; + LIST_FOREACH(srv, &kore_servers, list) { + TAILQ_FOREACH(domain, &srv->domains, list) { + if (!strcmp(domain->domain, name)) + break; + } + } + + if (domain == NULL) + fatal("domain '%s' does not exist", name); + } else { + if ((domain = kore_domain_byid(1)) != NULL) + fatal("ambiguous domain on route, please specify one"); + if ((domain = kore_domain_byid(0)) == NULL) + fatal("no domains configured, please configure one"); + } + + return (domain); +} + +static int +python_route_methods(PyObject *obj, PyObject *kwargs, + struct kore_module_handle *hdlr) +{ + const char *val; + PyObject *item; + int method; + Py_ssize_t list_len, idx; + + if (!PyList_CheckExact(obj)) + return (KORE_RESULT_ERROR); + + hdlr->methods = 0; + list_len = PyList_Size(obj); + + for (idx = 0; idx < list_len; idx++) { + if ((item = PyList_GetItem(obj, idx)) == NULL) + return (KORE_RESULT_ERROR); + + if ((val = PyUnicode_AsUTF8(item)) == NULL) + return (KORE_RESULT_ERROR); + + if ((method = http_method_value(val)) == 0) { + PyErr_Format(PyExc_RuntimeError, + "unknown HTTP method: %s", val); + return (KORE_RESULT_ERROR); + } + + hdlr->methods |= method; + if (method == HTTP_METHOD_GET) + hdlr->methods |= HTTP_METHOD_HEAD; + + if (!python_route_params(kwargs, hdlr, val, method)) + return (KORE_RESULT_ERROR); + } + + return (KORE_RESULT_OK); +} + +static int +python_route_params(PyObject *kwargs, struct kore_module_handle *hdlr, const char *method, int type) { Py_ssize_t idx; @@ -5182,7 +5340,7 @@ pydomain_params(PyObject *kwargs, struct kore_module_handle *hdlr, } static int -pydomain_auth(PyObject *dict, struct kore_module_handle *hdlr) +python_route_auth(PyObject *dict, struct kore_module_handle *hdlr) { int type; struct kore_auth *auth;