Previously, passing 0 resulted in reading the whole body and all negative values raised an exception. Now the behaviour is in consistentance with io.RawIOBase.read() interface, and passing 0 returns empty (byte) string, while -1 results in reading the whole body.
1213 lines
34 KiB
C
1213 lines
34 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,
|
|
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 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_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)
|
|
"unit._input", /* tp_name */
|
|
(int) sizeof(nxt_py_input_t), /* tp_basicsize */
|
|
0, /* tp_itemsize */
|
|
(destructor) nxt_py_input_dealloc, /* tp_dealloc */
|
|
0, /* tp_print */
|
|
0, /* tp_getattr */
|
|
0, /* tp_setattr */
|
|
0, /* tp_compare */
|
|
0, /* tp_repr */
|
|
0, /* tp_as_number */
|
|
0, /* tp_as_sequence */
|
|
0, /* tp_as_mapping */
|
|
0, /* tp_hash */
|
|
0, /* tp_call */
|
|
0, /* tp_str */
|
|
0, /* tp_getattro */
|
|
0, /* tp_setattro */
|
|
0, /* tp_as_buffer */
|
|
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
|
"unit input object.", /* tp_doc */
|
|
0, /* tp_traverse */
|
|
0, /* tp_clear */
|
|
0, /* tp_richcompare */
|
|
0, /* tp_weaklistoffset */
|
|
0, /* tp_iter */
|
|
0, /* tp_iternext */
|
|
nxt_py_input_methods, /* tp_methods */
|
|
0, /* tp_members */
|
|
0, /* tp_getset */
|
|
0, /* tp_base */
|
|
0, /* tp_dict */
|
|
0, /* tp_descr_get */
|
|
0, /* tp_descr_set */
|
|
0, /* tp_dictoffset */
|
|
0, /* tp_init */
|
|
0, /* tp_alloc */
|
|
0, /* tp_new */
|
|
0, /* tp_free */
|
|
0, /* tp_is_gc */
|
|
0, /* tp_bases */
|
|
0, /* tp_mro - method resolution order */
|
|
0, /* tp_cache */
|
|
0, /* tp_subclasses */
|
|
0, /* tp_weaklist */
|
|
0, /* tp_del */
|
|
0, /* tp_version_tag */
|
|
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION > 3
|
|
0, /* tp_finalize */
|
|
#endif
|
|
};
|
|
|
|
|
|
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 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);
|
|
|
|
obj = NULL;
|
|
module = NULL;
|
|
|
|
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 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 = NULL;
|
|
}
|
|
|
|
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 (obj == NULL) {
|
|
goto fail;
|
|
}
|
|
|
|
nxt_py_environ_ptyp = obj;
|
|
|
|
obj = Py_BuildValue("[s]", "unit");
|
|
if (obj == NULL) {
|
|
nxt_alert(task, "Python failed to create the \"sys.argv\" list");
|
|
goto fail;
|
|
}
|
|
|
|
if (PySys_SetObject((char *) "argv", obj) != 0) {
|
|
nxt_alert(task, "Python failed to set the \"sys.argv\" list");
|
|
goto fail;
|
|
}
|
|
|
|
Py_DECREF(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);
|
|
PyErr_PrintEx(1);
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
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);
|
|
PyErr_PrintEx(1);
|
|
goto fail;
|
|
}
|
|
|
|
Py_INCREF(obj);
|
|
Py_DECREF(module);
|
|
|
|
nxt_py_application = obj;
|
|
|
|
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)) {
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
rc = nxt_unit_run(unit_ctx);
|
|
|
|
nxt_unit_done(unit_ctx);
|
|
|
|
nxt_python_atexit();
|
|
|
|
exit(rc);
|
|
|
|
return NXT_OK;
|
|
|
|
fail:
|
|
|
|
Py_XDECREF(obj);
|
|
Py_XDECREF(module);
|
|
|
|
if (nxt_py_home != NULL) {
|
|
nxt_free(nxt_py_home);
|
|
}
|
|
|
|
return NXT_ERROR;
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_python_request_handler(nxt_unit_request_info_t *req)
|
|
{
|
|
int rc;
|
|
PyObject *result, *iterator, *item, *args, *environ;
|
|
nxt_python_run_ctx_t run_ctx = {-1, 0, NULL, req};
|
|
|
|
environ = nxt_python_get_environ(&run_ctx);
|
|
if (nxt_slow_path(environ == NULL)) {
|
|
nxt_unit_request_done(req, NXT_UNIT_ERROR);
|
|
|
|
return;
|
|
}
|
|
|
|
args = PyTuple_New(2);
|
|
if (nxt_slow_path(args == NULL)) {
|
|
nxt_unit_req_error(req, "Python failed to create arguments tuple");
|
|
|
|
nxt_unit_request_done(req, NXT_UNIT_ERROR);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
|
|
result = PyObject_CallObject(nxt_py_application, args);
|
|
|
|
Py_DECREF(args);
|
|
|
|
if (nxt_slow_path(result == NULL)) {
|
|
nxt_unit_req_error(req, "Python failed to call the application");
|
|
PyErr_Print();
|
|
|
|
nxt_unit_request_done(req, NXT_UNIT_ERROR);
|
|
nxt_python_run_ctx = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
item = NULL;
|
|
iterator = NULL;
|
|
|
|
/* Shortcut: avoid iterate over result string symbols. */
|
|
if (PyBytes_Check(result)) {
|
|
|
|
rc = nxt_python_write(&run_ctx, result);
|
|
if (nxt_slow_path(rc != NXT_UNIT_OK)) {
|
|
goto fail;
|
|
}
|
|
|
|
} else {
|
|
iterator = PyObject_GetIter(result);
|
|
|
|
if (nxt_slow_path(iterator == NULL)) {
|
|
nxt_unit_req_error(req, "the application returned "
|
|
"not an iterable object");
|
|
|
|
goto fail;
|
|
}
|
|
|
|
while (run_ctx.bytes_sent < run_ctx.content_length
|
|
&& (item = PyIter_Next(iterator)))
|
|
{
|
|
if (nxt_slow_path(!PyBytes_Check(item))) {
|
|
nxt_unit_req_error(req, "the application returned "
|
|
"not a bytestring object");
|
|
|
|
goto fail;
|
|
}
|
|
|
|
rc = nxt_python_write(&run_ctx, item);
|
|
if (nxt_slow_path(rc != NXT_UNIT_OK)) {
|
|
goto fail;
|
|
}
|
|
|
|
Py_DECREF(item);
|
|
}
|
|
|
|
Py_DECREF(iterator);
|
|
|
|
if (PyObject_HasAttrString(result, "close")) {
|
|
PyObject_CallMethod(result, (char *) "close", NULL);
|
|
}
|
|
}
|
|
|
|
if (nxt_slow_path(PyErr_Occurred() != NULL)) {
|
|
nxt_unit_req_error(req, "an application error occurred");
|
|
PyErr_Print();
|
|
}
|
|
|
|
nxt_unit_request_done(req, NXT_UNIT_OK);
|
|
|
|
Py_DECREF(result);
|
|
|
|
nxt_python_run_ctx = NULL;
|
|
|
|
return;
|
|
|
|
fail:
|
|
|
|
if (item != NULL) {
|
|
Py_DECREF(item);
|
|
}
|
|
|
|
if (iterator != NULL) {
|
|
Py_DECREF(iterator);
|
|
}
|
|
|
|
if (PyObject_HasAttrString(result, "close")) {
|
|
PyObject_CallMethod(result, (char *) "close", NULL);
|
|
}
|
|
|
|
Py_DECREF(result);
|
|
nxt_python_run_ctx = NULL;
|
|
|
|
nxt_unit_request_done(req, NXT_UNIT_ERROR);
|
|
}
|
|
|
|
|
|
static void
|
|
nxt_python_atexit(void)
|
|
{
|
|
Py_DECREF(nxt_py_application);
|
|
Py_DECREF(nxt_py_start_resp_obj);
|
|
Py_DECREF(nxt_py_write_obj);
|
|
Py_DECREF(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;
|
|
}
|
|
|
|
|
|
obj = PyString_FromStringAndSize("http", nxt_length("http"));
|
|
|
|
if (nxt_slow_path(obj == NULL)) {
|
|
nxt_alert(task,
|
|
"Python failed to create the \"wsgi.url_scheme\" environ value");
|
|
goto fail;
|
|
}
|
|
|
|
if (nxt_slow_path(PyDict_SetItemString(environ, "wsgi.url_scheme", obj)
|
|
!= 0))
|
|
{
|
|
nxt_alert(task,
|
|
"Python failed to set the \"wsgi.url_scheme\" environ value");
|
|
goto fail;
|
|
}
|
|
|
|
Py_DECREF(obj);
|
|
obj = NULL;
|
|
|
|
|
|
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, *host_start, *port_start;
|
|
uint32_t i, host_length, port_length;
|
|
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));
|
|
|
|
if (r->query.offset) {
|
|
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));
|
|
|
|
RC(nxt_python_add_sptr(ctx, "SERVER_PROTOCOL", &r->version,
|
|
r->version_length));
|
|
|
|
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));
|
|
}
|
|
|
|
if (r->host_field != NXT_UNIT_NONE_FIELD) {
|
|
f = r->fields + r->host_field;
|
|
|
|
host_start = nxt_unit_sptr_get(&f->value);
|
|
host_length = f->value_length;
|
|
|
|
} else {
|
|
host_start = NULL;
|
|
host_length = 0;
|
|
}
|
|
|
|
nxt_unit_split_host(host_start, host_length, &host_start, &host_length,
|
|
&port_start, &port_length);
|
|
|
|
RC(nxt_python_add_str(ctx, "SERVER_NAME", host_start, host_length));
|
|
RC(nxt_python_add_str(ctx, "SERVER_PORT", port_start, port_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);
|
|
PyErr_PrintEx(1);
|
|
|
|
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,
|
|
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);
|
|
PyErr_PrintEx(1);
|
|
|
|
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 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;
|
|
}
|