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 <python/nxt_python_asgi.h>
151624Smax.romanov@nginx.com #include <python/nxt_python_asgi_str.h>
161624Smax.romanov@nginx.com 
171624Smax.romanov@nginx.com 
181624Smax.romanov@nginx.com typedef struct {
191624Smax.romanov@nginx.com     PyObject_HEAD
201624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
211624Smax.romanov@nginx.com     nxt_queue_link_t         link;
221624Smax.romanov@nginx.com     PyObject                 *receive_future;
231624Smax.romanov@nginx.com     PyObject                 *send_future;
241624Smax.romanov@nginx.com     uint64_t                 content_length;
251624Smax.romanov@nginx.com     uint64_t                 bytes_sent;
261624Smax.romanov@nginx.com     PyObject                 *send_body;
271624Smax.romanov@nginx.com     Py_ssize_t               send_body_off;
28*1916Smax.romanov@nginx.com     uint8_t                  complete;
29*1916Smax.romanov@nginx.com     uint8_t                  closed;
30*1916Smax.romanov@nginx.com     uint8_t                  empty_body_received;
311624Smax.romanov@nginx.com } nxt_py_asgi_http_t;
321624Smax.romanov@nginx.com 
331624Smax.romanov@nginx.com 
341624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_receive(PyObject *self, PyObject *none);
351624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_read_msg(nxt_py_asgi_http_t *http);
361624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_send(PyObject *self, PyObject *dict);
371624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_response_start(nxt_py_asgi_http_t *http,
381624Smax.romanov@nginx.com     PyObject *dict);
391624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http,
401624Smax.romanov@nginx.com     PyObject *dict);
41*1916Smax.romanov@nginx.com static void nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http);
421624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_done(PyObject *self, PyObject *future);
431624Smax.romanov@nginx.com 
441624Smax.romanov@nginx.com 
451624Smax.romanov@nginx.com static PyMethodDef nxt_py_asgi_http_methods[] = {
461624Smax.romanov@nginx.com     { "receive",   nxt_py_asgi_http_receive, METH_NOARGS, 0 },
471624Smax.romanov@nginx.com     { "send",      nxt_py_asgi_http_send,    METH_O,      0 },
481624Smax.romanov@nginx.com     { "_done",     nxt_py_asgi_http_done,    METH_O,      0 },
491624Smax.romanov@nginx.com     { NULL, NULL, 0, 0 }
501624Smax.romanov@nginx.com };
511624Smax.romanov@nginx.com 
521624Smax.romanov@nginx.com static PyAsyncMethods nxt_py_asgi_async_methods = {
531624Smax.romanov@nginx.com     .am_await = nxt_py_asgi_await,
541624Smax.romanov@nginx.com };
551624Smax.romanov@nginx.com 
561624Smax.romanov@nginx.com static PyTypeObject nxt_py_asgi_http_type = {
571624Smax.romanov@nginx.com     PyVarObject_HEAD_INIT(NULL, 0)
581624Smax.romanov@nginx.com 
591624Smax.romanov@nginx.com     .tp_name      = "unit._asgi_http",
601624Smax.romanov@nginx.com     .tp_basicsize = sizeof(nxt_py_asgi_http_t),
611624Smax.romanov@nginx.com     .tp_dealloc   = nxt_py_asgi_dealloc,
621624Smax.romanov@nginx.com     .tp_as_async  = &nxt_py_asgi_async_methods,
631624Smax.romanov@nginx.com     .tp_flags     = Py_TPFLAGS_DEFAULT,
641624Smax.romanov@nginx.com     .tp_doc       = "unit ASGI HTTP request object",
651624Smax.romanov@nginx.com     .tp_iter      = nxt_py_asgi_iter,
661624Smax.romanov@nginx.com     .tp_iternext  = nxt_py_asgi_next,
671624Smax.romanov@nginx.com     .tp_methods   = nxt_py_asgi_http_methods,
681624Smax.romanov@nginx.com };
691624Smax.romanov@nginx.com 
701624Smax.romanov@nginx.com static Py_ssize_t  nxt_py_asgi_http_body_buf_size = 32 * 1024 * 1024;
711624Smax.romanov@nginx.com 
721624Smax.romanov@nginx.com 
731681Smax.romanov@nginx.com int
741681Smax.romanov@nginx.com nxt_py_asgi_http_init(void)
751624Smax.romanov@nginx.com {
761624Smax.romanov@nginx.com     if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_http_type) != 0)) {
771681Smax.romanov@nginx.com         nxt_unit_alert(NULL,
781681Smax.romanov@nginx.com                        "Python failed to initialize the 'http' type object");
791681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
801624Smax.romanov@nginx.com     }
811624Smax.romanov@nginx.com 
821681Smax.romanov@nginx.com     return NXT_UNIT_OK;
831624Smax.romanov@nginx.com }
841624Smax.romanov@nginx.com 
851624Smax.romanov@nginx.com 
861624Smax.romanov@nginx.com PyObject *
871624Smax.romanov@nginx.com nxt_py_asgi_http_create(nxt_unit_request_info_t *req)
881624Smax.romanov@nginx.com {
891624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
901624Smax.romanov@nginx.com 
911624Smax.romanov@nginx.com     http = PyObject_New(nxt_py_asgi_http_t, &nxt_py_asgi_http_type);
921624Smax.romanov@nginx.com 
931624Smax.romanov@nginx.com     if (nxt_fast_path(http != NULL)) {
941624Smax.romanov@nginx.com         http->req = req;
951624Smax.romanov@nginx.com         http->receive_future = NULL;
961624Smax.romanov@nginx.com         http->send_future = NULL;
971624Smax.romanov@nginx.com         http->content_length = -1;
981624Smax.romanov@nginx.com         http->bytes_sent = 0;
99*1916Smax.romanov@nginx.com         http->send_body = NULL;
100*1916Smax.romanov@nginx.com         http->send_body_off = 0;
1011624Smax.romanov@nginx.com         http->complete = 0;
1021715Smax.romanov@nginx.com         http->closed = 0;
103*1916Smax.romanov@nginx.com         http->empty_body_received = 0;
1041624Smax.romanov@nginx.com     }
1051624Smax.romanov@nginx.com 
1061624Smax.romanov@nginx.com     return (PyObject *) http;
1071624Smax.romanov@nginx.com }
1081624Smax.romanov@nginx.com 
1091624Smax.romanov@nginx.com 
1101624Smax.romanov@nginx.com static PyObject *
1111624Smax.romanov@nginx.com nxt_py_asgi_http_receive(PyObject *self, PyObject *none)
1121624Smax.romanov@nginx.com {
1131624Smax.romanov@nginx.com     PyObject                 *msg, *future;
1141624Smax.romanov@nginx.com     nxt_py_asgi_http_t       *http;
1151681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t   *ctx_data;
1161624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
1171624Smax.romanov@nginx.com 
1181624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
1191624Smax.romanov@nginx.com     req = http->req;
1201624Smax.romanov@nginx.com 
1211624Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_receive");
1221624Smax.romanov@nginx.com 
123*1916Smax.romanov@nginx.com     if (nxt_slow_path(http->closed || http->complete )) {
1241715Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(req, nxt_py_http_disconnect_str);
1251715Smax.romanov@nginx.com 
1261715Smax.romanov@nginx.com     } else {
1271715Smax.romanov@nginx.com         msg = nxt_py_asgi_http_read_msg(http);
1281715Smax.romanov@nginx.com     }
1291715Smax.romanov@nginx.com 
1301624Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
1311624Smax.romanov@nginx.com         return NULL;
1321624Smax.romanov@nginx.com     }
1331624Smax.romanov@nginx.com 
1341681Smax.romanov@nginx.com     ctx_data = req->ctx->data;
1351681Smax.romanov@nginx.com 
1361681Smax.romanov@nginx.com     future = PyObject_CallObject(ctx_data->loop_create_future, NULL);
1371624Smax.romanov@nginx.com     if (nxt_slow_path(future == NULL)) {
1381624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create Future object");
1391624Smax.romanov@nginx.com         nxt_python_print_exception();
1401624Smax.romanov@nginx.com 
1411624Smax.romanov@nginx.com         Py_DECREF(msg);
1421624Smax.romanov@nginx.com 
1431624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
1441624Smax.romanov@nginx.com                             "failed to create Future object");
1451624Smax.romanov@nginx.com     }
1461624Smax.romanov@nginx.com 
1471624Smax.romanov@nginx.com     if (msg != Py_None) {
1481681Smax.romanov@nginx.com         return nxt_py_asgi_set_result_soon(req, ctx_data, future, msg);
1491624Smax.romanov@nginx.com     }
1501624Smax.romanov@nginx.com 
1511624Smax.romanov@nginx.com     http->receive_future = future;
1521624Smax.romanov@nginx.com     Py_INCREF(http->receive_future);
1531624Smax.romanov@nginx.com 
1541624Smax.romanov@nginx.com     Py_DECREF(msg);
1551624Smax.romanov@nginx.com 
1561624Smax.romanov@nginx.com     return future;
1571624Smax.romanov@nginx.com }
1581624Smax.romanov@nginx.com 
1591624Smax.romanov@nginx.com 
1601624Smax.romanov@nginx.com static PyObject *
1611624Smax.romanov@nginx.com nxt_py_asgi_http_read_msg(nxt_py_asgi_http_t *http)
1621624Smax.romanov@nginx.com {
1631624Smax.romanov@nginx.com     char                     *body_buf;
1641624Smax.romanov@nginx.com     ssize_t                  read_res;
1651624Smax.romanov@nginx.com     PyObject                 *msg, *body;
1661624Smax.romanov@nginx.com     Py_ssize_t               size;
1671624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
1681624Smax.romanov@nginx.com 
1691624Smax.romanov@nginx.com     req = http->req;
1701624Smax.romanov@nginx.com 
1711624Smax.romanov@nginx.com     size = req->content_length;
1721624Smax.romanov@nginx.com 
1731624Smax.romanov@nginx.com     if (size > nxt_py_asgi_http_body_buf_size) {
1741624Smax.romanov@nginx.com         size = nxt_py_asgi_http_body_buf_size;
1751624Smax.romanov@nginx.com     }
1761624Smax.romanov@nginx.com 
177*1916Smax.romanov@nginx.com     if (size == 0) {
178*1916Smax.romanov@nginx.com         if (http->empty_body_received) {
179*1916Smax.romanov@nginx.com             Py_RETURN_NONE;
180*1916Smax.romanov@nginx.com         }
181*1916Smax.romanov@nginx.com 
182*1916Smax.romanov@nginx.com         http->empty_body_received = 1;
183*1916Smax.romanov@nginx.com     }
184*1916Smax.romanov@nginx.com 
1851624Smax.romanov@nginx.com     if (size > 0) {
1861624Smax.romanov@nginx.com         body = PyBytes_FromStringAndSize(NULL, size);
1871624Smax.romanov@nginx.com         if (nxt_slow_path(body == NULL)) {
1881624Smax.romanov@nginx.com             nxt_unit_req_alert(req, "Python failed to create body byte string");
1891624Smax.romanov@nginx.com             nxt_python_print_exception();
1901624Smax.romanov@nginx.com 
1911624Smax.romanov@nginx.com             return PyErr_Format(PyExc_RuntimeError,
1921624Smax.romanov@nginx.com                                 "failed to create Bytes object");
1931624Smax.romanov@nginx.com         }
1941624Smax.romanov@nginx.com 
1951624Smax.romanov@nginx.com         body_buf = PyBytes_AS_STRING(body);
1961624Smax.romanov@nginx.com 
1971624Smax.romanov@nginx.com         read_res = nxt_unit_request_read(req, body_buf, size);
1981624Smax.romanov@nginx.com 
1991624Smax.romanov@nginx.com     } else {
2001624Smax.romanov@nginx.com         body = NULL;
2011624Smax.romanov@nginx.com         read_res = 0;
2021624Smax.romanov@nginx.com     }
2031624Smax.romanov@nginx.com 
2041624Smax.romanov@nginx.com     if (read_res > 0 || read_res == size) {
2051624Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(req, nxt_py_http_request_str);
2061624Smax.romanov@nginx.com         if (nxt_slow_path(msg == NULL)) {
2071624Smax.romanov@nginx.com             Py_XDECREF(body);
2081624Smax.romanov@nginx.com 
2091624Smax.romanov@nginx.com             return NULL;
2101624Smax.romanov@nginx.com         }
2111624Smax.romanov@nginx.com 
2121624Smax.romanov@nginx.com #define SET_ITEM(dict, key, value) \
2131624Smax.romanov@nginx.com     if (nxt_slow_path(PyDict_SetItem(dict, nxt_py_ ## key ## _str, value)      \
2141624Smax.romanov@nginx.com                         == -1))                                                \
2151624Smax.romanov@nginx.com     {                                                                          \
2161624Smax.romanov@nginx.com         nxt_unit_req_alert(req,                                                \
2171624Smax.romanov@nginx.com                            "Python failed to set '" #dict "." #key "' item");  \
2181624Smax.romanov@nginx.com         PyErr_SetString(PyExc_RuntimeError,                                    \
2191624Smax.romanov@nginx.com                         "Python failed to set '" #dict "." #key "' item");     \
2201624Smax.romanov@nginx.com         goto fail;                                                             \
2211624Smax.romanov@nginx.com     }
2221624Smax.romanov@nginx.com 
2231624Smax.romanov@nginx.com         if (body != NULL) {
2241624Smax.romanov@nginx.com             SET_ITEM(msg, body, body)
2251624Smax.romanov@nginx.com         }
2261624Smax.romanov@nginx.com 
2271624Smax.romanov@nginx.com         if (req->content_length > 0) {
2281624Smax.romanov@nginx.com             SET_ITEM(msg, more_body, Py_True)
2291624Smax.romanov@nginx.com         }
2301624Smax.romanov@nginx.com 
2311624Smax.romanov@nginx.com #undef SET_ITEM
2321624Smax.romanov@nginx.com 
2331624Smax.romanov@nginx.com         Py_XDECREF(body);
2341624Smax.romanov@nginx.com 
2351624Smax.romanov@nginx.com         return msg;
2361624Smax.romanov@nginx.com     }
2371624Smax.romanov@nginx.com 
2381624Smax.romanov@nginx.com     Py_XDECREF(body);
2391624Smax.romanov@nginx.com 
2401624Smax.romanov@nginx.com     Py_RETURN_NONE;
2411624Smax.romanov@nginx.com 
2421624Smax.romanov@nginx.com fail:
2431624Smax.romanov@nginx.com 
2441624Smax.romanov@nginx.com     Py_DECREF(msg);
2451624Smax.romanov@nginx.com     Py_XDECREF(body);
2461624Smax.romanov@nginx.com 
2471624Smax.romanov@nginx.com     return NULL;
2481624Smax.romanov@nginx.com }
2491624Smax.romanov@nginx.com 
2501624Smax.romanov@nginx.com 
2511624Smax.romanov@nginx.com static PyObject *
2521624Smax.romanov@nginx.com nxt_py_asgi_http_send(PyObject *self, PyObject *dict)
2531624Smax.romanov@nginx.com {
2541624Smax.romanov@nginx.com     PyObject            *type;
2551624Smax.romanov@nginx.com     const char          *type_str;
2561624Smax.romanov@nginx.com     Py_ssize_t          type_len;
2571624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
2581624Smax.romanov@nginx.com 
2591624Smax.romanov@nginx.com     static const nxt_str_t  response_start = nxt_string("http.response.start");
2601624Smax.romanov@nginx.com     static const nxt_str_t  response_body = nxt_string("http.response.body");
2611624Smax.romanov@nginx.com 
2621624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
2631624Smax.romanov@nginx.com 
2641624Smax.romanov@nginx.com     type = PyDict_GetItem(dict, nxt_py_type_str);
2651624Smax.romanov@nginx.com     if (nxt_slow_path(type == NULL || !PyUnicode_Check(type))) {
2661624Smax.romanov@nginx.com         nxt_unit_req_error(http->req, "asgi_http_send: "
2671624Smax.romanov@nginx.com                                       "'type' is not a unicode string");
2681624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'type' is not a unicode string");
2691624Smax.romanov@nginx.com     }
2701624Smax.romanov@nginx.com 
2711624Smax.romanov@nginx.com     type_str = PyUnicode_AsUTF8AndSize(type, &type_len);
2721624Smax.romanov@nginx.com 
2731624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_send type is '%.*s'",
2741624Smax.romanov@nginx.com                        (int) type_len, type_str);
2751624Smax.romanov@nginx.com 
2761717Smax.romanov@nginx.com     if (nxt_unit_response_is_init(http->req)) {
2771717Smax.romanov@nginx.com         if (nxt_str_eq(&response_body, type_str, (size_t) type_len)) {
2781717Smax.romanov@nginx.com             return nxt_py_asgi_http_response_body(http, dict);
2791717Smax.romanov@nginx.com         }
2801717Smax.romanov@nginx.com 
2811717Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
2821717Smax.romanov@nginx.com                             "Expected ASGI message 'http.response.body', "
2831717Smax.romanov@nginx.com                             "but got '%U'", type);
2841717Smax.romanov@nginx.com     }
2851717Smax.romanov@nginx.com 
2861717Smax.romanov@nginx.com     if (nxt_str_eq(&response_start, type_str, (size_t) type_len)) {
2871624Smax.romanov@nginx.com         return nxt_py_asgi_http_response_start(http, dict);
2881624Smax.romanov@nginx.com     }
2891624Smax.romanov@nginx.com 
2901717Smax.romanov@nginx.com     return PyErr_Format(PyExc_RuntimeError,
2911717Smax.romanov@nginx.com                         "Expected ASGI message 'http.response.start', "
2921717Smax.romanov@nginx.com                         "but got '%U'", type);
2931624Smax.romanov@nginx.com }
2941624Smax.romanov@nginx.com 
2951624Smax.romanov@nginx.com 
2961624Smax.romanov@nginx.com static PyObject *
2971624Smax.romanov@nginx.com nxt_py_asgi_http_response_start(nxt_py_asgi_http_t *http, PyObject *dict)
2981624Smax.romanov@nginx.com {
2991624Smax.romanov@nginx.com     int                          rc;
3001624Smax.romanov@nginx.com     PyObject                     *status, *headers, *res;
3011624Smax.romanov@nginx.com     nxt_py_asgi_calc_size_ctx_t  calc_size_ctx;
3021624Smax.romanov@nginx.com     nxt_py_asgi_add_field_ctx_t  add_field_ctx;
3031624Smax.romanov@nginx.com 
3041624Smax.romanov@nginx.com     status = PyDict_GetItem(dict, nxt_py_status_str);
3051624Smax.romanov@nginx.com     if (nxt_slow_path(status == NULL || !PyLong_Check(status))) {
3061624Smax.romanov@nginx.com         nxt_unit_req_error(http->req, "asgi_http_response_start: "
3071624Smax.romanov@nginx.com                                       "'status' is not an integer");
3081624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'status' is not an integer");
3091624Smax.romanov@nginx.com     }
3101624Smax.romanov@nginx.com 
3111624Smax.romanov@nginx.com     calc_size_ctx.fields_size = 0;
3121624Smax.romanov@nginx.com     calc_size_ctx.fields_count = 0;
3131624Smax.romanov@nginx.com 
3141624Smax.romanov@nginx.com     headers = PyDict_GetItem(dict, nxt_py_headers_str);
3151624Smax.romanov@nginx.com     if (headers != NULL) {
3161624Smax.romanov@nginx.com         res = nxt_py_asgi_enum_headers(headers, nxt_py_asgi_calc_size,
3171624Smax.romanov@nginx.com                                        &calc_size_ctx);
3181624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
3191624Smax.romanov@nginx.com             return NULL;
3201624Smax.romanov@nginx.com         }
3211624Smax.romanov@nginx.com 
3221624Smax.romanov@nginx.com         Py_DECREF(res);
3231624Smax.romanov@nginx.com     }
3241624Smax.romanov@nginx.com 
3251624Smax.romanov@nginx.com     rc = nxt_unit_response_init(http->req, PyLong_AsLong(status),
3261624Smax.romanov@nginx.com                                 calc_size_ctx.fields_count,
3271624Smax.romanov@nginx.com                                 calc_size_ctx.fields_size);
3281624Smax.romanov@nginx.com     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
3291624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
3301624Smax.romanov@nginx.com                             "failed to allocate response object");
3311624Smax.romanov@nginx.com     }
3321624Smax.romanov@nginx.com 
3331624Smax.romanov@nginx.com     add_field_ctx.req = http->req;
3341624Smax.romanov@nginx.com     add_field_ctx.content_length = -1;
3351624Smax.romanov@nginx.com 
3361624Smax.romanov@nginx.com     if (headers != NULL) {
3371624Smax.romanov@nginx.com         res = nxt_py_asgi_enum_headers(headers, nxt_py_asgi_add_field,
3381624Smax.romanov@nginx.com                                        &add_field_ctx);
3391624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
3401624Smax.romanov@nginx.com             return NULL;
3411624Smax.romanov@nginx.com         }
3421624Smax.romanov@nginx.com 
3431624Smax.romanov@nginx.com         Py_DECREF(res);
3441624Smax.romanov@nginx.com     }
3451624Smax.romanov@nginx.com 
3461624Smax.romanov@nginx.com     http->content_length = add_field_ctx.content_length;
3471624Smax.romanov@nginx.com 
3481624Smax.romanov@nginx.com     Py_INCREF(http);
3491624Smax.romanov@nginx.com     return (PyObject *) http;
3501624Smax.romanov@nginx.com }
3511624Smax.romanov@nginx.com 
3521624Smax.romanov@nginx.com 
3531624Smax.romanov@nginx.com static PyObject *
3541624Smax.romanov@nginx.com nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict)
3551624Smax.romanov@nginx.com {
3561681Smax.romanov@nginx.com     int                     rc;
3571681Smax.romanov@nginx.com     char                    *body_str;
3581681Smax.romanov@nginx.com     ssize_t                 sent;
3591681Smax.romanov@nginx.com     PyObject                *body, *more_body, *future;
3601681Smax.romanov@nginx.com     Py_ssize_t              body_len, body_off;
3611681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
3621624Smax.romanov@nginx.com 
3631624Smax.romanov@nginx.com     body = PyDict_GetItem(dict, nxt_py_body_str);
3641624Smax.romanov@nginx.com     if (nxt_slow_path(body != NULL && !PyBytes_Check(body))) {
3651624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'body' is not a byte string");
3661624Smax.romanov@nginx.com     }
3671624Smax.romanov@nginx.com 
3681624Smax.romanov@nginx.com     more_body = PyDict_GetItem(dict, nxt_py_more_body_str);
3691624Smax.romanov@nginx.com     if (nxt_slow_path(more_body != NULL && !PyBool_Check(more_body))) {
3701624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'more_body' is not a bool");
3711624Smax.romanov@nginx.com     }
3721624Smax.romanov@nginx.com 
3731624Smax.romanov@nginx.com     if (nxt_slow_path(http->complete)) {
3741624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
3751624Smax.romanov@nginx.com                             "Unexpected ASGI message 'http.response.body' "
3761624Smax.romanov@nginx.com                             "sent, after response already completed");
3771624Smax.romanov@nginx.com     }
3781624Smax.romanov@nginx.com 
3791624Smax.romanov@nginx.com     if (nxt_slow_path(http->send_future != NULL)) {
3801624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError, "Concurrent send");
3811624Smax.romanov@nginx.com     }
3821624Smax.romanov@nginx.com 
3831624Smax.romanov@nginx.com     if (body != NULL) {
3841624Smax.romanov@nginx.com         body_str = PyBytes_AS_STRING(body);
3851624Smax.romanov@nginx.com         body_len = PyBytes_GET_SIZE(body);
3861624Smax.romanov@nginx.com 
3871624Smax.romanov@nginx.com         nxt_unit_req_debug(http->req, "asgi_http_response_body: %d, %d",
3881624Smax.romanov@nginx.com                            (int) body_len, (more_body == Py_True) );
3891624Smax.romanov@nginx.com 
3901624Smax.romanov@nginx.com         if (nxt_slow_path(http->bytes_sent + body_len
3911624Smax.romanov@nginx.com                               > http->content_length))
3921624Smax.romanov@nginx.com         {
3931624Smax.romanov@nginx.com             return PyErr_Format(PyExc_RuntimeError,
3941624Smax.romanov@nginx.com                                 "Response content longer than Content-Length");
3951624Smax.romanov@nginx.com         }
3961624Smax.romanov@nginx.com 
3971624Smax.romanov@nginx.com         body_off = 0;
3981624Smax.romanov@nginx.com 
3991681Smax.romanov@nginx.com         ctx_data = http->req->ctx->data;
4001681Smax.romanov@nginx.com 
4011624Smax.romanov@nginx.com         while (body_len > 0) {
4021624Smax.romanov@nginx.com             sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
4031624Smax.romanov@nginx.com             if (nxt_slow_path(sent < 0)) {
4041624Smax.romanov@nginx.com                 return PyErr_Format(PyExc_RuntimeError, "failed to send body");
4051624Smax.romanov@nginx.com             }
4061624Smax.romanov@nginx.com 
4071624Smax.romanov@nginx.com             if (nxt_slow_path(sent == 0)) {
4081624Smax.romanov@nginx.com                 nxt_unit_req_debug(http->req, "asgi_http_response_body: "
4091624Smax.romanov@nginx.com                                    "out of shared memory, %d",
4101624Smax.romanov@nginx.com                                    (int) body_len);
4111624Smax.romanov@nginx.com 
4121681Smax.romanov@nginx.com                 future = PyObject_CallObject(ctx_data->loop_create_future,
4131681Smax.romanov@nginx.com                                              NULL);
4141624Smax.romanov@nginx.com                 if (nxt_slow_path(future == NULL)) {
4151624Smax.romanov@nginx.com                     nxt_unit_req_alert(http->req,
4161624Smax.romanov@nginx.com                                        "Python failed to create Future object");
4171624Smax.romanov@nginx.com                     nxt_python_print_exception();
4181624Smax.romanov@nginx.com 
4191624Smax.romanov@nginx.com                     return PyErr_Format(PyExc_RuntimeError,
4201624Smax.romanov@nginx.com                                         "failed to create Future object");
4211624Smax.romanov@nginx.com                 }
4221624Smax.romanov@nginx.com 
4231624Smax.romanov@nginx.com                 http->send_body = body;
4241624Smax.romanov@nginx.com                 Py_INCREF(http->send_body);
4251624Smax.romanov@nginx.com                 http->send_body_off = body_off;
4261624Smax.romanov@nginx.com 
4271681Smax.romanov@nginx.com                 nxt_py_asgi_drain_wait(http->req, &http->link);
4281624Smax.romanov@nginx.com 
4291624Smax.romanov@nginx.com                 http->send_future = future;
4301624Smax.romanov@nginx.com                 Py_INCREF(http->send_future);
4311624Smax.romanov@nginx.com 
4321624Smax.romanov@nginx.com                 return future;
4331624Smax.romanov@nginx.com             }
4341624Smax.romanov@nginx.com 
4351624Smax.romanov@nginx.com             body_str += sent;
4361624Smax.romanov@nginx.com             body_len -= sent;
4371624Smax.romanov@nginx.com             body_off += sent;
4381624Smax.romanov@nginx.com             http->bytes_sent += sent;
4391624Smax.romanov@nginx.com         }
4401624Smax.romanov@nginx.com 
4411624Smax.romanov@nginx.com     } else {
4421624Smax.romanov@nginx.com         nxt_unit_req_debug(http->req, "asgi_http_response_body: 0, %d",
4431624Smax.romanov@nginx.com                            (more_body == Py_True) );
4441624Smax.romanov@nginx.com 
4451624Smax.romanov@nginx.com         if (!nxt_unit_response_is_sent(http->req)) {
4461624Smax.romanov@nginx.com             rc = nxt_unit_response_send(http->req);
4471624Smax.romanov@nginx.com             if (nxt_slow_path(rc != NXT_UNIT_OK)) {
4481624Smax.romanov@nginx.com                 return PyErr_Format(PyExc_RuntimeError,
4491624Smax.romanov@nginx.com                                     "failed to send response");
4501624Smax.romanov@nginx.com             }
4511624Smax.romanov@nginx.com         }
4521624Smax.romanov@nginx.com     }
4531624Smax.romanov@nginx.com 
4541624Smax.romanov@nginx.com     if (more_body == NULL || more_body == Py_False) {
4551624Smax.romanov@nginx.com         http->complete = 1;
456*1916Smax.romanov@nginx.com 
457*1916Smax.romanov@nginx.com         nxt_py_asgi_http_emit_disconnect(http);
4581624Smax.romanov@nginx.com     }
4591624Smax.romanov@nginx.com 
4601624Smax.romanov@nginx.com     Py_INCREF(http);
4611624Smax.romanov@nginx.com     return (PyObject *) http;
4621624Smax.romanov@nginx.com }
4631624Smax.romanov@nginx.com 
4641624Smax.romanov@nginx.com 
465*1916Smax.romanov@nginx.com static void
466*1916Smax.romanov@nginx.com nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http)
467*1916Smax.romanov@nginx.com {
468*1916Smax.romanov@nginx.com     PyObject  *msg, *future, *res;
469*1916Smax.romanov@nginx.com 
470*1916Smax.romanov@nginx.com     if (http->receive_future == NULL) {
471*1916Smax.romanov@nginx.com         return;
472*1916Smax.romanov@nginx.com     }
473*1916Smax.romanov@nginx.com 
474*1916Smax.romanov@nginx.com     msg = nxt_py_asgi_new_msg(http->req, nxt_py_http_disconnect_str);
475*1916Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
476*1916Smax.romanov@nginx.com         return;
477*1916Smax.romanov@nginx.com     }
478*1916Smax.romanov@nginx.com 
479*1916Smax.romanov@nginx.com     if (msg == Py_None) {
480*1916Smax.romanov@nginx.com         Py_DECREF(msg);
481*1916Smax.romanov@nginx.com         return;
482*1916Smax.romanov@nginx.com     }
483*1916Smax.romanov@nginx.com 
484*1916Smax.romanov@nginx.com     future = http->receive_future;
485*1916Smax.romanov@nginx.com     http->receive_future = NULL;
486*1916Smax.romanov@nginx.com 
487*1916Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, NULL);
488*1916Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
489*1916Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "'set_result' call failed");
490*1916Smax.romanov@nginx.com         nxt_python_print_exception();
491*1916Smax.romanov@nginx.com     }
492*1916Smax.romanov@nginx.com 
493*1916Smax.romanov@nginx.com     Py_XDECREF(res);
494*1916Smax.romanov@nginx.com     Py_DECREF(future);
495*1916Smax.romanov@nginx.com 
496*1916Smax.romanov@nginx.com     Py_DECREF(msg);
497*1916Smax.romanov@nginx.com }
498*1916Smax.romanov@nginx.com 
499*1916Smax.romanov@nginx.com 
5001624Smax.romanov@nginx.com void
5011624Smax.romanov@nginx.com nxt_py_asgi_http_data_handler(nxt_unit_request_info_t *req)
5021624Smax.romanov@nginx.com {
5031624Smax.romanov@nginx.com     PyObject            *msg, *future, *res;
5041624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
5051624Smax.romanov@nginx.com 
5061624Smax.romanov@nginx.com     http = req->data;
5071624Smax.romanov@nginx.com 
5081624Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_data_handler");
5091624Smax.romanov@nginx.com 
5101624Smax.romanov@nginx.com     if (http->receive_future == NULL) {
5111624Smax.romanov@nginx.com         return;
5121624Smax.romanov@nginx.com     }
5131624Smax.romanov@nginx.com 
5141624Smax.romanov@nginx.com     msg = nxt_py_asgi_http_read_msg(http);
5151624Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
5161624Smax.romanov@nginx.com         return;
5171624Smax.romanov@nginx.com     }
5181624Smax.romanov@nginx.com 
5191624Smax.romanov@nginx.com     if (msg == Py_None) {
5201624Smax.romanov@nginx.com         Py_DECREF(msg);
5211624Smax.romanov@nginx.com         return;
5221624Smax.romanov@nginx.com     }
5231624Smax.romanov@nginx.com 
5241624Smax.romanov@nginx.com     future = http->receive_future;
5251624Smax.romanov@nginx.com     http->receive_future = NULL;
5261624Smax.romanov@nginx.com 
5271624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, NULL);
5281624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
5291624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "'set_result' call failed");
5301624Smax.romanov@nginx.com         nxt_python_print_exception();
5311624Smax.romanov@nginx.com     }
5321624Smax.romanov@nginx.com 
5331624Smax.romanov@nginx.com     Py_XDECREF(res);
5341624Smax.romanov@nginx.com     Py_DECREF(future);
5351624Smax.romanov@nginx.com 
5361624Smax.romanov@nginx.com     Py_DECREF(msg);
5371624Smax.romanov@nginx.com }
5381624Smax.romanov@nginx.com 
5391624Smax.romanov@nginx.com 
5401624Smax.romanov@nginx.com int
5411624Smax.romanov@nginx.com nxt_py_asgi_http_drain(nxt_queue_link_t *lnk)
5421624Smax.romanov@nginx.com {
5431624Smax.romanov@nginx.com     char                *body_str;
5441624Smax.romanov@nginx.com     ssize_t             sent;
5451624Smax.romanov@nginx.com     PyObject            *future, *exc, *res;
5461624Smax.romanov@nginx.com     Py_ssize_t          body_len;
5471624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
5481624Smax.romanov@nginx.com 
5491624Smax.romanov@nginx.com     http = nxt_container_of(lnk, nxt_py_asgi_http_t, link);
5501624Smax.romanov@nginx.com 
5511624Smax.romanov@nginx.com     body_str = PyBytes_AS_STRING(http->send_body) + http->send_body_off;
5521624Smax.romanov@nginx.com     body_len = PyBytes_GET_SIZE(http->send_body) - http->send_body_off;
5531624Smax.romanov@nginx.com 
5541624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_drain: %d", (int) body_len);
5551624Smax.romanov@nginx.com 
5561624Smax.romanov@nginx.com     while (body_len > 0) {
5571624Smax.romanov@nginx.com         sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
5581624Smax.romanov@nginx.com         if (nxt_slow_path(sent < 0)) {
5591624Smax.romanov@nginx.com             goto fail;
5601624Smax.romanov@nginx.com         }
5611624Smax.romanov@nginx.com 
5621624Smax.romanov@nginx.com         if (nxt_slow_path(sent == 0)) {
5631624Smax.romanov@nginx.com             return NXT_UNIT_AGAIN;
5641624Smax.romanov@nginx.com         }
5651624Smax.romanov@nginx.com 
5661624Smax.romanov@nginx.com         body_str += sent;
5671624Smax.romanov@nginx.com         body_len -= sent;
5681624Smax.romanov@nginx.com 
5691624Smax.romanov@nginx.com         http->send_body_off += sent;
5701624Smax.romanov@nginx.com         http->bytes_sent += sent;
5711624Smax.romanov@nginx.com     }
5721624Smax.romanov@nginx.com 
5731624Smax.romanov@nginx.com     Py_CLEAR(http->send_body);
5741624Smax.romanov@nginx.com 
5751624Smax.romanov@nginx.com     future = http->send_future;
5761624Smax.romanov@nginx.com     http->send_future = NULL;
5771624Smax.romanov@nginx.com 
5781624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, Py_None,
5791624Smax.romanov@nginx.com                                      NULL);
5801624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
5811624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "'set_result' call failed");
5821624Smax.romanov@nginx.com         nxt_python_print_exception();
5831624Smax.romanov@nginx.com     }
5841624Smax.romanov@nginx.com 
5851624Smax.romanov@nginx.com     Py_XDECREF(res);
5861624Smax.romanov@nginx.com     Py_DECREF(future);
5871624Smax.romanov@nginx.com 
5881624Smax.romanov@nginx.com     return NXT_UNIT_OK;
5891624Smax.romanov@nginx.com 
5901624Smax.romanov@nginx.com fail:
5911624Smax.romanov@nginx.com 
5921624Smax.romanov@nginx.com     exc = PyObject_CallFunctionObjArgs(PyExc_RuntimeError,
5931624Smax.romanov@nginx.com                                        nxt_py_failed_to_send_body_str,
5941624Smax.romanov@nginx.com                                        NULL);
5951624Smax.romanov@nginx.com     if (nxt_slow_path(exc == NULL)) {
5961624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "RuntimeError create failed");
5971624Smax.romanov@nginx.com         nxt_python_print_exception();
5981624Smax.romanov@nginx.com 
5991624Smax.romanov@nginx.com         exc = Py_None;
6001624Smax.romanov@nginx.com         Py_INCREF(exc);
6011624Smax.romanov@nginx.com     }
6021624Smax.romanov@nginx.com 
6031624Smax.romanov@nginx.com     future = http->send_future;
6041624Smax.romanov@nginx.com     http->send_future = NULL;
6051624Smax.romanov@nginx.com 
6061624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_exception_str, exc,
6071624Smax.romanov@nginx.com                                      NULL);
6081624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
6091624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "'set_exception' call failed");
6101624Smax.romanov@nginx.com         nxt_python_print_exception();
6111624Smax.romanov@nginx.com     }
6121624Smax.romanov@nginx.com 
6131624Smax.romanov@nginx.com     Py_XDECREF(res);
6141624Smax.romanov@nginx.com     Py_DECREF(future);
6151624Smax.romanov@nginx.com     Py_DECREF(exc);
6161624Smax.romanov@nginx.com 
6171624Smax.romanov@nginx.com     return NXT_UNIT_ERROR;
6181624Smax.romanov@nginx.com }
6191624Smax.romanov@nginx.com 
6201624Smax.romanov@nginx.com 
6211715Smax.romanov@nginx.com void
6221715Smax.romanov@nginx.com nxt_py_asgi_http_close_handler(nxt_unit_request_info_t *req)
6231715Smax.romanov@nginx.com {
6241715Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
6251715Smax.romanov@nginx.com 
6261715Smax.romanov@nginx.com     http = req->data;
6271715Smax.romanov@nginx.com 
6281715Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_close_handler");
6291715Smax.romanov@nginx.com 
6301715Smax.romanov@nginx.com     http->closed = 1;
6311715Smax.romanov@nginx.com 
632*1916Smax.romanov@nginx.com     nxt_py_asgi_http_emit_disconnect(http);
6331715Smax.romanov@nginx.com }
6341715Smax.romanov@nginx.com 
6351715Smax.romanov@nginx.com 
6361624Smax.romanov@nginx.com static PyObject *
6371624Smax.romanov@nginx.com nxt_py_asgi_http_done(PyObject *self, PyObject *future)
6381624Smax.romanov@nginx.com {
6391624Smax.romanov@nginx.com     int                 rc;
6401624Smax.romanov@nginx.com     PyObject            *res;
6411624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
6421624Smax.romanov@nginx.com 
6431624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
6441624Smax.romanov@nginx.com 
6451624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_done");
6461624Smax.romanov@nginx.com 
6471624Smax.romanov@nginx.com     /*
6481624Smax.romanov@nginx.com      * Get Future.result() and it raises an exception, if coroutine exited
6491624Smax.romanov@nginx.com      * with exception.
6501624Smax.romanov@nginx.com      */
6511624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL);
6521624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
6531624Smax.romanov@nginx.com         nxt_unit_req_error(http->req,
6541624Smax.romanov@nginx.com                            "Python failed to call 'future.result()'");
6551624Smax.romanov@nginx.com         nxt_python_print_exception();
6561624Smax.romanov@nginx.com 
6571624Smax.romanov@nginx.com         rc = NXT_UNIT_ERROR;
6581624Smax.romanov@nginx.com 
6591624Smax.romanov@nginx.com     } else {
6601624Smax.romanov@nginx.com         Py_DECREF(res);
6611624Smax.romanov@nginx.com 
6621624Smax.romanov@nginx.com         rc = NXT_UNIT_OK;
6631624Smax.romanov@nginx.com     }
6641624Smax.romanov@nginx.com 
6651624Smax.romanov@nginx.com     nxt_unit_request_done(http->req, rc);
6661624Smax.romanov@nginx.com 
6671624Smax.romanov@nginx.com     Py_RETURN_NONE;
6681624Smax.romanov@nginx.com }
6691624Smax.romanov@nginx.com 
6701624Smax.romanov@nginx.com 
6711624Smax.romanov@nginx.com #endif /* NXT_HAVE_ASGI */
672