From f74a19a71a67b3e23a0a7eeda00e11c5f4af5622 Mon Sep 17 00:00:00 2001 From: "roberto@mrspurr" Date: Mon, 21 Jun 2010 17:42:03 +0200 Subject: [PATCH] preliminary routing support (PCRE needed) --- protocol.c | 10 ++++++ pyutils.c | 20 ++++++++++++ routing.c | 55 ++++++++++++++++++++++++++++++++ utils.c | 4 +++ uwsgi.c | 21 +++++++++---- uwsgi.h | 52 +++++++++++++++++++++++++++++++ uwsgi.xml | 10 ++++-- uwsgiconfig.py | 10 ++++++ wsgi_handlers.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-- xmlconf.c | 70 ++++++++++++++++++++++++++++++++++++++--- 10 files changed, 320 insertions(+), 15 deletions(-) create mode 100644 routing.c diff --git a/protocol.c b/protocol.c index df2a59fc..9c0a1f92 100644 --- a/protocol.c +++ b/protocol.c @@ -351,6 +351,16 @@ int uwsgi_parse_vars(struct uwsgi_server *uwsgi, struct wsgi_request *wsgi_req) if (!strncmp("SCRIPT_NAME", wsgi_req->hvec[wsgi_req->var_cnt].iov_base, wsgi_req->hvec[wsgi_req->var_cnt].iov_len)) { wsgi_req->script_name = ptrbuf; wsgi_req->script_name_len = strsize; +#ifdef UWSGI_DEBUG + uwsgi_debug("SCRIPT_NAME=%.*s\n", wsgi_req->script_name_len, wsgi_req->script_name); +#endif + } + else if (!strncmp("PATH_INFO", wsgi_req->hvec[wsgi_req->var_cnt].iov_base, wsgi_req->hvec[wsgi_req->var_cnt].iov_len)) { + wsgi_req->path_info = ptrbuf; + wsgi_req->path_info_len = strsize; +#ifdef UWSGI_DEBUG + uwsgi_debug("PATH_INFO=%.*s\n", wsgi_req->path_info_len, wsgi_req->path_info); +#endif } else if (!strncmp("SERVER_PROTOCOL", wsgi_req->hvec[wsgi_req->var_cnt].iov_base, wsgi_req->hvec[wsgi_req->var_cnt].iov_len)) { wsgi_req->protocol = ptrbuf; diff --git a/pyutils.c b/pyutils.c index 4c1da016..97000293 100644 --- a/pyutils.c +++ b/pyutils.c @@ -52,11 +52,21 @@ int manage_python_response(struct uwsgi_server *uwsgi, struct wsgi_request *wsgi uwsgi_log("invalid WSGI2.0 response.\n"); goto clear; } +#ifdef UWSGI_DEBUG + uwsgi_debug("wsgi_req->async_result = %d\n", ((PyObject *)wsgi_req->async_result)->ob_refcnt); +#endif if (py_uwsgi_spit(NULL, (PyObject *)wsgi_req->async_result) == Py_None) { goto clear; } +#ifdef UWSGI_DEBUG + uwsgi_debug("wsgi_req->async_result = %d\n", ((PyObject *)wsgi_req->async_result)->ob_refcnt); +#endif wsgi_req->async_orig_result = wsgi_req->async_result ; wsgi_req->async_result = PyTuple_GetItem((PyObject *)wsgi_req->async_result, 2); +#ifdef UWSGI_DEBUG + uwsgi_debug("wsgi_req->async_orig_result = %d\n", ((PyObject *)wsgi_req->async_orig_result)->ob_refcnt); + uwsgi_debug("wsgi_req->async_result = %d\n", ((PyObject *)wsgi_req->async_result)->ob_refcnt); +#endif return UWSGI_AGAIN; } #endif @@ -133,9 +143,15 @@ clear2: if (wsgi_req->async_placeholder) { uwsgi_debug("wsgi_req->async_placeholder: %d\n", ((PyObject *)wsgi_req->async_placeholder)->ob_refcnt); } + if (wsgi_req->async_orig_result) { + uwsgi_debug("wsgi_req->async_orig_result: %d\n", ((PyObject *)wsgi_req->async_orig_result)->ob_refcnt); + } if (wsgi_req->async_result) { uwsgi_debug("wsgi_req->async_result: %d\n", ((PyObject *)wsgi_req->async_result)->ob_refcnt); } + if (wsgi_req->async_app) { + uwsgi_debug("wsgi_req->async_app: %d\n", ((PyObject *)wsgi_req->async_app)->ob_refcnt); + } #endif return UWSGI_OK; } @@ -146,10 +162,14 @@ PyObject *python_call(PyObject *callable, PyObject *args) { PyObject *pyret; pyret = PyEval_CallObject(callable, args); +#ifdef UWSGI_DEBUG + uwsgi_debug("called %p %p %d\n", callable, args, pyret->ob_refcnt); +#endif if (PyErr_Occurred()) { PyErr_Print(); } + Py_INCREF(pyret); return pyret; } diff --git a/routing.c b/routing.c new file mode 100644 index 00000000..95531f57 --- /dev/null +++ b/routing.c @@ -0,0 +1,55 @@ +#include "uwsgi.h" + +void routing_setup(struct uwsgi_server *uwsgi) { + + int i; + struct uwsgi_route *ur; + int max_ovec = 0 ; + + for(i=0;iroutes;i++) { + uwsgi_log("%d = %p\n", i, uwsgi->shared->routes[i].pattern); + ur = &uwsgi->shared->routes[i]; + if (ur->args > max_ovec) { + max_ovec = ur->args; + } + } + + uwsgi->async_ovector = malloc( sizeof(int *) * uwsgi->async); + if (!uwsgi->async_ovector) { + uwsgi_error("malloc()"); + exit(1); + } + + uwsgi_log("max_ovec = %d\n", max_ovec); + for(i=0;iasync;i++) { + uwsgi->async_ovector[i] = malloc(sizeof(int) * ((max_ovec+1)*3)); + if (!uwsgi->async_ovector[i]) { + uwsgi_error("malloc()"); + exit(1); + } + + } + +} + +int check_route(struct uwsgi_server *uwsgi, struct wsgi_request *wsgi_req) { + + int ret,i ; + struct uwsgi_route *ur; + + for(i=0;iroutes;i++) { + + uwsgi_log("checking route %d\n", i); + ur = &uwsgi->shared->routes[i]; + ret = pcre_exec(ur->pattern, ur->pattern_extra, wsgi_req->path_info, wsgi_req->path_info_len, 0, 0, wsgi_req->ovector, (ur->args+1)*3 ); + + if (ret >= 0) { + uwsgi_log("found route %d for PATH_INFO=%.*s\n", i, wsgi_req->path_info_len, wsgi_req->path_info); + return i; + } + + /* TODO check for errors if < 0 && != NO_MATCH */ + } + + return -1; +} diff --git a/utils.c b/utils.c index cbe7b7f9..d2c108e2 100644 --- a/utils.c +++ b/utils.c @@ -328,6 +328,10 @@ void wsgi_req_setup(struct wsgi_request *wsgi_req, int async_id) { wsgi_req->hvec = &uwsgi.async_hvec[wsgi_req->async_id]; wsgi_req->buffer = uwsgi.async_buf[wsgi_req->async_id]; +#ifdef UWSGI_ROUTING + wsgi_req->ovector = uwsgi.async_ovector[wsgi_req->async_id]; +#endif + if (uwsgi.post_buffering > 0) { wsgi_req->post_buffering_buf = uwsgi.async_post_buf[wsgi_req->async_id]; } diff --git a/uwsgi.c b/uwsgi.c index 6c3eddff..a66cdee0 100644 --- a/uwsgi.c +++ b/uwsgi.c @@ -385,6 +385,9 @@ int main(int argc, char *argv[], char *envp[]) { {"grunt", no_argument, &uwsgi.grunt, 1}, {"no-site", no_argument, &Py_NoSiteFlag, 1}, {"vhost", no_argument, &uwsgi.vhost, 1}, +#ifdef UWSGI_ROUTING + {"routing", no_argument, &uwsgi.routing, 1}, +#endif {"version", no_argument, 0, LONG_ARGS_VERSION}, {0, 0, 0, 0} }; @@ -942,18 +945,20 @@ int main(int argc, char *argv[], char *envp[]) { else if (uwsgi.wsgi_file != NULL) { uwsgi_wsgi_file_config(); } -#ifdef UWSGI_XML - else if (uwsgi.xml_config != NULL) { - uwsgi_xml_config(uwsgi.wsgi_req, NULL); - } -#endif - #ifdef UWSGI_PASTE else if (uwsgi.paste != NULL) { uwsgi_paste_config(); } #endif +// parse xml anyway +#ifdef UWSGI_XML + if (uwsgi.xml_config != NULL) { + uwsgi_xml_config(uwsgi.wsgi_req, NULL); + } +#endif + + if (uwsgi.test_module != NULL) { if (PyImport_ImportModule(uwsgi.test_module)) { exit(0); @@ -1016,6 +1021,9 @@ int main(int argc, char *argv[], char *envp[]) { } #endif +#ifdef UWSGI_ROUTING + routing_setup(&uwsgi); +#endif if (!uwsgi.master_process) { if (uwsgi.numproc == 1) { @@ -1846,6 +1854,7 @@ int init_uwsgi_app(PyObject * force_wsgi_dict, PyObject * my_callable) { } + wi->wsgi_dict = wsgi_dict ; if (!wi->wsgi_callable) { PyErr_Print(); diff --git a/uwsgi.h b/uwsgi.h index f55474b5..bc6d88d5 100644 --- a/uwsgi.h +++ b/uwsgi.h @@ -37,6 +37,10 @@ #define UWSGI_PLUGIN_BASE "" #endif +#ifdef UWSGI_ROUTING +#include +#endif + #include #include #include @@ -248,6 +252,7 @@ struct uwsgi_app { PyThreadState *interpreter; PyObject *pymain_dict; + PyObject *wsgi_dict; PyObject *wsgi_callable; @@ -272,6 +277,28 @@ struct uwsgi_app { }; +#ifdef UWSGI_ROUTING +struct uwsgi_route { + + const char *mountpoint; + const char *callbase; + + pcre *pattern; + pcre_extra *pattern_extra; + pcre *method; + pcre_extra *method_extra; + + const char *call; + + int modifier1; + int modifier2; + + void *callable; + void *callable_args; + int args; +}; +#endif + struct __attribute__ ((packed)) uwsgi_header { uint8_t modifier1; uint16_t pktsize; @@ -319,6 +346,9 @@ struct wsgi_request { char *host; uint16_t host_len; + char *path_info; + uint16_t path_info_len; + char *wsgi_script; uint16_t wsgi_script_len; char *wsgi_module; @@ -369,6 +399,7 @@ struct wsgi_request { int async_plagued; + int *ovector; size_t post_cl; char *post_buffering_buf; uint64_t post_buffering_read; @@ -413,6 +444,10 @@ struct uwsgi_server { char **async_buf; char **async_post_buf; +#ifdef UWSGI_ROUTING + int **async_ovector; +#endif + struct rlimit rl; size_t limit_post; int prio; @@ -525,6 +560,11 @@ struct uwsgi_server { char *pyargv; #endif +#ifdef UWSGI_ROUTING + int routing; + int routes; +#endif + char *wsgi_config; char *paste; char *wsgi_file; @@ -634,6 +674,13 @@ struct uwsgi_shared { #endif +#ifdef UWSGI_ROUTING +#ifndef MAX_UWSGI_ROUTES +#define MAX_UWSGI_ROUTES 64 +#endif + struct uwsgi_route routes[MAX_UWSGI_ROUTES]; +#endif + }; struct uwsgi_worker { @@ -954,3 +1001,8 @@ void embed_plugins(struct uwsgi_server *); #endif + +#ifdef UWSGI_ROUTING +void routing_setup(struct uwsgi_server *); +int check_route(struct uwsgi_server *, struct wsgi_request *); +#endif diff --git a/uwsgi.xml b/uwsgi.xml index ea870e52..ffd10f1d 100644 --- a/uwsgi.xml +++ b/uwsgi.xml @@ -1,5 +1,9 @@ - - - + simple_app + + + homepage + view1 + index + diff --git a/uwsgiconfig.py b/uwsgiconfig.py index 28368477..8199bede 100644 --- a/uwsgiconfig.py +++ b/uwsgiconfig.py @@ -19,6 +19,7 @@ ASYNC=True UGREEN=True EVDIS=True WSGI2=True +ROUTING=True STACKLESS=False #PLUGINS = ['psgi'] PLUGINS = [] @@ -207,11 +208,20 @@ def parse_vars(): cflags.append("-DUWSGI_NAGIOS") gcc_list.append('nagios') + if DEBUG: + cflags.append("-DUWSGI_DEBUG") + if PROXY: depends_on("PROXY", ['ASYNC']) cflags.append("-DUWSGI_PROXY") gcc_list.append('proxy') + if ROUTING: + depends_on("ROUTING", ['WSGI2', 'XML']) + cflags.append("-DUWSGI_ROUTING") + gcc_list.append('routing') + libs.append('-lpcre') + if EVDIS: cflags.append("-DUWSGI_EVDIS") gcc_list.append('evdis') diff --git a/wsgi_handlers.c b/wsgi_handlers.c index 9932823c..40600c46 100644 --- a/wsgi_handlers.c +++ b/wsgi_handlers.c @@ -198,7 +198,11 @@ int uwsgi_request_wsgi(struct uwsgi_server *uwsgi, struct wsgi_request *wsgi_req for (i = 0; i < wsgi_req->var_cnt; i += 2) { - //uwsgi_log("%.*s: %.*s\n", wsgi_req->hvec[i].iov_len, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i+1].iov_len, wsgi_req->hvec[i+1].iov_base); +/* +#ifdef UWSGI_DEBUG + uwsgi_debug("%.*s: %.*s\n", wsgi_req->hvec[i].iov_len, wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i+1].iov_len, wsgi_req->hvec[i+1].iov_base); +#endif +*/ pydictkey = PyString_FromStringAndSize(wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len); pydictvalue = PyString_FromStringAndSize(wsgi_req->hvec[i + 1].iov_base, wsgi_req->hvec[i + 1].iov_len); PyDict_SetItem(wsgi_req->async_environ, pydictkey, pydictvalue); @@ -316,7 +320,79 @@ int uwsgi_request_wsgi(struct uwsgi_server *uwsgi, struct wsgi_request *wsgi_req PyDict_SetItemString(wsgi_req->async_environ, "wsgi.url_scheme", zero); Py_DECREF(zero); + + wsgi_req->async_app = wi->wsgi_callable ; + + +#ifdef UWSGI_ROUTING + uwsgi_log("routing %d routes %d\n", uwsgi->routing, uwsgi->routes); + if (uwsgi->routing && uwsgi->routes > 0) { + int route_id; + struct uwsgi_route *ur; + PyObject *route_py_callbase; + PyObject *route_py_dict = wi->wsgi_dict; + + route_id = check_route(uwsgi, wsgi_req); + + if (route_id >= 0) { + ur = &uwsgi->shared->routes[route_id]; + + /* + we have: + ovector -> allocated per wsgi_req + ovector_size -> set per uwsgi_route + wsgi_req->async_args -> point to a tuple of env+(CAPTURECOUNT-1) + wsgi_req->async_callable -> cached callbase.call + + all this part must be done on initialization to avoid races !!! + */ + + uwsgi_log("setting route %d\n", route_id); + if (ur->callable == NULL) { + if (ur->callbase) { + route_py_callbase = PyImport_ImportModule(ur->callbase); + if (route_py_callbase == NULL) { + PyErr_Print(); + } + else { + uwsgi_log("callbase dict ok for %s\n", ur->call); + route_py_dict = PyModule_GetDict(route_py_callbase); + } + } + + ur->callable = PyDict_GetItemString(route_py_dict, ur->call); + if (ur->callable == NULL) { + uwsgi_log("route_py_dict: %p call: %s\n", route_py_dict, ur->call); + PyErr_Print(); + } + + ur->callable_args = PyTuple_New(ur->args+1); + } + + if (ur->callable) { + uwsgi_log("route callable dict ok: %d\n", ur->args); + wsgi_req->async_app = ur->callable; + wsgi_req->async_args = ur->callable_args; + + for (i=1;i<=ur->args;i++) { + uwsgi_log("%d\n", i); + uwsgi_log("%d / %d\n", wsgi_req->ovector[i*2], wsgi_req->ovector[(i*2)+1]); + uwsgi_log("%d = %.*s\n", i,wsgi_req->ovector[(i*2)+1] - wsgi_req->ovector[i*2], + wsgi_req->path_info + wsgi_req->ovector[i*2]); + PyTuple_SetItem(wsgi_req->async_args, i, PyString_FromStringAndSize( + wsgi_req->path_info + wsgi_req->ovector[i*2], + wsgi_req->ovector[(i*2)+1] - wsgi_req->ovector[i*2] + )); + } + uwsgi_log("route callable built\n"); + } + } + } +#endif + + // call + #ifdef UWSGI_PROFILER if (uwsgi->enable_profiler == 1) { PyDict_SetItem(wi->pymain_dict, PyString_FromFormat("uwsgi_environ__%d", wsgi_req->app_id), wsgi_req->async_environ); @@ -332,7 +408,9 @@ int uwsgi_request_wsgi(struct uwsgi_server *uwsgi, struct wsgi_request *wsgi_req PyTuple_SetItem(wsgi_req->async_args, 0, wsgi_req->async_environ); - wsgi_req->async_result = python_call(wi->wsgi_callable, wsgi_req->async_args); + uwsgi_log("ready to call %p %p\n", wsgi_req->async_app, wsgi_req->async_environ); + wsgi_req->async_result = python_call(wsgi_req->async_app, wsgi_req->async_args); + uwsgi_log("called.\n"); #ifdef UWSGI_PROFILER } @@ -342,6 +420,7 @@ int uwsgi_request_wsgi(struct uwsgi_server *uwsgi, struct wsgi_request *wsgi_req if (wsgi_req->async_result) { + uwsgi_log("managing response\n"); while ( manage_python_response(uwsgi, wsgi_req) != UWSGI_OK) { #ifdef UWSGI_ASYNC if (uwsgi->async > 1) { diff --git a/xmlconf.c b/xmlconf.c index 8be4015b..1547856e 100644 --- a/xmlconf.c +++ b/xmlconf.c @@ -43,7 +43,7 @@ void uwsgi_xml_config(struct wsgi_request *wsgi_req, struct option *long_options if (long_options) { - // first check for pythonpath + // first check for options for (node = element->children; node; node = node->next) { if (node->type == XML_ELEMENT_NODE) { lopt = long_options; @@ -82,7 +82,7 @@ void uwsgi_xml_config(struct wsgi_request *wsgi_req, struct option *long_options } else { - // ... then for wsgi apps + // ... then for apps and routing for (node = element->children; node; node = node->next) { if (node->type == XML_ELEMENT_NODE) { @@ -114,13 +114,75 @@ void uwsgi_xml_config(struct wsgi_request *wsgi_req, struct option *long_options } } } +#ifdef UWSGI_ROUTING + else if (!strcmp((char *) node->name, "routing")) { + char *default_route_mountpoint = NULL; + char *default_route_callbase = NULL ; + xmlChar *tmp_val; + int default_route_modifier1 = 0; + int default_route_modifier2 = 0; + const char *errstr; + int erroff; + + default_route_mountpoint = xmlGetProp(node, (const xmlChar *) "mountpoint"); + default_route_callbase = xmlGetProp(node, (const xmlChar *) "base"); + + tmp_val = xmlGetProp(node, (const xmlChar *) "modifier1"); + if (tmp_val) { + default_route_modifier1 = atoi(tmp_val); + } + + tmp_val = xmlGetProp(node, (const xmlChar *) "modifier2"); + if (tmp_val) { + default_route_modifier2 = atoi(tmp_val); + } + + + for (node2 = node->children; node2; node2 = node2->next) { + if (node2->type == XML_ELEMENT_NODE) { + if (!strcmp((char *) node2->name, "route") && uwsgi.routes < MAX_UWSGI_ROUTES) { + if (!node2->children) { + uwsgi_log( "no route callable defined. skip.\n"); + continue; + } + uwsgi.shared->routes[uwsgi.routes].mountpoint = default_route_mountpoint; + uwsgi.shared->routes[uwsgi.routes].callbase = default_route_callbase; + uwsgi.shared->routes[uwsgi.routes].modifier1 = default_route_modifier1; + uwsgi.shared->routes[uwsgi.routes].modifier2 = default_route_modifier2; + uwsgi.shared->routes[uwsgi.routes].call = node2->children->content; + if (uwsgi.shared->routes[uwsgi.routes].call == NULL) { + uwsgi_log( "no route callable defined. skip.\n"); + continue; + } + + tmp_val = xmlGetProp(node2, (const xmlChar *) "pattern"); + if (!tmp_val) { + uwsgi_log( "no route pattern defined. skip.\n"); + continue; + } + + uwsgi.shared->routes[uwsgi.routes].pattern = pcre_compile(tmp_val, 0, &errstr, &erroff, NULL); + uwsgi.shared->routes[uwsgi.routes].pattern_extra = pcre_study(uwsgi.shared->routes[uwsgi.routes].pattern, 0, &errstr); + + + pcre_fullinfo(uwsgi.shared->routes[uwsgi.routes].pattern, uwsgi.shared->routes[uwsgi.routes].pattern_extra, PCRE_INFO_CAPTURECOUNT, &uwsgi.shared->routes[uwsgi.routes].args); + + uwsgi_log("route call: %s %d\n", uwsgi.shared->routes[uwsgi.routes].call, uwsgi.shared->routes[uwsgi.routes].args); + + uwsgi.routes++; + } + } + } + + } +#endif } } } - /* We cannot free xml resources on the first round as the string pointer must be valid for all the server lifecycle */ - if (!long_options) { + /* We cannot free xml resources on the first round (and with routing enabled) as the string pointer must be valid for all the server lifecycle */ + if (!long_options && !uwsgi.routing) { xmlFreeDoc (doc); xmlCleanupParser (); }