Python: support for multiple targets.

This commit is contained in:
Oisin Canty
2021-05-20 13:02:45 +00:00
parent 2f0cca2e2b
commit f60389a782
10 changed files with 391 additions and 84 deletions

View File

@@ -31,6 +31,12 @@ NGINX Unit updated to 1.24.0.
date="" time="" date="" time=""
packager="Andrei Belov <defan@nginx.com>"> packager="Andrei Belov <defan@nginx.com>">
<change type="feature">
<para>
multiple "targets" in Python applications.
</para>
</change>
<change type="feature"> <change type="feature">
<para> <para>
a shim for automatic overriding "http" and "websocket" modules in Node.js. a shim for automatic overriding "http" and "websocket" modules in Node.js.

View File

@@ -54,6 +54,7 @@ typedef struct {
nxt_str_t protocol; nxt_str_t protocol;
uint32_t threads; uint32_t threads;
uint32_t thread_stack_size; uint32_t thread_stack_size;
nxt_conf_value_t *targets;
} nxt_python_app_conf_t; } nxt_python_app_conf_t;

View File

@@ -100,6 +100,14 @@ static nxt_int_t nxt_conf_vldt_return(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data); nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data); nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_python(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_python_targets_exclusive(
nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_python_targets(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_python_target(nxt_conf_validation_t *vldt,
nxt_str_t *name, nxt_conf_value_t *value);
static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data); nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt,
@@ -518,7 +526,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_external_members[] = {
}; };
static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { static nxt_conf_vldt_object_t nxt_conf_vldt_python_common_members[] = {
{ {
.name = nxt_string("home"), .name = nxt_string("home"),
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,
@@ -526,13 +534,6 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
.name = nxt_string("path"), .name = nxt_string("path"),
.type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY,
.validator = nxt_conf_vldt_python_path, .validator = nxt_conf_vldt_python_path,
}, {
.name = nxt_string("module"),
.type = NXT_CONF_VLDT_STRING,
.flags = NXT_CONF_VLDT_REQUIRED,
}, {
.name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING,
}, { }, {
.name = nxt_string("protocol"), .name = nxt_string("protocol"),
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,
@@ -550,6 +551,54 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members) NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members)
}; };
static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
{
.name = nxt_string("module"),
.type = NXT_CONF_VLDT_STRING,
.validator = nxt_conf_vldt_python_targets_exclusive,
.u.string = "module",
}, {
.name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING,
.validator = nxt_conf_vldt_python_targets_exclusive,
.u.string = "callable",
}, {
.name = nxt_string("targets"),
.type = NXT_CONF_VLDT_OBJECT,
.validator = nxt_conf_vldt_python_targets,
},
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members)
};
static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[] = {
{
.name = nxt_string("module"),
.type = NXT_CONF_VLDT_STRING,
.flags = NXT_CONF_VLDT_REQUIRED,
}, {
.name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING,
},
NXT_CONF_VLDT_END
};
static nxt_conf_vldt_object_t nxt_conf_vldt_python_notargets_members[] = {
{
.name = nxt_string("module"),
.type = NXT_CONF_VLDT_STRING,
.flags = NXT_CONF_VLDT_REQUIRED,
}, {
.name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING,
},
NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members)
};
static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = { static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = {
{ {
@@ -1419,6 +1468,71 @@ nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
} }
static nxt_int_t
nxt_conf_vldt_python(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
void *data)
{
nxt_conf_value_t *targets;
static nxt_str_t targets_str = nxt_string("targets");
targets = nxt_conf_get_object_member(value, &targets_str, NULL);
if (targets != NULL) {
return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_python_members);
}
return nxt_conf_vldt_object(vldt, value,
nxt_conf_vldt_python_notargets_members);
}
static nxt_int_t
nxt_conf_vldt_python_targets_exclusive(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data)
{
return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive "
"with the \"targets\" object.", data);
}
static nxt_int_t
nxt_conf_vldt_python_targets(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data)
{
nxt_uint_t n;
n = nxt_conf_object_members_count(value);
if (n > 254) {
return nxt_conf_vldt_error(vldt, "The \"targets\" object must not "
"contain more than 254 members.");
}
return nxt_conf_vldt_object_iterator(vldt, value,
&nxt_conf_vldt_python_target);
}
static nxt_int_t
nxt_conf_vldt_python_target(nxt_conf_validation_t *vldt, nxt_str_t *name,
nxt_conf_value_t *value)
{
if (name->length == 0) {
return nxt_conf_vldt_error(vldt,
"The Python target name must not be empty.");
}
if (nxt_conf_type(value) != NXT_CONF_OBJECT) {
return nxt_conf_vldt_error(vldt, "The \"%V\" Python target must be "
"an object.", name);
}
return nxt_conf_vldt_object(vldt, value,
&nxt_conf_vldt_python_target_members);
}
static nxt_int_t static nxt_int_t
nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data) nxt_conf_value_t *value, void *data)
@@ -1959,7 +2073,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name,
} types[] = { } types[] = {
{ nxt_conf_vldt_object, nxt_conf_vldt_external_members }, { nxt_conf_vldt_object, nxt_conf_vldt_external_members },
{ nxt_conf_vldt_object, nxt_conf_vldt_python_members }, { nxt_conf_vldt_python, NULL },
{ nxt_conf_vldt_php, NULL }, { nxt_conf_vldt_php, NULL },
{ nxt_conf_vldt_object, nxt_conf_vldt_perl_members }, { nxt_conf_vldt_object, nxt_conf_vldt_perl_members },
{ nxt_conf_vldt_object, nxt_conf_vldt_ruby_members }, { nxt_conf_vldt_object, nxt_conf_vldt_ruby_members },

View File

@@ -210,6 +210,12 @@ static nxt_conf_map_t nxt_python_app_conf[] = {
offsetof(nxt_common_app_conf_t, u.python.threads), offsetof(nxt_common_app_conf_t, u.python.threads),
}, },
{
nxt_string("targets"),
NXT_CONF_MAP_PTR,
offsetof(nxt_common_app_conf_t, u.python.targets),
},
{ {
nxt_string("thread_stack_size"), nxt_string("thread_stack_size"),
NXT_CONF_MAP_INT32, NXT_CONF_MAP_INT32,

View File

@@ -24,6 +24,8 @@ typedef struct {
static nxt_int_t nxt_python_start(nxt_task_t *task, static nxt_int_t nxt_python_start(nxt_task_t *task,
nxt_process_data_t *data); nxt_process_data_t *data);
static nxt_int_t nxt_python_set_target(nxt_task_t *task,
nxt_python_target_t *target, nxt_conf_value_t *conf);
static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value); static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value);
static int nxt_python_init_threads(nxt_python_app_conf_t *c); static int nxt_python_init_threads(nxt_python_app_conf_t *c);
static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx); static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx);
@@ -49,7 +51,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = {
}; };
static PyObject *nxt_py_stderr_flush; static PyObject *nxt_py_stderr_flush;
PyObject *nxt_py_application; nxt_python_targets_t *nxt_py_targets;
#if PY_MAJOR_VERSION == 3 #if PY_MAJOR_VERSION == 3
static wchar_t *nxt_py_home; static wchar_t *nxt_py_home;
@@ -66,18 +68,19 @@ static nxt_int_t
nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) nxt_python_start(nxt_task_t *task, nxt_process_data_t *data)
{ {
int rc; int rc;
char *nxt_py_module; size_t len, size;
size_t len; uint32_t next;
PyObject *obj, *module; PyObject *obj, *module;
nxt_str_t proto; nxt_str_t proto, probe_proto, name;
const char *callable; nxt_int_t ret, n, i;
nxt_unit_ctx_t *unit_ctx; nxt_unit_ctx_t *unit_ctx;
nxt_unit_init_t python_init; nxt_unit_init_t python_init;
nxt_conf_value_t *cv;
nxt_python_targets_t *targets;
nxt_common_app_conf_t *app_conf; nxt_common_app_conf_t *app_conf;
nxt_python_app_conf_t *c; nxt_python_app_conf_t *c;
#if PY_MAJOR_VERSION == 3 #if PY_MAJOR_VERSION == 3
char *path; char *path;
size_t size;
nxt_int_t pep405; nxt_int_t pep405;
static const char pyvenv[] = "/pyvenv.cfg"; static const char pyvenv[] = "/pyvenv.cfg";
@@ -190,39 +193,43 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data)
Py_CLEAR(obj); Py_CLEAR(obj);
nxt_py_module = nxt_alloca(c->module.length + 1); n = (c->targets != NULL ? nxt_conf_object_members_count(c->targets) : 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); size = sizeof(nxt_python_targets_t) + n * sizeof(nxt_python_target_t);
if (nxt_slow_path(module == NULL)) {
nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); targets = nxt_unit_malloc(NULL, size);
nxt_python_print_exception(); if (nxt_slow_path(targets == NULL)) {
nxt_alert(task, "Could not allocate targets");
goto fail; goto fail;
} }
callable = (c->callable != NULL) ? c->callable : "application"; memset(targets, 0, size);
obj = PyDict_GetItemString(PyModule_GetDict(module), callable); targets->count = n;
if (nxt_slow_path(obj == NULL)) { nxt_py_targets = targets;
nxt_alert(task, "Python failed to get \"%s\" "
"from module \"%s\"", callable, nxt_py_module); if (c->targets != NULL) {
goto fail; next = 0;
for (i = 0; /* void */; i++) {
cv = nxt_conf_next_object_member(c->targets, &name, &next);
if (cv == NULL) {
break;
}
ret = nxt_python_set_target(task, &targets->target[i], cv);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
}
} else {
ret = nxt_python_set_target(task, &targets->target[0], app_conf->self);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
} }
if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
nxt_alert(task, "\"%s\" in module \"%s\" "
"is not a callable object", callable, nxt_py_module);
goto fail;
}
nxt_py_application = obj;
obj = NULL;
Py_INCREF(nxt_py_application);
Py_CLEAR(module);
nxt_unit_default_init(task, &python_init); nxt_unit_default_init(task, &python_init);
python_init.data = c; python_init.data = c;
@@ -232,7 +239,18 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data)
proto = c->protocol; proto = c->protocol;
if (proto.length == 0) { if (proto.length == 0) {
proto = nxt_python_asgi_check(nxt_py_application) ? asgi : wsgi; proto = nxt_python_asgi_check(targets->target[0].application)
? asgi : wsgi;
for (i = 1; i < targets->count; i++) {
probe_proto = nxt_python_asgi_check(targets->target[i].application)
? asgi : wsgi;
if (probe_proto.start != proto.start) {
nxt_alert(task, "A mix of ASGI & WSGI targets is forbidden, "
"specify protocol in config if incorrect");
goto fail;
}
}
} }
if (nxt_strstr_eq(&proto, &asgi)) { if (nxt_strstr_eq(&proto, &asgi)) {
@@ -298,6 +316,81 @@ fail:
} }
static nxt_int_t
nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
nxt_conf_value_t *conf)
{
char *callable, *module_name;
PyObject *module, *obj;
nxt_str_t str;
nxt_conf_value_t *value;
static nxt_str_t module_str = nxt_string("module");
static nxt_str_t callable_str = nxt_string("callable");
module = obj = NULL;
value = nxt_conf_get_object_member(conf, &module_str, NULL);
if (nxt_slow_path(value == NULL)) {
goto fail;
}
nxt_conf_get_string(value, &str);
module_name = nxt_alloca(str.length + 1);
nxt_memcpy(module_name, str.start, str.length);
module_name[str.length] = '\0';
module = PyImport_ImportModule(module_name);
if (nxt_slow_path(module == NULL)) {
nxt_alert(task, "Python failed to import module \"%s\"", module_name);
nxt_python_print_exception();
goto fail;
}
value = nxt_conf_get_object_member(conf, &callable_str, NULL);
if (value == NULL) {
callable = nxt_alloca(12);
nxt_memcpy(callable, "application", 12);
} else {
nxt_conf_get_string(value, &str);
callable = nxt_alloca(str.length + 1);
nxt_memcpy(callable, str.start, str.length);
callable[str.length] = '\0';
}
obj = PyDict_GetItemString(PyModule_GetDict(module), callable);
if (nxt_slow_path(obj == NULL)) {
nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"",
callable, module);
goto fail;
}
if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object",
callable, module);
goto fail;
}
target->application = obj;
obj = NULL;
Py_INCREF(target->application);
Py_CLEAR(module);
return NXT_OK;
fail:
Py_XDECREF(obj);
Py_XDECREF(module);
return NXT_ERROR;
}
static nxt_int_t static nxt_int_t
nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value) nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value)
{ {
@@ -596,12 +689,21 @@ nxt_python_done_strings(nxt_python_string_t *pstr)
static void static void
nxt_python_atexit(void) nxt_python_atexit(void)
{ {
nxt_int_t i;
if (nxt_py_proto.done != NULL) { if (nxt_py_proto.done != NULL) {
nxt_py_proto.done(); nxt_py_proto.done();
} }
Py_XDECREF(nxt_py_stderr_flush); Py_XDECREF(nxt_py_stderr_flush);
Py_XDECREF(nxt_py_application);
if (nxt_py_targets != NULL) {
for (i = 0; i < nxt_py_targets->count; i++) {
Py_XDECREF(nxt_py_targets->target[i].application);
}
nxt_unit_free(NULL, nxt_py_targets);
}
Py_Finalize(); Py_Finalize();

View File

@@ -37,13 +37,28 @@
#define NXT_HAVE_ASGI 1 #define NXT_HAVE_ASGI 1
#endif #endif
extern PyObject *nxt_py_application;
typedef struct {
PyObject *application;
nxt_bool_t asgi_legacy;
} nxt_python_target_t;
typedef struct {
nxt_int_t count;
nxt_python_target_t target[0];
} nxt_python_targets_t;
extern nxt_python_targets_t *nxt_py_targets;
typedef struct { typedef struct {
nxt_str_t string; nxt_str_t string;
PyObject **object_p; PyObject **object_p;
} nxt_python_string_t; } nxt_python_string_t;
typedef struct { typedef struct {
int (*ctx_data_alloc)(void **pdata); int (*ctx_data_alloc)(void **pdata);
void (*ctx_data_free)(void *data); void (*ctx_data_free)(void *data);

View File

@@ -43,8 +43,6 @@ static void nxt_py_asgi_shm_ack_handler(nxt_unit_ctx_t *ctx);
static PyObject *nxt_py_asgi_port_read(PyObject *self, PyObject *args); static PyObject *nxt_py_asgi_port_read(PyObject *self, PyObject *args);
static void nxt_python_asgi_done(void); static void nxt_python_asgi_done(void);
int nxt_py_asgi_legacy;
static PyObject *nxt_py_port_read; static PyObject *nxt_py_port_read;
static nxt_unit_port_t *nxt_py_shared_port; static nxt_unit_port_t *nxt_py_shared_port;
@@ -137,6 +135,7 @@ int
nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto) nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto)
{ {
PyObject *func; PyObject *func;
nxt_int_t i;
PyCodeObject *code; PyCodeObject *code;
nxt_unit_debug(NULL, "asgi_init"); nxt_unit_debug(NULL, "asgi_init");
@@ -161,22 +160,24 @@ nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto)
return NXT_UNIT_ERROR; return NXT_UNIT_ERROR;
} }
func = nxt_python_asgi_get_func(nxt_py_application); for (i = 0; i < nxt_py_targets->count; i++) {
if (nxt_slow_path(func == NULL)) { func = nxt_python_asgi_get_func(nxt_py_targets->target[i].application);
nxt_unit_alert(NULL, "Python cannot find function for callable"); if (nxt_slow_path(func == NULL)) {
return NXT_UNIT_ERROR; nxt_unit_alert(NULL, "Python cannot find function for callable");
return NXT_UNIT_ERROR;
}
code = (PyCodeObject *) PyFunction_GET_CODE(func);
if ((code->co_flags & CO_COROUTINE) == 0) {
nxt_unit_debug(NULL, "asgi: callable is not a coroutine function "
"switching to legacy mode");
nxt_py_targets->target[i].asgi_legacy = 1;
}
Py_DECREF(func);
} }
code = (PyCodeObject *) PyFunction_GET_CODE(func);
if ((code->co_flags & CO_COROUTINE) == 0) {
nxt_unit_debug(NULL, "asgi: callable is not a coroutine function "
"switching to legacy mode");
nxt_py_asgi_legacy = 1;
}
Py_DECREF(func);
init->callbacks.request_handler = nxt_py_asgi_request_handler; init->callbacks.request_handler = nxt_py_asgi_request_handler;
init->callbacks.data_handler = nxt_py_asgi_http_data_handler; init->callbacks.data_handler = nxt_py_asgi_http_data_handler;
init->callbacks.websocket_handler = nxt_py_asgi_websocket_handler; init->callbacks.websocket_handler = nxt_py_asgi_websocket_handler;
@@ -408,6 +409,7 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req)
{ {
PyObject *scope, *res, *task, *receive, *send, *done, *asgi; PyObject *scope, *res, *task, *receive, *send, *done, *asgi;
PyObject *stage2; PyObject *stage2;
nxt_python_target_t *target;
nxt_py_asgi_ctx_data_t *ctx_data; nxt_py_asgi_ctx_data_t *ctx_data;
if (req->request->websocket_handshake) { if (req->request->websocket_handshake) {
@@ -456,17 +458,18 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req)
} }
req->data = asgi; req->data = asgi;
target = &nxt_py_targets->target[req->request->app_target];
if (!nxt_py_asgi_legacy) { if (!target->asgi_legacy) {
nxt_unit_req_debug(req, "Python call ASGI 3.0 application"); nxt_unit_req_debug(req, "Python call ASGI 3.0 application");
res = PyObject_CallFunctionObjArgs(nxt_py_application, res = PyObject_CallFunctionObjArgs(target->application,
scope, receive, send, NULL); scope, receive, send, NULL);
} else { } else {
nxt_unit_req_debug(req, "Python call legacy application"); nxt_unit_req_debug(req, "Python call legacy application");
res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL); res = PyObject_CallFunctionObjArgs(target->application, scope, NULL);
if (nxt_slow_path(res == NULL)) { if (nxt_slow_path(res == NULL)) {
nxt_unit_req_error(req, "Python failed to call legacy app stage1"); nxt_unit_req_error(req, "Python failed to call legacy app stage1");

View File

@@ -33,7 +33,7 @@ typedef struct {
PyObject *loop_remove_reader; PyObject *loop_remove_reader;
PyObject *quit_future; PyObject *quit_future;
PyObject *quit_future_set_result; PyObject *quit_future_set_result;
PyObject *lifespan; PyObject **target_lifespans;
nxt_unit_port_t *port; nxt_unit_port_t *port;
} nxt_py_asgi_ctx_data_t; } nxt_py_asgi_ctx_data_t;
@@ -69,6 +69,4 @@ int nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data);
int nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx); int nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx);
extern int nxt_py_asgi_legacy;
#endif /* _NXT_PYTHON_ASGI_H_INCLUDED_ */ #endif /* _NXT_PYTHON_ASGI_H_INCLUDED_ */

View File

@@ -27,7 +27,10 @@ typedef struct {
PyObject *receive_future; PyObject *receive_future;
} nxt_py_asgi_lifespan_t; } nxt_py_asgi_lifespan_t;
static PyObject *nxt_py_asgi_lifespan_target_startup(
nxt_py_asgi_ctx_data_t *ctx_data, nxt_python_target_t *target);
static int nxt_py_asgi_lifespan_target_shutdown(
nxt_py_asgi_lifespan_t *lifespan);
static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none); static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none);
static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict); static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict);
static PyObject *nxt_py_asgi_lifespan_send_startup( static PyObject *nxt_py_asgi_lifespan_send_startup(
@@ -69,24 +72,60 @@ static PyTypeObject nxt_py_asgi_lifespan_type = {
int int
nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
{ {
int rc; size_t size;
PyObject *lifespan;
PyObject **target_lifespans;
nxt_int_t i;
nxt_python_target_t *target;
size = nxt_py_targets->count * sizeof(PyObject*);
target_lifespans = nxt_unit_malloc(NULL, size);
if (nxt_slow_path(target_lifespans == NULL)) {
nxt_unit_alert(NULL, "Failed to allocate lifespan data");
return NXT_UNIT_ERROR;
}
memset(target_lifespans, 0, size);
for (i = 0; i < nxt_py_targets->count; i++) {
target = &nxt_py_targets->target[i];
lifespan = nxt_py_asgi_lifespan_target_startup(ctx_data, target);
if (nxt_slow_path(lifespan == NULL)) {
return NXT_UNIT_ERROR;
}
target_lifespans[i] = lifespan;
}
ctx_data->target_lifespans = target_lifespans;
return NXT_UNIT_OK;
}
static PyObject *
nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data,
nxt_python_target_t *target)
{
PyObject *scope, *res, *py_task, *receive, *send, *done; PyObject *scope, *res, *py_task, *receive, *send, *done;
PyObject *stage2; PyObject *stage2;
nxt_py_asgi_lifespan_t *lifespan; nxt_py_asgi_lifespan_t *lifespan, *ret;
if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) { if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) {
nxt_unit_alert(NULL, nxt_unit_alert(NULL,
"Python failed to initialize the 'asgi_lifespan' type object"); "Python failed to initialize the 'asgi_lifespan' type object");
return NXT_UNIT_ERROR; return NULL;
} }
lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type); lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type);
if (nxt_slow_path(lifespan == NULL)) { if (nxt_slow_path(lifespan == NULL)) {
nxt_unit_alert(NULL, "Python failed to create lifespan object"); nxt_unit_alert(NULL, "Python failed to create lifespan object");
return NXT_UNIT_ERROR; return NULL;
} }
rc = NXT_UNIT_ERROR; ret = NULL;
receive = PyObject_GetAttrString((PyObject *) lifespan, "receive"); receive = PyObject_GetAttrString((PyObject *) lifespan, "receive");
if (nxt_slow_path(receive == NULL)) { if (nxt_slow_path(receive == NULL)) {
@@ -130,23 +169,25 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
goto release_future; goto release_future;
} }
if (!nxt_py_asgi_legacy) { if (!target->asgi_legacy) {
nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application"); nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application");
res = PyObject_CallFunctionObjArgs(nxt_py_application, res = PyObject_CallFunctionObjArgs(target->application,
scope, receive, send, NULL); scope, receive, send, NULL);
} else { } else {
nxt_unit_req_debug(NULL, "Python call legacy application"); nxt_unit_req_debug(NULL, "Python call legacy application");
res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL); res = PyObject_CallFunctionObjArgs(target->application, scope, NULL);
if (nxt_slow_path(res == NULL)) { if (nxt_slow_path(res == NULL)) {
nxt_unit_log(NULL, NXT_UNIT_LOG_INFO, nxt_unit_log(NULL, NXT_UNIT_LOG_INFO,
"ASGI Lifespan processing exception"); "ASGI Lifespan processing exception");
nxt_python_print_exception(); nxt_python_print_exception();
lifespan->disabled = 1; lifespan->disabled = 1;
rc = NXT_UNIT_OK;
Py_INCREF(lifespan);
ret = lifespan;
goto release_scope; goto release_scope;
} }
@@ -211,10 +252,9 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
Py_DECREF(res); Py_DECREF(res);
if (lifespan->startup_sent == 1 || lifespan->disabled) { if (lifespan->startup_sent == 1 || lifespan->disabled) {
ctx_data->lifespan = (PyObject *) lifespan; Py_INCREF(lifespan);
Py_INCREF(ctx_data->lifespan);
rc = NXT_UNIT_OK; ret = lifespan;
} }
release_task: release_task:
@@ -232,20 +272,41 @@ release_receive:
release_lifespan: release_lifespan:
Py_DECREF(lifespan); Py_DECREF(lifespan);
return rc; return (PyObject *) ret;
} }
int int
nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx) nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx)
{ {
PyObject *msg, *future, *res; nxt_int_t i, ret;
nxt_py_asgi_lifespan_t *lifespan; nxt_py_asgi_lifespan_t *lifespan;
nxt_py_asgi_ctx_data_t *ctx_data; nxt_py_asgi_ctx_data_t *ctx_data;
ctx_data = ctx->data; ctx_data = ctx->data;
lifespan = (nxt_py_asgi_lifespan_t *) ctx_data->lifespan; for (i = 0; i < nxt_py_targets->count; i++) {
lifespan = (nxt_py_asgi_lifespan_t *)ctx_data->target_lifespans[i];
ret = nxt_py_asgi_lifespan_target_shutdown(lifespan);
if (nxt_slow_path(ret != NXT_UNIT_OK)) {
return NXT_UNIT_ERROR;
}
}
nxt_unit_free(NULL, ctx_data->target_lifespans);
return NXT_UNIT_OK;
}
static int
nxt_py_asgi_lifespan_target_shutdown(nxt_py_asgi_lifespan_t *lifespan)
{
PyObject *msg, *future, *res;
nxt_py_asgi_ctx_data_t *ctx_data;
ctx_data = lifespan->ctx_data;
if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) { if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) {
return NXT_UNIT_OK; return NXT_UNIT_OK;

View File

@@ -302,7 +302,7 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
{ {
int rc; int rc;
PyObject *environ, *args, *response, *iterator, *item; PyObject *environ, *args, *response, *iterator, *item;
PyObject *close, *result; PyObject *close, *result, *application;
nxt_bool_t prepare_environ; nxt_bool_t prepare_environ;
nxt_python_ctx_t *pctx; nxt_python_ctx_t *pctx;
@@ -348,7 +348,8 @@ nxt_python_request_handler(nxt_unit_request_info_t *req)
Py_INCREF(pctx->start_resp); Py_INCREF(pctx->start_resp);
PyTuple_SET_ITEM(args, 1, pctx->start_resp); PyTuple_SET_ITEM(args, 1, pctx->start_resp);
response = PyObject_CallObject(nxt_py_application, args); application = nxt_py_targets->target[req->request->app_target].application;
response = PyObject_CallObject(application, args);
Py_DECREF(args); Py_DECREF(args);