From d483aa74e61af411e40e98153a597d5a0473e2f1 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Mon, 14 Sep 2020 12:07:30 +0300 Subject: Python: source file moved to 'python' sub-directory. No functional changes. Get ready for an increase in file number. --- src/nxt_python_wsgi.c | 1446 ------------------------------------------ src/python/nxt_python_wsgi.c | 1446 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1446 insertions(+), 1446 deletions(-) delete mode 100644 src/nxt_python_wsgi.c create mode 100644 src/python/nxt_python_wsgi.c (limited to 'src') diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c deleted file mode 100644 index c4b7702e..00000000 --- a/src/nxt_python_wsgi.c +++ /dev/null @@ -1,1446 +0,0 @@ - -/* - * Copyright (C) Max Romanov - * Copyright (C) Valentin V. Bartenev - * Copyright (C) NGINX, Inc. - */ - - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include NXT_PYTHON_MOUNTS_H - -/* - * According to "PEP 3333 / A Note On String Types" - * [https://www.python.org/dev/peps/pep-3333/#a-note-on-string-types] - * - * WSGI therefore defines two kinds of "string": - * - * - "Native" strings (which are always implemented using the type named str ) - * that are used for request/response headers and metadata - * - * will use PyString_* or corresponding PyUnicode_* functions - * - * - "Bytestrings" (which are implemented using the bytes type in Python 3, and - * str elsewhere), that are used for the bodies of requests and responses - * (e.g. POST/PUT input data and HTML page outputs). - * - * will use PyString_* or corresponding PyBytes_* functions - */ - - -#if PY_MAJOR_VERSION == 3 -#define NXT_PYTHON_BYTES_TYPE "bytestring" - -#define PyString_FromStringAndSize(str, size) \ - PyUnicode_DecodeLatin1((str), (size), "strict") - -#else -#define NXT_PYTHON_BYTES_TYPE "string" - -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#define PyBytes_Check PyString_Check -#define PyBytes_GET_SIZE PyString_GET_SIZE -#define PyBytes_AS_STRING PyString_AS_STRING -#define PyUnicode_InternInPlace PyString_InternInPlace -#define PyUnicode_AsUTF8 PyString_AS_STRING -#endif - -typedef struct nxt_python_run_ctx_s nxt_python_run_ctx_t; - -typedef struct { - PyObject_HEAD -} nxt_py_input_t; - - -typedef struct { - PyObject_HEAD -} nxt_py_error_t; - -static nxt_int_t nxt_python_start(nxt_task_t *task, - nxt_process_data_t *data); -static nxt_int_t nxt_python_init_strings(void); -static void nxt_python_request_handler(nxt_unit_request_info_t *req); -static void nxt_python_atexit(void); - -static PyObject *nxt_python_create_environ(nxt_task_t *task); -static PyObject *nxt_python_get_environ(nxt_python_run_ctx_t *ctx); -static int nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, PyObject *name, - nxt_unit_sptr_t *sptr, uint32_t size); -static int nxt_python_add_field(nxt_python_run_ctx_t *ctx, - nxt_unit_field_t *field); -static int nxt_python_add_obj(nxt_python_run_ctx_t *ctx, PyObject *name, - PyObject *value); - -static PyObject *nxt_py_start_resp(PyObject *self, PyObject *args); -static int nxt_python_response_add_field(nxt_python_run_ctx_t *ctx, - PyObject *name, PyObject *value, int i); -static int nxt_python_str_buf(PyObject *str, char **buf, uint32_t *len, - PyObject **bytes); -static PyObject *nxt_py_write(PyObject *self, PyObject *args); - -static void nxt_py_input_dealloc(nxt_py_input_t *self); -static PyObject *nxt_py_input_read(nxt_py_input_t *self, PyObject *args); -static PyObject *nxt_py_input_readline(nxt_py_input_t *self, PyObject *args); -static PyObject *nxt_py_input_getline(nxt_python_run_ctx_t *ctx, size_t size); -static PyObject *nxt_py_input_readlines(nxt_py_input_t *self, PyObject *args); - -static PyObject *nxt_py_input_iter(PyObject *self); -static PyObject *nxt_py_input_next(PyObject *self); - -static void nxt_python_print_exception(void); -static int nxt_python_write(nxt_python_run_ctx_t *ctx, PyObject *bytes); - -struct nxt_python_run_ctx_s { - uint64_t content_length; - uint64_t bytes_sent; - PyObject *environ; - nxt_unit_request_info_t *req; -}; - -static uint32_t compat[] = { - NXT_VERNUM, NXT_DEBUG, -}; - - -NXT_EXPORT nxt_app_module_t nxt_app_module = { - sizeof(compat), - compat, - nxt_string("python"), - PY_VERSION, - nxt_python_mounts, - nxt_nitems(nxt_python_mounts), - NULL, - nxt_python_start, -}; - - -static PyMethodDef nxt_py_start_resp_method[] = { - {"unit_start_response", nxt_py_start_resp, METH_VARARGS, ""} -}; - - -static PyMethodDef nxt_py_write_method[] = { - {"unit_write", nxt_py_write, METH_O, ""} -}; - - -static PyMethodDef nxt_py_input_methods[] = { - { "read", (PyCFunction) nxt_py_input_read, METH_VARARGS, 0 }, - { "readline", (PyCFunction) nxt_py_input_readline, METH_VARARGS, 0 }, - { "readlines", (PyCFunction) nxt_py_input_readlines, METH_VARARGS, 0 }, - { NULL, NULL, 0, 0 } -}; - - -static PyTypeObject nxt_py_input_type = { - PyVarObject_HEAD_INIT(NULL, 0) - - .tp_name = "unit._input", - .tp_basicsize = sizeof(nxt_py_input_t), - .tp_dealloc = (destructor) nxt_py_input_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = "unit input object.", - .tp_iter = nxt_py_input_iter, - .tp_iternext = nxt_py_input_next, - .tp_methods = nxt_py_input_methods, -}; - - -static PyObject *nxt_py_stderr_flush; -static PyObject *nxt_py_application; -static PyObject *nxt_py_start_resp_obj; -static PyObject *nxt_py_write_obj; -static PyObject *nxt_py_environ_ptyp; - -#if PY_MAJOR_VERSION == 3 -static wchar_t *nxt_py_home; -#else -static char *nxt_py_home; -#endif - -static PyThreadState *nxt_python_thread_state; -static nxt_python_run_ctx_t *nxt_python_run_ctx; - - -static PyObject *nxt_py_80_str; -static PyObject *nxt_py_close_str; -static PyObject *nxt_py_content_length_str; -static PyObject *nxt_py_content_type_str; -static PyObject *nxt_py_http_str; -static PyObject *nxt_py_https_str; -static PyObject *nxt_py_path_info_str; -static PyObject *nxt_py_query_string_str; -static PyObject *nxt_py_remote_addr_str; -static PyObject *nxt_py_request_method_str; -static PyObject *nxt_py_request_uri_str; -static PyObject *nxt_py_server_addr_str; -static PyObject *nxt_py_server_name_str; -static PyObject *nxt_py_server_port_str; -static PyObject *nxt_py_server_protocol_str; -static PyObject *nxt_py_wsgi_uri_scheme_str; - -typedef struct { - nxt_str_t string; - PyObject **object_p; -} nxt_python_string_t; - -static nxt_python_string_t nxt_python_strings[] = { - { nxt_string("80"), &nxt_py_80_str }, - { nxt_string("close"), &nxt_py_close_str }, - { nxt_string("CONTENT_LENGTH"), &nxt_py_content_length_str }, - { nxt_string("CONTENT_TYPE"), &nxt_py_content_type_str }, - { nxt_string("http"), &nxt_py_http_str }, - { nxt_string("https"), &nxt_py_https_str }, - { nxt_string("PATH_INFO"), &nxt_py_path_info_str }, - { nxt_string("QUERY_STRING"), &nxt_py_query_string_str }, - { nxt_string("REMOTE_ADDR"), &nxt_py_remote_addr_str }, - { nxt_string("REQUEST_METHOD"), &nxt_py_request_method_str }, - { nxt_string("REQUEST_URI"), &nxt_py_request_uri_str }, - { nxt_string("SERVER_ADDR"), &nxt_py_server_addr_str }, - { nxt_string("SERVER_NAME"), &nxt_py_server_name_str }, - { nxt_string("SERVER_PORT"), &nxt_py_server_port_str }, - { nxt_string("SERVER_PROTOCOL"), &nxt_py_server_protocol_str }, - { nxt_string("wsgi.url_scheme"), &nxt_py_wsgi_uri_scheme_str }, -}; - - -static nxt_int_t -nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) -{ - int rc; - char *nxt_py_module; - size_t len; - PyObject *obj, *pypath, *module; - nxt_unit_ctx_t *unit_ctx; - nxt_unit_init_t python_init; - nxt_common_app_conf_t *app_conf; - nxt_python_app_conf_t *c; -#if PY_MAJOR_VERSION == 3 - char *path; - size_t size; - nxt_int_t pep405; - - static const char pyvenv[] = "/pyvenv.cfg"; - static const char bin_python[] = "/bin/python"; -#endif - - app_conf = data->app; - c = &app_conf->u.python; - - if (c->home != NULL) { - len = nxt_strlen(c->home); - -#if PY_MAJOR_VERSION == 3 - - path = nxt_malloc(len + sizeof(pyvenv)); - if (nxt_slow_path(path == NULL)) { - nxt_alert(task, "Failed to allocate memory"); - return NXT_ERROR; - } - - nxt_memcpy(path, c->home, len); - nxt_memcpy(path + len, pyvenv, sizeof(pyvenv)); - - pep405 = (access(path, R_OK) == 0); - - nxt_free(path); - - if (pep405) { - size = (len + sizeof(bin_python)) * sizeof(wchar_t); - - } else { - size = (len + 1) * sizeof(wchar_t); - } - - nxt_py_home = nxt_malloc(size); - if (nxt_slow_path(nxt_py_home == NULL)) { - nxt_alert(task, "Failed to allocate memory"); - return NXT_ERROR; - } - - if (pep405) { - mbstowcs(nxt_py_home, c->home, len); - mbstowcs(nxt_py_home + len, bin_python, sizeof(bin_python)); - Py_SetProgramName(nxt_py_home); - - } else { - mbstowcs(nxt_py_home, c->home, len + 1); - Py_SetPythonHome(nxt_py_home); - } - -#else - nxt_py_home = nxt_malloc(len + 1); - if (nxt_slow_path(nxt_py_home == NULL)) { - nxt_alert(task, "Failed to allocate memory"); - return NXT_ERROR; - } - - nxt_memcpy(nxt_py_home, c->home, len + 1); - Py_SetPythonHome(nxt_py_home); -#endif - } - - Py_InitializeEx(0); - - module = NULL; - obj = NULL; - - if (nxt_slow_path(nxt_python_init_strings() != NXT_OK)) { - nxt_alert(task, "Python failed to init string objects"); - goto fail; - } - - obj = PySys_GetObject((char *) "stderr"); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to get \"sys.stderr\" object"); - goto fail; - } - - nxt_py_stderr_flush = PyObject_GetAttrString(obj, "flush"); - if (nxt_slow_path(nxt_py_stderr_flush == NULL)) { - nxt_alert(task, "Python failed to get \"flush\" attribute of " - "\"sys.stderr\" object"); - goto fail; - } - - Py_DECREF(obj); - - if (c->path.length > 0) { - obj = PyString_FromStringAndSize((char *) c->path.start, - c->path.length); - - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to create string object \"%V\"", - &c->path); - goto fail; - } - - pypath = PySys_GetObject((char *) "path"); - - if (nxt_slow_path(pypath == NULL)) { - nxt_alert(task, "Python failed to get \"sys.path\" list"); - goto fail; - } - - if (nxt_slow_path(PyList_Insert(pypath, 0, obj) != 0)) { - nxt_alert(task, "Python failed to insert \"%V\" into \"sys.path\"", - &c->path); - goto fail; - } - - Py_DECREF(obj); - } - - obj = PyCFunction_New(nxt_py_start_resp_method, NULL); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, - "Python failed to initialize the \"start_response\" function"); - goto fail; - } - - nxt_py_start_resp_obj = obj; - - obj = PyCFunction_New(nxt_py_write_method, NULL); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to initialize the \"write\" function"); - goto fail; - } - - nxt_py_write_obj = obj; - - obj = nxt_python_create_environ(task); - if (nxt_slow_path(obj == NULL)) { - goto fail; - } - - nxt_py_environ_ptyp = obj; - - obj = Py_BuildValue("[s]", "unit"); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to create the \"sys.argv\" list"); - goto fail; - } - - if (nxt_slow_path(PySys_SetObject((char *) "argv", obj) != 0)) { - nxt_alert(task, "Python failed to set the \"sys.argv\" list"); - goto fail; - } - - Py_CLEAR(obj); - - nxt_py_module = nxt_alloca(c->module.length + 1); - nxt_memcpy(nxt_py_module, c->module.start, c->module.length); - nxt_py_module[c->module.length] = '\0'; - - module = PyImport_ImportModule(nxt_py_module); - if (nxt_slow_path(module == NULL)) { - nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); - nxt_python_print_exception(); - goto fail; - } - - obj = PyDict_GetItemString(PyModule_GetDict(module), "application"); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to get \"application\" " - "from module \"%s\"", nxt_py_module); - goto fail; - } - - if (nxt_slow_path(PyCallable_Check(obj) == 0)) { - nxt_alert(task, "\"application\" in module \"%s\" " - "is not a callable object", nxt_py_module); - goto fail; - } - - Py_INCREF(obj); - Py_CLEAR(module); - - nxt_py_application = obj; - obj = NULL; - - nxt_unit_default_init(task, &python_init); - - python_init.callbacks.request_handler = nxt_python_request_handler; - python_init.shm_limit = data->app->shm_limit; - - unit_ctx = nxt_unit_init(&python_init); - if (nxt_slow_path(unit_ctx == NULL)) { - goto fail; - } - - nxt_python_thread_state = PyEval_SaveThread(); - - rc = nxt_unit_run(unit_ctx); - - nxt_unit_done(unit_ctx); - - PyEval_RestoreThread(nxt_python_thread_state); - - nxt_python_atexit(); - - exit(rc); - - return NXT_OK; - -fail: - - Py_XDECREF(obj); - Py_XDECREF(module); - - nxt_python_atexit(); - - return NXT_ERROR; -} - - -static nxt_int_t -nxt_python_init_strings(void) -{ - PyObject *obj; - nxt_uint_t i; - nxt_python_string_t *pstr; - - for (i = 0; i < nxt_nitems(nxt_python_strings); i++) { - pstr = &nxt_python_strings[i]; - - obj = PyString_FromStringAndSize((char *) pstr->string.start, - pstr->string.length); - if (nxt_slow_path(obj == NULL)) { - return NXT_ERROR; - } - - PyUnicode_InternInPlace(&obj); - - *pstr->object_p = obj; - } - - return NXT_OK; -} - - -static void -nxt_python_request_handler(nxt_unit_request_info_t *req) -{ - int rc; - PyObject *environ, *args, *response, *iterator, *item; - PyObject *close, *result; - nxt_python_run_ctx_t run_ctx = {-1, 0, NULL, req}; - - PyEval_RestoreThread(nxt_python_thread_state); - - environ = nxt_python_get_environ(&run_ctx); - if (nxt_slow_path(environ == NULL)) { - rc = NXT_UNIT_ERROR; - goto done; - } - - args = PyTuple_New(2); - if (nxt_slow_path(args == NULL)) { - Py_DECREF(environ); - - nxt_unit_req_error(req, "Python failed to create arguments tuple"); - - rc = NXT_UNIT_ERROR; - goto done; - } - - PyTuple_SET_ITEM(args, 0, environ); - - Py_INCREF(nxt_py_start_resp_obj); - PyTuple_SET_ITEM(args, 1, nxt_py_start_resp_obj); - - nxt_python_run_ctx = &run_ctx; - - response = PyObject_CallObject(nxt_py_application, args); - - Py_DECREF(args); - - if (nxt_slow_path(response == NULL)) { - nxt_unit_req_error(req, "Python failed to call the application"); - nxt_python_print_exception(); - - rc = NXT_UNIT_ERROR; - goto done; - } - - /* Shortcut: avoid iterate over response string symbols. */ - if (PyBytes_Check(response)) { - rc = nxt_python_write(&run_ctx, response); - - } else { - iterator = PyObject_GetIter(response); - - if (nxt_fast_path(iterator != NULL)) { - rc = NXT_UNIT_OK; - - while (run_ctx.bytes_sent < run_ctx.content_length) { - item = PyIter_Next(iterator); - - if (item == NULL) { - if (nxt_slow_path(PyErr_Occurred() != NULL)) { - nxt_unit_req_error(req, "Python failed to iterate over " - "the application response object"); - nxt_python_print_exception(); - - rc = NXT_UNIT_ERROR; - } - - break; - } - - if (nxt_fast_path(PyBytes_Check(item))) { - rc = nxt_python_write(&run_ctx, item); - - } else { - nxt_unit_req_error(req, "the application returned " - "not a bytestring object"); - rc = NXT_UNIT_ERROR; - } - - Py_DECREF(item); - - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - break; - } - } - - Py_DECREF(iterator); - - } else { - nxt_unit_req_error(req, - "the application returned not an iterable object"); - nxt_python_print_exception(); - - rc = NXT_UNIT_ERROR; - } - - close = PyObject_GetAttr(response, nxt_py_close_str); - - if (close != NULL) { - result = PyObject_CallFunction(close, NULL); - if (nxt_slow_path(result == NULL)) { - nxt_unit_req_error(req, "Python failed to call the close() " - "method of the application response"); - nxt_python_print_exception(); - - } else { - Py_DECREF(result); - } - - Py_DECREF(close); - - } else { - PyErr_Clear(); - } - } - - Py_DECREF(response); - -done: - - nxt_python_thread_state = PyEval_SaveThread(); - - nxt_python_run_ctx = NULL; - nxt_unit_request_done(req, rc); -} - - -static void -nxt_python_atexit(void) -{ - nxt_uint_t i; - - for (i = 0; i < nxt_nitems(nxt_python_strings); i++) { - Py_XDECREF(*nxt_python_strings[i].object_p); - } - - Py_XDECREF(nxt_py_stderr_flush); - Py_XDECREF(nxt_py_application); - Py_XDECREF(nxt_py_start_resp_obj); - Py_XDECREF(nxt_py_write_obj); - Py_XDECREF(nxt_py_environ_ptyp); - - Py_Finalize(); - - if (nxt_py_home != NULL) { - nxt_free(nxt_py_home); - } -} - - -static PyObject * -nxt_python_create_environ(nxt_task_t *task) -{ - PyObject *obj, *err, *environ; - - environ = PyDict_New(); - - if (nxt_slow_path(environ == NULL)) { - nxt_alert(task, "Python failed to create the \"environ\" dictionary"); - return NULL; - } - - obj = PyString_FromStringAndSize((char *) nxt_server.start, - nxt_server.length); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, - "Python failed to create the \"SERVER_SOFTWARE\" environ value"); - goto fail; - } - - if (nxt_slow_path(PyDict_SetItemString(environ, "SERVER_SOFTWARE", obj) - != 0)) - { - nxt_alert(task, - "Python failed to set the \"SERVER_SOFTWARE\" environ value"); - goto fail; - } - - Py_DECREF(obj); - - obj = Py_BuildValue("(ii)", 1, 0); - - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, - "Python failed to build the \"wsgi.version\" environ value"); - goto fail; - } - - if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.version", obj) != 0)) - { - nxt_alert(task, - "Python failed to set the \"wsgi.version\" environ value"); - goto fail; - } - - Py_DECREF(obj); - obj = NULL; - - - if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.multithread", - Py_False) - != 0)) - { - nxt_alert(task, - "Python failed to set the \"wsgi.multithread\" environ value"); - goto fail; - } - - if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.multiprocess", - Py_True) - != 0)) - { - nxt_alert(task, - "Python failed to set the \"wsgi.multiprocess\" environ value"); - goto fail; - } - - if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.run_once", - Py_False) - != 0)) - { - nxt_alert(task, - "Python failed to set the \"wsgi.run_once\" environ value"); - goto fail; - } - - - if (nxt_slow_path(PyType_Ready(&nxt_py_input_type) != 0)) { - nxt_alert(task, - "Python failed to initialize the \"wsgi.input\" type object"); - goto fail; - } - - obj = (PyObject *) PyObject_New(nxt_py_input_t, &nxt_py_input_type); - - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to create the \"wsgi.input\" object"); - goto fail; - } - - if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.input", obj) != 0)) { - nxt_alert(task, - "Python failed to set the \"wsgi.input\" environ value"); - goto fail; - } - - Py_DECREF(obj); - obj = NULL; - - - err = PySys_GetObject((char *) "stderr"); - - if (nxt_slow_path(err == NULL)) { - nxt_alert(task, "Python failed to get \"sys.stderr\" object"); - goto fail; - } - - if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.errors", err) != 0)) - { - nxt_alert(task, - "Python failed to set the \"wsgi.errors\" environ value"); - goto fail; - } - - return environ; - -fail: - - Py_XDECREF(obj); - Py_DECREF(environ); - - return NULL; -} - - -static PyObject * -nxt_python_get_environ(nxt_python_run_ctx_t *ctx) -{ - int rc; - uint32_t i; - PyObject *environ; - nxt_unit_field_t *f; - nxt_unit_request_t *r; - - environ = PyDict_Copy(nxt_py_environ_ptyp); - if (nxt_slow_path(environ == NULL)) { - nxt_unit_req_error(ctx->req, - "Python failed to copy the \"environ\" dictionary"); - - return NULL; - } - - ctx->environ = environ; - - r = ctx->req->request; - -#define RC(S) \ - do { \ - rc = (S); \ - if (nxt_slow_path(rc != NXT_UNIT_OK)) { \ - goto fail; \ - } \ - } while(0) - - RC(nxt_python_add_sptr(ctx, nxt_py_request_method_str, &r->method, - r->method_length)); - RC(nxt_python_add_sptr(ctx, nxt_py_request_uri_str, &r->target, - r->target_length)); - RC(nxt_python_add_sptr(ctx, nxt_py_query_string_str, &r->query, - r->query_length)); - RC(nxt_python_add_sptr(ctx, nxt_py_path_info_str, &r->path, - r->path_length)); - - RC(nxt_python_add_sptr(ctx, nxt_py_remote_addr_str, &r->remote, - r->remote_length)); - RC(nxt_python_add_sptr(ctx, nxt_py_server_addr_str, &r->local, - r->local_length)); - - if (r->tls) { - RC(nxt_python_add_obj(ctx, nxt_py_wsgi_uri_scheme_str, - nxt_py_https_str)); - } else { - RC(nxt_python_add_obj(ctx, nxt_py_wsgi_uri_scheme_str, - nxt_py_http_str)); - } - - RC(nxt_python_add_sptr(ctx, nxt_py_server_protocol_str, &r->version, - r->version_length)); - - RC(nxt_python_add_sptr(ctx, nxt_py_server_name_str, &r->server_name, - r->server_name_length)); - RC(nxt_python_add_obj(ctx, nxt_py_server_port_str, nxt_py_80_str)); - - for (i = 0; i < r->fields_count; i++) { - f = r->fields + i; - - RC(nxt_python_add_field(ctx, f)); - } - - if (r->content_length_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->content_length_field; - - RC(nxt_python_add_sptr(ctx, nxt_py_content_length_str, &f->value, - f->value_length)); - } - - if (r->content_type_field != NXT_UNIT_NONE_FIELD) { - f = r->fields + r->content_type_field; - - RC(nxt_python_add_sptr(ctx, nxt_py_content_type_str, &f->value, - f->value_length)); - } - -#undef RC - - return environ; - -fail: - - Py_DECREF(environ); - - return NULL; -} - - -static int -nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, PyObject *name, - nxt_unit_sptr_t *sptr, uint32_t size) -{ - char *src; - PyObject *value; - - src = nxt_unit_sptr_get(sptr); - - value = PyString_FromStringAndSize(src, size); - if (nxt_slow_path(value == NULL)) { - nxt_unit_req_error(ctx->req, - "Python failed to create value string \"%.*s\"", - (int) size, src); - nxt_python_print_exception(); - - return NXT_UNIT_ERROR; - } - - if (nxt_slow_path(PyDict_SetItem(ctx->environ, name, value) != 0)) { - nxt_unit_req_error(ctx->req, - "Python failed to set the \"%s\" environ value", - PyUnicode_AsUTF8(name)); - Py_DECREF(value); - - return NXT_UNIT_ERROR; - } - - Py_DECREF(value); - - return NXT_UNIT_OK; -} - - -static int -nxt_python_add_field(nxt_python_run_ctx_t *ctx, nxt_unit_field_t *field) -{ - char *src; - PyObject *name, *value; - - src = nxt_unit_sptr_get(&field->name); - - name = PyString_FromStringAndSize(src, field->name_length); - if (nxt_slow_path(name == NULL)) { - nxt_unit_req_error(ctx->req, - "Python failed to create name string \"%.*s\"", - (int) field->name_length, src); - nxt_python_print_exception(); - - return NXT_UNIT_ERROR; - } - - src = nxt_unit_sptr_get(&field->value); - - value = PyString_FromStringAndSize(src, field->value_length); - if (nxt_slow_path(value == NULL)) { - nxt_unit_req_error(ctx->req, - "Python failed to create value string \"%.*s\"", - (int) field->value_length, src); - nxt_python_print_exception(); - - goto fail; - } - - if (nxt_slow_path(PyDict_SetItem(ctx->environ, name, value) != 0)) { - nxt_unit_req_error(ctx->req, - "Python failed to set the \"%s\" environ value", - PyUnicode_AsUTF8(name)); - goto fail; - } - - Py_DECREF(name); - Py_DECREF(value); - - return NXT_UNIT_OK; - -fail: - - Py_DECREF(name); - Py_XDECREF(value); - - return NXT_UNIT_ERROR; -} - - -static int -nxt_python_add_obj(nxt_python_run_ctx_t *ctx, PyObject *name, PyObject *value) -{ - if (nxt_slow_path(PyDict_SetItem(ctx->environ, name, value) != 0)) { - nxt_unit_req_error(ctx->req, - "Python failed to set the \"%s\" environ value", - PyUnicode_AsUTF8(name)); - - return NXT_UNIT_ERROR; - } - - return NXT_UNIT_OK; -} - - -static PyObject * -nxt_py_start_resp(PyObject *self, PyObject *args) -{ - int rc, status; - char *status_str, *space_ptr; - uint32_t status_len; - PyObject *headers, *tuple, *string, *status_bytes; - Py_ssize_t i, n, fields_size, fields_count; - nxt_python_run_ctx_t *ctx; - - ctx = nxt_python_run_ctx; - if (nxt_slow_path(ctx == NULL)) { - return PyErr_Format(PyExc_RuntimeError, - "start_response() is called " - "outside of WSGI request processing"); - } - - n = PyTuple_GET_SIZE(args); - - if (n < 2 || n > 3) { - return PyErr_Format(PyExc_TypeError, "invalid number of arguments"); - } - - string = PyTuple_GET_ITEM(args, 0); - if (!PyBytes_Check(string) && !PyUnicode_Check(string)) { - return PyErr_Format(PyExc_TypeError, - "failed to write first argument (not a string?)"); - } - - headers = PyTuple_GET_ITEM(args, 1); - if (!PyList_Check(headers)) { - return PyErr_Format(PyExc_TypeError, - "the second argument is not a response headers list"); - } - - fields_size = 0; - fields_count = PyList_GET_SIZE(headers); - - for (i = 0; i < fields_count; i++) { - tuple = PyList_GET_ITEM(headers, i); - - if (!PyTuple_Check(tuple)) { - return PyErr_Format(PyExc_TypeError, - "the response headers must be a list of tuples"); - } - - if (PyTuple_GET_SIZE(tuple) != 2) { - return PyErr_Format(PyExc_TypeError, - "each header must be a tuple of two items"); - } - - string = PyTuple_GET_ITEM(tuple, 0); - if (PyBytes_Check(string)) { - fields_size += PyBytes_GET_SIZE(string); - - } else if (PyUnicode_Check(string)) { - fields_size += PyUnicode_GET_SIZE(string); - - } else { - return PyErr_Format(PyExc_TypeError, - "header #%d name is not a string", (int) i); - } - - string = PyTuple_GET_ITEM(tuple, 1); - if (PyBytes_Check(string)) { - fields_size += PyBytes_GET_SIZE(string); - - } else if (PyUnicode_Check(string)) { - fields_size += PyUnicode_GET_SIZE(string); - - } else { - return PyErr_Format(PyExc_TypeError, - "header #%d value is not a string", (int) i); - } - } - - ctx->content_length = -1; - - string = PyTuple_GET_ITEM(args, 0); - rc = nxt_python_str_buf(string, &status_str, &status_len, &status_bytes); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - return PyErr_Format(PyExc_TypeError, "status is not a string"); - } - - space_ptr = memchr(status_str, ' ', status_len); - if (space_ptr != NULL) { - status_len = space_ptr - status_str; - } - - status = nxt_int_parse((u_char *) status_str, status_len); - if (nxt_slow_path(status < 0)) { - return PyErr_Format(PyExc_TypeError, "failed to parse status code"); - } - - Py_XDECREF(status_bytes); - - /* - * PEP 3333: - * - * ... applications can replace their originally intended output with error - * output, up until the last possible moment. - */ - rc = nxt_unit_response_init(ctx->req, status, fields_count, fields_size); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - return PyErr_Format(PyExc_RuntimeError, - "failed to allocate response object"); - } - - for (i = 0; i < fields_count; i++) { - tuple = PyList_GET_ITEM(headers, i); - - rc = nxt_python_response_add_field(ctx, PyTuple_GET_ITEM(tuple, 0), - PyTuple_GET_ITEM(tuple, 1), i); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - return PyErr_Format(PyExc_RuntimeError, - "failed to add header #%d", (int) i); - } - } - - /* - * PEP 3333: - * - * However, the start_response callable must not actually transmit the - * response headers. Instead, it must store them for the server or gateway - * to transmit only after the first iteration of the application return - * value that yields a non-empty bytestring, or upon the application's - * first invocation of the write() callable. In other words, response - * headers must not be sent until there is actual body data available, or - * until the application's returned iterable is exhausted. (The only - * possible exception to this rule is if the response headers explicitly - * include a Content-Length of zero.) - */ - if (ctx->content_length == 0) { - rc = nxt_unit_response_send(ctx->req); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - return PyErr_Format(PyExc_RuntimeError, - "failed to send response headers"); - } - } - - Py_INCREF(nxt_py_write_obj); - return nxt_py_write_obj; -} - - -static int -nxt_python_response_add_field(nxt_python_run_ctx_t *ctx, PyObject *name, - PyObject *value, int i) -{ - int rc; - char *name_str, *value_str; - uint32_t name_length, value_length; - PyObject *name_bytes, *value_bytes; - nxt_off_t content_length; - - name_bytes = NULL; - value_bytes = NULL; - - rc = nxt_python_str_buf(name, &name_str, &name_length, &name_bytes); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - goto fail; - } - - rc = nxt_python_str_buf(value, &value_str, &value_length, &value_bytes); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - goto fail; - } - - rc = nxt_unit_response_add_field(ctx->req, name_str, name_length, - value_str, value_length); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - goto fail; - } - - if (ctx->req->response->fields[i].hash == NXT_UNIT_HASH_CONTENT_LENGTH) { - content_length = nxt_off_t_parse((u_char *) value_str, value_length); - if (nxt_slow_path(content_length < 0)) { - nxt_unit_req_error(ctx->req, "failed to parse Content-Length " - "value %.*s", (int) value_length, value_str); - - } else { - ctx->content_length = content_length; - } - } - -fail: - - Py_XDECREF(name_bytes); - Py_XDECREF(value_bytes); - - return rc; -} - - -static int -nxt_python_str_buf(PyObject *str, char **buf, uint32_t *len, PyObject **bytes) -{ - if (PyBytes_Check(str)) { - *buf = PyBytes_AS_STRING(str); - *len = PyBytes_GET_SIZE(str); - *bytes = NULL; - - } else { - *bytes = PyUnicode_AsLatin1String(str); - if (nxt_slow_path(*bytes == NULL)) { - return NXT_UNIT_ERROR; - } - - *buf = PyBytes_AS_STRING(*bytes); - *len = PyBytes_GET_SIZE(*bytes); - } - - return NXT_UNIT_OK; -} - - -static PyObject * -nxt_py_write(PyObject *self, PyObject *str) -{ - int rc; - - if (nxt_fast_path(!PyBytes_Check(str))) { - return PyErr_Format(PyExc_TypeError, "the argument is not a %s", - NXT_PYTHON_BYTES_TYPE); - } - - rc = nxt_python_write(nxt_python_run_ctx, str); - if (nxt_slow_path(rc != NXT_UNIT_OK)) { - return PyErr_Format(PyExc_RuntimeError, - "failed to write response value"); - } - - Py_RETURN_NONE; -} - - -static void -nxt_py_input_dealloc(nxt_py_input_t *self) -{ - PyObject_Del(self); -} - - -static PyObject * -nxt_py_input_read(nxt_py_input_t *self, PyObject *args) -{ - char *buf; - PyObject *content, *obj; - Py_ssize_t size, n; - nxt_python_run_ctx_t *ctx; - - ctx = nxt_python_run_ctx; - if (nxt_slow_path(ctx == NULL)) { - return PyErr_Format(PyExc_RuntimeError, - "wsgi.input.read() is called " - "outside of WSGI request processing"); - } - - size = ctx->req->content_length; - - n = PyTuple_GET_SIZE(args); - - if (n > 0) { - if (n != 1) { - return PyErr_Format(PyExc_TypeError, "invalid number of arguments"); - } - - obj = PyTuple_GET_ITEM(args, 0); - - size = PyNumber_AsSsize_t(obj, PyExc_OverflowError); - - if (nxt_slow_path(size < 0)) { - if (size == -1 && PyErr_Occurred()) { - return NULL; - } - - if (size != -1) { - return PyErr_Format(PyExc_ValueError, - "the read body size cannot be zero or less"); - } - } - - if (size == -1 || size > (Py_ssize_t) ctx->req->content_length) { - size = ctx->req->content_length; - } - } - - content = PyBytes_FromStringAndSize(NULL, size); - if (nxt_slow_path(content == NULL)) { - return NULL; - } - - buf = PyBytes_AS_STRING(content); - - size = nxt_unit_request_read(ctx->req, buf, size); - - return content; -} - - -static PyObject * -nxt_py_input_readline(nxt_py_input_t *self, PyObject *args) -{ - ssize_t ssize; - PyObject *obj; - Py_ssize_t n; - nxt_python_run_ctx_t *ctx; - - ctx = nxt_python_run_ctx; - if (nxt_slow_path(ctx == NULL)) { - return PyErr_Format(PyExc_RuntimeError, - "wsgi.input.readline() is called " - "outside of WSGI request processing"); - } - - n = PyTuple_GET_SIZE(args); - - if (n > 0) { - if (n != 1) { - return PyErr_Format(PyExc_TypeError, "invalid number of arguments"); - } - - obj = PyTuple_GET_ITEM(args, 0); - - ssize = PyNumber_AsSsize_t(obj, PyExc_OverflowError); - - if (nxt_fast_path(ssize > 0)) { - return nxt_py_input_getline(ctx, ssize); - } - - if (ssize == 0) { - return PyBytes_FromStringAndSize("", 0); - } - - if (ssize != -1) { - return PyErr_Format(PyExc_ValueError, - "the read line size cannot be zero or less"); - } - - if (PyErr_Occurred()) { - return NULL; - } - } - - return nxt_py_input_getline(ctx, SSIZE_MAX); -} - - -static PyObject * -nxt_py_input_getline(nxt_python_run_ctx_t *ctx, size_t size) -{ - void *buf; - ssize_t res; - PyObject *content; - - res = nxt_unit_request_readline_size(ctx->req, size); - if (nxt_slow_path(res < 0)) { - return NULL; - } - - if (res == 0) { - return PyBytes_FromStringAndSize("", 0); - } - - content = PyBytes_FromStringAndSize(NULL, res); - if (nxt_slow_path(content == NULL)) { - return NULL; - } - - buf = PyBytes_AS_STRING(content); - - res = nxt_unit_request_read(ctx->req, buf, res); - - return content; -} - - -static PyObject * -nxt_py_input_readlines(nxt_py_input_t *self, PyObject *args) -{ - PyObject *res; - nxt_python_run_ctx_t *ctx; - - ctx = nxt_python_run_ctx; - if (nxt_slow_path(ctx == NULL)) { - return PyErr_Format(PyExc_RuntimeError, - "wsgi.input.readlines() is called " - "outside of WSGI request processing"); - } - - res = PyList_New(0); - if (nxt_slow_path(res == NULL)) { - return NULL; - } - - for ( ;; ) { - PyObject *line = nxt_py_input_getline(ctx, SSIZE_MAX); - if (nxt_slow_path(line == NULL)) { - Py_DECREF(res); - return NULL; - } - - if (PyBytes_GET_SIZE(line) == 0) { - Py_DECREF(line); - return res; - } - - PyList_Append(res, line); - Py_DECREF(line); - } - - return res; -} - - -static PyObject * -nxt_py_input_iter(PyObject *self) -{ - Py_INCREF(self); - return self; -} - - -static PyObject * -nxt_py_input_next(PyObject *self) -{ - PyObject *line; - nxt_python_run_ctx_t *ctx; - - ctx = nxt_python_run_ctx; - if (nxt_slow_path(ctx == NULL)) { - return PyErr_Format(PyExc_RuntimeError, - "wsgi.input.next() is called " - "outside of WSGI request processing"); - } - - line = nxt_py_input_getline(ctx, SSIZE_MAX); - if (nxt_slow_path(line == NULL)) { - return NULL; - } - - if (PyBytes_GET_SIZE(line) == 0) { - Py_DECREF(line); - PyErr_SetNone(PyExc_StopIteration); - return NULL; - } - - return line; -} - - -static void -nxt_python_print_exception(void) -{ - PyErr_Print(); - -#if PY_MAJOR_VERSION == 3 - /* The backtrace may be buffered in sys.stderr file object. */ - { - PyObject *result; - - result = PyObject_CallFunction(nxt_py_stderr_flush, NULL); - if (nxt_slow_path(result == NULL)) { - PyErr_Clear(); - return; - } - - Py_DECREF(result); - } -#endif -} - - -static int -nxt_python_write(nxt_python_run_ctx_t *ctx, PyObject *bytes) -{ - int rc; - char *str_buf; - uint32_t str_length; - - str_buf = PyBytes_AS_STRING(bytes); - str_length = PyBytes_GET_SIZE(bytes); - - if (nxt_slow_path(str_length == 0)) { - return NXT_UNIT_OK; - } - - /* - * PEP 3333: - * - * If the application supplies a Content-Length header, the server should - * not transmit more bytes to the client than the header allows, and should - * stop iterating over the response when enough data has been sent, or raise - * an error if the application tries to write() past that point. - */ - if (nxt_slow_path(str_length > ctx->content_length - ctx->bytes_sent)) { - nxt_unit_req_error(ctx->req, "content length %"PRIu64" exceeded", - ctx->content_length); - - return NXT_UNIT_ERROR; - } - - rc = nxt_unit_response_write(ctx->req, str_buf, str_length); - if (nxt_fast_path(rc == NXT_UNIT_OK)) { - ctx->bytes_sent += str_length; - } - - return rc; -} diff --git a/src/python/nxt_python_wsgi.c b/src/python/nxt_python_wsgi.c new file mode 100644 index 00000000..c4b7702e --- /dev/null +++ b/src/python/nxt_python_wsgi.c @@ -0,0 +1,1446 @@ + +/* + * Copyright (C) Max Romanov + * Copyright (C) Valentin V. Bartenev + * Copyright (C) NGINX, Inc. + */ + + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include NXT_PYTHON_MOUNTS_H + +/* + * According to "PEP 3333 / A Note On String Types" + * [https://www.python.org/dev/peps/pep-3333/#a-note-on-string-types] + * + * WSGI therefore defines two kinds of "string": + * + * - "Native" strings (which are always implemented using the type named str ) + * that are used for request/response headers and metadata + * + * will use PyString_* or corresponding PyUnicode_* functions + * + * - "Bytestrings" (which are implemented using the bytes type in Python 3, and + * str elsewhere), that are used for the bodies of requests and responses + * (e.g. POST/PUT input data and HTML page outputs). + * + * will use PyString_* or corresponding PyBytes_* functions + */ + + +#if PY_MAJOR_VERSION == 3 +#define NXT_PYTHON_BYTES_TYPE "bytestring" + +#define PyString_FromStringAndSize(str, size) \ + PyUnicode_DecodeLatin1((str), (size), "strict") + +#else +#define NXT_PYTHON_BYTES_TYPE "string" + +#define PyBytes_FromStringAndSize PyString_FromStringAndSize +#define PyBytes_Check PyString_Check +#define PyBytes_GET_SIZE PyString_GET_SIZE +#define PyBytes_AS_STRING PyString_AS_STRING +#define PyUnicode_InternInPlace PyString_InternInPlace +#define PyUnicode_AsUTF8 PyString_AS_STRING +#endif + +typedef struct nxt_python_run_ctx_s nxt_python_run_ctx_t; + +typedef struct { + PyObject_HEAD +} nxt_py_input_t; + + +typedef struct { + PyObject_HEAD +} nxt_py_error_t; + +static nxt_int_t nxt_python_start(nxt_task_t *task, + nxt_process_data_t *data); +static nxt_int_t nxt_python_init_strings(void); +static void nxt_python_request_handler(nxt_unit_request_info_t *req); +static void nxt_python_atexit(void); + +static PyObject *nxt_python_create_environ(nxt_task_t *task); +static PyObject *nxt_python_get_environ(nxt_python_run_ctx_t *ctx); +static int nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, PyObject *name, + nxt_unit_sptr_t *sptr, uint32_t size); +static int nxt_python_add_field(nxt_python_run_ctx_t *ctx, + nxt_unit_field_t *field); +static int nxt_python_add_obj(nxt_python_run_ctx_t *ctx, PyObject *name, + PyObject *value); + +static PyObject *nxt_py_start_resp(PyObject *self, PyObject *args); +static int nxt_python_response_add_field(nxt_python_run_ctx_t *ctx, + PyObject *name, PyObject *value, int i); +static int nxt_python_str_buf(PyObject *str, char **buf, uint32_t *len, + PyObject **bytes); +static PyObject *nxt_py_write(PyObject *self, PyObject *args); + +static void nxt_py_input_dealloc(nxt_py_input_t *self); +static PyObject *nxt_py_input_read(nxt_py_input_t *self, PyObject *args); +static PyObject *nxt_py_input_readline(nxt_py_input_t *self, PyObject *args); +static PyObject *nxt_py_input_getline(nxt_python_run_ctx_t *ctx, size_t size); +static PyObject *nxt_py_input_readlines(nxt_py_input_t *self, PyObject *args); + +static PyObject *nxt_py_input_iter(PyObject *self); +static PyObject *nxt_py_input_next(PyObject *self); + +static void nxt_python_print_exception(void); +static int nxt_python_write(nxt_python_run_ctx_t *ctx, PyObject *bytes); + +struct nxt_python_run_ctx_s { + uint64_t content_length; + uint64_t bytes_sent; + PyObject *environ; + nxt_unit_request_info_t *req; +}; + +static uint32_t compat[] = { + NXT_VERNUM, NXT_DEBUG, +}; + + +NXT_EXPORT nxt_app_module_t nxt_app_module = { + sizeof(compat), + compat, + nxt_string("python"), + PY_VERSION, + nxt_python_mounts, + nxt_nitems(nxt_python_mounts), + NULL, + nxt_python_start, +}; + + +static PyMethodDef nxt_py_start_resp_method[] = { + {"unit_start_response", nxt_py_start_resp, METH_VARARGS, ""} +}; + + +static PyMethodDef nxt_py_write_method[] = { + {"unit_write", nxt_py_write, METH_O, ""} +}; + + +static PyMethodDef nxt_py_input_methods[] = { + { "read", (PyCFunction) nxt_py_input_read, METH_VARARGS, 0 }, + { "readline", (PyCFunction) nxt_py_input_readline, METH_VARARGS, 0 }, + { "readlines", (PyCFunction) nxt_py_input_readlines, METH_VARARGS, 0 }, + { NULL, NULL, 0, 0 } +}; + + +static PyTypeObject nxt_py_input_type = { + PyVarObject_HEAD_INIT(NULL, 0) + + .tp_name = "unit._input", + .tp_basicsize = sizeof(nxt_py_input_t), + .tp_dealloc = (destructor) nxt_py_input_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "unit input object.", + .tp_iter = nxt_py_input_iter, + .tp_iternext = nxt_py_input_next, + .tp_methods = nxt_py_input_methods, +}; + + +static PyObject *nxt_py_stderr_flush; +static PyObject *nxt_py_application; +static PyObject *nxt_py_start_resp_obj; +static PyObject *nxt_py_write_obj; +static PyObject *nxt_py_environ_ptyp; + +#if PY_MAJOR_VERSION == 3 +static wchar_t *nxt_py_home; +#else +static char *nxt_py_home; +#endif + +static PyThreadState *nxt_python_thread_state; +static nxt_python_run_ctx_t *nxt_python_run_ctx; + + +static PyObject *nxt_py_80_str; +static PyObject *nxt_py_close_str; +static PyObject *nxt_py_content_length_str; +static PyObject *nxt_py_content_type_str; +static PyObject *nxt_py_http_str; +static PyObject *nxt_py_https_str; +static PyObject *nxt_py_path_info_str; +static PyObject *nxt_py_query_string_str; +static PyObject *nxt_py_remote_addr_str; +static PyObject *nxt_py_request_method_str; +static PyObject *nxt_py_request_uri_str; +static PyObject *nxt_py_server_addr_str; +static PyObject *nxt_py_server_name_str; +static PyObject *nxt_py_server_port_str; +static PyObject *nxt_py_server_protocol_str; +static PyObject *nxt_py_wsgi_uri_scheme_str; + +typedef struct { + nxt_str_t string; + PyObject **object_p; +} nxt_python_string_t; + +static nxt_python_string_t nxt_python_strings[] = { + { nxt_string("80"), &nxt_py_80_str }, + { nxt_string("close"), &nxt_py_close_str }, + { nxt_string("CONTENT_LENGTH"), &nxt_py_content_length_str }, + { nxt_string("CONTENT_TYPE"), &nxt_py_content_type_str }, + { nxt_string("http"), &nxt_py_http_str }, + { nxt_string("https"), &nxt_py_https_str }, + { nxt_string("PATH_INFO"), &nxt_py_path_info_str }, + { nxt_string("QUERY_STRING"), &nxt_py_query_string_str }, + { nxt_string("REMOTE_ADDR"), &nxt_py_remote_addr_str }, + { nxt_string("REQUEST_METHOD"), &nxt_py_request_method_str }, + { nxt_string("REQUEST_URI"), &nxt_py_request_uri_str }, + { nxt_string("SERVER_ADDR"), &nxt_py_server_addr_str }, + { nxt_string("SERVER_NAME"), &nxt_py_server_name_str }, + { nxt_string("SERVER_PORT"), &nxt_py_server_port_str }, + { nxt_string("SERVER_PROTOCOL"), &nxt_py_server_protocol_str }, + { nxt_string("wsgi.url_scheme"), &nxt_py_wsgi_uri_scheme_str }, +}; + + +static nxt_int_t +nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) +{ + int rc; + char *nxt_py_module; + size_t len; + PyObject *obj, *pypath, *module; + nxt_unit_ctx_t *unit_ctx; + nxt_unit_init_t python_init; + nxt_common_app_conf_t *app_conf; + nxt_python_app_conf_t *c; +#if PY_MAJOR_VERSION == 3 + char *path; + size_t size; + nxt_int_t pep405; + + static const char pyvenv[] = "/pyvenv.cfg"; + static const char bin_python[] = "/bin/python"; +#endif + + app_conf = data->app; + c = &app_conf->u.python; + + if (c->home != NULL) { + len = nxt_strlen(c->home); + +#if PY_MAJOR_VERSION == 3 + + path = nxt_malloc(len + sizeof(pyvenv)); + if (nxt_slow_path(path == NULL)) { + nxt_alert(task, "Failed to allocate memory"); + return NXT_ERROR; + } + + nxt_memcpy(path, c->home, len); + nxt_memcpy(path + len, pyvenv, sizeof(pyvenv)); + + pep405 = (access(path, R_OK) == 0); + + nxt_free(path); + + if (pep405) { + size = (len + sizeof(bin_python)) * sizeof(wchar_t); + + } else { + size = (len + 1) * sizeof(wchar_t); + } + + nxt_py_home = nxt_malloc(size); + if (nxt_slow_path(nxt_py_home == NULL)) { + nxt_alert(task, "Failed to allocate memory"); + return NXT_ERROR; + } + + if (pep405) { + mbstowcs(nxt_py_home, c->home, len); + mbstowcs(nxt_py_home + len, bin_python, sizeof(bin_python)); + Py_SetProgramName(nxt_py_home); + + } else { + mbstowcs(nxt_py_home, c->home, len + 1); + Py_SetPythonHome(nxt_py_home); + } + +#else + nxt_py_home = nxt_malloc(len + 1); + if (nxt_slow_path(nxt_py_home == NULL)) { + nxt_alert(task, "Failed to allocate memory"); + return NXT_ERROR; + } + + nxt_memcpy(nxt_py_home, c->home, len + 1); + Py_SetPythonHome(nxt_py_home); +#endif + } + + Py_InitializeEx(0); + + module = NULL; + obj = NULL; + + if (nxt_slow_path(nxt_python_init_strings() != NXT_OK)) { + nxt_alert(task, "Python failed to init string objects"); + goto fail; + } + + obj = PySys_GetObject((char *) "stderr"); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to get \"sys.stderr\" object"); + goto fail; + } + + nxt_py_stderr_flush = PyObject_GetAttrString(obj, "flush"); + if (nxt_slow_path(nxt_py_stderr_flush == NULL)) { + nxt_alert(task, "Python failed to get \"flush\" attribute of " + "\"sys.stderr\" object"); + goto fail; + } + + Py_DECREF(obj); + + if (c->path.length > 0) { + obj = PyString_FromStringAndSize((char *) c->path.start, + c->path.length); + + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to create string object \"%V\"", + &c->path); + goto fail; + } + + pypath = PySys_GetObject((char *) "path"); + + if (nxt_slow_path(pypath == NULL)) { + nxt_alert(task, "Python failed to get \"sys.path\" list"); + goto fail; + } + + if (nxt_slow_path(PyList_Insert(pypath, 0, obj) != 0)) { + nxt_alert(task, "Python failed to insert \"%V\" into \"sys.path\"", + &c->path); + goto fail; + } + + Py_DECREF(obj); + } + + obj = PyCFunction_New(nxt_py_start_resp_method, NULL); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, + "Python failed to initialize the \"start_response\" function"); + goto fail; + } + + nxt_py_start_resp_obj = obj; + + obj = PyCFunction_New(nxt_py_write_method, NULL); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to initialize the \"write\" function"); + goto fail; + } + + nxt_py_write_obj = obj; + + obj = nxt_python_create_environ(task); + if (nxt_slow_path(obj == NULL)) { + goto fail; + } + + nxt_py_environ_ptyp = obj; + + obj = Py_BuildValue("[s]", "unit"); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to create the \"sys.argv\" list"); + goto fail; + } + + if (nxt_slow_path(PySys_SetObject((char *) "argv", obj) != 0)) { + nxt_alert(task, "Python failed to set the \"sys.argv\" list"); + goto fail; + } + + Py_CLEAR(obj); + + nxt_py_module = nxt_alloca(c->module.length + 1); + nxt_memcpy(nxt_py_module, c->module.start, c->module.length); + nxt_py_module[c->module.length] = '\0'; + + module = PyImport_ImportModule(nxt_py_module); + if (nxt_slow_path(module == NULL)) { + nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); + nxt_python_print_exception(); + goto fail; + } + + obj = PyDict_GetItemString(PyModule_GetDict(module), "application"); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to get \"application\" " + "from module \"%s\"", nxt_py_module); + goto fail; + } + + if (nxt_slow_path(PyCallable_Check(obj) == 0)) { + nxt_alert(task, "\"application\" in module \"%s\" " + "is not a callable object", nxt_py_module); + goto fail; + } + + Py_INCREF(obj); + Py_CLEAR(module); + + nxt_py_application = obj; + obj = NULL; + + nxt_unit_default_init(task, &python_init); + + python_init.callbacks.request_handler = nxt_python_request_handler; + python_init.shm_limit = data->app->shm_limit; + + unit_ctx = nxt_unit_init(&python_init); + if (nxt_slow_path(unit_ctx == NULL)) { + goto fail; + } + + nxt_python_thread_state = PyEval_SaveThread(); + + rc = nxt_unit_run(unit_ctx); + + nxt_unit_done(unit_ctx); + + PyEval_RestoreThread(nxt_python_thread_state); + + nxt_python_atexit(); + + exit(rc); + + return NXT_OK; + +fail: + + Py_XDECREF(obj); + Py_XDECREF(module); + + nxt_python_atexit(); + + return NXT_ERROR; +} + + +static nxt_int_t +nxt_python_init_strings(void) +{ + PyObject *obj; + nxt_uint_t i; + nxt_python_string_t *pstr; + + for (i = 0; i < nxt_nitems(nxt_python_strings); i++) { + pstr = &nxt_python_strings[i]; + + obj = PyString_FromStringAndSize((char *) pstr->string.start, + pstr->string.length); + if (nxt_slow_path(obj == NULL)) { + return NXT_ERROR; + } + + PyUnicode_InternInPlace(&obj); + + *pstr->object_p = obj; + } + + return NXT_OK; +} + + +static void +nxt_python_request_handler(nxt_unit_request_info_t *req) +{ + int rc; + PyObject *environ, *args, *response, *iterator, *item; + PyObject *close, *result; + nxt_python_run_ctx_t run_ctx = {-1, 0, NULL, req}; + + PyEval_RestoreThread(nxt_python_thread_state); + + environ = nxt_python_get_environ(&run_ctx); + if (nxt_slow_path(environ == NULL)) { + rc = NXT_UNIT_ERROR; + goto done; + } + + args = PyTuple_New(2); + if (nxt_slow_path(args == NULL)) { + Py_DECREF(environ); + + nxt_unit_req_error(req, "Python failed to create arguments tuple"); + + rc = NXT_UNIT_ERROR; + goto done; + } + + PyTuple_SET_ITEM(args, 0, environ); + + Py_INCREF(nxt_py_start_resp_obj); + PyTuple_SET_ITEM(args, 1, nxt_py_start_resp_obj); + + nxt_python_run_ctx = &run_ctx; + + response = PyObject_CallObject(nxt_py_application, args); + + Py_DECREF(args); + + if (nxt_slow_path(response == NULL)) { + nxt_unit_req_error(req, "Python failed to call the application"); + nxt_python_print_exception(); + + rc = NXT_UNIT_ERROR; + goto done; + } + + /* Shortcut: avoid iterate over response string symbols. */ + if (PyBytes_Check(response)) { + rc = nxt_python_write(&run_ctx, response); + + } else { + iterator = PyObject_GetIter(response); + + if (nxt_fast_path(iterator != NULL)) { + rc = NXT_UNIT_OK; + + while (run_ctx.bytes_sent < run_ctx.content_length) { + item = PyIter_Next(iterator); + + if (item == NULL) { + if (nxt_slow_path(PyErr_Occurred() != NULL)) { + nxt_unit_req_error(req, "Python failed to iterate over " + "the application response object"); + nxt_python_print_exception(); + + rc = NXT_UNIT_ERROR; + } + + break; + } + + if (nxt_fast_path(PyBytes_Check(item))) { + rc = nxt_python_write(&run_ctx, item); + + } else { + nxt_unit_req_error(req, "the application returned " + "not a bytestring object"); + rc = NXT_UNIT_ERROR; + } + + Py_DECREF(item); + + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + break; + } + } + + Py_DECREF(iterator); + + } else { + nxt_unit_req_error(req, + "the application returned not an iterable object"); + nxt_python_print_exception(); + + rc = NXT_UNIT_ERROR; + } + + close = PyObject_GetAttr(response, nxt_py_close_str); + + if (close != NULL) { + result = PyObject_CallFunction(close, NULL); + if (nxt_slow_path(result == NULL)) { + nxt_unit_req_error(req, "Python failed to call the close() " + "method of the application response"); + nxt_python_print_exception(); + + } else { + Py_DECREF(result); + } + + Py_DECREF(close); + + } else { + PyErr_Clear(); + } + } + + Py_DECREF(response); + +done: + + nxt_python_thread_state = PyEval_SaveThread(); + + nxt_python_run_ctx = NULL; + nxt_unit_request_done(req, rc); +} + + +static void +nxt_python_atexit(void) +{ + nxt_uint_t i; + + for (i = 0; i < nxt_nitems(nxt_python_strings); i++) { + Py_XDECREF(*nxt_python_strings[i].object_p); + } + + Py_XDECREF(nxt_py_stderr_flush); + Py_XDECREF(nxt_py_application); + Py_XDECREF(nxt_py_start_resp_obj); + Py_XDECREF(nxt_py_write_obj); + Py_XDECREF(nxt_py_environ_ptyp); + + Py_Finalize(); + + if (nxt_py_home != NULL) { + nxt_free(nxt_py_home); + } +} + + +static PyObject * +nxt_python_create_environ(nxt_task_t *task) +{ + PyObject *obj, *err, *environ; + + environ = PyDict_New(); + + if (nxt_slow_path(environ == NULL)) { + nxt_alert(task, "Python failed to create the \"environ\" dictionary"); + return NULL; + } + + obj = PyString_FromStringAndSize((char *) nxt_server.start, + nxt_server.length); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, + "Python failed to create the \"SERVER_SOFTWARE\" environ value"); + goto fail; + } + + if (nxt_slow_path(PyDict_SetItemString(environ, "SERVER_SOFTWARE", obj) + != 0)) + { + nxt_alert(task, + "Python failed to set the \"SERVER_SOFTWARE\" environ value"); + goto fail; + } + + Py_DECREF(obj); + + obj = Py_BuildValue("(ii)", 1, 0); + + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, + "Python failed to build the \"wsgi.version\" environ value"); + goto fail; + } + + if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.version", obj) != 0)) + { + nxt_alert(task, + "Python failed to set the \"wsgi.version\" environ value"); + goto fail; + } + + Py_DECREF(obj); + obj = NULL; + + + if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.multithread", + Py_False) + != 0)) + { + nxt_alert(task, + "Python failed to set the \"wsgi.multithread\" environ value"); + goto fail; + } + + if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.multiprocess", + Py_True) + != 0)) + { + nxt_alert(task, + "Python failed to set the \"wsgi.multiprocess\" environ value"); + goto fail; + } + + if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.run_once", + Py_False) + != 0)) + { + nxt_alert(task, + "Python failed to set the \"wsgi.run_once\" environ value"); + goto fail; + } + + + if (nxt_slow_path(PyType_Ready(&nxt_py_input_type) != 0)) { + nxt_alert(task, + "Python failed to initialize the \"wsgi.input\" type object"); + goto fail; + } + + obj = (PyObject *) PyObject_New(nxt_py_input_t, &nxt_py_input_type); + + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to create the \"wsgi.input\" object"); + goto fail; + } + + if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.input", obj) != 0)) { + nxt_alert(task, + "Python failed to set the \"wsgi.input\" environ value"); + goto fail; + } + + Py_DECREF(obj); + obj = NULL; + + + err = PySys_GetObject((char *) "stderr"); + + if (nxt_slow_path(err == NULL)) { + nxt_alert(task, "Python failed to get \"sys.stderr\" object"); + goto fail; + } + + if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.errors", err) != 0)) + { + nxt_alert(task, + "Python failed to set the \"wsgi.errors\" environ value"); + goto fail; + } + + return environ; + +fail: + + Py_XDECREF(obj); + Py_DECREF(environ); + + return NULL; +} + + +static PyObject * +nxt_python_get_environ(nxt_python_run_ctx_t *ctx) +{ + int rc; + uint32_t i; + PyObject *environ; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + environ = PyDict_Copy(nxt_py_environ_ptyp); + if (nxt_slow_path(environ == NULL)) { + nxt_unit_req_error(ctx->req, + "Python failed to copy the \"environ\" dictionary"); + + return NULL; + } + + ctx->environ = environ; + + r = ctx->req->request; + +#define RC(S) \ + do { \ + rc = (S); \ + if (nxt_slow_path(rc != NXT_UNIT_OK)) { \ + goto fail; \ + } \ + } while(0) + + RC(nxt_python_add_sptr(ctx, nxt_py_request_method_str, &r->method, + r->method_length)); + RC(nxt_python_add_sptr(ctx, nxt_py_request_uri_str, &r->target, + r->target_length)); + RC(nxt_python_add_sptr(ctx, nxt_py_query_string_str, &r->query, + r->query_length)); + RC(nxt_python_add_sptr(ctx, nxt_py_path_info_str, &r->path, + r->path_length)); + + RC(nxt_python_add_sptr(ctx, nxt_py_remote_addr_str, &r->remote, + r->remote_length)); + RC(nxt_python_add_sptr(ctx, nxt_py_server_addr_str, &r->local, + r->local_length)); + + if (r->tls) { + RC(nxt_python_add_obj(ctx, nxt_py_wsgi_uri_scheme_str, + nxt_py_https_str)); + } else { + RC(nxt_python_add_obj(ctx, nxt_py_wsgi_uri_scheme_str, + nxt_py_http_str)); + } + + RC(nxt_python_add_sptr(ctx, nxt_py_server_protocol_str, &r->version, + r->version_length)); + + RC(nxt_python_add_sptr(ctx, nxt_py_server_name_str, &r->server_name, + r->server_name_length)); + RC(nxt_python_add_obj(ctx, nxt_py_server_port_str, nxt_py_80_str)); + + for (i = 0; i < r->fields_count; i++) { + f = r->fields + i; + + RC(nxt_python_add_field(ctx, f)); + } + + if (r->content_length_field != NXT_UNIT_NONE_FIELD) { + f = r->fields + r->content_length_field; + + RC(nxt_python_add_sptr(ctx, nxt_py_content_length_str, &f->value, + f->value_length)); + } + + if (r->content_type_field != NXT_UNIT_NONE_FIELD) { + f = r->fields + r->content_type_field; + + RC(nxt_python_add_sptr(ctx, nxt_py_content_type_str, &f->value, + f->value_length)); + } + +#undef RC + + return environ; + +fail: + + Py_DECREF(environ); + + return NULL; +} + + +static int +nxt_python_add_sptr(nxt_python_run_ctx_t *ctx, PyObject *name, + nxt_unit_sptr_t *sptr, uint32_t size) +{ + char *src; + PyObject *value; + + src = nxt_unit_sptr_get(sptr); + + value = PyString_FromStringAndSize(src, size); + if (nxt_slow_path(value == NULL)) { + nxt_unit_req_error(ctx->req, + "Python failed to create value string \"%.*s\"", + (int) size, src); + nxt_python_print_exception(); + + return NXT_UNIT_ERROR; + } + + if (nxt_slow_path(PyDict_SetItem(ctx->environ, name, value) != 0)) { + nxt_unit_req_error(ctx->req, + "Python failed to set the \"%s\" environ value", + PyUnicode_AsUTF8(name)); + Py_DECREF(value); + + return NXT_UNIT_ERROR; + } + + Py_DECREF(value); + + return NXT_UNIT_OK; +} + + +static int +nxt_python_add_field(nxt_python_run_ctx_t *ctx, nxt_unit_field_t *field) +{ + char *src; + PyObject *name, *value; + + src = nxt_unit_sptr_get(&field->name); + + name = PyString_FromStringAndSize(src, field->name_length); + if (nxt_slow_path(name == NULL)) { + nxt_unit_req_error(ctx->req, + "Python failed to create name string \"%.*s\"", + (int) field->name_length, src); + nxt_python_print_exception(); + + return NXT_UNIT_ERROR; + } + + src = nxt_unit_sptr_get(&field->value); + + value = PyString_FromStringAndSize(src, field->value_length); + if (nxt_slow_path(value == NULL)) { + nxt_unit_req_error(ctx->req, + "Python failed to create value string \"%.*s\"", + (int) field->value_length, src); + nxt_python_print_exception(); + + goto fail; + } + + if (nxt_slow_path(PyDict_SetItem(ctx->environ, name, value) != 0)) { + nxt_unit_req_error(ctx->req, + "Python failed to set the \"%s\" environ value", + PyUnicode_AsUTF8(name)); + goto fail; + } + + Py_DECREF(name); + Py_DECREF(value); + + return NXT_UNIT_OK; + +fail: + + Py_DECREF(name); + Py_XDECREF(value); + + return NXT_UNIT_ERROR; +} + + +static int +nxt_python_add_obj(nxt_python_run_ctx_t *ctx, PyObject *name, PyObject *value) +{ + if (nxt_slow_path(PyDict_SetItem(ctx->environ, name, value) != 0)) { + nxt_unit_req_error(ctx->req, + "Python failed to set the \"%s\" environ value", + PyUnicode_AsUTF8(name)); + + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +static PyObject * +nxt_py_start_resp(PyObject *self, PyObject *args) +{ + int rc, status; + char *status_str, *space_ptr; + uint32_t status_len; + PyObject *headers, *tuple, *string, *status_bytes; + Py_ssize_t i, n, fields_size, fields_count; + nxt_python_run_ctx_t *ctx; + + ctx = nxt_python_run_ctx; + if (nxt_slow_path(ctx == NULL)) { + return PyErr_Format(PyExc_RuntimeError, + "start_response() is called " + "outside of WSGI request processing"); + } + + n = PyTuple_GET_SIZE(args); + + if (n < 2 || n > 3) { + return PyErr_Format(PyExc_TypeError, "invalid number of arguments"); + } + + string = PyTuple_GET_ITEM(args, 0); + if (!PyBytes_Check(string) && !PyUnicode_Check(string)) { + return PyErr_Format(PyExc_TypeError, + "failed to write first argument (not a string?)"); + } + + headers = PyTuple_GET_ITEM(args, 1); + if (!PyList_Check(headers)) { + return PyErr_Format(PyExc_TypeError, + "the second argument is not a response headers list"); + } + + fields_size = 0; + fields_count = PyList_GET_SIZE(headers); + + for (i = 0; i < fields_count; i++) { + tuple = PyList_GET_ITEM(headers, i); + + if (!PyTuple_Check(tuple)) { + return PyErr_Format(PyExc_TypeError, + "the response headers must be a list of tuples"); + } + + if (PyTuple_GET_SIZE(tuple) != 2) { + return PyErr_Format(PyExc_TypeError, + "each header must be a tuple of two items"); + } + + string = PyTuple_GET_ITEM(tuple, 0); + if (PyBytes_Check(string)) { + fields_size += PyBytes_GET_SIZE(string); + + } else if (PyUnicode_Check(string)) { + fields_size += PyUnicode_GET_SIZE(string); + + } else { + return PyErr_Format(PyExc_TypeError, + "header #%d name is not a string", (int) i); + } + + string = PyTuple_GET_ITEM(tuple, 1); + if (PyBytes_Check(string)) { + fields_size += PyBytes_GET_SIZE(string); + + } else if (PyUnicode_Check(string)) { + fields_size += PyUnicode_GET_SIZE(string); + + } else { + return PyErr_Format(PyExc_TypeError, + "header #%d value is not a string", (int) i); + } + } + + ctx->content_length = -1; + + string = PyTuple_GET_ITEM(args, 0); + rc = nxt_python_str_buf(string, &status_str, &status_len, &status_bytes); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return PyErr_Format(PyExc_TypeError, "status is not a string"); + } + + space_ptr = memchr(status_str, ' ', status_len); + if (space_ptr != NULL) { + status_len = space_ptr - status_str; + } + + status = nxt_int_parse((u_char *) status_str, status_len); + if (nxt_slow_path(status < 0)) { + return PyErr_Format(PyExc_TypeError, "failed to parse status code"); + } + + Py_XDECREF(status_bytes); + + /* + * PEP 3333: + * + * ... applications can replace their originally intended output with error + * output, up until the last possible moment. + */ + rc = nxt_unit_response_init(ctx->req, status, fields_count, fields_size); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return PyErr_Format(PyExc_RuntimeError, + "failed to allocate response object"); + } + + for (i = 0; i < fields_count; i++) { + tuple = PyList_GET_ITEM(headers, i); + + rc = nxt_python_response_add_field(ctx, PyTuple_GET_ITEM(tuple, 0), + PyTuple_GET_ITEM(tuple, 1), i); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return PyErr_Format(PyExc_RuntimeError, + "failed to add header #%d", (int) i); + } + } + + /* + * PEP 3333: + * + * However, the start_response callable must not actually transmit the + * response headers. Instead, it must store them for the server or gateway + * to transmit only after the first iteration of the application return + * value that yields a non-empty bytestring, or upon the application's + * first invocation of the write() callable. In other words, response + * headers must not be sent until there is actual body data available, or + * until the application's returned iterable is exhausted. (The only + * possible exception to this rule is if the response headers explicitly + * include a Content-Length of zero.) + */ + if (ctx->content_length == 0) { + rc = nxt_unit_response_send(ctx->req); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return PyErr_Format(PyExc_RuntimeError, + "failed to send response headers"); + } + } + + Py_INCREF(nxt_py_write_obj); + return nxt_py_write_obj; +} + + +static int +nxt_python_response_add_field(nxt_python_run_ctx_t *ctx, PyObject *name, + PyObject *value, int i) +{ + int rc; + char *name_str, *value_str; + uint32_t name_length, value_length; + PyObject *name_bytes, *value_bytes; + nxt_off_t content_length; + + name_bytes = NULL; + value_bytes = NULL; + + rc = nxt_python_str_buf(name, &name_str, &name_length, &name_bytes); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + goto fail; + } + + rc = nxt_python_str_buf(value, &value_str, &value_length, &value_bytes); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + goto fail; + } + + rc = nxt_unit_response_add_field(ctx->req, name_str, name_length, + value_str, value_length); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + goto fail; + } + + if (ctx->req->response->fields[i].hash == NXT_UNIT_HASH_CONTENT_LENGTH) { + content_length = nxt_off_t_parse((u_char *) value_str, value_length); + if (nxt_slow_path(content_length < 0)) { + nxt_unit_req_error(ctx->req, "failed to parse Content-Length " + "value %.*s", (int) value_length, value_str); + + } else { + ctx->content_length = content_length; + } + } + +fail: + + Py_XDECREF(name_bytes); + Py_XDECREF(value_bytes); + + return rc; +} + + +static int +nxt_python_str_buf(PyObject *str, char **buf, uint32_t *len, PyObject **bytes) +{ + if (PyBytes_Check(str)) { + *buf = PyBytes_AS_STRING(str); + *len = PyBytes_GET_SIZE(str); + *bytes = NULL; + + } else { + *bytes = PyUnicode_AsLatin1String(str); + if (nxt_slow_path(*bytes == NULL)) { + return NXT_UNIT_ERROR; + } + + *buf = PyBytes_AS_STRING(*bytes); + *len = PyBytes_GET_SIZE(*bytes); + } + + return NXT_UNIT_OK; +} + + +static PyObject * +nxt_py_write(PyObject *self, PyObject *str) +{ + int rc; + + if (nxt_fast_path(!PyBytes_Check(str))) { + return PyErr_Format(PyExc_TypeError, "the argument is not a %s", + NXT_PYTHON_BYTES_TYPE); + } + + rc = nxt_python_write(nxt_python_run_ctx, str); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + return PyErr_Format(PyExc_RuntimeError, + "failed to write response value"); + } + + Py_RETURN_NONE; +} + + +static void +nxt_py_input_dealloc(nxt_py_input_t *self) +{ + PyObject_Del(self); +} + + +static PyObject * +nxt_py_input_read(nxt_py_input_t *self, PyObject *args) +{ + char *buf; + PyObject *content, *obj; + Py_ssize_t size, n; + nxt_python_run_ctx_t *ctx; + + ctx = nxt_python_run_ctx; + if (nxt_slow_path(ctx == NULL)) { + return PyErr_Format(PyExc_RuntimeError, + "wsgi.input.read() is called " + "outside of WSGI request processing"); + } + + size = ctx->req->content_length; + + n = PyTuple_GET_SIZE(args); + + if (n > 0) { + if (n != 1) { + return PyErr_Format(PyExc_TypeError, "invalid number of arguments"); + } + + obj = PyTuple_GET_ITEM(args, 0); + + size = PyNumber_AsSsize_t(obj, PyExc_OverflowError); + + if (nxt_slow_path(size < 0)) { + if (size == -1 && PyErr_Occurred()) { + return NULL; + } + + if (size != -1) { + return PyErr_Format(PyExc_ValueError, + "the read body size cannot be zero or less"); + } + } + + if (size == -1 || size > (Py_ssize_t) ctx->req->content_length) { + size = ctx->req->content_length; + } + } + + content = PyBytes_FromStringAndSize(NULL, size); + if (nxt_slow_path(content == NULL)) { + return NULL; + } + + buf = PyBytes_AS_STRING(content); + + size = nxt_unit_request_read(ctx->req, buf, size); + + return content; +} + + +static PyObject * +nxt_py_input_readline(nxt_py_input_t *self, PyObject *args) +{ + ssize_t ssize; + PyObject *obj; + Py_ssize_t n; + nxt_python_run_ctx_t *ctx; + + ctx = nxt_python_run_ctx; + if (nxt_slow_path(ctx == NULL)) { + return PyErr_Format(PyExc_RuntimeError, + "wsgi.input.readline() is called " + "outside of WSGI request processing"); + } + + n = PyTuple_GET_SIZE(args); + + if (n > 0) { + if (n != 1) { + return PyErr_Format(PyExc_TypeError, "invalid number of arguments"); + } + + obj = PyTuple_GET_ITEM(args, 0); + + ssize = PyNumber_AsSsize_t(obj, PyExc_OverflowError); + + if (nxt_fast_path(ssize > 0)) { + return nxt_py_input_getline(ctx, ssize); + } + + if (ssize == 0) { + return PyBytes_FromStringAndSize("", 0); + } + + if (ssize != -1) { + return PyErr_Format(PyExc_ValueError, + "the read line size cannot be zero or less"); + } + + if (PyErr_Occurred()) { + return NULL; + } + } + + return nxt_py_input_getline(ctx, SSIZE_MAX); +} + + +static PyObject * +nxt_py_input_getline(nxt_python_run_ctx_t *ctx, size_t size) +{ + void *buf; + ssize_t res; + PyObject *content; + + res = nxt_unit_request_readline_size(ctx->req, size); + if (nxt_slow_path(res < 0)) { + return NULL; + } + + if (res == 0) { + return PyBytes_FromStringAndSize("", 0); + } + + content = PyBytes_FromStringAndSize(NULL, res); + if (nxt_slow_path(content == NULL)) { + return NULL; + } + + buf = PyBytes_AS_STRING(content); + + res = nxt_unit_request_read(ctx->req, buf, res); + + return content; +} + + +static PyObject * +nxt_py_input_readlines(nxt_py_input_t *self, PyObject *args) +{ + PyObject *res; + nxt_python_run_ctx_t *ctx; + + ctx = nxt_python_run_ctx; + if (nxt_slow_path(ctx == NULL)) { + return PyErr_Format(PyExc_RuntimeError, + "wsgi.input.readlines() is called " + "outside of WSGI request processing"); + } + + res = PyList_New(0); + if (nxt_slow_path(res == NULL)) { + return NULL; + } + + for ( ;; ) { + PyObject *line = nxt_py_input_getline(ctx, SSIZE_MAX); + if (nxt_slow_path(line == NULL)) { + Py_DECREF(res); + return NULL; + } + + if (PyBytes_GET_SIZE(line) == 0) { + Py_DECREF(line); + return res; + } + + PyList_Append(res, line); + Py_DECREF(line); + } + + return res; +} + + +static PyObject * +nxt_py_input_iter(PyObject *self) +{ + Py_INCREF(self); + return self; +} + + +static PyObject * +nxt_py_input_next(PyObject *self) +{ + PyObject *line; + nxt_python_run_ctx_t *ctx; + + ctx = nxt_python_run_ctx; + if (nxt_slow_path(ctx == NULL)) { + return PyErr_Format(PyExc_RuntimeError, + "wsgi.input.next() is called " + "outside of WSGI request processing"); + } + + line = nxt_py_input_getline(ctx, SSIZE_MAX); + if (nxt_slow_path(line == NULL)) { + return NULL; + } + + if (PyBytes_GET_SIZE(line) == 0) { + Py_DECREF(line); + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } + + return line; +} + + +static void +nxt_python_print_exception(void) +{ + PyErr_Print(); + +#if PY_MAJOR_VERSION == 3 + /* The backtrace may be buffered in sys.stderr file object. */ + { + PyObject *result; + + result = PyObject_CallFunction(nxt_py_stderr_flush, NULL); + if (nxt_slow_path(result == NULL)) { + PyErr_Clear(); + return; + } + + Py_DECREF(result); + } +#endif +} + + +static int +nxt_python_write(nxt_python_run_ctx_t *ctx, PyObject *bytes) +{ + int rc; + char *str_buf; + uint32_t str_length; + + str_buf = PyBytes_AS_STRING(bytes); + str_length = PyBytes_GET_SIZE(bytes); + + if (nxt_slow_path(str_length == 0)) { + return NXT_UNIT_OK; + } + + /* + * PEP 3333: + * + * If the application supplies a Content-Length header, the server should + * not transmit more bytes to the client than the header allows, and should + * stop iterating over the response when enough data has been sent, or raise + * an error if the application tries to write() past that point. + */ + if (nxt_slow_path(str_length > ctx->content_length - ctx->bytes_sent)) { + nxt_unit_req_error(ctx->req, "content length %"PRIu64" exceeded", + ctx->content_length); + + return NXT_UNIT_ERROR; + } + + rc = nxt_unit_response_write(ctx->req, str_buf, str_length); + if (nxt_fast_path(rc == NXT_UNIT_OK)) { + ctx->bytes_sent += str_length; + } + + return rc; +} -- cgit