diff --git a/plugins/python/python_plugin.c b/plugins/python/python_plugin.c index acba17ac..69dd44f7 100644 --- a/plugins/python/python_plugin.c +++ b/plugins/python/python_plugin.c @@ -156,6 +156,8 @@ struct uwsgi_option uwsgi_python_options[] = { {"wsgi-env-behavior", required_argument, 0, "set the strategy for allocating/deallocating the WSGI env", uwsgi_opt_set_str, &up.wsgi_env_behaviour, 0}, {"start_response-nodelay", no_argument, 0, "send WSGI http headers as soon as possible (PEP violation)", uwsgi_opt_true, &up.start_response_nodelay, 0}, + {"wsgi-strict", no_argument, 0, "expect the WSGI callable to return ONLY strings (python 2.x) or bytes (python 3.x)", uwsgi_opt_true, &up.wsgi_strict, 0}, + {"python-version", no_argument, 0, "report python version", uwsgi_opt_pyver, NULL, UWSGI_OPT_IMMEDIATE}, {0, 0, 0, 0, 0, 0, 0}, diff --git a/plugins/python/uwsgi_python.h b/plugins/python/uwsgi_python.h index 9c97d9e7..3420e89d 100644 --- a/plugins/python/uwsgi_python.h +++ b/plugins/python/uwsgi_python.h @@ -180,6 +180,7 @@ struct uwsgi_python { int start_response_nodelay; char *programname; + int wsgi_strict; }; diff --git a/plugins/python/wsgi_subhandler.c b/plugins/python/wsgi_subhandler.c index 15a1b918..0b03d605 100644 --- a/plugins/python/wsgi_subhandler.c +++ b/plugins/python/wsgi_subhandler.c @@ -5,6 +5,57 @@ extern struct uwsgi_python up; extern struct uwsgi_plugin python_plugin; extern PyTypeObject uwsgi_InputType; +/* + + Albeit PEP 333/3333 is clear about what kind of return object we must + expect from a WSGI callable, we use the buffer api to optimize for lower-level + returns type: bytes, bytearray, array.array + + the "strict" can behavhiour can be forced with --wsgi-strict + +*/ + +int uwsgi_python_send_body(struct wsgi_request *wsgi_req, PyObject *chunk) { + char *content = NULL; + size_t content_len = 0; + if (!up.wsgi_strict) { +#if defined(PYTHREE) || defined(Py_TPFLAGS_HAVE_NEWBUFFER) + if (PyObject_CheckBuffer(chunk)) { + Py_buffer pbuf; + if (!PyObject_GetBuffer(chunk, &pbuf, PyBUF_SIMPLE)) { + content = (char *) pbuf.buf; + content_len = (size_t) pbuf.len; + goto found; + } + } +#else + if (PyObject_CheckReadBuffer(chunk)) { + if (!PyObject_AsCharBuffer(chunk, (const char **) &content, (Py_ssize_t *) &content_len)) { + goto found; + } + } +#endif + } + // fallback + if (PyString_Check(chunk)) { + content = PyString_AsString(chunk); + content_len = PyString_Size(chunk); + } + +found: + if (content) { + UWSGI_RELEASE_GIL + uwsgi_response_write_body_do(wsgi_req, content, content_len); + UWSGI_GET_GIL + uwsgi_py_check_write_errors { + uwsgi_py_write_exception(wsgi_req); + return -1; + } + return 1; + } + return 0; +} + /* this is a hack for supporting non-file object passed to wsgi.file_wrapper */ @@ -169,19 +220,10 @@ int uwsgi_response_subhandler_wsgi(struct wsgi_request *wsgi_req) { PyObject *pychunk; // return or yield ? - if (PyString_Check((PyObject *)wsgi_req->async_result)) { - char *content = PyString_AsString((PyObject *)wsgi_req->async_result); - size_t content_len = PyString_Size((PyObject *)wsgi_req->async_result); - UWSGI_RELEASE_GIL - uwsgi_response_write_body_do(wsgi_req, content, content_len); - UWSGI_GET_GIL - uwsgi_py_check_write_errors { - uwsgi_py_write_exception(wsgi_req); - } + if (uwsgi_python_send_body(wsgi_req, (PyObject *)wsgi_req->async_result)) { goto clear; } - if (wsgi_req->sendfile_obj == wsgi_req->async_result) { if (wsgi_req->sendfile_fd >= 0) { UWSGI_RELEASE_GIL @@ -222,19 +264,13 @@ exception: - if (PyString_Check(pychunk)) { - char *content = PyString_AsString(pychunk); - size_t content_len = PyString_Size(pychunk); - UWSGI_RELEASE_GIL - uwsgi_response_write_body_do(wsgi_req, content, content_len); - UWSGI_GET_GIL - uwsgi_py_check_write_errors { - uwsgi_py_write_exception(wsgi_req); + int ret = uwsgi_python_send_body(wsgi_req, pychunk); + if (ret != 0) { + if (ret < 0) { Py_DECREF(pychunk); goto clear; } } - else if (wsgi_req->sendfile_obj == pychunk) { if (wsgi_req->sendfile_fd >= 0) { UWSGI_RELEASE_GIL diff --git a/t/python/testba.py b/t/python/testba.py new file mode 100644 index 00000000..461f54b7 --- /dev/null +++ b/t/python/testba.py @@ -0,0 +1,7 @@ +import array +def application(e, sr): + sr('200 OK', [('Content-Type','text/html')]) + a = array.array('b',[54,55,56,57]) + yield a + yield bytearray(b'abcdef') + yield b'ciao'