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 e8e01980fc45cddf62f6eca3fffc6d2c9e9ae2ae
parent 97ef486d2264f17d50e916c09458da5fbe12371c
Author: Joris Vink <joris@coders.se>
Date:   Tue, 14 Dec 2021 23:15:21 +0100

Python: allow route hooks via kore.route().

Adding the hooks keyword with a dictionary attached to specify
the relevant hooks will hook them for the given route.

Eg:

domain.route("/", self.index, methods=["get"],
    hooks={
        "on_free": self.request_free
    }
)

These are the same hooks available via a normal Kore route configuration.

Diffstat:
examples/async-curl/src/ftp.c | 2+-
examples/async-curl/src/http.c | 2+-
examples/pgsql/src/pgsql.c | 2+-
include/kore/http.h | 3+--
include/kore/kore.h | 4++++
src/config.c | 20++++++++++++++++++++
src/http.c | 9+++------
src/python.c | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/runtime.c | 18++++++++++++++++++
9 files changed, 153 insertions(+), 11 deletions(-)

diff --git a/examples/async-curl/src/ftp.c b/examples/async-curl/src/ftp.c @@ -44,7 +44,7 @@ state_setup(struct http_request *req) { struct kore_curl *client; - client = http_state_create(req, sizeof(*client), NULL); + client = http_state_create(req, sizeof(*client)); if (!kore_curl_init(client, "http://ftp.eu.openbsd.org/pub/OpenBSD/README", KORE_CURL_ASYNC)) { diff --git a/examples/async-curl/src/http.c b/examples/async-curl/src/http.c @@ -59,7 +59,7 @@ state_setup(struct http_request *req) { struct kore_curl *client; - client = http_state_create(req, sizeof(*client), NULL); + client = http_state_create(req, sizeof(*client)); /* Initialize curl. */ if (!kore_curl_init(client, "https://kore.io", KORE_CURL_ASYNC)) { diff --git a/examples/pgsql/src/pgsql.c b/examples/pgsql/src/pgsql.c @@ -85,7 +85,7 @@ request_perform_init(struct http_request *req) /* Setup our state context (if not yet set). */ if (!http_state_exists(req)) { - state = http_state_create(req, sizeof(*state), NULL); + state = http_state_create(req, sizeof(*state)); /* * Initialize the kore_pgsql data structure and bind it diff --git a/include/kore/http.h b/include/kore/http.h @@ -413,8 +413,7 @@ const char *http_media_type(const char *); void *http_state_get(struct http_request *); int http_state_exists(struct http_request *); void http_state_cleanup(struct http_request *); -void *http_state_create(struct http_request *, size_t, - void (*onfree)(struct http_request *)); +void *http_state_create(struct http_request *, size_t); int http_argument_urldecode(char *); int http_header_recv(struct netbuf *); diff --git a/include/kore/kore.h b/include/kore/kore.h @@ -287,6 +287,7 @@ struct kore_runtime { int type; #if !defined(KORE_NO_HTTP) int (*http_request)(void *, struct http_request *); + void (*http_request_free)(void *, struct http_request *); void (*http_body_chunk)(void *, struct http_request *, const void *, size_t); int (*validator)(void *, struct http_request *, const void *); @@ -327,6 +328,7 @@ struct kore_route { struct kore_domain *dom; struct kore_auth *auth; struct kore_runtime_call *rcall; + struct kore_runtime_call *on_free; struct kore_runtime_call *on_headers; struct kore_runtime_call *on_body_chunk; @@ -993,6 +995,8 @@ void kore_runtime_connect(struct kore_runtime_call *, struct connection *); #if !defined(KORE_NO_HTTP) int kore_runtime_http_request(struct kore_runtime_call *, struct http_request *); +void kore_runtime_http_request_free(struct kore_runtime_call *, + struct http_request *); void kore_runtime_http_body_chunk(struct kore_runtime_call *, struct http_request *, const void *, size_t); int kore_runtime_validator(struct kore_runtime_call *, diff --git a/src/config.c b/src/config.c @@ -111,6 +111,7 @@ static int configure_client_verify_depth(char *); static int configure_route(char *); static int configure_route_methods(char *); static int configure_route_handler(char *); +static int configure_route_on_free(char *); static int configure_route_on_headers(char *); static int configure_route_on_body_chunk(char *); static int configure_filemap(char *); @@ -201,6 +202,7 @@ static struct { { "handler", configure_route_handler }, { "on_headers", configure_route_on_headers }, { "on_body_chunk", configure_route_on_body_chunk }, + { "on_free", configure_route_on_free }, { "methods", configure_route_methods }, { "filemap", configure_filemap }, { "redirect", configure_redirect }, @@ -1204,6 +1206,24 @@ configure_route_on_body_chunk(char *name) } static int +configure_route_on_free(char *name) +{ + if (current_route == NULL) { + kore_log(LOG_ERR, + "on_free keyword not inside of route context"); + return (KORE_RESULT_ERROR); + } + + if ((current_route->on_free = kore_runtime_getcall(name)) == NULL) { + kore_log(LOG_ERR, "on_free callback '%s' for '%s' not found", + name, current_route->path); + return (KORE_RESULT_ERROR); + } + + return (KORE_RESULT_OK); +} + +static int configure_route_methods(char *options) { int i, cnt; diff --git a/src/http.c b/src/http.c @@ -442,8 +442,8 @@ http_request_free(struct http_request *req) struct http_header *hdr, *next; struct http_cookie *ck, *cknext; - if (req->onfree != NULL) - req->onfree(req); + if (req->rt->on_free != NULL) + kore_runtime_http_request_free(req->rt->on_free, req); if (req->runlock != NULL) { LIST_REMOVE(req->runlock, list); @@ -1454,14 +1454,12 @@ http_state_exists(struct http_request *req) } void * -http_state_create(struct http_request *req, size_t len, - void (*onfree)(struct http_request *)) +http_state_create(struct http_request *req, size_t len) { if (req->hdlr_extra != NULL) fatal("http_state_create: state already exists"); req->state_len = len; - req->onfree = onfree; req->hdlr_extra = kore_calloc(1, len); return (req->hdlr_extra); @@ -2035,7 +2033,6 @@ http_request_new(struct connection *c, const char *host, req->status = 0; req->method = m; req->agent = NULL; - req->onfree = NULL; req->referer = NULL; req->runlock = NULL; req->flags = flags; diff --git a/src/python.c b/src/python.c @@ -88,6 +88,9 @@ static int python_route_params(PyObject *, struct kore_route *, static int python_route_methods(PyObject *, PyObject *, struct kore_route *); static int python_route_auth(PyObject *, struct kore_route *); +static int python_route_hooks(PyObject *, struct kore_route *); +static int python_route_hook_set(PyObject *, const char *, + struct kore_runtime_call **); static int python_coro_run(struct python_coro *); static void python_coro_wakeup(struct python_coro *); @@ -146,6 +149,9 @@ static void python_push_type(const char *, PyObject *, PyTypeObject *); static int python_validator_check(PyObject *); static int python_runtime_http_request(void *, struct http_request *); +static void python_runtime_http_request_free(void *, struct http_request *); +static void python_runtime_http_body_chunk(void *, struct http_request *, + const void *, size_t); static int python_runtime_validator(void *, struct http_request *, const void *); static void python_runtime_wsmessage(void *, struct connection *, @@ -175,6 +181,8 @@ struct kore_module_functions kore_python_module = { struct kore_runtime kore_python_runtime = { KORE_RUNTIME_PYTHON, .http_request = python_runtime_http_request, + .http_body_chunk = python_runtime_http_body_chunk, + .http_request_free = python_runtime_http_request_free, .validator = python_runtime_validator, .wsconnect = python_runtime_connect, .wsmessage = python_runtime_wsmessage, @@ -1318,6 +1326,49 @@ python_runtime_http_request(void *addr, struct http_request *req) return (KORE_RESULT_OK); } +static void +python_runtime_http_request_free(void *addr, struct http_request *req) +{ + PyObject *ret; + + if (req->py_req == NULL) + fatal("%s: py_req is NULL", __func__); + + PyErr_Clear(); + ret = PyObject_CallFunctionObjArgs(addr, req->py_req, NULL); + + if (ret == NULL) + kore_python_log_error("python_runtime_http_request_free"); + + Py_XDECREF(ret); +} + +static void +python_runtime_http_body_chunk(void *addr, struct http_request *req, + const void *data, size_t len) +{ + PyObject *args, *ret; + + if (req->py_req == NULL) { + if ((req->py_req = pyhttp_request_alloc(req)) == NULL) + fatal("%s: pyreq alloc failed", __func__); + } + + if ((args = Py_BuildValue("(Oy#)", req->py_req, data, len)) == NULL) { + kore_python_log_error("python_runtime_http_body_chunk"); + return; + } + + PyErr_Clear(); + ret = PyObject_Call(addr, args, NULL); + + if (ret == NULL) + kore_python_log_error("python_runtime_http_body_chunk"); + + Py_XDECREF(ret); + Py_DECREF(args); +} + static int python_runtime_validator(void *addr, struct http_request *req, const void *data) { @@ -5358,6 +5409,14 @@ python_route_install(struct pyroute *route) return (KORE_RESULT_ERROR); } } + + if ((obj = PyDict_GetItemString(kwargs, "hooks")) != NULL) { + if (!python_route_hooks(obj, rt)) { + kore_python_log_error("python_route_install"); + kore_route_free(rt); + return (KORE_RESULT_ERROR); + } + } } if (rt->path[0] == '/') { @@ -5605,6 +5664,51 @@ python_route_auth(PyObject *dict, struct kore_route *rt) return (KORE_RESULT_OK); } +static int +python_route_hooks(PyObject *dict, struct kore_route *rt) +{ + if (!PyDict_CheckExact(dict)) + return (KORE_RESULT_ERROR); + + if (!python_route_hook_set(dict, "on_free", &rt->on_free)) + return (KORE_RESULT_ERROR); + + if (!python_route_hook_set(dict, "on_headers", &rt->on_headers)) + return (KORE_RESULT_ERROR); + + if (!python_route_hook_set(dict, "on_body_chunk", &rt->on_body_chunk)) + return (KORE_RESULT_ERROR); + + return (KORE_RESULT_OK); +} + +static int +python_route_hook_set(PyObject *dict, const char *name, + struct kore_runtime_call **out) +{ + PyObject *obj; + struct kore_runtime_call *rcall; + + if ((obj = PyDict_GetItemString(dict, name)) == NULL) + return (KORE_RESULT_OK); + + if (!PyCallable_Check(obj)) { + PyErr_Format(PyExc_RuntimeError, + "%s for a route not callable", name); + Py_DECREF(obj); + return (KORE_RESULT_ERROR); + } + + rcall = kore_calloc(1, sizeof(struct kore_runtime_call)); + rcall->addr = obj; + rcall->runtime = &kore_python_runtime; + + Py_INCREF(rcall->addr); + *out = rcall; + + return (KORE_RESULT_OK); +} + #if defined(KORE_USE_PGSQL) static PyObject * python_kore_pgsql_query(PyObject *self, PyObject *args, PyObject *kwargs) diff --git a/src/runtime.c b/src/runtime.c @@ -33,6 +33,7 @@ static void native_runtime_connect(void *, struct connection *); static void native_runtime_configure(void *, int, char **); #if !defined(KORE_NO_HTTP) static int native_runtime_http_request(void *, struct http_request *); +static void native_runtime_http_request_free(void *, struct http_request *); static void native_runtime_http_body_chunk(void *, struct http_request *, const void *, size_t); static int native_runtime_validator(void *, struct http_request *, @@ -46,6 +47,7 @@ struct kore_runtime kore_native_runtime = { KORE_RUNTIME_NATIVE, #if !defined(KORE_NO_HTTP) .http_request = native_runtime_http_request, + .http_request_free = native_runtime_http_request_free, .http_body_chunk = native_runtime_http_body_chunk, .validator = native_runtime_validator, .wsconnect = native_runtime_connect, @@ -109,6 +111,13 @@ kore_runtime_http_request(struct kore_runtime_call *rcall, } void +kore_runtime_http_request_free(struct kore_runtime_call *rcall, + struct http_request *req) +{ + rcall->runtime->http_request_free(rcall->addr, req); +} + +void kore_runtime_http_body_chunk(struct kore_runtime_call *rcall, struct http_request *req, const void *data, size_t len) { @@ -189,6 +198,15 @@ native_runtime_http_request(void *addr, struct http_request *req) } static void +native_runtime_http_request_free(void *addr, struct http_request *req) +{ + int (*cb)(struct http_request *); + + *(void **)&(cb) = addr; + cb(req); +} + +static void native_runtime_http_body_chunk(void *addr, struct http_request *req, const void *data, size_t len) {