diff options
author | synodriver <diguohuangjiajinweijun@gmail.com> | 2023-05-27 22:18:46 +0800 |
---|---|---|
committer | Andrew Clayton <a.clayton@nginx.com> | 2023-06-01 00:25:03 +0100 |
commit | 93ed66958e11b40cc06dcb32fc8e623967af6347 (patch) | |
tree | 720ddd839d0594a0ffc6fb36e8c8a71fcd42b2a9 /src/python | |
parent | 31ff94add9c4043a753683d9e8b68733c69aa1ac (diff) | |
download | unit-93ed66958e11b40cc06dcb32fc8e623967af6347.tar.gz unit-93ed66958e11b40cc06dcb32fc8e623967af6347.tar.bz2 |
Python: Add ASGI lifespan state support.
Lifespan state is a special dict in asgi lifespan scope, which allow
applications to persist data from the lifespan cycle to request/response
handling. The scope["state"] namespace provides a place to store these
sorts of things. The server will ensure that a shallow copy of the
namespace is passed into each subsequent request/response call into the
application.
Some frameworks are already taking advantage of this feature, for
example, starlette, and without this feature they wouldn't work
properly.
Signed-off-by: synodriver <diguohuangjiajinweijun@gmail.com>
Reviewed-by: Andrew Clayton <a.clayton@nginx.com>
[ Minor code tweaks to avoid lines > 80 chars, static a function and
re-work the PyMemberDef structure initialisation for Python <3.7
and -Wwrite-strings compatibility - Andrew ]
Tested-by: <https://github.com/synodriver>
Tested-by: <https://github.com/hawiliali>
Closes: <https://github.com/nginx/unit/issues/864>
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
Diffstat (limited to 'src/python')
-rw-r--r-- | src/python/nxt_python_asgi.c | 30 | ||||
-rw-r--r-- | src/python/nxt_python_asgi_lifespan.c | 54 | ||||
-rw-r--r-- | src/python/nxt_python_asgi_str.c | 2 | ||||
-rw-r--r-- | src/python/nxt_python_asgi_str.h | 1 |
4 files changed, 84 insertions, 3 deletions
diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c index 9f6cde3b..5882491b 100644 --- a/src/python/nxt_python_asgi.c +++ b/src/python/nxt_python_asgi.c @@ -450,6 +450,7 @@ static void nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) { PyObject *scope, *res, *task, *receive, *send, *done, *asgi; + PyObject *state, *newstate, *lifespan; PyObject *stage2; nxt_python_target_t *target; nxt_py_asgi_ctx_data_t *ctx_data; @@ -493,15 +494,41 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) } req->data = asgi; + ctx_data = req->ctx->data; target = &nxt_py_targets->target[req->request->app_target]; + lifespan = ctx_data->target_lifespans[req->request->app_target]; + state = PyObject_GetAttr(lifespan, nxt_py_state_str); + if (nxt_slow_path(state == NULL)) { + nxt_unit_req_alert(req, "Python failed to get 'state' attribute"); + nxt_unit_request_done(req, NXT_UNIT_ERROR); + + goto release_done; + } + + newstate = PyDict_Copy(state); + if (nxt_slow_path(newstate == NULL)) { + nxt_unit_req_alert(req, "Python failed to call state.copy()"); + nxt_unit_request_done(req, NXT_UNIT_ERROR); + Py_DECREF(state); + goto release_done; + } + Py_DECREF(state); scope = nxt_py_asgi_create_http_scope(req, target); if (nxt_slow_path(scope == NULL)) { nxt_unit_request_done(req, NXT_UNIT_ERROR); - + Py_DECREF(newstate); goto release_done; } + if (nxt_slow_path(PyDict_SetItem(scope, nxt_py_state_str, newstate) + == -1)) + { + Py_DECREF(newstate); + goto release_scope; + } + Py_DECREF(newstate); + if (!target->asgi_legacy) { nxt_unit_req_debug(req, "Python call ASGI 3.0 application"); @@ -555,7 +582,6 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) goto release_scope; } - ctx_data = req->ctx->data; task = PyObject_CallFunctionObjArgs(ctx_data->loop_create_task, res, NULL); if (nxt_slow_path(task == NULL)) { diff --git a/src/python/nxt_python_asgi_lifespan.c b/src/python/nxt_python_asgi_lifespan.c index 1fc0e6b7..041cca21 100644 --- a/src/python/nxt_python_asgi_lifespan.c +++ b/src/python/nxt_python_asgi_lifespan.c @@ -12,6 +12,8 @@ #include <python/nxt_python_asgi.h> #include <python/nxt_python_asgi_str.h> +#include <structmember.h> + typedef struct { PyObject_HEAD @@ -25,6 +27,7 @@ typedef struct { PyObject *startup_future; PyObject *shutdown_future; PyObject *receive_future; + PyObject *state; } nxt_py_asgi_lifespan_t; static PyObject *nxt_py_asgi_lifespan_target_startup( @@ -41,6 +44,7 @@ static PyObject *nxt_py_asgi_lifespan_send_shutdown( nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict); static PyObject *nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan); static PyObject *nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future); +static void nxt_py_asgi_lifespan_dealloc(PyObject *self); static PyMethodDef nxt_py_asgi_lifespan_methods[] = { @@ -50,6 +54,26 @@ static PyMethodDef nxt_py_asgi_lifespan_methods[] = { { NULL, NULL, 0, 0 } }; +static PyMemberDef nxt_py_asgi_lifespan_members[] = { + { +#if PY_VERSION_HEX >= NXT_PYTHON_VER(3, 7) + .name = "state", +#else + .name = (char *)"state", +#endif + .type = T_OBJECT_EX, + .offset = offsetof(nxt_py_asgi_lifespan_t, state), + .flags = READONLY, +#if PY_VERSION_HEX >= NXT_PYTHON_VER(3, 7) + .doc = PyDoc_STR("lifespan.state") +#else + .doc = (char *)PyDoc_STR("lifespan.state") +#endif + }, + + { NULL, 0, 0, 0, NULL } +}; + static PyAsyncMethods nxt_py_asgi_async_methods = { .am_await = nxt_py_asgi_await, }; @@ -59,13 +83,14 @@ static PyTypeObject nxt_py_asgi_lifespan_type = { .tp_name = "unit._asgi_lifespan", .tp_basicsize = sizeof(nxt_py_asgi_lifespan_t), - .tp_dealloc = nxt_py_asgi_dealloc, + .tp_dealloc = nxt_py_asgi_lifespan_dealloc, .tp_as_async = &nxt_py_asgi_async_methods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "unit ASGI Lifespan object", .tp_iter = nxt_py_asgi_iter, .tp_iternext = nxt_py_asgi_next, .tp_methods = nxt_py_asgi_lifespan_methods, + .tp_members = nxt_py_asgi_lifespan_members, }; @@ -163,12 +188,29 @@ nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data, lifespan->shutdown_called = 0; lifespan->shutdown_future = NULL; lifespan->receive_future = NULL; + lifespan->state = NULL; scope = nxt_py_asgi_new_scope(NULL, nxt_py_lifespan_str, nxt_py_2_0_str); if (nxt_slow_path(scope == NULL)) { goto release_future; } + lifespan->state = PyDict_New(); + if (nxt_slow_path(lifespan->state == NULL)) { + nxt_unit_req_error(NULL, + "Python failed to create 'state' dict"); + goto release_future; + } + + if (nxt_slow_path(PyDict_SetItem(scope, nxt_py_state_str, + lifespan->state) == -1)) + { + nxt_unit_req_error(NULL, + "Python failed to set 'scope.state' item"); + Py_CLEAR(lifespan->state); + goto release_future; + } + if (!target->asgi_legacy) { nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application"); @@ -604,4 +646,14 @@ nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future) } +static void +nxt_py_asgi_lifespan_dealloc(PyObject *self) +{ + nxt_py_asgi_lifespan_t *lifespan = (nxt_py_asgi_lifespan_t *)self; + + Py_CLEAR(lifespan->state); + PyObject_Del(self); +} + + #endif /* NXT_HAVE_ASGI */ diff --git a/src/python/nxt_python_asgi_str.c b/src/python/nxt_python_asgi_str.c index 7171d52b..3bea87d5 100644 --- a/src/python/nxt_python_asgi_str.c +++ b/src/python/nxt_python_asgi_str.c @@ -55,6 +55,7 @@ PyObject *nxt_py_subprotocol_str; PyObject *nxt_py_subprotocols_str; PyObject *nxt_py_text_str; PyObject *nxt_py_type_str; +PyObject *nxt_py_state_str; PyObject *nxt_py_version_str; PyObject *nxt_py_websocket_str; PyObject *nxt_py_websocket_accept_str; @@ -110,6 +111,7 @@ static nxt_python_string_t nxt_py_asgi_strings[] = { { nxt_string("subprotocols"), &nxt_py_subprotocols_str }, { nxt_string("text"), &nxt_py_text_str }, { nxt_string("type"), &nxt_py_type_str }, + { nxt_string("state"), &nxt_py_state_str }, { nxt_string("version"), &nxt_py_version_str }, { nxt_string("websocket"), &nxt_py_websocket_str }, { nxt_string("websocket.accept"), &nxt_py_websocket_accept_str }, diff --git a/src/python/nxt_python_asgi_str.h b/src/python/nxt_python_asgi_str.h index 92969fd2..3c7a3ed9 100644 --- a/src/python/nxt_python_asgi_str.h +++ b/src/python/nxt_python_asgi_str.h @@ -50,6 +50,7 @@ extern PyObject *nxt_py_subprotocol_str; extern PyObject *nxt_py_subprotocols_str; extern PyObject *nxt_py_text_str; extern PyObject *nxt_py_type_str; +extern PyObject *nxt_py_state_str; extern PyObject *nxt_py_version_str; extern PyObject *nxt_py_websocket_str; extern PyObject *nxt_py_websocket_accept_str; |