diff --git a/plugins/python/python_plugin.c b/plugins/python/python_plugin.c index 8dd57a35..c05686a5 100644 --- a/plugins/python/python_plugin.c +++ b/plugins/python/python_plugin.c @@ -127,6 +127,8 @@ struct uwsgi_option uwsgi_python_options[] = { {"py-auto-reload-ignore", required_argument, 0, "ignore the specified module during auto-reload scan (can be specified multiple times)", uwsgi_opt_add_string_list, &up.auto_reload_ignore, UWSGI_OPT_THREADS|UWSGI_OPT_MASTER}, #endif + {"start_response-nodelay", no_argument, 0, "send WSGI http headers as soon as possible (PEP violation)", uwsgi_opt_true, &up.start_response_nodelay, 0}, + {0, 0, 0, 0, 0, 0, 0}, }; diff --git a/plugins/python/uwsgi_python.h b/plugins/python/uwsgi_python.h index 275a2798..4f189e88 100644 --- a/plugins/python/uwsgi_python.h +++ b/plugins/python/uwsgi_python.h @@ -178,6 +178,7 @@ struct uwsgi_python { PyObject *after_req_hook_args; char *pyrun; + int start_response_nodelay; }; @@ -266,6 +267,7 @@ char *uwsgi_pythonize(char *); void *uwsgi_python_autoreloader_thread(void *); int uwsgi_python_manage_exceptions(void); +int uwsgi_python_do_send_headers(struct wsgi_request *); #ifdef UWSGI_PYPY #undef UWSGI_MINTERPRETERS diff --git a/plugins/python/wsgi_handlers.c b/plugins/python/wsgi_handlers.c index 2b91ef14..659fc430 100644 --- a/plugins/python/wsgi_handlers.c +++ b/plugins/python/wsgi_handlers.c @@ -267,16 +267,21 @@ PyTypeObject uwsgi_InputType = { PyObject *py_uwsgi_write(PyObject * self, PyObject * args) { PyObject *data; char *content; - int len; + size_t content_len; struct wsgi_request *wsgi_req = current_wsgi_req(); data = PyTuple_GetItem(args, 0); if (PyString_Check(data)) { content = PyString_AsString(data); - len = PyString_Size(data); + content_len = PyString_Size(data); + if (content_len > 0 && !wsgi_req->headers_sent) { + if (uwsgi_python_do_send_headers(wsgi_req)) { + return NULL; + } + } UWSGI_RELEASE_GIL - wsgi_req->response_size = wsgi_req->socket->proto_write(wsgi_req, content, len); + wsgi_req->response_size = wsgi_req->socket->proto_write(wsgi_req, content, content_len); UWSGI_GET_GIL // this is a special case for the write callable // no need to honout write-errors-exception-only diff --git a/plugins/python/wsgi_headers.c b/plugins/python/wsgi_headers.c index d7ed9285..57033f6b 100644 --- a/plugins/python/wsgi_headers.c +++ b/plugins/python/wsgi_headers.c @@ -19,6 +19,11 @@ PyObject *py_uwsgi_spit(PyObject * self, PyObject * args) { int base = 0; + // avoid double sending of headers + if (wsgi_req->headers_sent) { + return PyErr_Format(PyExc_IOError, "headers already sent"); + } + // this must be done before headers management if (PyTuple_Size(args) > 2) { exc_info = PyTuple_GetItem(args, 2); @@ -45,7 +50,7 @@ PyObject *py_uwsgi_spit(PyObject * self, PyObject * args) { head = PyTuple_GetItem(args, 0); if (!head) { - goto clear; + return PyErr_Format(PyExc_TypeError, "start_response() takes at least 2 arguments"); } #ifdef PYTHREE @@ -53,8 +58,7 @@ PyObject *py_uwsgi_spit(PyObject * self, PyObject * args) { #else if (!PyString_Check(head)) { #endif - uwsgi_log( "http status must be a string !\n"); - goto clear; + return PyErr_Format(PyExc_TypeError, "http status must be a string"); } @@ -101,12 +105,13 @@ PyObject *py_uwsgi_spit(PyObject * self, PyObject * args) { } headers = PyTuple_GetItem(args, 1); - if (!headers) goto clear; + if (!headers) { + return PyErr_Format(PyExc_TypeError, "start_response() takes at least 2 arguments"); + } if (!PyList_Check(headers)) { - uwsgi_log( "http headers must be in a python list\n"); - goto clear; + return PyErr_Format(PyExc_TypeError, "http headers must be in a python list"); } wsgi_req->header_cnt = PyList_Size(headers); @@ -117,20 +122,36 @@ PyObject *py_uwsgi_spit(PyObject * self, PyObject * args) { j = (i * 4) + base; head = PyList_GetItem(headers, i); if (!head) { + PyErr_Print(); goto clear; } if (!PyTuple_Check(head)) { - uwsgi_log( "http header must be defined in a tuple !\n"); - goto clear; + return PyErr_Format(PyExc_TypeError, "http header must be defined in a tuple"); } h_key = PyTuple_GetItem(head, 0); if (!h_key) { - goto clear; + return PyErr_Format(PyExc_TypeError, "http header must be a 2-item tuple"); + } +#ifdef PYTHREE + if (!PyUnicode_Check(h_key)) { +#else + if (!PyString_Check(h_key)) { +#endif + return PyErr_Format(PyExc_TypeError, "http header key must be a string"); } h_value = PyTuple_GetItem(head, 1); if (!h_value) { - goto clear; + return PyErr_Format(PyExc_TypeError, "http header must be a 2-item tuple"); } +#ifdef PYTHREE + if (!PyUnicode_Check(h_value)) { +#else + if (!PyString_Check(h_value)) { +#endif + return PyErr_Format(PyExc_TypeError, "http header value must be a string"); + } + + #ifdef PYTHREE wsgi_req->hvec[j].iov_base = PyBytes_AsString(PyUnicode_AsASCIIString(h_key)); @@ -181,30 +202,13 @@ PyObject *py_uwsgi_spit(PyObject * self, PyObject * args) { wsgi_req->hvec[j].iov_base = nl; wsgi_req->hvec[j].iov_len = NL_SIZE; -#ifdef __sun__ - int remains = j + 1; - int iov_size; - struct iovec* iov_ptr = wsgi_req->hvec; - ssize_t iov_ret; - while(remains) { - iov_size = UMIN(remains, IOV_MAX); - UWSGI_RELEASE_GIL - iov_ret = wsgi_req->socket->proto_writev_header(wsgi_req, iov_ptr, iov_size); - UWSGI_GET_GIL - wsgi_req->headers_size += iov_ret; - iov_ptr += iov_size; - remains -= iov_size; - } -#else - UWSGI_RELEASE_GIL - wsgi_req->headers_size = wsgi_req->socket->proto_writev_header(wsgi_req, wsgi_req->hvec, j + 1); - UWSGI_GET_GIL -#endif + wsgi_req->headers_hvec = j; - if (wsgi_req->write_errors > uwsgi.write_errors_tolerance) { - uwsgi_py_write_set_exception(wsgi_req); - return NULL; - } + if (up.start_response_nodelay) { + if (uwsgi_python_do_send_headers(wsgi_req)) { + return NULL; + } + } //uwsgi_log("%d %p\n", wsgi_req->poll.fd, up.wsgi_writeout); Py_INCREF(up.wsgi_writeout); @@ -216,3 +220,36 @@ clear: Py_INCREF(Py_None); return Py_None; } + +int uwsgi_python_do_send_headers(struct wsgi_request *wsgi_req) { + +#ifdef __sun__ + int remains = wsgi_req->headers_hvec + 1; + int iov_size; + struct iovec* iov_ptr = wsgi_req->hvec; + ssize_t iov_ret; + while(remains) { + iov_size = UMIN(remains, IOV_MAX); + UWSGI_RELEASE_GIL + iov_ret = wsgi_req->socket->proto_writev_header(wsgi_req, iov_ptr, iov_size); + UWSGI_GET_GIL + wsgi_req->headers_size += iov_ret; + iov_ptr += iov_size; + remains -= iov_size; + } +#else + UWSGI_RELEASE_GIL + wsgi_req->headers_size = wsgi_req->socket->proto_writev_header(wsgi_req, wsgi_req->hvec, wsgi_req->headers_hvec + 1); + UWSGI_GET_GIL +#endif + + wsgi_req->headers_sent = 1; + + if (wsgi_req->write_errors > uwsgi.write_errors_tolerance) { + uwsgi_py_write_set_exception(wsgi_req); + return -1; + } + + return 0; + +} diff --git a/plugins/python/wsgi_subhandler.c b/plugins/python/wsgi_subhandler.c index ce048b59..0fb0e782 100644 --- a/plugins/python/wsgi_subhandler.c +++ b/plugins/python/wsgi_subhandler.c @@ -166,6 +166,11 @@ int uwsgi_response_subhandler_wsgi(struct wsgi_request *wsgi_req) { if (PyString_Check((PyObject *)wsgi_req->async_result)) { char *content = PyString_AsString(wsgi_req->async_result); size_t content_len = PyString_Size(wsgi_req->async_result); + if (content_len > 0 && !wsgi_req->headers_sent) { + if (uwsgi_python_do_send_headers(wsgi_req)) { + goto clear; + } + } UWSGI_RELEASE_GIL wsgi_req->response_size += wsgi_req->socket->proto_write(wsgi_req, content, content_len); UWSGI_GET_GIL @@ -252,6 +257,11 @@ int uwsgi_response_subhandler_wsgi(struct wsgi_request *wsgi_req) { if (PyString_Check(pychunk)) { char *content = PyString_AsString(pychunk); size_t content_len = PyString_Size(pychunk); + if (content_len > 0 && !wsgi_req->headers_sent) { + if (uwsgi_python_do_send_headers(wsgi_req)) { + goto clear; + } + } UWSGI_RELEASE_GIL wsgi_req->response_size += wsgi_req->socket->proto_write(wsgi_req, content, content_len); UWSGI_GET_GIL diff --git a/uwsgi.h b/uwsgi.h index fe316260..460aa40a 100644 --- a/uwsgi.h +++ b/uwsgi.h @@ -935,6 +935,10 @@ struct wsgi_request { // current socket mapped to request struct uwsgi_socket *socket; + // check if headers are already sent + int headers_sent; + int headers_hvec; + int body_as_file; //for generic use size_t buf_pos;