xref: /unit/src/python/nxt_python_asgi.c (revision 2068:e7eded7e5de9)
11624Smax.romanov@nginx.com 
21624Smax.romanov@nginx.com /*
31624Smax.romanov@nginx.com  * Copyright (C) NGINX, Inc.
41624Smax.romanov@nginx.com  */
51624Smax.romanov@nginx.com 
61624Smax.romanov@nginx.com 
71624Smax.romanov@nginx.com #include <python/nxt_python.h>
81624Smax.romanov@nginx.com 
91624Smax.romanov@nginx.com #if (NXT_HAVE_ASGI)
101624Smax.romanov@nginx.com 
111624Smax.romanov@nginx.com #include <nxt_main.h>
121624Smax.romanov@nginx.com #include <nxt_unit.h>
131624Smax.romanov@nginx.com #include <nxt_unit_request.h>
141624Smax.romanov@nginx.com #include <nxt_unit_response.h>
151624Smax.romanov@nginx.com #include <python/nxt_python_asgi.h>
161624Smax.romanov@nginx.com #include <python/nxt_python_asgi_str.h>
171624Smax.romanov@nginx.com 
181624Smax.romanov@nginx.com 
191697Smax.romanov@nginx.com static PyObject *nxt_python_asgi_get_func(PyObject *obj);
201918Smax.romanov@nginx.com static int nxt_python_asgi_ctx_data_alloc(void **pdata, int main);
211681Smax.romanov@nginx.com static void nxt_python_asgi_ctx_data_free(void *data);
221681Smax.romanov@nginx.com static int nxt_python_asgi_startup(void *data);
231681Smax.romanov@nginx.com static int nxt_python_asgi_run(nxt_unit_ctx_t *ctx);
241681Smax.romanov@nginx.com 
251681Smax.romanov@nginx.com static void nxt_py_asgi_remove_reader(nxt_unit_ctx_t *ctx,
261681Smax.romanov@nginx.com     nxt_unit_port_t *port);
271624Smax.romanov@nginx.com static void nxt_py_asgi_request_handler(nxt_unit_request_info_t *req);
281715Smax.romanov@nginx.com static void nxt_py_asgi_close_handler(nxt_unit_request_info_t *req);
291624Smax.romanov@nginx.com 
301624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req);
311624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_create_address(nxt_unit_sptr_t *sptr, uint8_t len,
321624Smax.romanov@nginx.com     uint16_t port);
331624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_create_header(nxt_unit_field_t *f);
341624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_create_subprotocols(nxt_unit_field_t *f);
351624Smax.romanov@nginx.com 
361624Smax.romanov@nginx.com static int nxt_py_asgi_add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port);
371979Smax.romanov@nginx.com static int nxt_py_asgi_add_reader(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port);
381980Smax.romanov@nginx.com static void nxt_py_asgi_remove_port(nxt_unit_t *lib, nxt_unit_ctx_t *ctx,
391980Smax.romanov@nginx.com     nxt_unit_port_t *port);
401624Smax.romanov@nginx.com static void nxt_py_asgi_quit(nxt_unit_ctx_t *ctx);
411624Smax.romanov@nginx.com static void nxt_py_asgi_shm_ack_handler(nxt_unit_ctx_t *ctx);
421624Smax.romanov@nginx.com 
431624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_port_read(PyObject *self, PyObject *args);
441681Smax.romanov@nginx.com static void nxt_python_asgi_done(void);
451624Smax.romanov@nginx.com 
461681Smax.romanov@nginx.com static PyObject           *nxt_py_port_read;
471624Smax.romanov@nginx.com 
481624Smax.romanov@nginx.com static PyMethodDef        nxt_py_port_read_method =
491624Smax.romanov@nginx.com     {"unit_port_read", nxt_py_asgi_port_read, METH_VARARGS, ""};
501624Smax.romanov@nginx.com 
511681Smax.romanov@nginx.com static nxt_python_proto_t  nxt_py_asgi_proto = {
521681Smax.romanov@nginx.com     .ctx_data_alloc = nxt_python_asgi_ctx_data_alloc,
531681Smax.romanov@nginx.com     .ctx_data_free  = nxt_python_asgi_ctx_data_free,
541681Smax.romanov@nginx.com     .startup        = nxt_python_asgi_startup,
551681Smax.romanov@nginx.com     .run            = nxt_python_asgi_run,
561681Smax.romanov@nginx.com     .done           = nxt_python_asgi_done,
571681Smax.romanov@nginx.com };
581681Smax.romanov@nginx.com 
591624Smax.romanov@nginx.com #define NXT_UNIT_HASH_WS_PROTOCOL  0xED0A
601624Smax.romanov@nginx.com 
611624Smax.romanov@nginx.com 
621624Smax.romanov@nginx.com int
nxt_python_asgi_check(PyObject * obj)631624Smax.romanov@nginx.com nxt_python_asgi_check(PyObject *obj)
641624Smax.romanov@nginx.com {
651624Smax.romanov@nginx.com     int           res;
661697Smax.romanov@nginx.com     PyObject      *func;
671624Smax.romanov@nginx.com     PyCodeObject  *code;
681624Smax.romanov@nginx.com 
691697Smax.romanov@nginx.com     func = nxt_python_asgi_get_func(obj);
701697Smax.romanov@nginx.com 
711697Smax.romanov@nginx.com     if (func == NULL) {
721697Smax.romanov@nginx.com         return 0;
731697Smax.romanov@nginx.com     }
741697Smax.romanov@nginx.com 
751697Smax.romanov@nginx.com     code = (PyCodeObject *) PyFunction_GET_CODE(func);
761697Smax.romanov@nginx.com 
771697Smax.romanov@nginx.com     nxt_unit_debug(NULL, "asgi_check: callable is %sa coroutine function with "
781697Smax.romanov@nginx.com                          "%d argument(s)",
791697Smax.romanov@nginx.com                    (code->co_flags & CO_COROUTINE) != 0 ? "" : "not ",
801697Smax.romanov@nginx.com                    code->co_argcount);
811697Smax.romanov@nginx.com 
821697Smax.romanov@nginx.com     res = (code->co_flags & CO_COROUTINE) != 0 || code->co_argcount == 1;
831697Smax.romanov@nginx.com 
841697Smax.romanov@nginx.com     Py_DECREF(func);
851697Smax.romanov@nginx.com 
861697Smax.romanov@nginx.com     return res;
871697Smax.romanov@nginx.com }
881697Smax.romanov@nginx.com 
891697Smax.romanov@nginx.com 
901697Smax.romanov@nginx.com static PyObject *
nxt_python_asgi_get_func(PyObject * obj)911697Smax.romanov@nginx.com nxt_python_asgi_get_func(PyObject *obj)
921697Smax.romanov@nginx.com {
931697Smax.romanov@nginx.com     PyObject  *call;
941697Smax.romanov@nginx.com 
951624Smax.romanov@nginx.com     if (PyFunction_Check(obj)) {
961697Smax.romanov@nginx.com         Py_INCREF(obj);
971697Smax.romanov@nginx.com         return obj;
981624Smax.romanov@nginx.com     }
991624Smax.romanov@nginx.com 
1001624Smax.romanov@nginx.com     if (PyMethod_Check(obj)) {
1011624Smax.romanov@nginx.com         obj = PyMethod_GET_FUNCTION(obj);
1021624Smax.romanov@nginx.com 
1031697Smax.romanov@nginx.com         Py_INCREF(obj);
1041697Smax.romanov@nginx.com         return obj;
1051624Smax.romanov@nginx.com     }
1061624Smax.romanov@nginx.com 
1071624Smax.romanov@nginx.com     call = PyObject_GetAttrString(obj, "__call__");
1081624Smax.romanov@nginx.com 
1091624Smax.romanov@nginx.com     if (call == NULL) {
1101697Smax.romanov@nginx.com         return NULL;
1111624Smax.romanov@nginx.com     }
1121624Smax.romanov@nginx.com 
1131624Smax.romanov@nginx.com     if (PyFunction_Check(call)) {
1141697Smax.romanov@nginx.com         return call;
1151697Smax.romanov@nginx.com     }
1161624Smax.romanov@nginx.com 
1171697Smax.romanov@nginx.com     if (PyMethod_Check(call)) {
1181697Smax.romanov@nginx.com         obj = PyMethod_GET_FUNCTION(call);
1191624Smax.romanov@nginx.com 
1202067Smax.romanov@nginx.com         if (PyFunction_Check(obj)) {
1212067Smax.romanov@nginx.com             Py_INCREF(obj);
1221697Smax.romanov@nginx.com 
1232067Smax.romanov@nginx.com         } else {
1242067Smax.romanov@nginx.com             obj = NULL;
1252067Smax.romanov@nginx.com         }
1262067Smax.romanov@nginx.com 
1272067Smax.romanov@nginx.com     } else {
1282067Smax.romanov@nginx.com         obj = NULL;
1291624Smax.romanov@nginx.com     }
1301624Smax.romanov@nginx.com 
1311624Smax.romanov@nginx.com     Py_DECREF(call);
1321624Smax.romanov@nginx.com 
1332067Smax.romanov@nginx.com     return obj;
1341624Smax.romanov@nginx.com }
1351624Smax.romanov@nginx.com 
1361624Smax.romanov@nginx.com 
1371681Smax.romanov@nginx.com int
nxt_python_asgi_init(nxt_unit_init_t * init,nxt_python_proto_t * proto)1381681Smax.romanov@nginx.com nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto)
1391624Smax.romanov@nginx.com {
1401697Smax.romanov@nginx.com     PyObject      *func;
1411872So.canty@f5.com     nxt_int_t     i;
1421697Smax.romanov@nginx.com     PyCodeObject  *code;
1431697Smax.romanov@nginx.com 
1441681Smax.romanov@nginx.com     nxt_unit_debug(NULL, "asgi_init");
1451624Smax.romanov@nginx.com 
1461681Smax.romanov@nginx.com     if (nxt_slow_path(nxt_py_asgi_str_init() != NXT_UNIT_OK)) {
1471681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to init string objects");
1481681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
1491624Smax.romanov@nginx.com     }
1501624Smax.romanov@nginx.com 
1511624Smax.romanov@nginx.com     nxt_py_port_read = PyCFunction_New(&nxt_py_port_read_method, NULL);
1521624Smax.romanov@nginx.com     if (nxt_slow_path(nxt_py_port_read == NULL)) {
1531681Smax.romanov@nginx.com         nxt_unit_alert(NULL,
1541681Smax.romanov@nginx.com                        "Python failed to initialize the 'port_read' function");
1551681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
1561624Smax.romanov@nginx.com     }
1571624Smax.romanov@nginx.com 
1581681Smax.romanov@nginx.com     if (nxt_slow_path(nxt_py_asgi_http_init() == NXT_UNIT_ERROR)) {
1591681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
1601624Smax.romanov@nginx.com     }
1611624Smax.romanov@nginx.com 
1621681Smax.romanov@nginx.com     if (nxt_slow_path(nxt_py_asgi_websocket_init() == NXT_UNIT_ERROR)) {
1631681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
1641624Smax.romanov@nginx.com     }
1651624Smax.romanov@nginx.com 
1661872So.canty@f5.com     for (i = 0; i < nxt_py_targets->count; i++) {
1671872So.canty@f5.com         func = nxt_python_asgi_get_func(nxt_py_targets->target[i].application);
1681872So.canty@f5.com         if (nxt_slow_path(func == NULL)) {
1692067Smax.romanov@nginx.com             nxt_unit_debug(NULL, "asgi: cannot find function for callable, "
170*2068Smax.romanov@nginx.com                                  "unable to check for legacy mode (#%d)",
171*2068Smax.romanov@nginx.com                                  (int) i);
1722067Smax.romanov@nginx.com             continue;
1731872So.canty@f5.com         }
1741697Smax.romanov@nginx.com 
1751872So.canty@f5.com         code = (PyCodeObject *) PyFunction_GET_CODE(func);
1761697Smax.romanov@nginx.com 
1771872So.canty@f5.com         if ((code->co_flags & CO_COROUTINE) == 0) {
1781872So.canty@f5.com             nxt_unit_debug(NULL, "asgi: callable is not a coroutine function "
1791872So.canty@f5.com                                  "switching to legacy mode");
1801872So.canty@f5.com             nxt_py_targets->target[i].asgi_legacy = 1;
1811872So.canty@f5.com         }
1821872So.canty@f5.com 
1831872So.canty@f5.com         Py_DECREF(func);
1841697Smax.romanov@nginx.com     }
1851697Smax.romanov@nginx.com 
1861624Smax.romanov@nginx.com     init->callbacks.request_handler = nxt_py_asgi_request_handler;
1871624Smax.romanov@nginx.com     init->callbacks.data_handler = nxt_py_asgi_http_data_handler;
1881624Smax.romanov@nginx.com     init->callbacks.websocket_handler = nxt_py_asgi_websocket_handler;
1891715Smax.romanov@nginx.com     init->callbacks.close_handler = nxt_py_asgi_close_handler;
1901624Smax.romanov@nginx.com     init->callbacks.quit = nxt_py_asgi_quit;
1911624Smax.romanov@nginx.com     init->callbacks.shm_ack_handler = nxt_py_asgi_shm_ack_handler;
1921624Smax.romanov@nginx.com     init->callbacks.add_port = nxt_py_asgi_add_port;
1931624Smax.romanov@nginx.com     init->callbacks.remove_port = nxt_py_asgi_remove_port;
1941624Smax.romanov@nginx.com 
1951681Smax.romanov@nginx.com     *proto = nxt_py_asgi_proto;
1961681Smax.romanov@nginx.com 
1971681Smax.romanov@nginx.com     return NXT_UNIT_OK;
1981681Smax.romanov@nginx.com }
1991681Smax.romanov@nginx.com 
2001681Smax.romanov@nginx.com 
2011681Smax.romanov@nginx.com static int
nxt_python_asgi_ctx_data_alloc(void ** pdata,int main)2021918Smax.romanov@nginx.com nxt_python_asgi_ctx_data_alloc(void **pdata, int main)
2031681Smax.romanov@nginx.com {
2041681Smax.romanov@nginx.com     uint32_t                i;
2051918Smax.romanov@nginx.com     PyObject                *asyncio, *loop, *event_loop, *obj;
2061918Smax.romanov@nginx.com     const char              *event_loop_func;
2071681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
2081681Smax.romanov@nginx.com 
2091681Smax.romanov@nginx.com     ctx_data = nxt_unit_malloc(NULL, sizeof(nxt_py_asgi_ctx_data_t));
2101681Smax.romanov@nginx.com     if (nxt_slow_path(ctx_data == NULL)) {
2111681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Failed to allocate context data");
2121681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
2131681Smax.romanov@nginx.com     }
2141681Smax.romanov@nginx.com 
2151681Smax.romanov@nginx.com     memset(ctx_data, 0, sizeof(nxt_py_asgi_ctx_data_t));
2161681Smax.romanov@nginx.com 
2171681Smax.romanov@nginx.com     nxt_queue_init(&ctx_data->drain_queue);
2181681Smax.romanov@nginx.com 
2191681Smax.romanov@nginx.com     struct {
2201681Smax.romanov@nginx.com         const char  *key;
2211681Smax.romanov@nginx.com         PyObject    **handler;
2221681Smax.romanov@nginx.com 
2231681Smax.romanov@nginx.com     } handlers[] = {
2241681Smax.romanov@nginx.com         { "create_task",        &ctx_data->loop_create_task },
2251681Smax.romanov@nginx.com         { "add_reader",         &ctx_data->loop_add_reader },
2261681Smax.romanov@nginx.com         { "remove_reader",      &ctx_data->loop_remove_reader },
2271681Smax.romanov@nginx.com         { "call_soon",          &ctx_data->loop_call_soon },
2281681Smax.romanov@nginx.com         { "run_until_complete", &ctx_data->loop_run_until_complete },
2291681Smax.romanov@nginx.com         { "create_future",      &ctx_data->loop_create_future },
2301681Smax.romanov@nginx.com     };
2311681Smax.romanov@nginx.com 
2321681Smax.romanov@nginx.com     loop = NULL;
2331681Smax.romanov@nginx.com 
2341681Smax.romanov@nginx.com     asyncio = PyImport_ImportModule("asyncio");
2351681Smax.romanov@nginx.com     if (nxt_slow_path(asyncio == NULL)) {
2361681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to import module 'asyncio'");
2371681Smax.romanov@nginx.com         nxt_python_print_exception();
2381681Smax.romanov@nginx.com         goto fail;
2391681Smax.romanov@nginx.com     }
2401681Smax.romanov@nginx.com 
2411918Smax.romanov@nginx.com     event_loop_func = main ? "get_event_loop" : "new_event_loop";
2421918Smax.romanov@nginx.com 
2431918Smax.romanov@nginx.com     event_loop = PyDict_GetItemString(PyModule_GetDict(asyncio),
2441918Smax.romanov@nginx.com                                       event_loop_func);
2451918Smax.romanov@nginx.com     if (nxt_slow_path(event_loop == NULL)) {
2461681Smax.romanov@nginx.com         nxt_unit_alert(NULL,
2471918Smax.romanov@nginx.com                        "Python failed to get '%s' from module 'asyncio'",
2481918Smax.romanov@nginx.com                        event_loop_func);
2491681Smax.romanov@nginx.com         goto fail;
2501681Smax.romanov@nginx.com     }
2511681Smax.romanov@nginx.com 
2521918Smax.romanov@nginx.com     if (nxt_slow_path(PyCallable_Check(event_loop) == 0)) {
2531681Smax.romanov@nginx.com         nxt_unit_alert(NULL,
2541918Smax.romanov@nginx.com                        "'asyncio.%s' is not a callable object",
2551918Smax.romanov@nginx.com                        event_loop_func);
2561681Smax.romanov@nginx.com         goto fail;
2571681Smax.romanov@nginx.com     }
2581681Smax.romanov@nginx.com 
2591918Smax.romanov@nginx.com     loop = PyObject_CallObject(event_loop, NULL);
2601681Smax.romanov@nginx.com     if (nxt_slow_path(loop == NULL)) {
2611918Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to call 'asyncio.%s'",
2621918Smax.romanov@nginx.com                        event_loop_func);
2631681Smax.romanov@nginx.com         goto fail;
2641681Smax.romanov@nginx.com     }
2651681Smax.romanov@nginx.com 
2661681Smax.romanov@nginx.com     for (i = 0; i < nxt_nitems(handlers); i++) {
2671681Smax.romanov@nginx.com         obj = PyObject_GetAttrString(loop, handlers[i].key);
2681681Smax.romanov@nginx.com         if (nxt_slow_path(obj == NULL)) {
2691681Smax.romanov@nginx.com             nxt_unit_alert(NULL, "Python failed to get 'loop.%s'",
2701681Smax.romanov@nginx.com                                  handlers[i].key);
2711681Smax.romanov@nginx.com             goto fail;
2721681Smax.romanov@nginx.com         }
2731681Smax.romanov@nginx.com 
2741681Smax.romanov@nginx.com         *handlers[i].handler = obj;
2751681Smax.romanov@nginx.com 
2761681Smax.romanov@nginx.com         if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
2771681Smax.romanov@nginx.com             nxt_unit_alert(NULL, "'loop.%s' is not a callable object",
2781681Smax.romanov@nginx.com                                  handlers[i].key);
2791681Smax.romanov@nginx.com             goto fail;
2801681Smax.romanov@nginx.com         }
2811681Smax.romanov@nginx.com     }
2821681Smax.romanov@nginx.com 
2831681Smax.romanov@nginx.com     obj = PyObject_CallObject(ctx_data->loop_create_future, NULL);
2841681Smax.romanov@nginx.com     if (nxt_slow_path(obj == NULL)) {
2851681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to create Future ");
2861681Smax.romanov@nginx.com         nxt_python_print_exception();
2871681Smax.romanov@nginx.com         goto fail;
2881681Smax.romanov@nginx.com     }
2891681Smax.romanov@nginx.com 
2901681Smax.romanov@nginx.com     ctx_data->quit_future = obj;
2911681Smax.romanov@nginx.com 
2921681Smax.romanov@nginx.com     obj = PyObject_GetAttrString(ctx_data->quit_future, "set_result");
2931681Smax.romanov@nginx.com     if (nxt_slow_path(obj == NULL)) {
2941681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "Python failed to get 'future.set_result'");
2951681Smax.romanov@nginx.com         goto fail;
2961681Smax.romanov@nginx.com     }
2971681Smax.romanov@nginx.com 
2981681Smax.romanov@nginx.com     ctx_data->quit_future_set_result = obj;
2991681Smax.romanov@nginx.com 
3001681Smax.romanov@nginx.com     if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
3011681Smax.romanov@nginx.com         nxt_unit_alert(NULL, "'future.set_result' is not a callable object");
3021681Smax.romanov@nginx.com         goto fail;
3031681Smax.romanov@nginx.com     }
3041681Smax.romanov@nginx.com 
3051624Smax.romanov@nginx.com     Py_DECREF(loop);
3061624Smax.romanov@nginx.com     Py_DECREF(asyncio);
3071624Smax.romanov@nginx.com 
3081681Smax.romanov@nginx.com     *pdata = ctx_data;
3091681Smax.romanov@nginx.com 
3101681Smax.romanov@nginx.com     return NXT_UNIT_OK;
3111624Smax.romanov@nginx.com 
3121624Smax.romanov@nginx.com fail:
3131624Smax.romanov@nginx.com 
3141681Smax.romanov@nginx.com     nxt_python_asgi_ctx_data_free(ctx_data);
3151681Smax.romanov@nginx.com 
3161624Smax.romanov@nginx.com     Py_XDECREF(loop);
3171681Smax.romanov@nginx.com     Py_XDECREF(asyncio);
3181624Smax.romanov@nginx.com 
3191681Smax.romanov@nginx.com     return NXT_UNIT_ERROR;
3201624Smax.romanov@nginx.com }
3211624Smax.romanov@nginx.com 
3221624Smax.romanov@nginx.com 
3231681Smax.romanov@nginx.com static void
nxt_python_asgi_ctx_data_free(void * data)3241681Smax.romanov@nginx.com nxt_python_asgi_ctx_data_free(void *data)
3251681Smax.romanov@nginx.com {
3261681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
3271681Smax.romanov@nginx.com 
3281681Smax.romanov@nginx.com     ctx_data = data;
3291681Smax.romanov@nginx.com 
3301681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->loop_run_until_complete);
3311681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->loop_create_future);
3321681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->loop_create_task);
3331681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->loop_call_soon);
3341681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->loop_add_reader);
3351681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->loop_remove_reader);
3361681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->quit_future);
3371681Smax.romanov@nginx.com     Py_XDECREF(ctx_data->quit_future_set_result);
3381681Smax.romanov@nginx.com 
3391681Smax.romanov@nginx.com     nxt_unit_free(NULL, ctx_data);
3401681Smax.romanov@nginx.com }
3411681Smax.romanov@nginx.com 
3421681Smax.romanov@nginx.com 
3431681Smax.romanov@nginx.com static int
nxt_python_asgi_startup(void * data)3441681Smax.romanov@nginx.com nxt_python_asgi_startup(void *data)
3451681Smax.romanov@nginx.com {
3461681Smax.romanov@nginx.com     return nxt_py_asgi_lifespan_startup(data);
3471681Smax.romanov@nginx.com }
3481681Smax.romanov@nginx.com 
3491681Smax.romanov@nginx.com 
3501681Smax.romanov@nginx.com static int
nxt_python_asgi_run(nxt_unit_ctx_t * ctx)3511624Smax.romanov@nginx.com nxt_python_asgi_run(nxt_unit_ctx_t *ctx)
3521624Smax.romanov@nginx.com {
3531681Smax.romanov@nginx.com     PyObject                *res;
3541681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
3551624Smax.romanov@nginx.com 
3561681Smax.romanov@nginx.com     ctx_data = ctx->data;
3571681Smax.romanov@nginx.com 
3581681Smax.romanov@nginx.com     res = PyObject_CallFunctionObjArgs(ctx_data->loop_run_until_complete,
3591681Smax.romanov@nginx.com                                        ctx_data->quit_future, NULL);
3601624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
3611624Smax.romanov@nginx.com         nxt_unit_alert(ctx, "Python failed to call loop.run_until_complete");
3621624Smax.romanov@nginx.com         nxt_python_print_exception();
3631624Smax.romanov@nginx.com 
3641681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
3651624Smax.romanov@nginx.com     }
3661624Smax.romanov@nginx.com 
3671624Smax.romanov@nginx.com     Py_DECREF(res);
3681624Smax.romanov@nginx.com 
3691681Smax.romanov@nginx.com     nxt_py_asgi_lifespan_shutdown(ctx);
3701681Smax.romanov@nginx.com 
3711681Smax.romanov@nginx.com     return NXT_UNIT_OK;
3721681Smax.romanov@nginx.com }
3731681Smax.romanov@nginx.com 
3741624Smax.romanov@nginx.com 
3751681Smax.romanov@nginx.com static void
nxt_py_asgi_remove_reader(nxt_unit_ctx_t * ctx,nxt_unit_port_t * port)3761681Smax.romanov@nginx.com nxt_py_asgi_remove_reader(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port)
3771681Smax.romanov@nginx.com {
3781682Smax.romanov@nginx.com     PyObject                *res, *fd;
3791681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
3801681Smax.romanov@nginx.com 
3811681Smax.romanov@nginx.com     if (port == NULL || port->in_fd == -1) {
3821681Smax.romanov@nginx.com         return;
3831681Smax.romanov@nginx.com     }
3841681Smax.romanov@nginx.com 
3851681Smax.romanov@nginx.com     ctx_data = ctx->data;
3861681Smax.romanov@nginx.com 
3871681Smax.romanov@nginx.com     nxt_unit_debug(ctx, "asgi_remove_reader %d %p", port->in_fd, port);
3881681Smax.romanov@nginx.com 
3891682Smax.romanov@nginx.com     fd = PyLong_FromLong(port->in_fd);
3901682Smax.romanov@nginx.com     if (nxt_slow_path(fd == NULL)) {
3911682Smax.romanov@nginx.com         nxt_unit_alert(ctx, "Python failed to create Long object");
3921681Smax.romanov@nginx.com         nxt_python_print_exception();
3931681Smax.romanov@nginx.com 
3941681Smax.romanov@nginx.com         return;
3951681Smax.romanov@nginx.com     }
3961681Smax.romanov@nginx.com 
3971682Smax.romanov@nginx.com     res = PyObject_CallFunctionObjArgs(ctx_data->loop_remove_reader, fd, NULL);
3981682Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
3991682Smax.romanov@nginx.com         nxt_unit_alert(ctx, "Python failed to remove_reader");
4001682Smax.romanov@nginx.com         nxt_python_print_exception();
4011682Smax.romanov@nginx.com 
4021682Smax.romanov@nginx.com     } else {
4031682Smax.romanov@nginx.com         Py_DECREF(res);
4041682Smax.romanov@nginx.com     }
4051682Smax.romanov@nginx.com 
4061682Smax.romanov@nginx.com     Py_DECREF(fd);
4071624Smax.romanov@nginx.com }
4081624Smax.romanov@nginx.com 
4091624Smax.romanov@nginx.com 
4101624Smax.romanov@nginx.com static void
nxt_py_asgi_request_handler(nxt_unit_request_info_t * req)4111624Smax.romanov@nginx.com nxt_py_asgi_request_handler(nxt_unit_request_info_t *req)
4121624Smax.romanov@nginx.com {
4131681Smax.romanov@nginx.com     PyObject                *scope, *res, *task, *receive, *send, *done, *asgi;
4141697Smax.romanov@nginx.com     PyObject                *stage2;
4151872So.canty@f5.com     nxt_python_target_t     *target;
4161681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
4171624Smax.romanov@nginx.com 
4181624Smax.romanov@nginx.com     if (req->request->websocket_handshake) {
4191624Smax.romanov@nginx.com         asgi = nxt_py_asgi_websocket_create(req);
4201624Smax.romanov@nginx.com 
4211624Smax.romanov@nginx.com     } else {
4221624Smax.romanov@nginx.com         asgi = nxt_py_asgi_http_create(req);
4231624Smax.romanov@nginx.com     }
4241624Smax.romanov@nginx.com 
4251624Smax.romanov@nginx.com     if (nxt_slow_path(asgi == NULL)) {
4261624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create asgi object");
4271624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
4281624Smax.romanov@nginx.com 
4291624Smax.romanov@nginx.com         return;
4301624Smax.romanov@nginx.com     }
4311624Smax.romanov@nginx.com 
4321624Smax.romanov@nginx.com     receive = PyObject_GetAttrString(asgi, "receive");
4331624Smax.romanov@nginx.com     if (nxt_slow_path(receive == NULL)) {
4341624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to get 'receive' method");
4351624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
4361624Smax.romanov@nginx.com 
4371624Smax.romanov@nginx.com         goto release_asgi;
4381624Smax.romanov@nginx.com     }
4391624Smax.romanov@nginx.com 
4401624Smax.romanov@nginx.com     send = PyObject_GetAttrString(asgi, "send");
4411624Smax.romanov@nginx.com     if (nxt_slow_path(receive == NULL)) {
4421624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to get 'send' method");
4431624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
4441624Smax.romanov@nginx.com 
4451624Smax.romanov@nginx.com         goto release_receive;
4461624Smax.romanov@nginx.com     }
4471624Smax.romanov@nginx.com 
4481624Smax.romanov@nginx.com     done = PyObject_GetAttrString(asgi, "_done");
4491624Smax.romanov@nginx.com     if (nxt_slow_path(receive == NULL)) {
4501624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to get '_done' method");
4511624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
4521624Smax.romanov@nginx.com 
4531624Smax.romanov@nginx.com         goto release_send;
4541624Smax.romanov@nginx.com     }
4551624Smax.romanov@nginx.com 
4561624Smax.romanov@nginx.com     scope = nxt_py_asgi_create_http_scope(req);
4571624Smax.romanov@nginx.com     if (nxt_slow_path(scope == NULL)) {
4581624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
4591624Smax.romanov@nginx.com 
4601624Smax.romanov@nginx.com         goto release_done;
4611624Smax.romanov@nginx.com     }
4621624Smax.romanov@nginx.com 
4631624Smax.romanov@nginx.com     req->data = asgi;
4641872So.canty@f5.com     target = &nxt_py_targets->target[req->request->app_target];
4651624Smax.romanov@nginx.com 
4661872So.canty@f5.com     if (!target->asgi_legacy) {
4671697Smax.romanov@nginx.com         nxt_unit_req_debug(req, "Python call ASGI 3.0 application");
4681697Smax.romanov@nginx.com 
4691872So.canty@f5.com         res = PyObject_CallFunctionObjArgs(target->application,
4701697Smax.romanov@nginx.com                                            scope, receive, send, NULL);
4711697Smax.romanov@nginx.com 
4721697Smax.romanov@nginx.com     } else {
4731697Smax.romanov@nginx.com         nxt_unit_req_debug(req, "Python call legacy application");
4741697Smax.romanov@nginx.com 
4751872So.canty@f5.com         res = PyObject_CallFunctionObjArgs(target->application, scope, NULL);
4761697Smax.romanov@nginx.com 
4771697Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
4781697Smax.romanov@nginx.com             nxt_unit_req_error(req, "Python failed to call legacy app stage1");
4791697Smax.romanov@nginx.com             nxt_python_print_exception();
4801697Smax.romanov@nginx.com             nxt_unit_request_done(req, NXT_UNIT_ERROR);
4811697Smax.romanov@nginx.com 
4821697Smax.romanov@nginx.com             goto release_scope;
4831697Smax.romanov@nginx.com         }
4841697Smax.romanov@nginx.com 
4851697Smax.romanov@nginx.com         if (nxt_slow_path(PyCallable_Check(res) == 0)) {
4861697Smax.romanov@nginx.com             nxt_unit_req_error(req,
4871697Smax.romanov@nginx.com                               "Legacy ASGI application returns not a callable");
4881697Smax.romanov@nginx.com             nxt_unit_request_done(req, NXT_UNIT_ERROR);
4891697Smax.romanov@nginx.com 
4901697Smax.romanov@nginx.com             Py_DECREF(res);
4911697Smax.romanov@nginx.com 
4921697Smax.romanov@nginx.com             goto release_scope;
4931697Smax.romanov@nginx.com         }
4941697Smax.romanov@nginx.com 
4951697Smax.romanov@nginx.com         stage2 = res;
4961697Smax.romanov@nginx.com 
4971697Smax.romanov@nginx.com         res = PyObject_CallFunctionObjArgs(stage2, receive, send, NULL);
4981697Smax.romanov@nginx.com 
4991697Smax.romanov@nginx.com         Py_DECREF(stage2);
5001697Smax.romanov@nginx.com     }
5011697Smax.romanov@nginx.com 
5021624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
5031624Smax.romanov@nginx.com         nxt_unit_req_error(req, "Python failed to call the application");
5041624Smax.romanov@nginx.com         nxt_python_print_exception();
5051624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
5061624Smax.romanov@nginx.com 
5071624Smax.romanov@nginx.com         goto release_scope;
5081624Smax.romanov@nginx.com     }
5091624Smax.romanov@nginx.com 
5101624Smax.romanov@nginx.com     if (nxt_slow_path(!PyCoro_CheckExact(res))) {
5111624Smax.romanov@nginx.com         nxt_unit_req_error(req, "Application result type is not a coroutine");
5121624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
5131624Smax.romanov@nginx.com 
5141624Smax.romanov@nginx.com         Py_DECREF(res);
5151624Smax.romanov@nginx.com 
5161624Smax.romanov@nginx.com         goto release_scope;
5171624Smax.romanov@nginx.com     }
5181624Smax.romanov@nginx.com 
5191681Smax.romanov@nginx.com     ctx_data = req->ctx->data;
5201681Smax.romanov@nginx.com 
5211681Smax.romanov@nginx.com     task = PyObject_CallFunctionObjArgs(ctx_data->loop_create_task, res, NULL);
5221624Smax.romanov@nginx.com     if (nxt_slow_path(task == NULL)) {
5231624Smax.romanov@nginx.com         nxt_unit_req_error(req, "Python failed to call the create_task");
5241624Smax.romanov@nginx.com         nxt_python_print_exception();
5251624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
5261624Smax.romanov@nginx.com 
5271624Smax.romanov@nginx.com         Py_DECREF(res);
5281624Smax.romanov@nginx.com 
5291624Smax.romanov@nginx.com         goto release_scope;
5301624Smax.romanov@nginx.com     }
5311624Smax.romanov@nginx.com 
5321624Smax.romanov@nginx.com     Py_DECREF(res);
5331624Smax.romanov@nginx.com 
5341624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(task, nxt_py_add_done_callback_str, done,
5351624Smax.romanov@nginx.com                                      NULL);
5361624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
5371624Smax.romanov@nginx.com         nxt_unit_req_error(req,
5381624Smax.romanov@nginx.com                            "Python failed to call 'task.add_done_callback'");
5391624Smax.romanov@nginx.com         nxt_python_print_exception();
5401624Smax.romanov@nginx.com         nxt_unit_request_done(req, NXT_UNIT_ERROR);
5411624Smax.romanov@nginx.com 
5421624Smax.romanov@nginx.com         goto release_task;
5431624Smax.romanov@nginx.com     }
5441624Smax.romanov@nginx.com 
5451624Smax.romanov@nginx.com     Py_DECREF(res);
5461624Smax.romanov@nginx.com release_task:
5471624Smax.romanov@nginx.com     Py_DECREF(task);
5481624Smax.romanov@nginx.com release_scope:
5491624Smax.romanov@nginx.com     Py_DECREF(scope);
5501624Smax.romanov@nginx.com release_done:
5511624Smax.romanov@nginx.com     Py_DECREF(done);
5521624Smax.romanov@nginx.com release_send:
5531624Smax.romanov@nginx.com     Py_DECREF(send);
5541624Smax.romanov@nginx.com release_receive:
5551624Smax.romanov@nginx.com     Py_DECREF(receive);
5561624Smax.romanov@nginx.com release_asgi:
5571624Smax.romanov@nginx.com     Py_DECREF(asgi);
5581624Smax.romanov@nginx.com }
5591624Smax.romanov@nginx.com 
5601624Smax.romanov@nginx.com 
5611715Smax.romanov@nginx.com static void
nxt_py_asgi_close_handler(nxt_unit_request_info_t * req)5621715Smax.romanov@nginx.com nxt_py_asgi_close_handler(nxt_unit_request_info_t *req)
5631715Smax.romanov@nginx.com {
5641715Smax.romanov@nginx.com     if (req->request->websocket_handshake) {
5651715Smax.romanov@nginx.com         nxt_py_asgi_websocket_close_handler(req);
5661715Smax.romanov@nginx.com 
5671715Smax.romanov@nginx.com     } else {
5681715Smax.romanov@nginx.com         nxt_py_asgi_http_close_handler(req);
5691715Smax.romanov@nginx.com     }
5701715Smax.romanov@nginx.com }
5711715Smax.romanov@nginx.com 
5721715Smax.romanov@nginx.com 
5731624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_create_http_scope(nxt_unit_request_info_t * req)5741624Smax.romanov@nginx.com nxt_py_asgi_create_http_scope(nxt_unit_request_info_t *req)
5751624Smax.romanov@nginx.com {
5761624Smax.romanov@nginx.com     char                *p, *target, *query;
5771624Smax.romanov@nginx.com     uint32_t            target_length, i;
5781624Smax.romanov@nginx.com     PyObject            *scope, *v, *type, *scheme;
5791624Smax.romanov@nginx.com     PyObject            *headers, *header;
5801624Smax.romanov@nginx.com     nxt_unit_field_t    *f;
5811624Smax.romanov@nginx.com     nxt_unit_request_t  *r;
5821624Smax.romanov@nginx.com 
5831624Smax.romanov@nginx.com     static const nxt_str_t  ws_protocol = nxt_string("sec-websocket-protocol");
5841624Smax.romanov@nginx.com 
5851624Smax.romanov@nginx.com #define SET_ITEM(dict, key, value) \
5861624Smax.romanov@nginx.com     if (nxt_slow_path(PyDict_SetItem(dict, nxt_py_ ## key ## _str, value)      \
5871624Smax.romanov@nginx.com                       == -1))                                                  \
5881624Smax.romanov@nginx.com     {                                                                          \
5891624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to set '"                       \
5901624Smax.romanov@nginx.com                                 #dict "." #key "' item");                      \
5911624Smax.romanov@nginx.com         goto fail;                                                             \
5921624Smax.romanov@nginx.com     }
5931624Smax.romanov@nginx.com 
5941624Smax.romanov@nginx.com     v = NULL;
5951624Smax.romanov@nginx.com     headers = NULL;
5961624Smax.romanov@nginx.com 
5971624Smax.romanov@nginx.com     r = req->request;
5981624Smax.romanov@nginx.com 
5991624Smax.romanov@nginx.com     if (r->websocket_handshake) {
6001624Smax.romanov@nginx.com         type = nxt_py_websocket_str;
6011624Smax.romanov@nginx.com         scheme = r->tls ? nxt_py_wss_str : nxt_py_ws_str;
6021624Smax.romanov@nginx.com 
6031624Smax.romanov@nginx.com     } else {
6041624Smax.romanov@nginx.com         type = nxt_py_http_str;
6051624Smax.romanov@nginx.com         scheme = r->tls ? nxt_py_https_str : nxt_py_http_str;
6061624Smax.romanov@nginx.com     }
6071624Smax.romanov@nginx.com 
6081624Smax.romanov@nginx.com     scope = nxt_py_asgi_new_scope(req, type, nxt_py_2_1_str);
6091624Smax.romanov@nginx.com     if (nxt_slow_path(scope == NULL)) {
6101624Smax.romanov@nginx.com         return NULL;
6111624Smax.romanov@nginx.com     }
6121624Smax.romanov@nginx.com 
6131624Smax.romanov@nginx.com     p = nxt_unit_sptr_get(&r->version);
6141624Smax.romanov@nginx.com     SET_ITEM(scope, http_version, p[7] == '1' ? nxt_py_1_1_str
6151624Smax.romanov@nginx.com                                               : nxt_py_1_0_str)
6161624Smax.romanov@nginx.com     SET_ITEM(scope, scheme, scheme)
6171624Smax.romanov@nginx.com 
6181624Smax.romanov@nginx.com     v = PyString_FromStringAndSize(nxt_unit_sptr_get(&r->method),
6191624Smax.romanov@nginx.com                                    r->method_length);
6201624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
6211624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create 'method' string");
6221624Smax.romanov@nginx.com         goto fail;
6231624Smax.romanov@nginx.com     }
6241624Smax.romanov@nginx.com 
6251624Smax.romanov@nginx.com     SET_ITEM(scope, method, v)
6261624Smax.romanov@nginx.com     Py_DECREF(v);
6271624Smax.romanov@nginx.com 
6281624Smax.romanov@nginx.com     v = PyUnicode_DecodeUTF8(nxt_unit_sptr_get(&r->path), r->path_length,
6291624Smax.romanov@nginx.com                              "replace");
6301624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
6311624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create 'path' string");
6321624Smax.romanov@nginx.com         goto fail;
6331624Smax.romanov@nginx.com     }
6341624Smax.romanov@nginx.com 
6351624Smax.romanov@nginx.com     SET_ITEM(scope, path, v)
6361624Smax.romanov@nginx.com     Py_DECREF(v);
6371624Smax.romanov@nginx.com 
6381624Smax.romanov@nginx.com     target = nxt_unit_sptr_get(&r->target);
6391624Smax.romanov@nginx.com     query = nxt_unit_sptr_get(&r->query);
6401624Smax.romanov@nginx.com 
6411624Smax.romanov@nginx.com     if (r->query.offset != 0) {
6421624Smax.romanov@nginx.com         target_length = query - target - 1;
6431624Smax.romanov@nginx.com 
6441624Smax.romanov@nginx.com     } else {
6451624Smax.romanov@nginx.com         target_length = r->target_length;
6461624Smax.romanov@nginx.com     }
6471624Smax.romanov@nginx.com 
6481624Smax.romanov@nginx.com     v = PyBytes_FromStringAndSize(target, target_length);
6491624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
6501624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create 'raw_path' string");
6511624Smax.romanov@nginx.com         goto fail;
6521624Smax.romanov@nginx.com     }
6531624Smax.romanov@nginx.com 
6541624Smax.romanov@nginx.com     SET_ITEM(scope, raw_path, v)
6551624Smax.romanov@nginx.com     Py_DECREF(v);
6561624Smax.romanov@nginx.com 
6571624Smax.romanov@nginx.com     v = PyBytes_FromStringAndSize(query, r->query_length);
6581624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
6591624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create 'query' string");
6601624Smax.romanov@nginx.com         goto fail;
6611624Smax.romanov@nginx.com     }
6621624Smax.romanov@nginx.com 
6631624Smax.romanov@nginx.com     SET_ITEM(scope, query_string, v)
6641624Smax.romanov@nginx.com     Py_DECREF(v);
6651624Smax.romanov@nginx.com 
6661624Smax.romanov@nginx.com     v = nxt_py_asgi_create_address(&r->remote, r->remote_length, 0);
6671624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
6681624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create 'client' pair");
6691624Smax.romanov@nginx.com         goto fail;
6701624Smax.romanov@nginx.com     }
6711624Smax.romanov@nginx.com 
6721624Smax.romanov@nginx.com     SET_ITEM(scope, client, v)
6731624Smax.romanov@nginx.com     Py_DECREF(v);
6741624Smax.romanov@nginx.com 
6751624Smax.romanov@nginx.com     v = nxt_py_asgi_create_address(&r->local, r->local_length, 80);
6761624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
6771624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create 'server' pair");
6781624Smax.romanov@nginx.com         goto fail;
6791624Smax.romanov@nginx.com     }
6801624Smax.romanov@nginx.com 
6811624Smax.romanov@nginx.com     SET_ITEM(scope, server, v)
6821624Smax.romanov@nginx.com     Py_DECREF(v);
6831624Smax.romanov@nginx.com 
6841624Smax.romanov@nginx.com     v = NULL;
6851624Smax.romanov@nginx.com 
6861624Smax.romanov@nginx.com     headers = PyTuple_New(r->fields_count);
6871624Smax.romanov@nginx.com     if (nxt_slow_path(headers == NULL)) {
6881624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create 'headers' object");
6891624Smax.romanov@nginx.com         goto fail;
6901624Smax.romanov@nginx.com     }
6911624Smax.romanov@nginx.com 
6921624Smax.romanov@nginx.com     for (i = 0; i < r->fields_count; i++) {
6931624Smax.romanov@nginx.com         f = r->fields + i;
6941624Smax.romanov@nginx.com 
6951624Smax.romanov@nginx.com         header = nxt_py_asgi_create_header(f);
6961624Smax.romanov@nginx.com         if (nxt_slow_path(header == NULL)) {
6971624Smax.romanov@nginx.com             nxt_unit_req_alert(req, "Python failed to create 'header' pair");
6981624Smax.romanov@nginx.com             goto fail;
6991624Smax.romanov@nginx.com         }
7001624Smax.romanov@nginx.com 
7011624Smax.romanov@nginx.com         PyTuple_SET_ITEM(headers, i, header);
7021624Smax.romanov@nginx.com 
7031624Smax.romanov@nginx.com         if (f->hash == NXT_UNIT_HASH_WS_PROTOCOL
7041624Smax.romanov@nginx.com             && f->name_length == ws_protocol.length
7051624Smax.romanov@nginx.com             && f->value_length > 0
7061624Smax.romanov@nginx.com             && r->websocket_handshake)
7071624Smax.romanov@nginx.com         {
7081624Smax.romanov@nginx.com             v = nxt_py_asgi_create_subprotocols(f);
7091624Smax.romanov@nginx.com             if (nxt_slow_path(v == NULL)) {
7101624Smax.romanov@nginx.com                 nxt_unit_req_alert(req, "Failed to create subprotocols");
7111624Smax.romanov@nginx.com                 goto fail;
7121624Smax.romanov@nginx.com             }
7131624Smax.romanov@nginx.com 
7141624Smax.romanov@nginx.com             SET_ITEM(scope, subprotocols, v);
7151624Smax.romanov@nginx.com             Py_DECREF(v);
7161624Smax.romanov@nginx.com         }
7171624Smax.romanov@nginx.com     }
7181624Smax.romanov@nginx.com 
7191624Smax.romanov@nginx.com     SET_ITEM(scope, headers, headers)
7201624Smax.romanov@nginx.com     Py_DECREF(headers);
7211624Smax.romanov@nginx.com 
7221624Smax.romanov@nginx.com     return scope;
7231624Smax.romanov@nginx.com 
7241624Smax.romanov@nginx.com fail:
7251624Smax.romanov@nginx.com 
7261624Smax.romanov@nginx.com     Py_XDECREF(v);
7271624Smax.romanov@nginx.com     Py_XDECREF(headers);
7281624Smax.romanov@nginx.com     Py_DECREF(scope);
7291624Smax.romanov@nginx.com 
7301624Smax.romanov@nginx.com     return NULL;
7311624Smax.romanov@nginx.com 
7321624Smax.romanov@nginx.com #undef SET_ITEM
7331624Smax.romanov@nginx.com }
7341624Smax.romanov@nginx.com 
7351624Smax.romanov@nginx.com 
7361624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_create_address(nxt_unit_sptr_t * sptr,uint8_t len,uint16_t port)7371624Smax.romanov@nginx.com nxt_py_asgi_create_address(nxt_unit_sptr_t *sptr, uint8_t len, uint16_t port)
7381624Smax.romanov@nginx.com {
7391624Smax.romanov@nginx.com     char      *p, *s;
7401624Smax.romanov@nginx.com     PyObject  *pair, *v;
7411624Smax.romanov@nginx.com 
7421624Smax.romanov@nginx.com     pair = PyTuple_New(2);
7431624Smax.romanov@nginx.com     if (nxt_slow_path(pair == NULL)) {
7441624Smax.romanov@nginx.com         return NULL;
7451624Smax.romanov@nginx.com     }
7461624Smax.romanov@nginx.com 
7471624Smax.romanov@nginx.com     p = nxt_unit_sptr_get(sptr);
7481624Smax.romanov@nginx.com     s = memchr(p, ':', len);
7491624Smax.romanov@nginx.com 
7501624Smax.romanov@nginx.com     v = PyString_FromStringAndSize(p, s == NULL ? len : s - p);
7511624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
7521624Smax.romanov@nginx.com         Py_DECREF(pair);
7531624Smax.romanov@nginx.com 
7541624Smax.romanov@nginx.com         return NULL;
7551624Smax.romanov@nginx.com     }
7561624Smax.romanov@nginx.com 
7571624Smax.romanov@nginx.com     PyTuple_SET_ITEM(pair, 0, v);
7581624Smax.romanov@nginx.com 
7591624Smax.romanov@nginx.com     if (s != NULL) {
7601624Smax.romanov@nginx.com         p += len;
7611624Smax.romanov@nginx.com         v = PyLong_FromString(s + 1, &p, 10);
7621624Smax.romanov@nginx.com 
7631624Smax.romanov@nginx.com     } else {
7641624Smax.romanov@nginx.com         v = PyLong_FromLong(port);
7651624Smax.romanov@nginx.com     }
7661624Smax.romanov@nginx.com 
7671624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
7681624Smax.romanov@nginx.com         Py_DECREF(pair);
7691624Smax.romanov@nginx.com 
7701624Smax.romanov@nginx.com         return NULL;
7711624Smax.romanov@nginx.com     }
7721624Smax.romanov@nginx.com 
7731624Smax.romanov@nginx.com     PyTuple_SET_ITEM(pair, 1, v);
7741624Smax.romanov@nginx.com 
7751624Smax.romanov@nginx.com     return pair;
7761624Smax.romanov@nginx.com }
7771624Smax.romanov@nginx.com 
7781624Smax.romanov@nginx.com 
7791624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_create_header(nxt_unit_field_t * f)7801624Smax.romanov@nginx.com nxt_py_asgi_create_header(nxt_unit_field_t *f)
7811624Smax.romanov@nginx.com {
7821624Smax.romanov@nginx.com     char      c, *name;
7831624Smax.romanov@nginx.com     uint8_t   pos;
7841624Smax.romanov@nginx.com     PyObject  *header, *v;
7851624Smax.romanov@nginx.com 
7861624Smax.romanov@nginx.com     header = PyTuple_New(2);
7871624Smax.romanov@nginx.com     if (nxt_slow_path(header == NULL)) {
7881624Smax.romanov@nginx.com         return NULL;
7891624Smax.romanov@nginx.com     }
7901624Smax.romanov@nginx.com 
7911624Smax.romanov@nginx.com     name = nxt_unit_sptr_get(&f->name);
7921624Smax.romanov@nginx.com 
7931624Smax.romanov@nginx.com     for (pos = 0; pos < f->name_length; pos++) {
7941624Smax.romanov@nginx.com         c = name[pos];
7951624Smax.romanov@nginx.com         if (c >= 'A' && c <= 'Z') {
7961624Smax.romanov@nginx.com             name[pos] = (c | 0x20);
7971624Smax.romanov@nginx.com         }
7981624Smax.romanov@nginx.com     }
7991624Smax.romanov@nginx.com 
8001624Smax.romanov@nginx.com     v = PyBytes_FromStringAndSize(name, f->name_length);
8011624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
8021624Smax.romanov@nginx.com         Py_DECREF(header);
8031624Smax.romanov@nginx.com 
8041624Smax.romanov@nginx.com         return NULL;
8051624Smax.romanov@nginx.com     }
8061624Smax.romanov@nginx.com 
8071624Smax.romanov@nginx.com     PyTuple_SET_ITEM(header, 0, v);
8081624Smax.romanov@nginx.com 
8091624Smax.romanov@nginx.com     v = PyBytes_FromStringAndSize(nxt_unit_sptr_get(&f->value),
8101624Smax.romanov@nginx.com                                   f->value_length);
8111624Smax.romanov@nginx.com     if (nxt_slow_path(v == NULL)) {
8121624Smax.romanov@nginx.com         Py_DECREF(header);
8131624Smax.romanov@nginx.com 
8141624Smax.romanov@nginx.com         return NULL;
8151624Smax.romanov@nginx.com     }
8161624Smax.romanov@nginx.com 
8171624Smax.romanov@nginx.com     PyTuple_SET_ITEM(header, 1, v);
8181624Smax.romanov@nginx.com 
8191624Smax.romanov@nginx.com     return header;
8201624Smax.romanov@nginx.com }
8211624Smax.romanov@nginx.com 
8221624Smax.romanov@nginx.com 
8231624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_create_subprotocols(nxt_unit_field_t * f)8241624Smax.romanov@nginx.com nxt_py_asgi_create_subprotocols(nxt_unit_field_t *f)
8251624Smax.romanov@nginx.com {
8261624Smax.romanov@nginx.com     char      *v;
8271624Smax.romanov@nginx.com     uint32_t  i, n, start;
8281624Smax.romanov@nginx.com     PyObject  *res, *proto;
8291624Smax.romanov@nginx.com 
8301624Smax.romanov@nginx.com     v = nxt_unit_sptr_get(&f->value);
8311624Smax.romanov@nginx.com     n = 1;
8321624Smax.romanov@nginx.com 
8331624Smax.romanov@nginx.com     for (i = 0; i < f->value_length; i++) {
8341624Smax.romanov@nginx.com         if (v[i] == ',') {
8351624Smax.romanov@nginx.com             n++;
8361624Smax.romanov@nginx.com         }
8371624Smax.romanov@nginx.com     }
8381624Smax.romanov@nginx.com 
8391624Smax.romanov@nginx.com     res = PyTuple_New(n);
8401624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
8411624Smax.romanov@nginx.com         return NULL;
8421624Smax.romanov@nginx.com     }
8431624Smax.romanov@nginx.com 
8441624Smax.romanov@nginx.com     n = 0;
8451624Smax.romanov@nginx.com     start = 0;
8461624Smax.romanov@nginx.com 
8471624Smax.romanov@nginx.com     for (i = 0; i < f->value_length; ) {
8481624Smax.romanov@nginx.com         if (v[i] != ',') {
8491624Smax.romanov@nginx.com             i++;
8501624Smax.romanov@nginx.com 
8511624Smax.romanov@nginx.com             continue;
8521624Smax.romanov@nginx.com         }
8531624Smax.romanov@nginx.com 
8541624Smax.romanov@nginx.com         if (i - start > 0) {
8551624Smax.romanov@nginx.com             proto = PyString_FromStringAndSize(v + start, i - start);
8561624Smax.romanov@nginx.com             if (nxt_slow_path(proto == NULL)) {
8571624Smax.romanov@nginx.com                 goto fail;
8581624Smax.romanov@nginx.com             }
8591624Smax.romanov@nginx.com 
8601624Smax.romanov@nginx.com             PyTuple_SET_ITEM(res, n, proto);
8611624Smax.romanov@nginx.com 
8621624Smax.romanov@nginx.com             n++;
8631624Smax.romanov@nginx.com         }
8641624Smax.romanov@nginx.com 
8651624Smax.romanov@nginx.com         do {
8661624Smax.romanov@nginx.com             i++;
8671624Smax.romanov@nginx.com         } while (i < f->value_length && v[i] == ' ');
8681624Smax.romanov@nginx.com 
8691624Smax.romanov@nginx.com         start = i;
8701624Smax.romanov@nginx.com     }
8711624Smax.romanov@nginx.com 
8721624Smax.romanov@nginx.com     if (i - start > 0) {
8731624Smax.romanov@nginx.com         proto = PyString_FromStringAndSize(v + start, i - start);
8741624Smax.romanov@nginx.com         if (nxt_slow_path(proto == NULL)) {
8751624Smax.romanov@nginx.com             goto fail;
8761624Smax.romanov@nginx.com         }
8771624Smax.romanov@nginx.com 
8781624Smax.romanov@nginx.com         PyTuple_SET_ITEM(res, n, proto);
8791624Smax.romanov@nginx.com     }
8801624Smax.romanov@nginx.com 
8811624Smax.romanov@nginx.com     return res;
8821624Smax.romanov@nginx.com 
8831624Smax.romanov@nginx.com fail:
8841624Smax.romanov@nginx.com 
8851624Smax.romanov@nginx.com     Py_DECREF(res);
8861624Smax.romanov@nginx.com 
8871624Smax.romanov@nginx.com     return NULL;
8881624Smax.romanov@nginx.com }
8891624Smax.romanov@nginx.com 
8901624Smax.romanov@nginx.com 
8911624Smax.romanov@nginx.com static int
nxt_py_asgi_add_port(nxt_unit_ctx_t * ctx,nxt_unit_port_t * port)8921624Smax.romanov@nginx.com nxt_py_asgi_add_port(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port)
8931624Smax.romanov@nginx.com {
8941980Smax.romanov@nginx.com     int  nb;
8951624Smax.romanov@nginx.com 
8961624Smax.romanov@nginx.com     if (port->in_fd == -1) {
8971624Smax.romanov@nginx.com         return NXT_UNIT_OK;
8981624Smax.romanov@nginx.com     }
8991624Smax.romanov@nginx.com 
9001624Smax.romanov@nginx.com     nb = 1;
9011624Smax.romanov@nginx.com 
9021624Smax.romanov@nginx.com     if (nxt_slow_path(ioctl(port->in_fd, FIONBIO, &nb) == -1)) {
9031624Smax.romanov@nginx.com         nxt_unit_alert(ctx, "ioctl(%d, FIONBIO, 0) failed: %s (%d)",
9041624Smax.romanov@nginx.com                        port->in_fd, strerror(errno), errno);
9051624Smax.romanov@nginx.com 
9061624Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
9071624Smax.romanov@nginx.com     }
9081624Smax.romanov@nginx.com 
9091624Smax.romanov@nginx.com     nxt_unit_debug(ctx, "asgi_add_port %d %p %p", port->in_fd, ctx, port);
9101624Smax.romanov@nginx.com 
9111979Smax.romanov@nginx.com     return nxt_py_asgi_add_reader(ctx, port);
9121979Smax.romanov@nginx.com }
9131979Smax.romanov@nginx.com 
9141681Smax.romanov@nginx.com 
9151979Smax.romanov@nginx.com static int
nxt_py_asgi_add_reader(nxt_unit_ctx_t * ctx,nxt_unit_port_t * port)9161979Smax.romanov@nginx.com nxt_py_asgi_add_reader(nxt_unit_ctx_t *ctx, nxt_unit_port_t *port)
9171979Smax.romanov@nginx.com {
9181979Smax.romanov@nginx.com     int                     rc;
9191979Smax.romanov@nginx.com     PyObject                *res, *fd, *py_ctx, *py_port;
9201979Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
9211979Smax.romanov@nginx.com 
9221979Smax.romanov@nginx.com     nxt_unit_debug(ctx, "asgi_add_reader %d %p %p", port->in_fd, ctx, port);
9231979Smax.romanov@nginx.com 
9241979Smax.romanov@nginx.com     ctx_data = ctx->data;
9251682Smax.romanov@nginx.com 
9261682Smax.romanov@nginx.com     fd = PyLong_FromLong(port->in_fd);
9271682Smax.romanov@nginx.com     if (nxt_slow_path(fd == NULL)) {
9281682Smax.romanov@nginx.com         nxt_unit_alert(ctx, "Python failed to create fd");
9291682Smax.romanov@nginx.com         nxt_python_print_exception();
9301682Smax.romanov@nginx.com 
9311979Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
9321682Smax.romanov@nginx.com     }
9331682Smax.romanov@nginx.com 
9341979Smax.romanov@nginx.com     rc = NXT_UNIT_ERROR;
9351979Smax.romanov@nginx.com 
9361682Smax.romanov@nginx.com     py_ctx = PyLong_FromVoidPtr(ctx);
9371682Smax.romanov@nginx.com     if (nxt_slow_path(py_ctx == NULL)) {
9381682Smax.romanov@nginx.com         nxt_unit_alert(ctx, "Python failed to create py_ctx");
9391682Smax.romanov@nginx.com         nxt_python_print_exception();
9401682Smax.romanov@nginx.com 
9411682Smax.romanov@nginx.com         goto clean_fd;
9421682Smax.romanov@nginx.com     }
9431682Smax.romanov@nginx.com 
9441682Smax.romanov@nginx.com     py_port = PyLong_FromVoidPtr(port);
9451682Smax.romanov@nginx.com     if (nxt_slow_path(py_port == NULL)) {
9461682Smax.romanov@nginx.com         nxt_unit_alert(ctx, "Python failed to create py_port");
9471682Smax.romanov@nginx.com         nxt_python_print_exception();
9481682Smax.romanov@nginx.com 
9491682Smax.romanov@nginx.com         goto clean_py_ctx;
9501682Smax.romanov@nginx.com     }
9511682Smax.romanov@nginx.com 
9521681Smax.romanov@nginx.com     res = PyObject_CallFunctionObjArgs(ctx_data->loop_add_reader,
9531682Smax.romanov@nginx.com                                        fd, nxt_py_port_read,
9541682Smax.romanov@nginx.com                                        py_ctx, py_port, NULL);
9551624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
9561624Smax.romanov@nginx.com         nxt_unit_alert(ctx, "Python failed to add_reader");
9571681Smax.romanov@nginx.com         nxt_python_print_exception();
9581624Smax.romanov@nginx.com 
9591682Smax.romanov@nginx.com     } else {
9601682Smax.romanov@nginx.com         Py_DECREF(res);
9611682Smax.romanov@nginx.com 
9621682Smax.romanov@nginx.com         rc = NXT_UNIT_OK;
9631624Smax.romanov@nginx.com     }
9641624Smax.romanov@nginx.com 
9651682Smax.romanov@nginx.com     Py_DECREF(py_port);
9661682Smax.romanov@nginx.com 
9671682Smax.romanov@nginx.com clean_py_ctx:
9681682Smax.romanov@nginx.com 
9691682Smax.romanov@nginx.com     Py_DECREF(py_ctx);
9701624Smax.romanov@nginx.com 
9711682Smax.romanov@nginx.com clean_fd:
9721682Smax.romanov@nginx.com 
9731682Smax.romanov@nginx.com     Py_DECREF(fd);
9741682Smax.romanov@nginx.com 
9751682Smax.romanov@nginx.com     return rc;
9761624Smax.romanov@nginx.com }
9771624Smax.romanov@nginx.com 
9781624Smax.romanov@nginx.com 
9791624Smax.romanov@nginx.com static void
nxt_py_asgi_remove_port(nxt_unit_t * lib,nxt_unit_ctx_t * ctx,nxt_unit_port_t * port)9801980Smax.romanov@nginx.com nxt_py_asgi_remove_port(nxt_unit_t *lib, nxt_unit_ctx_t *ctx,
9811980Smax.romanov@nginx.com     nxt_unit_port_t *port)
9821624Smax.romanov@nginx.com {
9831980Smax.romanov@nginx.com     if (port->in_fd == -1 || ctx == NULL) {
9841624Smax.romanov@nginx.com         return;
9851624Smax.romanov@nginx.com     }
9861624Smax.romanov@nginx.com 
9871681Smax.romanov@nginx.com     nxt_unit_debug(NULL, "asgi_remove_port %d %p", port->in_fd, port);
9881681Smax.romanov@nginx.com 
9891980Smax.romanov@nginx.com     nxt_py_asgi_remove_reader(ctx, port);
9901624Smax.romanov@nginx.com }
9911624Smax.romanov@nginx.com 
9921624Smax.romanov@nginx.com 
9931624Smax.romanov@nginx.com static void
nxt_py_asgi_quit(nxt_unit_ctx_t * ctx)9941624Smax.romanov@nginx.com nxt_py_asgi_quit(nxt_unit_ctx_t *ctx)
9951624Smax.romanov@nginx.com {
9961682Smax.romanov@nginx.com     PyObject                *res, *p;
9971681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
9981624Smax.romanov@nginx.com 
9991624Smax.romanov@nginx.com     nxt_unit_debug(ctx, "asgi_quit %p", ctx);
10001624