commit bbcdec82fc3c26a1b6ed39b7f4f20d412ff6a891
parent 57840a8366d61604ac845de5b21a645bef528f20
Author: Joris Vink <joris@coders.se>
Date:   Thu, 12 Jan 2017 23:38:51 +0100
Add initial python support.
Based on work done by Stanislav Yudin.
Diffstat:
14 files changed, 1223 insertions(+), 103 deletions(-)
diff --git a/Makefile b/Makefile
@@ -9,7 +9,8 @@ INCLUDE_DIR=$(PREFIX)/include/kore
 
 S_SRC=	src/kore.c src/buf.c src/cli.c src/config.c src/connection.c \
 	src/domain.c src/mem.c src/msg.c src/module.c src/net.c \
-	src/pool.c src/timer.c src/utils.c src/worker.c src/keymgr.c
+	src/pool.c src/runtime.c src/timer.c src/utils.c src/worker.c \
+	src/keymgr.c
 
 CFLAGS+=-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes
 CFLAGS+=-Wmissing-declarations -Wshadow -Wpointer-arith -Wcast-qual
@@ -63,6 +64,12 @@ ifneq ("$(JSONRPC)", "")
 	CFLAGS+=-DKORE_USE_JSONRPC
 endif
 
+ifneq ("$(PYTHON)", "")
+	S_SRC+=src/python.c
+	LDFLAGS+=$(shell python3-config --ldflags)
+	CFLAGS+=$(shell python3-config --includes) -DKORE_USE_PYTHON
+endif
+
 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
diff --git a/includes/http.h b/includes/http.h
@@ -186,6 +186,10 @@ struct http_request {
 	char				*query_string;
 	struct kore_module_handle	*hdlr;
 
+#if defined(KORE_USE_PYTHON)
+	void				*py_object;
+#endif
+
 	LIST_HEAD(, kore_task)		tasks;
 	LIST_HEAD(, kore_pgsql)		pgsqls;
 
diff --git a/includes/kore.h b/includes/kore.h
@@ -207,11 +207,32 @@ TAILQ_HEAD(connection_list, connection);
 extern struct connection_list	connections;
 extern struct connection_list	disconnected;
 
+#define KORE_RUNTIME_NATIVE	0
+#define KORE_RUNTIME_PYTHON	1
+
+struct kore_runtime {
+	int	type;
+#if !defined(KORE_NO_HTTP)
+	int	(*http_request)(void *, struct http_request *);
+	int	(*validator)(void *, struct http_request *, void *);
+#endif
+	int	(*execute)(void *, void *);
+	int	(*onload)(void *, int);
+	void	(*connect)(void *, struct connection *);
+};
+
+struct kore_runtime_call {
+	void			*addr;
+	struct kore_runtime	*runtime;
+};
+
+extern struct kore_runtime	kore_native_runtime;
+
 struct listener {
-	u_int8_t		type;
-	u_int8_t		addrtype;
-	int			fd;
-	void			(*connect)(struct connection *);
+	u_int8_t			type;
+	u_int8_t			addrtype;
+	int				fd;
+	struct kore_runtime_call	*connect;
 
 	union {
 		struct sockaddr_in	ipv4;
@@ -250,32 +271,48 @@ struct kore_auth {
 #define HANDLER_TYPE_STATIC	1
 #define HANDLER_TYPE_DYNAMIC	2
 
-#endif
+#endif /* !KORE_NO_HTTP */
 
 #define KORE_MODULE_LOAD	1
 #define KORE_MODULE_UNLOAD	2
 
+#define KORE_MODULE_NATIVE	0
+#define KORE_MODULE_PYTHON	1
+
+struct kore_module;
+
+struct kore_module_functions {
+	void			(*free)(struct kore_module *);
+	void			(*reload)(struct kore_module *);
+	int			(*callback)(struct kore_module *, int);
+	void			(*load)(struct kore_module *, const char *);
+	void			*(*getsym)(struct kore_module *, const char *);
+};
+
 struct kore_module {
-	void			*handle;
-	char			*path;
-	char			*onload;
-	int			(*ocb)(int);
+	void				*handle;
+	char				*path;
+	char				*onload;
+	int				type;
+	time_t				mtime;
+	struct kore_runtime_call	*ocb;
 
-	time_t			mtime;
+	struct kore_module_functions	*fun;
+	struct kore_runtime		*runtime;
 
 	TAILQ_ENTRY(kore_module)	list;
 };
 
 struct kore_module_handle {
-	char			*path;
-	char			*func;
-	void			*addr;
-	int			type;
-	int			errors;
-	regex_t			rctx;
-	struct kore_domain	*dom;
+	char				*path;
+	char				*func;
+	int				type;
+	int				errors;
+	regex_t				rctx;
+	struct kore_domain		*dom;
+	struct kore_runtime_call	*rcall;
 #if !defined(KORE_NO_HTTP)
-	struct kore_auth	*auth;
+	struct kore_auth			*auth;
 	TAILQ_HEAD(, kore_handler_params)	params;
 #endif
 	TAILQ_ENTRY(kore_module_handle)		list;
@@ -313,15 +350,15 @@ TAILQ_HEAD(kore_domain_h, kore_domain);
 #define KORE_VALIDATOR_TYPE_FUNCTION	2
 
 struct kore_validator {
-	u_int8_t		type;
-	char			*name;
-	char			*arg;
-	regex_t			rctx;
-	int			(*func)(struct http_request *, char *);
+	u_int8_t			type;
+	char				*name;
+	char				*arg;
+	regex_t				rctx;
+	struct kore_runtime_call	*rcall;
 
 	TAILQ_ENTRY(kore_validator)	list;
 };
-#endif
+#endif /* !KORE_NO_HTTP */
 
 #define KORE_BUF_OWNER_API	0x0001
 
@@ -568,16 +605,27 @@ void		kore_module_reload(int);
 void		kore_module_onload(void);
 int		kore_module_loaded(void);
 void		kore_domain_closelogs(void);
-void		*kore_module_getsym(const char *);
+void		*kore_module_getsym(const char *, struct kore_runtime **);
 void		kore_domain_load_crl(void);
 void		kore_domain_keymgr_init(void);
-void		kore_module_load(const char *, const char *);
 void		kore_domain_sslstart(struct kore_domain *);
+void		kore_module_load(const char *, const char *, int);
 void		kore_domain_callback(void (*cb)(struct kore_domain *));
 int		kore_module_handler_new(const char *, const char *,
 		    const char *, const char *, int);
 void		kore_module_handler_free(struct kore_module_handle *);
 
+struct kore_runtime_call	*kore_runtime_getcall(const char *);
+
+int	kore_runtime_onload(struct kore_runtime_call *, int);
+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 *);
+int	kore_runtime_validator(struct kore_runtime_call *,
+	    struct http_request *, void *);
+#endif
+
 struct kore_domain		*kore_domain_lookup(const char *);
 struct kore_module_handle	*kore_module_handler_find(const char *,
 				    const char *);
diff --git a/includes/python_api.h b/includes/python_api.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2016 Stanislav Yudin <stan@endlessinsomnia.com>
+ * Copyright (c) 2017 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __H_PYTHON_H
+#define __H_PYTHON_H
+
+#include <Python.h>
+
+void		kore_python_init(void);
+void		kore_python_cleanup(void);
+void		kore_python_path(const char *);
+
+PyObject	*kore_python_callable(PyObject *, const char *);
+
+extern struct kore_module_functions	kore_python_module;
+extern struct kore_runtime		kore_python_runtime;
+
+#endif
diff --git a/includes/python_methods.h b/includes/python_methods.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2017 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+static PyObject		*python_exported_log(PyObject *, PyObject *);
+
+#define METHOD(n, c, a)		{ n, (PyCFunction)c, a, NULL }
+#define GETTER(n, g)		{ n, (getter)g, NULL, NULL, NULL }
+#define SETTER(n, s)		{ n, NULL, (setter)g, NULL, NULL }
+#define GETSET(n, g, s)		{ n, (getter)g, (setter)s, NULL, NULL }
+
+static struct PyMethodDef pykore_methods[] = {
+	METHOD("log", python_exported_log, METH_VARARGS),
+	{ NULL, NULL, 0, NULL }
+};
+
+static struct PyModuleDef pykore_module = {
+	PyModuleDef_HEAD_INIT, "kore", NULL, -1, pykore_methods
+};
+
+struct pyconnection {
+	PyObject_HEAD
+	struct connection	*c;
+};
+
+static PyMethodDef pyconnection_methods[] = {
+	METHOD(NULL, NULL, -1),
+};
+
+static PyGetSetDef pyconnection_getset[] = {
+	GETTER(NULL, NULL),
+};
+
+static void	pyconnection_dealloc(struct pyconnection *);
+
+static PyTypeObject pyconnection_type = {
+	PyVarObject_HEAD_INIT(NULL, 0)
+	.tp_name = "kore.connection",
+	.tp_doc = "struct connection",
+	.tp_getset = pyconnection_getset,
+	.tp_methods = pyconnection_methods,
+	.tp_basicsize = sizeof(struct pyconnection),
+	.tp_dealloc = (destructor)pyconnection_dealloc,
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+};
+
+#if !defined(KORE_NO_HTTP)
+struct pyhttp_request {
+	PyObject_HEAD
+	struct http_request	*req;
+};
+
+static void	pyhttp_dealloc(struct pyhttp_request *);
+
+static PyObject	*pyhttp_response(struct pyhttp_request *, PyObject *);
+static PyObject	*pyhttp_body_read(struct pyhttp_request *, PyObject *);
+static PyObject	*pyhttp_populate_get(struct pyhttp_request *, PyObject *);
+static PyObject	*pyhttp_populate_post(struct pyhttp_request *, PyObject *);
+static PyObject	*pyhttp_request_header(struct pyhttp_request *, PyObject *);
+static PyObject	*pyhttp_response_header(struct pyhttp_request *, PyObject *);
+
+static PyMethodDef pyhttp_request_methods[] = {
+	METHOD("response", pyhttp_response, METH_VARARGS),
+	METHOD("body_read", pyhttp_body_read, METH_VARARGS),
+	METHOD("populate_get", pyhttp_populate_get, METH_NOARGS),
+	METHOD("populate_post", pyhttp_populate_post, METH_NOARGS),
+	METHOD("request_header", pyhttp_request_header, METH_VARARGS),
+	METHOD("response_header", pyhttp_response_header, METH_VARARGS),
+	METHOD(NULL, NULL, -1)
+};
+
+static int	pyhttp_set_state(struct pyhttp_request *, PyObject *, void *);
+
+static PyObject	*pyhttp_get_host(struct pyhttp_request *, void *);
+static PyObject	*pyhttp_get_path(struct pyhttp_request *, void *);
+static PyObject	*pyhttp_get_body(struct pyhttp_request *, void *);
+static PyObject	*pyhttp_get_agent(struct pyhttp_request *, void *);
+static PyObject	*pyhttp_get_state(struct pyhttp_request *, void *);
+static PyObject	*pyhttp_get_method(struct pyhttp_request *, void *);
+static PyObject	*pyhttp_get_connection(struct pyhttp_request *, void *);
+
+static PyGetSetDef pyhttp_request_getset[] = {
+	GETTER("host", pyhttp_get_host),
+	GETTER("path", pyhttp_get_path),
+	GETTER("body", pyhttp_get_body),
+	GETTER("agent", pyhttp_get_agent),
+	GETTER("method", pyhttp_get_method),
+	GETTER("connection", pyhttp_get_connection),
+	GETSET("state", pyhttp_get_state, pyhttp_set_state),
+	GETTER(NULL, NULL)
+};
+
+static PyTypeObject pyhttp_request_type = {
+	PyVarObject_HEAD_INIT(NULL, 0)
+	.tp_name = "kore.http_request",
+	.tp_doc = "struct http_request",
+	.tp_getset = pyhttp_request_getset,
+	.tp_methods = pyhttp_request_methods,
+	.tp_dealloc = (destructor)pyhttp_dealloc,
+	.tp_basicsize = sizeof(struct pyhttp_request),
+	.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+};
+#endif
diff --git a/src/config.c b/src/config.c
@@ -35,6 +35,10 @@
 #include "tasks.h"
 #endif
 
+#if defined(KORE_USE_PYTHON)
+#include "python_api.h"
+#endif
+
 /* XXX - This is becoming a clusterfuck. Fix it. */
 
 #if !defined(KORE_SINGLE_BINARY)
@@ -99,6 +103,10 @@ static int		configure_pgsql_conn_max(char *);
 static int		configure_task_threads(char *);
 #endif
 
+#if defined(KORE_USE_PYTHON)
+static int		configure_python_import(char *);
+#endif
+
 static void		domain_sslstart(void);
 static void		kore_parse_config_file(const char *);
 
@@ -111,6 +119,9 @@ static struct {
 #if !defined(KORE_SINGLE_BINARY)
 	{ "load",			configure_load },
 #endif
+#if defined(KORE_USE_PYTHON)
+	{ "python_import",		configure_python_import },
+#endif
 	{ "domain",			configure_domain },
 	{ "chroot",			configure_chroot },
 	{ "runas",			configure_runas },
@@ -313,7 +324,7 @@ configure_load(char *options)
 	if (argv[0] == NULL)
 		return (KORE_RESULT_ERROR);
 
-	kore_module_load(argv[0], argv[1]);
+	kore_module_load(argv[0], argv[1], KORE_MODULE_NATIVE);
 	return (KORE_RESULT_OK);
 }
 #else
@@ -1073,3 +1084,18 @@ configure_task_threads(char *option)
 	return (KORE_RESULT_OK);
 }
 #endif
+
+#if defined(KORE_USE_PYTHON)
+static int
+configure_python_import(char *module)
+{
+	char		*argv[3];
+
+	kore_split_string(module, " ", argv, 3);
+	if (argv[0] == NULL)
+		return (KORE_RESULT_ERROR);
+
+	kore_module_load(argv[0], argv[1], KORE_MODULE_PYTHON);
+	return (KORE_RESULT_OK);
+}
+#endif
diff --git a/src/connection.c b/src/connection.c
@@ -128,7 +128,7 @@ kore_connection_accept(struct listener *listener, struct connection **out)
 	c->read = net_read;
 
 	if (listener->connect != NULL) {
-		listener->connect(c);
+		kore_runtime_connect(listener->connect, c);
 	} else {
 #if !defined(KORE_NO_HTTP)
 		c->proto = CONN_PROTO_HTTP;
@@ -265,7 +265,7 @@ kore_connection_handle(struct connection *c)
 		if (c->owner != NULL) {
 			listener = (struct listener *)c->owner;
 			if (listener->connect != NULL) {
-				listener->connect(c);
+				kore_runtime_connect(listener->connect, c);
 				return (KORE_RESULT_OK);
 			}
 		}
diff --git a/src/http.c b/src/http.c
@@ -204,6 +204,10 @@ http_request_new(struct connection *c, const char *host,
 	req->http_body_offset = 0;
 	req->http_body_path = NULL;
 
+#if defined(KORE_USE_PYTHON)
+	req->py_object = NULL;
+#endif
+
 	req->host = kore_pool_get(&http_host_pool);
 	memcpy(req->host, host, hostlen);
 	req->host[hostlen] = '\0';
@@ -298,7 +302,7 @@ http_process(void)
 void
 http_process_request(struct http_request *req)
 {
-	int		r, (*cb)(struct http_request *);
+	int		r;
 
 	kore_debug("http_process_request: %p->%p (%s)",
 	    req->owner, req, req->path);
@@ -314,10 +318,7 @@ http_process_request(struct http_request *req)
 
 	switch (r) {
 	case KORE_RESULT_OK:
-		*(void **)&(cb) = req->hdlr->addr;
-		worker->active_hdlr = req->hdlr;
-		r = cb(req);
-		worker->active_hdlr = NULL;
+		r = kore_runtime_http_request(req->hdlr->rcall, req);
 		break;
 	case KORE_RESULT_RETRY:
 		break;
diff --git a/src/kore.c b/src/kore.c
@@ -30,6 +30,10 @@
 #include "http.h"
 #endif
 
+#if defined(KORE_USE_PYTHON)
+#include "python_api.h"
+#endif
+
 volatile sig_atomic_t			sig_recv;
 
 struct listener_head	listeners;
@@ -175,6 +179,9 @@ main(int argc, char *argv[])
 	LIST_INIT(&listeners);
 
 	kore_log_init();
+#if defined(KORE_USE_PYTHON)
+	kore_python_init();
+#endif
 #if !defined(KORE_NO_HTTP)
 	kore_auth_init();
 	kore_validator_init();
@@ -187,7 +194,7 @@ main(int argc, char *argv[])
 	if (config_file == NULL)
 		usage();
 #else
-	kore_module_load(NULL, NULL);
+	kore_module_load(NULL, NULL, KORE_MODULE_NATIVE);
 #endif
 
 	kore_parse_config();
@@ -340,8 +347,7 @@ kore_server_bind(const char *ip, const char *port, const char *ccb)
 	}
 
 	if (ccb != NULL) {
-		*(void **)&(l->connect) = kore_module_getsym(ccb);
-		if (l->connect == NULL) {
+		if ((l->connect = kore_runtime_getcall(ccb)) == NULL) {
 			printf("no such callback: '%s'\n", ccb);
 			close(l->fd);
 			kore_free(l);
@@ -398,10 +404,10 @@ kore_server_sslstart(void)
 static void
 kore_server_start(void)
 {
-	u_int32_t	tmp;
-	int		quit;
+	u_int32_t			tmp;
+	int				quit;
 #if defined(KORE_SINGLE_BINARY)
-	void		(*preload)(void);
+	struct kore_runtime_call	*rcall;
 #endif
 
 	if (foreground == 0 && daemon(1, 1) == -1)
@@ -422,9 +428,11 @@ kore_server_start(void)
 	kore_log(LOG_NOTICE, "jsonrpc built-in enabled");
 #endif
 #if defined(KORE_SINGLE_BINARY)
-	*(void **)&(preload) = kore_module_getsym("kore_preload");
-	if (preload != NULL)
-		preload();
+	rcall = kore_runtime_getcall("kore_preload");
+	if (rcall != NULL) {
+		rcall->runtime->execute(rcall->addr, NULL);
+		kore_free(rcall);
+	}
 #endif
 
 	kore_platform_proctitle("kore [parent]");
diff --git a/src/module.c b/src/module.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2016 Joris Vink <joris@coders.se>
+ * Copyright (c) 2013-2017 Joris Vink <joris@coders.se>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -20,8 +20,28 @@
 
 #include "kore.h"
 
+#if !defined(KORE_NO_HTTP)
+#include "http.h"
+#endif
+
+#if defined(KORE_USE_PYTHON)
+#include "python_api.h"
+#endif
+
 static TAILQ_HEAD(, kore_module)	modules;
 
+static void	native_free(struct kore_module *);
+static void	native_reload(struct kore_module *);
+static void	native_load(struct kore_module *, const char *);
+static void	*native_getsym(struct kore_module *, const char *);
+
+struct kore_module_functions kore_native_module = {
+	.free = native_free,
+	.load = native_load,
+	.getsym = native_getsym,
+	.reload = native_reload,
+};
+
 void
 kore_module_init(void)
 {
@@ -36,15 +56,12 @@ kore_module_cleanup(void)
 	for (module = TAILQ_FIRST(&modules); module != NULL; module = next) {
 		next = TAILQ_NEXT(module, list);
 		TAILQ_REMOVE(&modules, module, list);
-
-		kore_free(module->path);
-		(void)dlclose(module->handle);
-		kore_free(module);
+		module->fun->free(module);
 	}
 }
 
 void
-kore_module_load(const char *path, const char *onload)
+kore_module_load(const char *path, const char *onload, int type)
 {
 #if !defined(KORE_SINGLE_BINARY)
 	struct stat		st;
@@ -54,8 +71,10 @@ kore_module_load(const char *path, const char *onload)
 	kore_debug("kore_module_load(%s, %s)", path, onload);
 
 	module = kore_malloc(sizeof(struct kore_module));
-	module->onload = NULL;
 	module->ocb = NULL;
+	module->type = type;
+	module->onload = NULL;
+	module->handle = NULL;
 
 #if !defined(KORE_SINGLE_BINARY)
 	if (stat(path, &st) == -1)
@@ -68,18 +87,34 @@ kore_module_load(const char *path, const char *onload)
 	module->mtime = 0;
 #endif
 
-	module->handle = dlopen(module->path, RTLD_NOW | RTLD_GLOBAL);
-	if (module->handle == NULL)
-		fatal("%s: %s", path, dlerror());
-
-	if (onload != NULL) {
-		module->onload = kore_strdup(onload);
-		*(void **)&(module->ocb) = dlsym(module->handle, onload);
-		if (module->ocb == NULL)
-			fatal("%s: onload '%s' not present", path, onload);
+	switch (module->type) {
+	case KORE_MODULE_NATIVE:
+		module->fun = &kore_native_module;
+		module->runtime = &kore_native_runtime;
+		break;
+#if defined(KORE_USE_PYTHON)
+	case KORE_MODULE_PYTHON:
+		module->fun = &kore_python_module;
+		module->runtime = &kore_python_runtime;
+		break;
+#endif
+	default:
+		fatal("kore_module_load: unknown type %d", type);
 	}
 
+	module->fun->load(module, onload);
 	TAILQ_INSERT_TAIL(&modules, module, list);
+
+	if (onload != NULL) {
+		module->ocb = kore_malloc(sizeof(*module->ocb));
+		module->ocb->runtime = module->runtime;
+		module->ocb->addr = module->fun->getsym(module, onload);
+
+		if (module->ocb->addr == NULL) {
+			fatal("%s: onload '%s' not present",
+			    module->path, onload);
+		}
+	}
 }
 
 void
@@ -92,7 +127,7 @@ kore_module_onload(void)
 		if (module->ocb == NULL)
 			continue;
 
-		(void)module->ocb(KORE_MODULE_LOAD);
+		kore_runtime_onload(module->ocb, KORE_MODULE_LOAD);
 	}
 #endif
 }
@@ -102,6 +137,7 @@ kore_module_reload(int cbs)
 {
 #if !defined(KORE_SINGLE_BINARY)
 	struct stat			st;
+	int				ret;
 	struct kore_domain		*dom;
 	struct kore_module_handle	*hdlr;
 	struct kore_module		*module;
@@ -113,11 +149,15 @@ kore_module_reload(int cbs)
 			continue;
 		}
 
-		if (module->mtime == st.st_mtime)
+		if (module->mtime == st.st_mtime) {
+			kore_log(LOG_NOTICE, "not reloading %s", module->path);
 			continue;
+		}
 
 		if (module->ocb != NULL && cbs == 1) {
-			if (!module->ocb(KORE_MODULE_UNLOAD)) {
+			ret = kore_runtime_onload(module->ocb,
+			    KORE_MODULE_UNLOAD);
+			if (ret == KORE_RESULT_ERROR) {
 				kore_log(LOG_NOTICE,
 				    "not reloading %s", module->path);
 				continue;
@@ -125,32 +165,31 @@ kore_module_reload(int cbs)
 		}
 
 		module->mtime = st.st_mtime;
-		if (dlclose(module->handle))
-			fatal("cannot close existing module: %s", dlerror());
-
-		module->handle = dlopen(module->path, RTLD_NOW | RTLD_GLOBAL);
-		if (module->handle == NULL)
-			fatal("kore_module_reload(): %s", dlerror());
+		module->fun->reload(module);
 
 		if (module->onload != NULL) {
-			*(void **)&(module->ocb) =
-			    dlsym(module->handle, module->onload);
-			if (module->ocb == NULL) {
+			kore_free(module->ocb);
+			module->ocb = kore_malloc(sizeof(*module->ocb));
+			module->ocb->runtime = module->runtime;
+			module->ocb->addr =
+			    module->fun->getsym(module, module->onload);
+			if (module->ocb->addr == NULL) {
 				fatal("%s: onload '%s' not present",
 				    module->path, module->onload);
 			}
-
-			if (cbs)
-				(void)module->ocb(KORE_MODULE_LOAD);
 		}
 
+		if (module->ocb != NULL && cbs == 1)
+			kore_runtime_onload(module->ocb, KORE_MODULE_LOAD);
+
 		kore_log(LOG_NOTICE, "reloaded '%s' module", module->path);
 	}
 
 	TAILQ_FOREACH(dom, &domains, list) {
 		TAILQ_FOREACH(hdlr, &(dom->handlers), list) {
-			hdlr->addr = kore_module_getsym(hdlr->func);
-			if (hdlr->func == NULL)
+			kore_free(hdlr->rcall);
+			hdlr->rcall = kore_runtime_getcall(hdlr->func);
+			if (hdlr->rcall == NULL)
 				fatal("no function '%s' found", hdlr->func);
 			hdlr->errors = 0;
 		}
@@ -177,19 +216,12 @@ kore_module_handler_new(const char *path, const char *domain,
     const char *func, const char *auth, int type)
 {
 	struct kore_auth		*ap;
-	void				*addr;
 	struct kore_domain		*dom;
 	struct kore_module_handle	*hdlr;
 
 	kore_debug("kore_module_handler_new(%s, %s, %s, %s, %d)", path,
 	    domain, func, auth, type);
 
-	addr = kore_module_getsym(func);
-	if (addr == NULL) {
-		kore_debug("function '%s' not found", func);
-		return (KORE_RESULT_ERROR);
-	}
-
 	if ((dom = kore_domain_lookup(domain)) == NULL)
 		return (KORE_RESULT_ERROR);
 
@@ -204,12 +236,18 @@ kore_module_handler_new(const char *path, const char *domain,
 	hdlr->auth = ap;
 	hdlr->dom = dom;
 	hdlr->errors = 0;
-	hdlr->addr = addr;
 	hdlr->type = type;
-	TAILQ_INIT(&(hdlr->params));
 	hdlr->path = kore_strdup(path);
 	hdlr->func = kore_strdup(func);
 
+	TAILQ_INIT(&(hdlr->params));
+
+	if ((hdlr->rcall = kore_runtime_getcall(func)) == NULL) {
+		kore_module_handler_free(hdlr);
+		kore_log(LOG_ERR, "function '%s' not found", func);
+		return (KORE_RESULT_ERROR);
+	}
+
 	if (hdlr->type == HANDLER_TYPE_DYNAMIC) {
 		if (regcomp(&(hdlr->rctx), hdlr->path,
 		    REG_EXTENDED | REG_NOSUB)) {
@@ -270,20 +308,55 @@ kore_module_handler_find(const char *domain, const char *path)
 
 	return (NULL);
 }
-
 #endif /* !KORE_NO_HTTP */
 
 void *
-kore_module_getsym(const char *symbol)
+kore_module_getsym(const char *symbol, struct kore_runtime **runtime)
 {
 	void			*ptr;
 	struct kore_module	*module;
 
+	if (runtime != NULL)
+		*runtime = NULL;
+
 	TAILQ_FOREACH(module, &modules, list) {
-		ptr = dlsym(module->handle, symbol);
-		if (ptr != NULL)
+		ptr = module->fun->getsym(module, symbol);
+		if (ptr != NULL) {
+			if (runtime != NULL)
+				*runtime = module->runtime;
 			return (ptr);
+		}
 	}
 
 	return (NULL);
 }
+
+static void *
+native_getsym(struct kore_module *module, const char *symbol)
+{
+	return (dlsym(module->handle, symbol));
+}
+
+static void
+native_free(struct kore_module *module)
+{
+	kore_free(module->path);
+	(void)dlclose(module->handle);
+	kore_free(module);
+}
+
+static void
+native_reload(struct kore_module *module)
+{
+	if (dlclose(module->handle))
+		fatal("cannot close existing module: %s", dlerror());
+	module->fun->load(module, module->onload);
+}
+
+static void
+native_load(struct kore_module *module, const char *onload)
+{
+	module->handle = dlopen(module->path, RTLD_NOW | RTLD_GLOBAL);
+	if (module->handle == NULL)
+		fatal("%s: %s", module->path, dlerror());
+}
diff --git a/src/python.c b/src/python.c
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 2016 Stanislav Yudin <stan@endlessinsomnia.com>
+ * Copyright (c) 2017 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+
+#include <libgen.h>
+
+#include "kore.h"
+
+#if !defined(KORE_NO_HTTP)
+#include "http.h"
+#endif
+
+#include "python_api.h"
+#include "python_methods.h"
+
+static PyMODINIT_FUNC	python_module_init(void);
+static PyObject		*python_import(const char *);
+static void		python_log_error(const char *);
+static PyObject		*pyconnection_alloc(struct connection *);
+static PyObject		*python_callable(PyObject *, const char *);
+
+#if !defined(KORE_NO_HTTP)
+static PyObject		*pyhttp_request_alloc(struct http_request *);
+#endif
+
+static void	python_append_path(const char *);
+static void	python_push_integer(PyObject *, const char *, long);
+static void	python_push_type(const char *, PyObject *, PyTypeObject *);
+
+#if !defined(KORE_NO_HTTP)
+static int	python_runtime_http_request(void *, struct http_request *);
+static int	python_runtime_validator(void *, struct http_request *, void *);
+#endif
+static int	python_runtime_onload(void *, int);
+static void	python_runtime_connect(void *, struct connection *);
+
+static void	python_module_free(struct kore_module *);
+static void	python_module_reload(struct kore_module *);
+static void	python_module_load(struct kore_module *, const char *);
+static void	*python_module_getsym(struct kore_module *, const char *);
+
+struct kore_module_functions kore_python_module = {
+	.free = python_module_free,
+	.load = python_module_load,
+	.getsym = python_module_getsym,
+	.reload = python_module_reload
+};
+
+struct kore_runtime kore_python_runtime = {
+	KORE_RUNTIME_PYTHON,
+#if !defined(KORE_NO_HTTP)
+	.http_request = python_runtime_http_request,
+	.validator = python_runtime_validator,
+#endif
+	.onload = python_runtime_onload,
+	.connect = python_runtime_connect
+};
+
+void
+kore_python_init(void)
+{
+	if (PyImport_AppendInittab("kore", &python_module_init) == -1)
+		fatal("kore_python_init: failed to add new module");
+
+	Py_Initialize();
+}
+
+void
+kore_python_cleanup(void)
+{
+	if (Py_IsInitialized())
+		Py_Finalize();
+}
+
+static void
+python_log_error(const char *function)
+{
+	PyObject	*type, *value, *traceback;
+
+	if (!PyErr_Occurred())
+		return;
+
+	PyErr_Fetch(&type, &value, &traceback);
+
+	if (type == NULL || value == NULL || traceback == NULL)
+		fatal("python_log_error(): exception but no error trace?");
+
+	kore_log(LOG_ERR,
+	    "python exception in '%s' - type:%s - value:%s - trace:%s",
+	    function,
+	    PyUnicode_AsUTF8AndSize(type, NULL),
+	    PyUnicode_AsUTF8AndSize(value, NULL),
+	    PyUnicode_AsUTF8AndSize(traceback, NULL));
+
+	Py_DECREF(type);
+	Py_DECREF(value);
+	Py_DECREF(traceback);
+}
+
+static void
+python_module_free(struct kore_module *module)
+{
+	kore_free(module->path);
+	Py_DECREF(module->handle);
+	kore_free(module);
+}
+
+static void
+python_module_reload(struct kore_module *module)
+{
+	/* Calls through to kore_python_module_load() below. */
+	module->fun->load(module, module->onload);
+}
+
+static void
+python_module_load(struct kore_module *module, const char *onload)
+{
+	if (module->handle != NULL)
+		Py_DECREF(module->handle);
+
+	kore_python_cleanup();
+	kore_python_init();
+
+	module->handle = python_import(module->path);
+	if (module->handle == NULL)
+		fatal("%s: failed to import module", module->path);
+}
+
+static void *
+python_module_getsym(struct kore_module *module, const char *symbol)
+{
+	return (python_callable(module->handle, symbol));
+}
+
+static void pyhttp_dealloc(struct pyhttp_request *pyreq)
+{
+	printf("pyreq %p goes byebye\n", (void *)pyreq);
+	PyObject_Del((PyObject *)pyreq);
+}
+
+static void pyconnection_dealloc(struct pyconnection *pyc)
+{
+	printf("pyc %p goes byebye\n", (void *)pyc);
+	PyObject_Del((PyObject *)pyc);
+}
+
+#if !defined(KORE_NO_HTTP)
+static int
+python_runtime_http_request(void *addr, struct http_request *req)
+{
+	int		ret;
+	PyObject	*pyret, *pyreq, *args, *callable;
+
+	callable = (PyObject *)addr;
+
+	pyreq = pyhttp_request_alloc(req);
+	if (pyreq == NULL) {
+		kore_log(LOG_ERR, "cannot create new pyhttp_request");
+		http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
+		return (KORE_RESULT_OK);
+	}
+
+	if ((args = PyTuple_New(1)) == NULL)
+		fatal("python_runtime_http_request: PyTuple_New failed");
+
+	printf("  args tuple: %p\n", (void *)args);
+
+	if (PyTuple_SetItem(args, 0, pyreq) != 0)
+		fatal("python_runtime_http_request: PyTuple_SetItem failed");
+
+	PyErr_Clear();
+	pyret = PyObject_Call(callable, args, NULL);
+	Py_DECREF(args);
+
+	if (pyret == NULL) {
+		Py_XDECREF(req->py_object);
+		python_log_error("python_runtime_http_request");
+		http_response(req, HTTP_STATUS_INTERNAL_ERROR, NULL, 0);
+		return (KORE_RESULT_OK);
+	}
+
+	if (!PyLong_Check(pyret))
+		fatal("python_runtime_http_request: unexpected return type");
+
+	ret = (int)PyLong_AsLong(pyret);
+	if (ret != KORE_RESULT_RETRY)
+		Py_XDECREF(req->py_object);
+
+	Py_DECREF(pyret);
+
+	return (ret);
+}
+
+static int
+python_runtime_validator(void *addr, struct http_request *req, void *data)
+{
+	printf("python_runtime_validator: XXX %s\n", req->path);
+	return (KORE_RESULT_OK);
+}
+#endif
+
+static int
+python_runtime_onload(void *addr, int action)
+{
+	int		ret;
+	PyObject	*pyret, *args, *pyact, *callable;
+
+	callable = (PyObject *)addr;
+
+	if ((pyact = PyLong_FromLong(action)) == NULL)
+		fatal("python_runtime_onload: PyLong_FromLong failed");
+
+	if ((args = PyTuple_New(1)) == NULL)
+		fatal("python_runtime_onload: PyTuple_New failed");
+
+	if (PyTuple_SetItem(args, 0, pyact) != 0)
+		fatal("python_runtime_onload: PyTuple_SetItem failed");
+
+	pyret = PyObject_Call(callable, args, NULL);
+	Py_DECREF(args);
+
+	if (pyret == NULL) {
+		python_log_error("python_runtime_onload");
+		return (KORE_RESULT_ERROR);
+	}
+
+	if (!PyLong_Check(pyret))
+		fatal("python_runtime_onload: unexpected return type");
+
+	ret = (int)PyLong_AsLong(pyret);
+	Py_DECREF(pyret);
+
+	return (ret);
+}
+
+static void
+python_runtime_connect(void *addr, struct connection *c)
+{
+	PyObject	*pyc, *pyret, *args, *callable;
+
+	callable = (PyObject *)addr;
+
+	if ((pyc = pyconnection_alloc(c)) == NULL) {
+		kore_log(LOG_ERR, "cannot create new pyconnection");
+		kore_connection_disconnect(c);
+		return;
+	}
+
+	if ((args = PyTuple_New(1)) == NULL)
+		fatal("python_runtime_connect: PyTuple_New failed");
+
+	if (PyTuple_SetItem(args, 0, pyc) != 0)
+		fatal("python_runtime_connect: PyTuple_SetItem failed");
+
+	pyret = PyObject_Call(callable, args, NULL);
+	Py_DECREF(args);
+
+	if (pyret == NULL) {
+		python_log_error("python_runtime_connect");
+		kore_connection_disconnect(c);
+	}
+
+	Py_DECREF(pyret);
+}
+
+static PyMODINIT_FUNC
+python_module_init(void)
+{
+	PyObject	*pykore;
+
+	if ((pykore = PyModule_Create(&pykore_module)) == NULL)
+		fatal("python_module_init: failed to setup pykore module");
+
+	python_push_type("pyconnection", pykore, &pyconnection_type);
+
+	python_push_integer(pykore, "RESULT_OK", KORE_RESULT_OK);
+	python_push_integer(pykore, "RESULT_RETRY", KORE_RESULT_RETRY);
+	python_push_integer(pykore, "RESULT_ERROR", KORE_RESULT_ERROR);
+	python_push_integer(pykore, "MODULE_LOAD", KORE_MODULE_LOAD);
+	python_push_integer(pykore, "MODULE_UNLOAD", KORE_MODULE_UNLOAD);
+
+	python_push_integer(pykore, "LOG_ERR", LOG_ERR);
+	python_push_integer(pykore, "LOG_INFO", LOG_INFO);
+	python_push_integer(pykore, "LOG_NOTICE", LOG_NOTICE);
+
+#if !defined(KORE_NO_HTTP)
+	python_push_type("pyhttp_request", pykore, &pyhttp_request_type);
+
+	python_push_integer(pykore, "METHOD_GET", HTTP_METHOD_GET);
+	python_push_integer(pykore, "METHOD_PUT", HTTP_METHOD_PUT);
+	python_push_integer(pykore, "METHOD_HEAD", HTTP_METHOD_HEAD);
+	python_push_integer(pykore, "METHOD_POST", HTTP_METHOD_POST);
+	python_push_integer(pykore, "METHOD_DELETE", HTTP_METHOD_DELETE);
+	python_push_integer(pykore,
+	    "WEBSOCKET_BROADCAST_LOCAL", WEBSOCKET_BROADCAST_LOCAL);
+	python_push_integer(pykore,
+	    "WEBSOCKET_BROADCAST_GLOBAL", WEBSOCKET_BROADCAST_GLOBAL);
+#endif
+
+	return (pykore);
+}
+
+static void
+python_append_path(const char *path)
+{
+	PyObject	*mpath, *spath;
+
+	if ((mpath = PyUnicode_FromString(path)) == NULL)
+		fatal("python_append_path: PyUnicode_FromString failed");
+
+	if ((spath = PySys_GetObject("path")) == NULL)
+		fatal("python_append_path: PySys_GetObject failed");
+
+	PyList_Append(spath, mpath);
+	Py_DECREF(mpath);
+}
+
+static void
+python_push_type(const char *name, PyObject *module, PyTypeObject *type)
+{
+	if (PyType_Ready(type) == -1)
+		fatal("python_push_type: failed to ready %s", name);
+
+	Py_INCREF(type);
+
+	if (PyModule_AddObject(module, name, (PyObject *)type) == -1)
+		fatal("python_push_type: failed to push %s", name);
+}
+
+static void
+python_push_integer(PyObject *module, const char *name, long value)
+{
+	int		ret;
+
+	if ((ret = PyModule_AddIntConstant(module, name, value)) == -1)
+		fatal("python_push_integer: failed to add %s", name);
+}
+
+static PyObject *
+python_exported_log(PyObject *self, PyObject *args)
+{
+	int		prio;
+	const char	*message;
+
+	if (!PyArg_ParseTuple(args, "is", &prio, &message)) {
+		PyErr_SetString(PyExc_TypeError, "invalid parameters");
+		return (NULL);
+	}
+
+	kore_log(prio, "%s", message);
+
+	Py_RETURN_TRUE;
+}
+
+static PyObject *
+python_import(const char *path)
+{
+	PyObject	*module;
+	char		*dir, *file, *copy, *p;
+
+	copy = kore_strdup(path);
+
+	if ((file = basename(copy)) == NULL)
+		fatal("basename: %s: %s", path, errno_s);
+	if ((dir = dirname(copy)) == NULL)
+		fatal("dirname: %s: %s", path, errno_s);
+
+	if ((p = strrchr(file, '.')) != NULL)
+		*p = '\0';
+
+	python_append_path(dir);
+	module = PyImport_ImportModule(file);
+	if (module == NULL)
+		PyErr_Print();
+
+	kore_free(copy);
+
+	return (module);
+}
+
+static PyObject *
+python_callable(PyObject *module, const char *symbol)
+{
+	PyObject	*obj;
+
+	if ((obj = PyObject_GetAttrString(module, symbol)) == NULL)
+		return (NULL);
+
+	if (!PyCallable_Check(obj)) {
+		Py_DECREF(obj);
+		return (NULL);
+	}
+
+	return (obj);
+}
+
+static PyObject *
+pyconnection_alloc(struct connection *c)
+{
+	struct pyconnection		*pyc;
+
+	pyc = PyObject_New(struct pyconnection, &pyconnection_type);
+	if (pyc == NULL)
+		return (NULL);
+
+	printf("  pyc: %p\n", (void *)pyc);
+	pyc->c = c;
+
+	return ((PyObject *)pyc);
+}
+
+#if !defined(KORE_NO_HTTP)
+static PyObject *
+pyhttp_request_alloc(struct http_request *req)
+{
+	struct pyhttp_request		*pyreq;
+
+	pyreq = PyObject_New(struct pyhttp_request, &pyhttp_request_type);
+	if (pyreq == NULL)
+		return (NULL);
+
+	pyreq->req = req;
+
+	return ((PyObject *)pyreq);
+}
+
+static PyObject *
+pyhttp_response(struct pyhttp_request *pyreq, PyObject *args)
+{
+	Py_buffer		body;
+	int			status;
+
+	if (!PyArg_ParseTuple(args, "iy*", &status, &body)) {
+		PyErr_SetString(PyExc_TypeError, "invalid parameters");
+		return (NULL);
+	}
+
+	http_response(pyreq->req, status, body.buf, body.len);
+	PyBuffer_Release(&body);
+
+	Py_RETURN_TRUE;
+}
+
+static PyObject *
+pyhttp_response_header(struct pyhttp_request *pyreq, PyObject *args)
+{
+	const char		*header, *value;
+
+	if (!PyArg_ParseTuple(args, "ss", &header, &value)) {
+		PyErr_SetString(PyExc_TypeError, "invalid parameters");
+		return (NULL);
+	}
+
+	http_response_header(pyreq->req, header, value);
+
+	Py_RETURN_TRUE;
+}
+
+static PyObject *
+pyhttp_request_header(struct pyhttp_request *pyreq, PyObject *args)
+{
+	char			*value;
+	const char		*header;
+	PyObject		*result;
+
+	if (!PyArg_ParseTuple(args, "s", &header)) {
+		PyErr_SetString(PyExc_TypeError, "invalid parameters");
+		return (NULL);
+	}
+
+	if (!http_request_header(pyreq->req, header, &value)) {
+		Py_RETURN_NONE;
+	}
+
+	if ((result = PyUnicode_FromString(value)) == NULL)
+		return (PyErr_NoMemory());
+
+	return (result);
+}
+
+static PyObject *
+pyhttp_body_read(struct pyhttp_request *pyreq, PyObject *args)
+{
+	ssize_t			ret;
+	size_t			len;
+	Py_ssize_t		pylen;
+	PyObject		*result;
+	u_int8_t		buf[1024];
+
+	if (!PyArg_ParseTuple(args, "n", &pylen) || pylen < 0) {
+		PyErr_SetString(PyExc_TypeError, "invalid parameters");
+		return (NULL);
+	}
+
+	len = (size_t)pylen;
+	if (len > sizeof(buf)) {
+		PyErr_SetString(PyExc_RuntimeError, "len > sizeof(buf)");
+		return (NULL);
+	}
+
+	ret = http_body_read(pyreq->req, buf, len);
+	if (ret == -1) {
+		PyErr_SetString(PyExc_RuntimeError, "http_body_read() failed");
+		return (NULL);
+	}
+
+	if (ret > INT_MAX) {
+		PyErr_SetString(PyExc_RuntimeError, "ret > INT_MAX");
+		return (NULL);
+	}
+
+	result = Py_BuildValue("ny#", ret, buf, (int)ret);
+	if (result == NULL)
+		return (PyErr_NoMemory());
+
+	return (result);
+}
+
+static PyObject *
+pyhttp_populate_get(struct pyhttp_request *pyreq, PyObject *args)
+{
+	http_populate_get(pyreq->req);
+	Py_RETURN_TRUE;
+}
+
+static PyObject *
+pyhttp_populate_post(struct pyhttp_request *pyreq, PyObject *args)
+{
+	http_populate_post(pyreq->req);
+	Py_RETURN_TRUE;
+}
+
+static PyObject *
+pyhttp_get_host(struct pyhttp_request *pyreq, void *closure)
+{
+	PyObject	*host;
+
+	if ((host = PyUnicode_FromString(pyreq->req->host)) == NULL)
+		return (PyErr_NoMemory());
+
+	return (host);
+}
+
+static PyObject *
+pyhttp_get_path(struct pyhttp_request *pyreq, void *closure)
+{
+	PyObject	*path;
+
+	if ((path = PyUnicode_FromString(pyreq->req->path)) == NULL)
+		return (PyErr_NoMemory());
+
+	return (path);
+}
+
+static PyObject *
+pyhttp_get_body(struct pyhttp_request *pyreq, void *closure)
+{
+	ssize_t			ret;
+	struct kore_buf		buf;
+	PyObject		*body;
+	u_int8_t		data[BUFSIZ];
+
+	kore_buf_init(&buf, 1024);
+
+	for (;;) {
+		ret = http_body_read(pyreq->req, data, sizeof(data));
+		if (ret == -1) {
+			kore_buf_cleanup(&buf);
+			PyErr_SetString(PyExc_RuntimeError,
+			    "http_body_read() failed");
+			return (NULL);
+		}
+
+		if (ret == 0)
+			break;
+
+		kore_buf_append(&buf, data, (size_t)ret);
+	}
+
+	body = PyBytes_FromStringAndSize((char *)buf.data, buf.offset);
+	kore_buf_free(&buf);
+
+	if (body == NULL)
+		return (PyErr_NoMemory());
+
+	return (body);
+}
+
+static PyObject *
+pyhttp_get_agent(struct pyhttp_request *pyreq, void *closure)
+{
+	PyObject	*agent;
+
+	if (pyreq->req->agent == NULL) {
+		Py_RETURN_NONE;
+	}
+
+	if ((agent = PyUnicode_FromString(pyreq->req->path)) == NULL)
+		return (PyErr_NoMemory());
+
+	return (agent);
+}
+
+static int
+pyhttp_set_state(struct pyhttp_request *pyreq, PyObject *value, void *closure)
+{
+	if (value == NULL) {
+		PyErr_SetString(PyExc_TypeError,
+		    "pyhttp_set_state: value is NULL");
+		return (-1);
+	}
+
+	Py_XDECREF(pyreq->req->py_object);
+	pyreq->req->py_object = value;
+	Py_INCREF(pyreq->req->py_object);
+
+	return (0);
+}
+
+static PyObject *
+pyhttp_get_state(struct pyhttp_request *pyreq, void *closure)
+{
+	if (pyreq->req->py_object == NULL)
+		Py_RETURN_NONE;
+
+	Py_INCREF(pyreq->req->py_object);
+
+	return (pyreq->req->py_object);
+}
+
+static PyObject *
+pyhttp_get_method(struct pyhttp_request *pyreq, void *closure)
+{
+	PyObject	*method;
+
+	if ((method = PyLong_FromUnsignedLong(pyreq->req->method)) == NULL)
+		return (PyErr_NoMemory());
+
+	return (method);
+}
+
+static PyObject *
+pyhttp_get_connection(struct pyhttp_request *pyreq, void *closure)
+{
+	PyObject	*pyc;
+
+	if ((pyc = pyconnection_alloc(pyreq->req->owner)) == NULL)
+		return (PyErr_NoMemory());
+
+	return (pyc);
+}
+#endif
diff --git a/src/runtime.c b/src/runtime.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2017 Joris Vink <joris@coders.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+
+#include "kore.h"
+
+#if !defined(KORE_NO_HTTP)
+#include "http.h"
+#endif
+
+#if defined(KORE_USE_PYTHON)
+#include "python_api.h"
+#endif
+
+static int	native_runtime_onload(void *, int);
+static void	native_runtime_connect(void *, struct connection *);
+#if !defined(KORE_NO_HTTP)
+static int	native_runtime_http_request(void *, struct http_request *);
+static int	native_runtime_validator(void *, struct http_request *, void *);
+#endif
+
+struct kore_runtime kore_native_runtime = {
+	KORE_RUNTIME_NATIVE,
+#if !defined(KORE_NO_HTTP)
+	.http_request = native_runtime_http_request,
+	.validator = native_runtime_validator,
+#endif
+	.onload = native_runtime_onload,
+	.connect = native_runtime_connect
+};
+
+struct kore_runtime_call *
+kore_runtime_getcall(const char *symbol)
+{
+	void				*ptr;
+	struct kore_runtime_call	*rcall;
+	struct kore_runtime		*runtime;
+
+	ptr = kore_module_getsym(symbol, &runtime);
+	if (ptr == NULL)
+		return (NULL);
+
+	rcall = kore_malloc(sizeof(*rcall));
+	rcall->addr = ptr;
+	rcall->runtime = runtime;
+
+	return (rcall);
+}
+
+int
+kore_runtime_onload(struct kore_runtime_call *rcall, int action)
+{
+	return (rcall->runtime->onload(rcall->addr, action));
+}
+
+void
+kore_runtime_connect(struct kore_runtime_call *rcall, struct connection *c)
+{
+	rcall->runtime->connect(rcall->addr, c);
+}
+
+#if !defined(KORE_NO_HTTP)
+int
+kore_runtime_http_request(struct kore_runtime_call *rcall,
+    struct http_request *req)
+{
+	return (rcall->runtime->http_request(rcall->addr, req));
+}
+
+int
+kore_runtime_validator(struct kore_runtime_call *rcall,
+    struct http_request *req, void *data)
+{
+	return (rcall->runtime->validator(rcall->addr, req, data));
+}
+#endif
+
+static void
+native_runtime_connect(void *addr, struct connection *c)
+{
+	void	(*cb)(struct connection *);
+
+	*(void **)&(cb) = addr;
+	cb(c);
+}
+
+static int
+native_runtime_onload(void *addr, int action)
+{
+	int		(*cb)(int);
+
+	*(void **)&(cb) = addr;
+	return (cb(action));
+}
+
+#if !defined(KORE_NO_HTTP)
+static int
+native_runtime_http_request(void *addr, struct http_request *req)
+{
+	int		(*cb)(struct http_request *);
+
+	*(void **)&(cb) = addr;
+	return (cb(req));
+}
+
+static int
+native_runtime_validator(void *addr, struct http_request *req, void *data)
+{
+	int		(*cb)(struct http_request *, void *);
+
+	*(void **)&(cb) = addr;
+	return (cb(req, data));
+}
+#endif
diff --git a/src/validator.c b/src/validator.c
@@ -42,8 +42,8 @@ kore_validator_add(const char *name, u_int8_t type, const char *arg)
 		}
 		break;
 	case KORE_VALIDATOR_TYPE_FUNCTION:
-		*(void **)(&val->func) = kore_module_getsym(arg);
-		if (val->func == NULL) {
+		val->rcall = kore_runtime_getcall(arg);
+		if (val->rcall == NULL) {
 			kore_free(val);
 			kore_log(LOG_NOTICE,
 			    "validator %s has undefined callback %s",
@@ -92,7 +92,7 @@ kore_validator_check(struct http_request *req, struct kore_validator *val,
 			r = KORE_RESULT_ERROR;
 		break;
 	case KORE_VALIDATOR_TYPE_FUNCTION:
-		r = val->func(req, data);
+		r = kore_runtime_validator(val->rcall, req, data);
 		break;
 	default:
 		r = KORE_RESULT_ERROR;
@@ -113,9 +113,10 @@ kore_validator_reload(void)
 		if (val->type != KORE_VALIDATOR_TYPE_FUNCTION)
 			continue;
 
-		*(void **)&(val->func) = kore_module_getsym(val->arg);
-		if (val->func == NULL)
-			fatal("no function for validator %s found", val->name);
+		kore_free(val->rcall);
+		val->rcall = kore_runtime_getcall(val->arg);
+		if (val->rcall == NULL)
+			fatal("no function for validator %s found", val->arg);
 	}
 }
 
diff --git a/src/worker.c b/src/worker.c
@@ -40,6 +40,10 @@
 #include "tasks.h"
 #endif
 
+#if defined(KORE_USE_PYTHON)
+#include "python_api.h"
+#endif
+
 #if defined(WORKER_DEBUG)
 #define worker_debug(fmt, ...)		printf(fmt, ##__VA_ARGS__)
 #else
@@ -262,11 +266,11 @@ kore_worker_privdrop(void)
 void
 kore_worker_entry(struct kore_worker *kw)
 {
-	char			buf[16];
-	int			quit, had_lock, r;
-	u_int64_t		now, next_lock, netwait;
+	char				buf[16];
+	int				quit, had_lock, r;
+	u_int64_t			now, next_lock, netwait;
 #if defined(KORE_SINGLE_BINARY)
-	void			(*onload)(void);
+	struct kore_runtime_call	*rcall;
 #endif
 
 	worker = kw;
@@ -332,9 +336,11 @@ kore_worker_entry(struct kore_worker *kw)
 	kore_log(LOG_NOTICE, "worker %d started (cpu#%d)", kw->id, kw->cpu);
 
 #if defined(KORE_SINGLE_BINARY)
-	*(void **)&(onload) = kore_module_getsym("kore_onload");
-	if (onload != NULL)
-		onload();
+	rcall = kore_runtime_getcall("kore_onload");
+	if (rcall != NULL) {
+		rcall->runtime->execute(rcall->addr, NULL);
+		kore_free(rcall);
+	}
 #else
 	kore_module_onload();
 #endif
@@ -406,6 +412,10 @@ kore_worker_entry(struct kore_worker *kw)
 #endif
 	net_cleanup();
 
+#if defined(KORE_USE_PYTHON)
+	kore_python_cleanup();
+#endif
+
 	kore_debug("worker %d shutting down", kw->id);
 	exit(0);
 }