Files
nginx-unit/src/nxt_python_wsgi.c
Valentin Bartenev c2976fb337 Python: avoiding buffering of exception backtraces.
A quote from the Python 3 documentation:

 | When interactive, stdout and stderr streams are line-buffered.
 | Otherwise, they are block-buffered like regular text files.

As a result, if an exception occurred and PyErr_Print() was called, its output
could be buffered but not printed to the log for a while (ultimately, until
the interpreter finalization).  If the application process crashed shortly,
the backtrace was completely lost.

Buffering can be disabled by redefining the sys.stderr stream object.
However, interference with standard environment objects was deemed undesirable.
Instead, sys.stderr.flush() is called every time after printing exceptions.
A potential advantage here is that lines from backtraces won't be mixed
with other lines in the log.
2019-11-14 17:48:48 +03:00

1182 lines
32 KiB
C

/*
* Copyright (C) Max Romanov
* Copyright (C) Valentin V. Bartenev
* Copyright (C) NGINX, Inc.
*/
#include <Python.h>
#include <compile.h>
#include <node.h>
#include <nxt_main.h>
#include <nxt_runtime.h>
#include <nxt_router.h>
#include <nxt_unit.h>
#include <nxt_unit_field.h>
#include <nxt_unit_request.h>
#include <nxt_unit_response.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
#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_init(nxt_task_t *task, nxt_common_app_conf_t *conf);
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, const char *name,
nxt_unit_sptr_t *sptr, uint32_t size);
static int nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name,
const char *str, uint32_t size);
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_readlines(nxt_py_input_t *self, PyObject *args);
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,
NULL,
nxt_python_init,
};
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_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 nxt_int_t
nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf)
{
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_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
c = &conf->u.python;
if (c->module.length == 0) {
nxt_alert(task, "python module is empty");
return NXT_ERROR;
}
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 = 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;
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 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_GetAttrString(response, "close");
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)
{
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;
char *name;
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, "REQUEST_METHOD", &r->method,
r->method_length));
RC(nxt_python_add_sptr(ctx, "REQUEST_URI", &r->target, r->target_length));
RC(nxt_python_add_sptr(ctx, "QUERY_STRING", &r->query, r->query_length));
RC(nxt_python_add_sptr(ctx, "PATH_INFO", &r->path, r->path_length));
RC(nxt_python_add_sptr(ctx, "REMOTE_ADDR", &r->remote, r->remote_length));
RC(nxt_python_add_sptr(ctx, "SERVER_ADDR", &r->local, r->local_length));
if (r->tls) {
RC(nxt_python_add_str(ctx, "wsgi.url_scheme", "https", 5));
} else {
RC(nxt_python_add_str(ctx, "wsgi.url_scheme", "http", 4));
}
RC(nxt_python_add_sptr(ctx, "SERVER_PROTOCOL", &r->version,
r->version_length));
RC(nxt_python_add_sptr(ctx, "SERVER_NAME", &r->server_name,
r->server_name_length));
RC(nxt_python_add_str(ctx, "SERVER_PORT", "80", 2));
for (i = 0; i < r->fields_count; i++) {
f = r->fields + i;
name = nxt_unit_sptr_get(&f->name);
RC(nxt_python_add_sptr(ctx, name, &f->value, f->value_length));
}
if (r->content_length_field != NXT_UNIT_NONE_FIELD) {
f = r->fields + r->content_length_field;
RC(nxt_python_add_sptr(ctx, "CONTENT_LENGTH", &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, "CONTENT_TYPE", &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, const char *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_SetItemString(ctx->environ, name, value) != 0)) {
nxt_unit_req_error(ctx->req,
"Python failed to set the \"%s\" environ value",
name);
Py_DECREF(value);
return NXT_UNIT_ERROR;
}
Py_DECREF(value);
return NXT_UNIT_OK;
}
static int
nxt_python_add_str(nxt_python_run_ctx_t *ctx, const char *name,
const char *str, uint32_t size)
{
PyObject *value;
if (nxt_slow_path(str == NULL)) {
return NXT_UNIT_OK;
}
value = PyString_FromStringAndSize(str, size);
if (nxt_slow_path(value == NULL)) {
nxt_unit_req_error(ctx->req,
"Python failed to create value string \"%.*s\"",
(int) size, str);
nxt_python_print_exception();
return NXT_UNIT_ERROR;
}
if (nxt_slow_path(PyDict_SetItemString(ctx->environ, name, value) != 0)) {
nxt_unit_req_error(ctx->req,
"Python failed to set the \"%s\" environ value",
name);
Py_DECREF(value);
return NXT_UNIT_ERROR;
}
Py_DECREF(value);
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)
{
return PyBytes_FromStringAndSize("", 0);
}
static PyObject *
nxt_py_input_readlines(nxt_py_input_t *self, PyObject *args)
{
return PyList_New(0);
}
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;
}