kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

commit fb335e1e0c3f0dff311dc9bbdbfbe992cb2b67f4
parent c4a60c54bb7a67680138419b72548a565c40984f
Author: Joris Vink <joris@coders.se>
Date:   Sun,  2 May 2021 00:23:11 +0200

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')

Diffstat:
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 | 330++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
11 files changed, 317 insertions(+), 105 deletions(-)

diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -450,6 +460,19 @@ kore_python_coro_pending(void) } 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) { const char *sval; @@ -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"); @@ -2097,6 +2131,35 @@ python_kore_domain(PyObject *self, PyObject *args, PyObject *kwargs) } 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) { struct pygather_op *op; @@ -4932,6 +4995,36 @@ pyhttp_file_get_filename(struct pyhttp_file *pyfile, void *closure) } 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) { PyObject_Del((PyObject *)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); + + Py_XINCREF(route->kwargs); + + route->func = obj; + Py_INCREF(route->func); + + TAILQ_INSERT_TAIL(&routes, route, list); + + Py_RETURN_NONE; +} + +static int +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->config; - hdlr->func = kore_strdup(val); - hdlr->path = kore_strdup(path); + hdlr->dom = domain; hdlr->methods = HTTP_METHOD_ALL; + hdlr->path = kore_strdup(route->path); + TAILQ_INIT(&hdlr->params); - Py_DECREF(repr); + 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 = callable; + 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 (!PyList_CheckExact(obj)) { + if (!python_route_methods(obj, kwargs, hdlr)) { + kore_python_log_error("python_route_install"); 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); - } + return (KORE_RESULT_ERROR); } } if ((obj = PyDict_GetItemString(kwargs, "auth")) != NULL) { - if (!pydomain_auth(obj, hdlr)) { + if (!python_route_auth(obj, hdlr)) { + kore_python_log_error("python_route_install"); kore_module_handler_free(hdlr); - return (NULL); + return (KORE_RESULT_ERROR); } } } - if (path[0] == '/') { + 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); + } - 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); + 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"); } - Py_INCREF(callable); - TAILQ_INSERT_TAIL(&domain->config->handlers, hdlr, list); + return (domain); +} - Py_RETURN_NONE; +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 -pydomain_params(PyObject *kwargs, struct kore_module_handle *hdlr, +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;