diff --git a/core/exceptions.c b/core/exceptions.c index 8217de15..af7752f0 100644 --- a/core/exceptions.c +++ b/core/exceptions.c @@ -1,3 +1,7 @@ +#include + +extern struct uwsgi_server uwsgi; + /* Exceptions management @@ -20,4 +24,130 @@ The exception-uwsgi packet is passed "as is" to the exception handler + Exceptions hooks: + + a request plugin can export that hooks: + + struct uwsgi_buffer *backtrace(struct wsgi_request *); + struct uwsgi_buffer *exception_class(struct wsgi_request *); + struct uwsgi_buffer *exception_msg(struct wsgi_request *); + struct uwsgi_buffer *exception_repr(struct wsgi_request *); + void exception_log(struct wsgi_request *); + + Remember to reset the exception status (if possibile) after each call + + Exceptions catcher: + + This is a special development-mode in which exceptions are printed + to the HTTP client. + */ + +static void append_vars_to_ubuf(char *key, uint16_t keylen, char *val, uint16_t vallen, void *data) { + struct uwsgi_buffer *ub = (struct uwsgi_buffer *) data; + + if (uwsgi_buffer_append(ub, key, keylen)) return; + if (uwsgi_buffer_append(ub, " = ", 3)) return; + if (uwsgi_buffer_append(ub, val, vallen)) return; + if (uwsgi_buffer_append(ub, "\n", 1)) return; +} + +int uwsgi_exceptions_catch(struct wsgi_request *wsgi_req) { + + if (uwsgi_response_prepare_headers(wsgi_req, "500 Internal Server Error", 25)) { + return -1; + } + + if (uwsgi_response_add_content_type(wsgi_req, "text/plain", 10)) { + return -1; + } + + struct uwsgi_buffer *ub = uwsgi_buffer_new(4096); + if (uwsgi_buffer_append(ub, "uWSGI exceptions catcher for \"", 30)) goto error; + if (uwsgi_buffer_append(ub, wsgi_req->method, wsgi_req->method_len)) goto error; + if (uwsgi_buffer_append(ub, " ", 1)) goto error; + if (uwsgi_buffer_append(ub, wsgi_req->uri, wsgi_req->uri_len)) goto error; + if (uwsgi_buffer_append(ub, "\"\n\n", 3)) goto error; + + if (uwsgi_buffer_append(ub, "Exception: ", 11)) goto error; + + if (uwsgi.p[wsgi_req->uh->modifier1]->exception_repr) { + struct uwsgi_buffer *ub_exc_repr = uwsgi.p[wsgi_req->uh->modifier1]->exception_repr(wsgi_req); + if (ub_exc_repr) { + if (uwsgi_buffer_append(ub, ub_exc_repr->buf, ub_exc_repr->pos)) { + uwsgi_buffer_destroy(ub_exc_repr); + goto error; + } + uwsgi_buffer_destroy(ub_exc_repr); + } + else { + goto notavail3; + } + } + else { +notavail3: + if (uwsgi_buffer_append(ub, "-Not available-", 15)) goto error; + } + + if (uwsgi_buffer_append(ub, "\n\n", 2)) goto error; + + if (uwsgi_buffer_append(ub, "Exception class: ", 17)) goto error; + + if (uwsgi.p[wsgi_req->uh->modifier1]->exception_class) { + struct uwsgi_buffer *ub_exc_class = uwsgi.p[wsgi_req->uh->modifier1]->exception_class(wsgi_req); + if (ub_exc_class) { + if (uwsgi_buffer_append(ub, ub_exc_class->buf, ub_exc_class->pos)) { + uwsgi_buffer_destroy(ub_exc_class); + goto error; + } + uwsgi_buffer_destroy(ub_exc_class); + } + else { + goto notavail; + } + } + else { +notavail: + if (uwsgi_buffer_append(ub, "-Not available-", 15)) goto error; + } + + if (uwsgi_buffer_append(ub, "\n\n", 2)) goto error; + + if (uwsgi_buffer_append(ub, "Exception message: ", 19)) goto error; + + if (uwsgi.p[wsgi_req->uh->modifier1]->exception_msg) { + struct uwsgi_buffer *ub_exc_msg = uwsgi.p[wsgi_req->uh->modifier1]->exception_msg(wsgi_req); + if (ub_exc_msg) { + if (uwsgi_buffer_append(ub, ub_exc_msg->buf, ub_exc_msg->pos)) { + uwsgi_buffer_destroy(ub_exc_msg); + goto error; + } + uwsgi_buffer_destroy(ub_exc_msg); + } + else { + goto notavail2; + } + } + else { +notavail2: + if (uwsgi_buffer_append(ub, "-Not available-", 15)) goto error; + } + + if (uwsgi_buffer_append(ub, "\n\n", 2)) goto error; + + if (uwsgi_hooked_parse(wsgi_req->buffer, wsgi_req->uh->pktsize, append_vars_to_ubuf, ub)) { + goto error; + } + + if (uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos)) { + goto error; + } + + uwsgi_buffer_destroy(ub); + return 0; + +error: + uwsgi_buffer_destroy(ub); + return -1; + +} diff --git a/core/uwsgi.c b/core/uwsgi.c index 931cfb12..274dd408 100644 --- a/core/uwsgi.c +++ b/core/uwsgi.c @@ -330,11 +330,13 @@ static struct uwsgi_option uwsgi_base_options[] = { {"no-default-app", no_argument, 0, "do not fallback to default app", uwsgi_opt_true, &uwsgi.no_default_app, 0}, {"manage-script-name", no_argument, 0, "automatically rewrite SCRIPT_NAME and PATH_INFO", uwsgi_opt_true, &uwsgi.manage_script_name, 0}, {"ignore-script-name", no_argument, 0, "ignore SCRIPT_NAME", uwsgi_opt_true, &uwsgi.ignore_script_name, 0}, - {"catch-exceptions", no_argument, 0, "report exception has http output (discouraged)", uwsgi_opt_true, &uwsgi.catch_exceptions, 0}, + + {"catch-exceptions", no_argument, 0, "report exception as http output (discouraged, use only for testing)", uwsgi_opt_true, &uwsgi.catch_exceptions, 0}, {"reload-on-exception", no_argument, 0, "reload a worker when an exception is raised", uwsgi_opt_true, &uwsgi.reload_on_exception, 0}, {"reload-on-exception-type", required_argument, 0, "reload a worker when a specific exception type is raised", uwsgi_opt_add_string_list, &uwsgi.reload_on_exception_type, 0}, {"reload-on-exception-value", required_argument, 0, "reload a worker when a specific exception value is raised", uwsgi_opt_add_string_list, &uwsgi.reload_on_exception_value, 0}, {"reload-on-exception-repr", required_argument, 0, "reload a worker when a specific exception type+value (language-specific) is raised", uwsgi_opt_add_string_list, &uwsgi.reload_on_exception_repr, 0}, + {"udp", required_argument, 0, "run the udp server on the specified address", uwsgi_opt_set_str, &uwsgi.udp_socket, UWSGI_OPT_MASTER}, {"stats", required_argument, 0, "enable the stats server on the specified address", uwsgi_opt_set_str, &uwsgi.stats, UWSGI_OPT_MASTER}, {"stats-server", required_argument, 0, "enable the stats server on the specified address", uwsgi_opt_set_str, &uwsgi.stats, UWSGI_OPT_MASTER}, diff --git a/plugins/python/python_plugin.c b/plugins/python/python_plugin.c index 24a9336f..1b85d3d7 100644 --- a/plugins/python/python_plugin.c +++ b/plugins/python/python_plugin.c @@ -1875,5 +1875,10 @@ struct uwsgi_plugin pypy_plugin = { .code_string = uwsgi_python_code_string, + .exception_class = uwsgi_python_exception_class, + .exception_msg = uwsgi_python_exception_msg, + .exception_repr = uwsgi_python_exception_repr, + .exception_log = uwsgi_python_exception_log, + }; diff --git a/plugins/python/pyutils.c b/plugins/python/pyutils.c index 05a1bbfe..42a7d036 100644 --- a/plugins/python/pyutils.c +++ b/plugins/python/pyutils.c @@ -91,6 +91,89 @@ int uwsgi_python_manage_exceptions(void) { return ret; } +struct uwsgi_buffer *uwsgi_python_exception_class(struct wsgi_request *wsgi_req) { + PyObject *type = NULL; + PyObject *value = NULL; + PyObject *traceback = NULL; + struct uwsgi_buffer *ub = NULL; + + PyErr_Fetch(&type, &value, &traceback); + PyErr_NormalizeException(&type, &value, &traceback); + + char *class = uwsgi_python_get_exception_type(type); + if (class) { + size_t class_len = strlen(class); + ub = uwsgi_buffer_new(class_len); + if (uwsgi_buffer_append(ub, class, class_len)) { + uwsgi_buffer_destroy(ub); + ub = NULL; + goto end; + } + } +end: + PyErr_Restore(type, value, traceback); + return ub; +} + +struct uwsgi_buffer *uwsgi_python_exception_msg(struct wsgi_request *wsgi_req) { + PyObject *type = NULL; + PyObject *value = NULL; + PyObject *traceback = NULL; + struct uwsgi_buffer *ub = NULL; + + PyErr_Fetch(&type, &value, &traceback); + PyErr_NormalizeException(&type, &value, &traceback); + + // value could be NULL ? + if (!value) goto end; + + char *msg = PyString_AsString( PyObject_Str(value) ); + if (msg) { + size_t msg_len = strlen(msg); + ub = uwsgi_buffer_new(msg_len); + if (uwsgi_buffer_append(ub, msg, msg_len)) { + uwsgi_buffer_destroy(ub); + ub = NULL; + goto end; + } + } +end: + PyErr_Restore(type, value, traceback); + return ub; +} + +struct uwsgi_buffer *uwsgi_python_exception_repr(struct wsgi_request *wsgi_req) { + + struct uwsgi_buffer *ub_class = uwsgi_python_exception_class(wsgi_req); + if (!ub_class) return NULL; + + struct uwsgi_buffer *ub_msg = uwsgi_python_exception_msg(wsgi_req); + if (!ub_msg) { + uwsgi_buffer_destroy(ub_class); + return NULL; + } + + struct uwsgi_buffer *ub = uwsgi_buffer_new(ub_class->pos + 2 + ub_msg->pos); + if (uwsgi_buffer_append(ub, ub_class->buf, ub_class->pos)) goto error; + if (uwsgi_buffer_append(ub, ": ", 2)) goto error; + if (uwsgi_buffer_append(ub, ub_msg->buf, ub_msg->pos)) goto error; + + uwsgi_buffer_destroy(ub_class); + uwsgi_buffer_destroy(ub_msg); + + return ub; + +error: + uwsgi_buffer_destroy(ub_class); + uwsgi_buffer_destroy(ub_msg); + uwsgi_buffer_destroy(ub); + return NULL; +} + +void uwsgi_python_exception_log(struct wsgi_request *wsgi_req) { + PyErr_Print(); +} + PyObject *python_call(PyObject *callable, PyObject *args, int catch, struct wsgi_request *wsgi_req) { //uwsgi_log("ready to call %p %p\n", callable, args); diff --git a/plugins/python/uwsgi_python.h b/plugins/python/uwsgi_python.h index 21a30f98..f76807a6 100644 --- a/plugins/python/uwsgi_python.h +++ b/plugins/python/uwsgi_python.h @@ -263,6 +263,11 @@ int uwsgi_python_do_send_headers(struct wsgi_request *); void *uwsgi_python_tracebacker_thread(void *); PyObject *uwsgi_python_setup_thread(char *); +struct uwsgi_buffer *uwsgi_python_exception_class(struct wsgi_request *); +struct uwsgi_buffer *uwsgi_python_exception_msg(struct wsgi_request *); +struct uwsgi_buffer *uwsgi_python_exception_repr(struct wsgi_request *); +void uwsgi_python_exception_log(struct wsgi_request *); + #ifdef UWSGI_PYPY #undef UWSGI_MINTERPRETERS #endif diff --git a/plugins/python/wsgi_handlers.c b/plugins/python/wsgi_handlers.c index 9489e6e6..19584fcf 100644 --- a/plugins/python/wsgi_handlers.c +++ b/plugins/python/wsgi_handlers.c @@ -245,7 +245,6 @@ int uwsgi_request_wsgi(struct wsgi_request *wsgi_req) { struct uwsgi_app *wi; - int tmp_stderr; int free_appid = 0; if (wsgi_req->async_status == UWSGI_AGAIN) { @@ -380,36 +379,8 @@ int uwsgi_request_wsgi(struct wsgi_request *wsgi_req) { } else if (uwsgi.catch_exceptions) { - - // LOCK THIS PART - - uwsgi_500(wsgi_req); - if (uwsgi_response_write_headers_do(wsgi_req)) goto clear; - - /* - sorry that is a hack to avoid the rewrite of PyErr_Print - temporarily map (using dup2) stderr to wsgi_req->fd - */ - tmp_stderr = dup(2); - if (tmp_stderr < 0) { - uwsgi_error("dup()"); - goto clear; - } - // map 2 to wsgi_req - if (dup2(wsgi_req->fd, 2) < 0) { - close(tmp_stderr); - uwsgi_error("dup2()"); - goto clear; - } - // print the error - UWSGI_GET_GIL + uwsgi_exceptions_catch(wsgi_req); PyErr_Print(); - UWSGI_RELEASE_GIL - // ...resume the original stderr, in case of error we are damaged forever !!! - if (dup2(tmp_stderr, 2) < 0) { - uwsgi_error("dup2()"); - } - close(tmp_stderr); } // this object must be freed/cleared always @@ -423,8 +394,6 @@ end: UWSGI_RELEASE_GIL -clear: - up.reset_ts(wsgi_req, wi); clear2: diff --git a/uwsgi.h b/uwsgi.h index f56e0111..5d1b0f67 100644 --- a/uwsgi.h +++ b/uwsgi.h @@ -930,6 +930,12 @@ struct uwsgi_cache { void (*master_cleanup) (void); + + struct uwsgi_buffer* (*backtrace)(struct wsgi_request *); + struct uwsgi_buffer* (*exception_class)(struct wsgi_request *); + struct uwsgi_buffer* (*exception_msg)(struct wsgi_request *); + struct uwsgi_buffer* (*exception_repr)(struct wsgi_request *); + void (*exception_log)(struct wsgi_request *); }; #ifdef UWSGI_PCRE @@ -3298,7 +3304,8 @@ socklen_t socket_to_in_addr6(char *, char *, int, struct sockaddr_in6 *); struct uwsgi_lock_item *uwsgi_lock_ipcsem_init(char *); void uwsgi_write_pidfile(char *); - int uwsgi_manage_exception(char *, char *, char *); +int uwsgi_manage_exception(char *, char *, char *); +int uwsgi_exceptions_catch(struct wsgi_request *); void uwsgi_protected_close(int); ssize_t uwsgi_protected_read(int, void *, size_t); diff --git a/uwsgiconfig.py b/uwsgiconfig.py index e80aed35..2600dbbd 100644 --- a/uwsgiconfig.py +++ b/uwsgiconfig.py @@ -446,7 +446,7 @@ class uConf(object): self.config.read(filename) self.gcc_list = ['core/utils', 'core/protocol', 'core/socket', 'core/logging', 'core/master', 'core/master_utils', 'core/emperor', 'core/notify', 'core/mule', 'core/subscription', 'core/stats', 'core/sendfile', 'core/async', 'core/master_checks', - 'core/offload', 'core/io', 'core/static', 'core/websockets', 'core/spooler', 'core/snmp', + 'core/offload', 'core/io', 'core/static', 'core/websockets', 'core/spooler', 'core/snmp', 'core/exceptions', 'core/setup_utils', 'core/clock', 'core/init', 'core/buffer', 'core/reader', 'core/writer', 'core/alarm', 'core/plugins', 'core/lock', 'core/cache', 'core/daemons', 'core/errors', 'core/hash', 'core/master_events', 'core/queue', 'core/event', 'core/signal', 'core/strings', 'core/progress', 'core/timebomb',