xref: /unit/src/python/nxt_python_asgi_http.c (revision 1980:43553aa72111)
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;
281916Smax.romanov@nginx.com     uint8_t                  complete;
291916Smax.romanov@nginx.com     uint8_t                  closed;
301916Smax.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);
411916Smax.romanov@nginx.com static void nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http);
421917Smax.romanov@nginx.com static void nxt_py_asgi_http_set_result(nxt_py_asgi_http_t *http,
431917Smax.romanov@nginx.com     PyObject *future, PyObject *msg);
441624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_done(PyObject *self, PyObject *future);
451624Smax.romanov@nginx.com 
461624Smax.romanov@nginx.com 
471624Smax.romanov@nginx.com static PyMethodDef nxt_py_asgi_http_methods[] = {
481624Smax.romanov@nginx.com     { "receive",   nxt_py_asgi_http_receive, METH_NOARGS, 0 },
491624Smax.romanov@nginx.com     { "send",      nxt_py_asgi_http_send,    METH_O,      0 },
501624Smax.romanov@nginx.com     { "_done",     nxt_py_asgi_http_done,    METH_O,      0 },
511624Smax.romanov@nginx.com     { NULL, NULL, 0, 0 }
521624Smax.romanov@nginx.com };
531624Smax.romanov@nginx.com 
541624Smax.romanov@nginx.com static PyAsyncMethods nxt_py_asgi_async_methods = {
551624Smax.romanov@nginx.com     .am_await = nxt_py_asgi_await,
561624Smax.romanov@nginx.com };
571624Smax.romanov@nginx.com 
581624Smax.romanov@nginx.com static PyTypeObject nxt_py_asgi_http_type = {
591624Smax.romanov@nginx.com     PyVarObject_HEAD_INIT(NULL, 0)
601624Smax.romanov@nginx.com 
611624Smax.romanov@nginx.com     .tp_name      = "unit._asgi_http",
621624Smax.romanov@nginx.com     .tp_basicsize = sizeof(nxt_py_asgi_http_t),
631624Smax.romanov@nginx.com     .tp_dealloc   = nxt_py_asgi_dealloc,
641624Smax.romanov@nginx.com     .tp_as_async  = &nxt_py_asgi_async_methods,
651624Smax.romanov@nginx.com     .tp_flags     = Py_TPFLAGS_DEFAULT,
661624Smax.romanov@nginx.com     .tp_doc       = "unit ASGI HTTP request object",
671624Smax.romanov@nginx.com     .tp_iter      = nxt_py_asgi_iter,
681624Smax.romanov@nginx.com     .tp_iternext  = nxt_py_asgi_next,
691624Smax.romanov@nginx.com     .tp_methods   = nxt_py_asgi_http_methods,
701624Smax.romanov@nginx.com };
711624Smax.romanov@nginx.com 
721624Smax.romanov@nginx.com static Py_ssize_t  nxt_py_asgi_http_body_buf_size = 32 * 1024 * 1024;
731624Smax.romanov@nginx.com 
741624Smax.romanov@nginx.com 
751681Smax.romanov@nginx.com int
nxt_py_asgi_http_init(void)761681Smax.romanov@nginx.com nxt_py_asgi_http_init(void)
771624Smax.romanov@nginx.com {
781624Smax.romanov@nginx.com     if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_http_type) != 0)) {
791681Smax.romanov@nginx.com         nxt_unit_alert(NULL,
801681Smax.romanov@nginx.com                        "Python failed to initialize the 'http' type object");
811681Smax.romanov@nginx.com         return NXT_UNIT_ERROR;
821624Smax.romanov@nginx.com     }
831624Smax.romanov@nginx.com 
841681Smax.romanov@nginx.com     return NXT_UNIT_OK;
851624Smax.romanov@nginx.com }
861624Smax.romanov@nginx.com 
871624Smax.romanov@nginx.com 
881624Smax.romanov@nginx.com PyObject *
nxt_py_asgi_http_create(nxt_unit_request_info_t * req)891624Smax.romanov@nginx.com nxt_py_asgi_http_create(nxt_unit_request_info_t *req)
901624Smax.romanov@nginx.com {
911624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
921624Smax.romanov@nginx.com 
931624Smax.romanov@nginx.com     http = PyObject_New(nxt_py_asgi_http_t, &nxt_py_asgi_http_type);
941624Smax.romanov@nginx.com 
951624Smax.romanov@nginx.com     if (nxt_fast_path(http != NULL)) {
961624Smax.romanov@nginx.com         http->req = req;
971624Smax.romanov@nginx.com         http->receive_future = NULL;
981624Smax.romanov@nginx.com         http->send_future = NULL;
991624Smax.romanov@nginx.com         http->content_length = -1;
1001624Smax.romanov@nginx.com         http->bytes_sent = 0;
1011916Smax.romanov@nginx.com         http->send_body = NULL;
1021916Smax.romanov@nginx.com         http->send_body_off = 0;
1031624Smax.romanov@nginx.com         http->complete = 0;
1041715Smax.romanov@nginx.com         http->closed = 0;
1051916Smax.romanov@nginx.com         http->empty_body_received = 0;
1061624Smax.romanov@nginx.com     }
1071624Smax.romanov@nginx.com 
1081624Smax.romanov@nginx.com     return (PyObject *) http;
1091624Smax.romanov@nginx.com }
1101624Smax.romanov@nginx.com 
1111624Smax.romanov@nginx.com 
1121624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_http_receive(PyObject * self,PyObject * none)1131624Smax.romanov@nginx.com nxt_py_asgi_http_receive(PyObject *self, PyObject *none)
1141624Smax.romanov@nginx.com {
1151624Smax.romanov@nginx.com     PyObject                 *msg, *future;
1161624Smax.romanov@nginx.com     nxt_py_asgi_http_t       *http;
1171681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t   *ctx_data;
1181624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
1191624Smax.romanov@nginx.com 
1201624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
1211624Smax.romanov@nginx.com     req = http->req;
1221624Smax.romanov@nginx.com 
1231624Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_receive");
1241624Smax.romanov@nginx.com 
1251916Smax.romanov@nginx.com     if (nxt_slow_path(http->closed || http->complete )) {
1261715Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(req, nxt_py_http_disconnect_str);
1271715Smax.romanov@nginx.com 
1281715Smax.romanov@nginx.com     } else {
1291715Smax.romanov@nginx.com         msg = nxt_py_asgi_http_read_msg(http);
1301715Smax.romanov@nginx.com     }
1311715Smax.romanov@nginx.com 
1321624Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
1331624Smax.romanov@nginx.com         return NULL;
1341624Smax.romanov@nginx.com     }
1351624Smax.romanov@nginx.com 
1361681Smax.romanov@nginx.com     ctx_data = req->ctx->data;
1371681Smax.romanov@nginx.com 
1381681Smax.romanov@nginx.com     future = PyObject_CallObject(ctx_data->loop_create_future, NULL);
1391624Smax.romanov@nginx.com     if (nxt_slow_path(future == NULL)) {
1401624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create Future object");
1411624Smax.romanov@nginx.com         nxt_python_print_exception();
1421624Smax.romanov@nginx.com 
1431624Smax.romanov@nginx.com         Py_DECREF(msg);
1441624Smax.romanov@nginx.com 
1451624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
1461624Smax.romanov@nginx.com                             "failed to create Future object");
1471624Smax.romanov@nginx.com     }
1481624Smax.romanov@nginx.com 
1491624Smax.romanov@nginx.com     if (msg != Py_None) {
1501681Smax.romanov@nginx.com         return nxt_py_asgi_set_result_soon(req, ctx_data, future, msg);
1511624Smax.romanov@nginx.com     }
1521624Smax.romanov@nginx.com 
1531624Smax.romanov@nginx.com     http->receive_future = future;
1541624Smax.romanov@nginx.com     Py_INCREF(http->receive_future);
1551624Smax.romanov@nginx.com 
1561624Smax.romanov@nginx.com     Py_DECREF(msg);
1571624Smax.romanov@nginx.com 
1581624Smax.romanov@nginx.com     return future;
1591624Smax.romanov@nginx.com }
1601624Smax.romanov@nginx.com 
1611624Smax.romanov@nginx.com 
1621624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_http_read_msg(nxt_py_asgi_http_t * http)1631624Smax.romanov@nginx.com nxt_py_asgi_http_read_msg(nxt_py_asgi_http_t *http)
1641624Smax.romanov@nginx.com {
1651624Smax.romanov@nginx.com     char                     *body_buf;
1661624Smax.romanov@nginx.com     ssize_t                  read_res;
1671624Smax.romanov@nginx.com     PyObject                 *msg, *body;
1681624Smax.romanov@nginx.com     Py_ssize_t               size;
1691624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
1701624Smax.romanov@nginx.com 
1711624Smax.romanov@nginx.com     req = http->req;
1721624Smax.romanov@nginx.com 
1731624Smax.romanov@nginx.com     size = req->content_length;
1741624Smax.romanov@nginx.com 
1751624Smax.romanov@nginx.com     if (size > nxt_py_asgi_http_body_buf_size) {
1761624Smax.romanov@nginx.com         size = nxt_py_asgi_http_body_buf_size;
1771624Smax.romanov@nginx.com     }
1781624Smax.romanov@nginx.com 
1791916Smax.romanov@nginx.com     if (size == 0) {
1801916Smax.romanov@nginx.com         if (http->empty_body_received) {
1811916Smax.romanov@nginx.com             Py_RETURN_NONE;
1821916Smax.romanov@nginx.com         }
1831916Smax.romanov@nginx.com 
1841916Smax.romanov@nginx.com         http->empty_body_received = 1;
1851916Smax.romanov@nginx.com     }
1861916Smax.romanov@nginx.com 
1871624Smax.romanov@nginx.com     if (size > 0) {
1881624Smax.romanov@nginx.com         body = PyBytes_FromStringAndSize(NULL, size);
1891624Smax.romanov@nginx.com         if (nxt_slow_path(body == NULL)) {
1901624Smax.romanov@nginx.com             nxt_unit_req_alert(req, "Python failed to create body byte string");
1911624Smax.romanov@nginx.com             nxt_python_print_exception();
1921624Smax.romanov@nginx.com 
1931624Smax.romanov@nginx.com             return PyErr_Format(PyExc_RuntimeError,
1941624Smax.romanov@nginx.com                                 "failed to create Bytes object");
1951624Smax.romanov@nginx.com         }
1961624Smax.romanov@nginx.com 
1971624Smax.romanov@nginx.com         body_buf = PyBytes_AS_STRING(body);
1981624Smax.romanov@nginx.com 
1991624Smax.romanov@nginx.com         read_res = nxt_unit_request_read(req, body_buf, size);
2001624Smax.romanov@nginx.com 
2011624Smax.romanov@nginx.com     } else {
2021624Smax.romanov@nginx.com         body = NULL;
2031624Smax.romanov@nginx.com         read_res = 0;
2041624Smax.romanov@nginx.com     }
2051624Smax.romanov@nginx.com 
2061624Smax.romanov@nginx.com     if (read_res > 0 || read_res == size) {
2071624Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(req, nxt_py_http_request_str);
2081624Smax.romanov@nginx.com         if (nxt_slow_path(msg == NULL)) {
2091624Smax.romanov@nginx.com             Py_XDECREF(body);
2101624Smax.romanov@nginx.com 
2111624Smax.romanov@nginx.com             return NULL;
2121624Smax.romanov@nginx.com         }
2131624Smax.romanov@nginx.com 
2141624Smax.romanov@nginx.com #define SET_ITEM(dict, key, value) \
2151624Smax.romanov@nginx.com     if (nxt_slow_path(PyDict_SetItem(dict, nxt_py_ ## key ## _str, value)      \
2161624Smax.romanov@nginx.com                         == -1))                                                \
2171624Smax.romanov@nginx.com     {                                                                          \
2181624Smax.romanov@nginx.com         nxt_unit_req_alert(req,                                                \
2191624Smax.romanov@nginx.com                            "Python failed to set '" #dict "." #key "' item");  \
2201624Smax.romanov@nginx.com         PyErr_SetString(PyExc_RuntimeError,                                    \
2211624Smax.romanov@nginx.com                         "Python failed to set '" #dict "." #key "' item");     \
2221624Smax.romanov@nginx.com         goto fail;                                                             \
2231624Smax.romanov@nginx.com     }
2241624Smax.romanov@nginx.com 
2251624Smax.romanov@nginx.com         if (body != NULL) {
2261624Smax.romanov@nginx.com             SET_ITEM(msg, body, body)
2271624Smax.romanov@nginx.com         }
2281624Smax.romanov@nginx.com 
2291624Smax.romanov@nginx.com         if (req->content_length > 0) {
2301624Smax.romanov@nginx.com             SET_ITEM(msg, more_body, Py_True)
2311624Smax.romanov@nginx.com         }
2321624Smax.romanov@nginx.com 
2331624Smax.romanov@nginx.com #undef SET_ITEM
2341624Smax.romanov@nginx.com 
2351624Smax.romanov@nginx.com         Py_XDECREF(body);
2361624Smax.romanov@nginx.com 
2371624Smax.romanov@nginx.com         return msg;
2381624Smax.romanov@nginx.com     }
2391624Smax.romanov@nginx.com 
2401624Smax.romanov@nginx.com     Py_XDECREF(body);
2411624Smax.romanov@nginx.com 
2421624Smax.romanov@nginx.com     Py_RETURN_NONE;
2431624Smax.romanov@nginx.com 
2441624Smax.romanov@nginx.com fail:
2451624Smax.romanov@nginx.com 
2461624Smax.romanov@nginx.com     Py_DECREF(msg);
2471624Smax.romanov@nginx.com     Py_XDECREF(body);
2481624Smax.romanov@nginx.com 
2491624Smax.romanov@nginx.com     return NULL;
2501624Smax.romanov@nginx.com }
2511624Smax.romanov@nginx.com 
2521624Smax.romanov@nginx.com 
2531624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_http_send(PyObject * self,PyObject * dict)2541624Smax.romanov@nginx.com nxt_py_asgi_http_send(PyObject *self, PyObject *dict)
2551624Smax.romanov@nginx.com {
2561624Smax.romanov@nginx.com     PyObject            *type;
2571624Smax.romanov@nginx.com     const char          *type_str;
2581624Smax.romanov@nginx.com     Py_ssize_t          type_len;
2591624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
2601624Smax.romanov@nginx.com 
2611624Smax.romanov@nginx.com     static const nxt_str_t  response_start = nxt_string("http.response.start");
2621624Smax.romanov@nginx.com     static const nxt_str_t  response_body = nxt_string("http.response.body");
2631624Smax.romanov@nginx.com 
2641624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
2651624Smax.romanov@nginx.com 
2661624Smax.romanov@nginx.com     type = PyDict_GetItem(dict, nxt_py_type_str);
2671624Smax.romanov@nginx.com     if (nxt_slow_path(type == NULL || !PyUnicode_Check(type))) {
2681624Smax.romanov@nginx.com         nxt_unit_req_error(http->req, "asgi_http_send: "
2691624Smax.romanov@nginx.com                                       "'type' is not a unicode string");
2701624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'type' is not a unicode string");
2711624Smax.romanov@nginx.com     }
2721624Smax.romanov@nginx.com 
2731624Smax.romanov@nginx.com     type_str = PyUnicode_AsUTF8AndSize(type, &type_len);
2741624Smax.romanov@nginx.com 
2751624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_send type is '%.*s'",
2761624Smax.romanov@nginx.com                        (int) type_len, type_str);
2771624Smax.romanov@nginx.com 
2781717Smax.romanov@nginx.com     if (nxt_unit_response_is_init(http->req)) {
2791717Smax.romanov@nginx.com         if (nxt_str_eq(&response_body, type_str, (size_t) type_len)) {
2801717Smax.romanov@nginx.com             return nxt_py_asgi_http_response_body(http, dict);
2811717Smax.romanov@nginx.com         }
2821717Smax.romanov@nginx.com 
2831717Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
2841717Smax.romanov@nginx.com                             "Expected ASGI message 'http.response.body', "
2851717Smax.romanov@nginx.com                             "but got '%U'", type);
2861717Smax.romanov@nginx.com     }
2871717Smax.romanov@nginx.com 
2881717Smax.romanov@nginx.com     if (nxt_str_eq(&response_start, type_str, (size_t) type_len)) {
2891624Smax.romanov@nginx.com         return nxt_py_asgi_http_response_start(http, dict);
2901624Smax.romanov@nginx.com     }
2911624Smax.romanov@nginx.com 
2921717Smax.romanov@nginx.com     return PyErr_Format(PyExc_RuntimeError,
2931717Smax.romanov@nginx.com                         "Expected ASGI message 'http.response.start', "
2941717Smax.romanov@nginx.com                         "but got '%U'", type);
2951624Smax.romanov@nginx.com }
2961624Smax.romanov@nginx.com 
2971624Smax.romanov@nginx.com 
2981624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_http_response_start(nxt_py_asgi_http_t * http,PyObject * dict)2991624Smax.romanov@nginx.com nxt_py_asgi_http_response_start(nxt_py_asgi_http_t *http, PyObject *dict)
3001624Smax.romanov@nginx.com {
3011624Smax.romanov@nginx.com     int                          rc;
3021624Smax.romanov@nginx.com     PyObject                     *status, *headers, *res;
3031624Smax.romanov@nginx.com     nxt_py_asgi_calc_size_ctx_t  calc_size_ctx;
3041624Smax.romanov@nginx.com     nxt_py_asgi_add_field_ctx_t  add_field_ctx;
3051624Smax.romanov@nginx.com 
3061624Smax.romanov@nginx.com     status = PyDict_GetItem(dict, nxt_py_status_str);
3071624Smax.romanov@nginx.com     if (nxt_slow_path(status == NULL || !PyLong_Check(status))) {
3081624Smax.romanov@nginx.com         nxt_unit_req_error(http->req, "asgi_http_response_start: "
3091624Smax.romanov@nginx.com                                       "'status' is not an integer");
3101624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'status' is not an integer");
3111624Smax.romanov@nginx.com     }
3121624Smax.romanov@nginx.com 
3131624Smax.romanov@nginx.com     calc_size_ctx.fields_size = 0;
3141624Smax.romanov@nginx.com     calc_size_ctx.fields_count = 0;
3151624Smax.romanov@nginx.com 
3161624Smax.romanov@nginx.com     headers = PyDict_GetItem(dict, nxt_py_headers_str);
3171624Smax.romanov@nginx.com     if (headers != NULL) {
3181624Smax.romanov@nginx.com         res = nxt_py_asgi_enum_headers(headers, nxt_py_asgi_calc_size,
3191624Smax.romanov@nginx.com                                        &calc_size_ctx);
3201624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
3211624Smax.romanov@nginx.com             return NULL;
3221624Smax.romanov@nginx.com         }
3231624Smax.romanov@nginx.com 
3241624Smax.romanov@nginx.com         Py_DECREF(res);
3251624Smax.romanov@nginx.com     }
3261624Smax.romanov@nginx.com 
3271624Smax.romanov@nginx.com     rc = nxt_unit_response_init(http->req, PyLong_AsLong(status),
3281624Smax.romanov@nginx.com                                 calc_size_ctx.fields_count,
3291624Smax.romanov@nginx.com                                 calc_size_ctx.fields_size);
3301624Smax.romanov@nginx.com     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
3311624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
3321624Smax.romanov@nginx.com                             "failed to allocate response object");
3331624Smax.romanov@nginx.com     }
3341624Smax.romanov@nginx.com 
3351624Smax.romanov@nginx.com     add_field_ctx.req = http->req;
3361624Smax.romanov@nginx.com     add_field_ctx.content_length = -1;
3371624Smax.romanov@nginx.com 
3381624Smax.romanov@nginx.com     if (headers != NULL) {
3391624Smax.romanov@nginx.com         res = nxt_py_asgi_enum_headers(headers, nxt_py_asgi_add_field,
3401624Smax.romanov@nginx.com                                        &add_field_ctx);
3411624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
3421624Smax.romanov@nginx.com             return NULL;
3431624Smax.romanov@nginx.com         }
3441624Smax.romanov@nginx.com 
3451624Smax.romanov@nginx.com         Py_DECREF(res);
3461624Smax.romanov@nginx.com     }
3471624Smax.romanov@nginx.com 
3481624Smax.romanov@nginx.com     http->content_length = add_field_ctx.content_length;
3491624Smax.romanov@nginx.com 
3501624Smax.romanov@nginx.com     Py_INCREF(http);
3511624Smax.romanov@nginx.com     return (PyObject *) http;
3521624Smax.romanov@nginx.com }
3531624Smax.romanov@nginx.com 
3541624Smax.romanov@nginx.com 
3551624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_http_response_body(nxt_py_asgi_http_t * http,PyObject * dict)3561624Smax.romanov@nginx.com nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict)
3571624Smax.romanov@nginx.com {
3581681Smax.romanov@nginx.com     int                     rc;
3591681Smax.romanov@nginx.com     char                    *body_str;
3601681Smax.romanov@nginx.com     ssize_t                 sent;
3611681Smax.romanov@nginx.com     PyObject                *body, *more_body, *future;
3621681Smax.romanov@nginx.com     Py_ssize_t              body_len, body_off;
3631681Smax.romanov@nginx.com     nxt_py_asgi_ctx_data_t  *ctx_data;
3641624Smax.romanov@nginx.com 
3651624Smax.romanov@nginx.com     body = PyDict_GetItem(dict, nxt_py_body_str);
3661624Smax.romanov@nginx.com     if (nxt_slow_path(body != NULL && !PyBytes_Check(body))) {
3671624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'body' is not a byte string");
3681624Smax.romanov@nginx.com     }
3691624Smax.romanov@nginx.com 
3701624Smax.romanov@nginx.com     more_body = PyDict_GetItem(dict, nxt_py_more_body_str);
3711624Smax.romanov@nginx.com     if (nxt_slow_path(more_body != NULL && !PyBool_Check(more_body))) {
3721624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'more_body' is not a bool");
3731624Smax.romanov@nginx.com     }
3741624Smax.romanov@nginx.com 
3751624Smax.romanov@nginx.com     if (nxt_slow_path(http->complete)) {
3761624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
3771624Smax.romanov@nginx.com                             "Unexpected ASGI message 'http.response.body' "
3781624Smax.romanov@nginx.com                             "sent, after response already completed");
3791624Smax.romanov@nginx.com     }
3801624Smax.romanov@nginx.com 
3811624Smax.romanov@nginx.com     if (nxt_slow_path(http->send_future != NULL)) {
3821624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError, "Concurrent send");
3831624Smax.romanov@nginx.com     }
3841624Smax.romanov@nginx.com 
3851624Smax.romanov@nginx.com     if (body != NULL) {
3861624Smax.romanov@nginx.com         body_str = PyBytes_AS_STRING(body);
3871624Smax.romanov@nginx.com         body_len = PyBytes_GET_SIZE(body);
3881624Smax.romanov@nginx.com 
3891624Smax.romanov@nginx.com         nxt_unit_req_debug(http->req, "asgi_http_response_body: %d, %d",
3901624Smax.romanov@nginx.com                            (int) body_len, (more_body == Py_True) );
3911624Smax.romanov@nginx.com 
3921624Smax.romanov@nginx.com         if (nxt_slow_path(http->bytes_sent + body_len
3931624Smax.romanov@nginx.com                               > http->content_length))
3941624Smax.romanov@nginx.com         {
3951624Smax.romanov@nginx.com             return PyErr_Format(PyExc_RuntimeError,
3961624Smax.romanov@nginx.com                                 "Response content longer than Content-Length");
3971624Smax.romanov@nginx.com         }
3981624Smax.romanov@nginx.com 
3991624Smax.romanov@nginx.com         body_off = 0;
4001624Smax.romanov@nginx.com 
4011681Smax.romanov@nginx.com         ctx_data = http->req->ctx->data;
4021681Smax.romanov@nginx.com 
4031624Smax.romanov@nginx.com         while (body_len > 0) {
4041624Smax.romanov@nginx.com             sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
4051624Smax.romanov@nginx.com             if (nxt_slow_path(sent < 0)) {
4061624Smax.romanov@nginx.com                 return PyErr_Format(PyExc_RuntimeError, "failed to send body");
4071624Smax.romanov@nginx.com             }
4081624Smax.romanov@nginx.com 
4091624Smax.romanov@nginx.com             if (nxt_slow_path(sent == 0)) {
4101624Smax.romanov@nginx.com                 nxt_unit_req_debug(http->req, "asgi_http_response_body: "
4111624Smax.romanov@nginx.com                                    "out of shared memory, %d",
4121624Smax.romanov@nginx.com                                    (int) body_len);
4131624Smax.romanov@nginx.com 
4141681Smax.romanov@nginx.com                 future = PyObject_CallObject(ctx_data->loop_create_future,
4151681Smax.romanov@nginx.com                                              NULL);
4161624Smax.romanov@nginx.com                 if (nxt_slow_path(future == NULL)) {
4171624Smax.romanov@nginx.com                     nxt_unit_req_alert(http->req,
4181624Smax.romanov@nginx.com                                        "Python failed to create Future object");
4191624Smax.romanov@nginx.com                     nxt_python_print_exception();
4201624Smax.romanov@nginx.com 
4211624Smax.romanov@nginx.com                     return PyErr_Format(PyExc_RuntimeError,
4221624Smax.romanov@nginx.com                                         "failed to create Future object");
4231624Smax.romanov@nginx.com                 }
4241624Smax.romanov@nginx.com 
4251624Smax.romanov@nginx.com                 http->send_body = body;
4261624Smax.romanov@nginx.com                 Py_INCREF(http->send_body);
4271624Smax.romanov@nginx.com                 http->send_body_off = body_off;
4281624Smax.romanov@nginx.com 
4291681Smax.romanov@nginx.com                 nxt_py_asgi_drain_wait(http->req, &http->link);
4301624Smax.romanov@nginx.com 
4311624Smax.romanov@nginx.com                 http->send_future = future;
4321624Smax.romanov@nginx.com                 Py_INCREF(http->send_future);
4331624Smax.romanov@nginx.com 
4341624Smax.romanov@nginx.com                 return future;
4351624Smax.romanov@nginx.com             }
4361624Smax.romanov@nginx.com 
4371624Smax.romanov@nginx.com             body_str += sent;
4381624Smax.romanov@nginx.com             body_len -= sent;
4391624Smax.romanov@nginx.com             body_off += sent;
4401624Smax.romanov@nginx.com             http->bytes_sent += sent;
4411624Smax.romanov@nginx.com         }
4421624Smax.romanov@nginx.com 
4431624Smax.romanov@nginx.com     } else {
4441624Smax.romanov@nginx.com         nxt_unit_req_debug(http->req, "asgi_http_response_body: 0, %d",
4451624Smax.romanov@nginx.com                            (more_body == Py_True) );
4461624Smax.romanov@nginx.com 
4471624Smax.romanov@nginx.com         if (!nxt_unit_response_is_sent(http->req)) {
4481624Smax.romanov@nginx.com             rc = nxt_unit_response_send(http->req);
4491624Smax.romanov@nginx.com             if (nxt_slow_path(rc != NXT_UNIT_OK)) {
4501624Smax.romanov@nginx.com                 return PyErr_Format(PyExc_RuntimeError,
4511624Smax.romanov@nginx.com                                     "failed to send response");
4521624Smax.romanov@nginx.com             }
4531624Smax.romanov@nginx.com         }
4541624Smax.romanov@nginx.com     }
4551624Smax.romanov@nginx.com 
4561624Smax.romanov@nginx.com     if (more_body == NULL || more_body == Py_False) {
4571624Smax.romanov@nginx.com         http->complete = 1;
4581916Smax.romanov@nginx.com 
4591916Smax.romanov@nginx.com         nxt_py_asgi_http_emit_disconnect(http);
4601624Smax.romanov@nginx.com     }
4611624Smax.romanov@nginx.com 
4621624Smax.romanov@nginx.com     Py_INCREF(http);
4631624Smax.romanov@nginx.com     return (PyObject *) http;
4641624Smax.romanov@nginx.com }
4651624Smax.romanov@nginx.com 
4661624Smax.romanov@nginx.com 
4671916Smax.romanov@nginx.com static void
nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t * http)4681916Smax.romanov@nginx.com nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http)
4691916Smax.romanov@nginx.com {
4701917Smax.romanov@nginx.com     PyObject  *msg, *future;
4711916Smax.romanov@nginx.com 
4721916Smax.romanov@nginx.com     if (http->receive_future == NULL) {
4731916Smax.romanov@nginx.com         return;
4741916Smax.romanov@nginx.com     }
4751916Smax.romanov@nginx.com 
4761916Smax.romanov@nginx.com     msg = nxt_py_asgi_new_msg(http->req, nxt_py_http_disconnect_str);
4771916Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
4781916Smax.romanov@nginx.com         return;
4791916Smax.romanov@nginx.com     }
4801916Smax.romanov@nginx.com 
4811916Smax.romanov@nginx.com     if (msg == Py_None) {
4821916Smax.romanov@nginx.com         Py_DECREF(msg);
4831916Smax.romanov@nginx.com         return;
4841916Smax.romanov@nginx.com     }
4851916Smax.romanov@nginx.com 
4861916Smax.romanov@nginx.com     future = http->receive_future;
4871916Smax.romanov@nginx.com     http->receive_future = NULL;
4881916Smax.romanov@nginx.com 
4891917Smax.romanov@nginx.com     nxt_py_asgi_http_set_result(http, future, msg);
4901917Smax.romanov@nginx.com 
4911917Smax.romanov@nginx.com     Py_DECREF(msg);
4921917Smax.romanov@nginx.com }
4931917Smax.romanov@nginx.com 
4941917Smax.romanov@nginx.com 
4951917Smax.romanov@nginx.com static void
nxt_py_asgi_http_set_result(nxt_py_asgi_http_t * http,PyObject * future,PyObject * msg)4961917Smax.romanov@nginx.com nxt_py_asgi_http_set_result(nxt_py_asgi_http_t *http, PyObject *future,
4971917Smax.romanov@nginx.com     PyObject *msg)
4981917Smax.romanov@nginx.com {
4991917Smax.romanov@nginx.com     PyObject  *res;
5001917Smax.romanov@nginx.com 
5011917Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_done_str, NULL);
5021916Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
5031917Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "'done' call failed");
5041916Smax.romanov@nginx.com         nxt_python_print_exception();
5051916Smax.romanov@nginx.com     }
5061916Smax.romanov@nginx.com 
5071917Smax.romanov@nginx.com     if (nxt_fast_path(res == Py_False)) {
5081917Smax.romanov@nginx.com         res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg,
5091917Smax.romanov@nginx.com                                          NULL);
5101917Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
5111917Smax.romanov@nginx.com             nxt_unit_req_alert(http->req, "'set_result' call failed");
5121917Smax.romanov@nginx.com             nxt_python_print_exception();
5131917Smax.romanov@nginx.com         }
5141917Smax.romanov@nginx.com 
5151917Smax.romanov@nginx.com     } else {
5161917Smax.romanov@nginx.com         res = NULL;
5171917Smax.romanov@nginx.com     }
5181917Smax.romanov@nginx.com 
5191916Smax.romanov@nginx.com     Py_XDECREF(res);
5201916Smax.romanov@nginx.com     Py_DECREF(future);
5211916Smax.romanov@nginx.com }
5221916Smax.romanov@nginx.com 
5231916Smax.romanov@nginx.com 
5241624Smax.romanov@nginx.com void
nxt_py_asgi_http_data_handler(nxt_unit_request_info_t * req)5251624Smax.romanov@nginx.com nxt_py_asgi_http_data_handler(nxt_unit_request_info_t *req)
5261624Smax.romanov@nginx.com {
5271917Smax.romanov@nginx.com     PyObject            *msg, *future;
5281624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
5291624Smax.romanov@nginx.com 
5301624Smax.romanov@nginx.com     http = req->data;
5311624Smax.romanov@nginx.com 
5321624Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_data_handler");
5331624Smax.romanov@nginx.com 
5341624Smax.romanov@nginx.com     if (http->receive_future == NULL) {
5351624Smax.romanov@nginx.com         return;
5361624Smax.romanov@nginx.com     }
5371624Smax.romanov@nginx.com 
5381624Smax.romanov@nginx.com     msg = nxt_py_asgi_http_read_msg(http);
5391624Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
5401624Smax.romanov@nginx.com         return;
5411624Smax.romanov@nginx.com     }
5421624Smax.romanov@nginx.com 
5431624Smax.romanov@nginx.com     if (msg == Py_None) {
5441624Smax.romanov@nginx.com         Py_DECREF(msg);
5451624Smax.romanov@nginx.com         return;
5461624Smax.romanov@nginx.com     }
5471624Smax.romanov@nginx.com 
5481624Smax.romanov@nginx.com     future = http->receive_future;
5491624Smax.romanov@nginx.com     http->receive_future = NULL;
5501624Smax.romanov@nginx.com 
5511917Smax.romanov@nginx.com     nxt_py_asgi_http_set_result(http, future, msg);
5521624Smax.romanov@nginx.com 
5531624Smax.romanov@nginx.com     Py_DECREF(msg);
5541624Smax.romanov@nginx.com }
5551624Smax.romanov@nginx.com 
5561624Smax.romanov@nginx.com 
5571624Smax.romanov@nginx.com int
nxt_py_asgi_http_drain(nxt_queue_link_t * lnk)5581624Smax.romanov@nginx.com nxt_py_asgi_http_drain(nxt_queue_link_t *lnk)
5591624Smax.romanov@nginx.com {
5601624Smax.romanov@nginx.com     char                *body_str;
5611624Smax.romanov@nginx.com     ssize_t             sent;
5621624Smax.romanov@nginx.com     PyObject            *future, *exc, *res;
5631624Smax.romanov@nginx.com     Py_ssize_t          body_len;
5641624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
5651624Smax.romanov@nginx.com 
5661624Smax.romanov@nginx.com     http = nxt_container_of(lnk, nxt_py_asgi_http_t, link);
5671624Smax.romanov@nginx.com 
5681624Smax.romanov@nginx.com     body_str = PyBytes_AS_STRING(http->send_body) + http->send_body_off;
5691624Smax.romanov@nginx.com     body_len = PyBytes_GET_SIZE(http->send_body) - http->send_body_off;
5701624Smax.romanov@nginx.com 
5711624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_drain: %d", (int) body_len);
5721624Smax.romanov@nginx.com 
5731624Smax.romanov@nginx.com     while (body_len > 0) {
5741624Smax.romanov@nginx.com         sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
5751624Smax.romanov@nginx.com         if (nxt_slow_path(sent < 0)) {
5761624Smax.romanov@nginx.com             goto fail;
5771624Smax.romanov@nginx.com         }
5781624Smax.romanov@nginx.com 
5791624Smax.romanov@nginx.com         if (nxt_slow_path(sent == 0)) {
5801624Smax.romanov@nginx.com             return NXT_UNIT_AGAIN;
5811624Smax.romanov@nginx.com         }
5821624Smax.romanov@nginx.com 
5831624Smax.romanov@nginx.com         body_str += sent;
5841624Smax.romanov@nginx.com         body_len -= sent;
5851624Smax.romanov@nginx.com 
5861624Smax.romanov@nginx.com         http->send_body_off += sent;
5871624Smax.romanov@nginx.com         http->bytes_sent += sent;
5881624Smax.romanov@nginx.com     }
5891624Smax.romanov@nginx.com 
5901624Smax.romanov@nginx.com     Py_CLEAR(http->send_body);
5911624Smax.romanov@nginx.com 
5921624Smax.romanov@nginx.com     future = http->send_future;
5931624Smax.romanov@nginx.com     http->send_future = NULL;
5941624Smax.romanov@nginx.com 
5951917Smax.romanov@nginx.com     nxt_py_asgi_http_set_result(http, future, Py_None);
5961624Smax.romanov@nginx.com 
5971624Smax.romanov@nginx.com     return NXT_UNIT_OK;
5981624Smax.romanov@nginx.com 
5991624Smax.romanov@nginx.com fail:
6001624Smax.romanov@nginx.com 
6011624Smax.romanov@nginx.com     exc = PyObject_CallFunctionObjArgs(PyExc_RuntimeError,
6021624Smax.romanov@nginx.com                                        nxt_py_failed_to_send_body_str,
6031624Smax.romanov@nginx.com                                        NULL);
6041624Smax.romanov@nginx.com     if (nxt_slow_path(exc == NULL)) {
6051624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "RuntimeError create failed");
6061624Smax.romanov@nginx.com         nxt_python_print_exception();
6071624Smax.romanov@nginx.com 
6081624Smax.romanov@nginx.com         exc = Py_None;
6091624Smax.romanov@nginx.com         Py_INCREF(exc);
6101624Smax.romanov@nginx.com     }
6111624Smax.romanov@nginx.com 
6121624Smax.romanov@nginx.com     future = http->send_future;
6131624Smax.romanov@nginx.com     http->send_future = NULL;
6141624Smax.romanov@nginx.com 
6151624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_exception_str, exc,
6161624Smax.romanov@nginx.com                                      NULL);
6171624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
6181624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "'set_exception' call failed");
6191624Smax.romanov@nginx.com         nxt_python_print_exception();
6201624Smax.romanov@nginx.com     }
6211624Smax.romanov@nginx.com 
6221624Smax.romanov@nginx.com     Py_XDECREF(res);
6231624Smax.romanov@nginx.com     Py_DECREF(future);
6241624Smax.romanov@nginx.com     Py_DECREF(exc);
6251624Smax.romanov@nginx.com 
6261624Smax.romanov@nginx.com     return NXT_UNIT_ERROR;
6271624Smax.romanov@nginx.com }
6281624Smax.romanov@nginx.com 
6291624Smax.romanov@nginx.com 
6301715Smax.romanov@nginx.com void
nxt_py_asgi_http_close_handler(nxt_unit_request_info_t * req)6311715Smax.romanov@nginx.com nxt_py_asgi_http_close_handler(nxt_unit_request_info_t *req)
6321715Smax.romanov@nginx.com {
6331715Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
6341715Smax.romanov@nginx.com 
6351715Smax.romanov@nginx.com     http = req->data;
6361715Smax.romanov@nginx.com 
6371715Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_close_handler");
6381715Smax.romanov@nginx.com 
639*1980Smax.romanov@nginx.com     if (nxt_fast_path(http != NULL)) {
640*1980Smax.romanov@nginx.com         http->closed = 1;
6411715Smax.romanov@nginx.com 
642*1980Smax.romanov@nginx.com         nxt_py_asgi_http_emit_disconnect(http);
643*1980Smax.romanov@nginx.com     }
6441715Smax.romanov@nginx.com }
6451715Smax.romanov@nginx.com 
6461715Smax.romanov@nginx.com 
6471624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_http_done(PyObject * self,PyObject * future)6481624Smax.romanov@nginx.com nxt_py_asgi_http_done(PyObject *self, PyObject *future)
6491624Smax.romanov@nginx.com {
6501624Smax.romanov@nginx.com     int                 rc;
6511624Smax.romanov@nginx.com     PyObject            *res;
6521624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
6531624Smax.romanov@nginx.com 
6541624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
6551624Smax.romanov@nginx.com 
6561624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_done");
6571624Smax.romanov@nginx.com 
6581624Smax.romanov@nginx.com     /*
6591624Smax.romanov@nginx.com      * Get Future.result() and it raises an exception, if coroutine exited
6601624Smax.romanov@nginx.com      * with exception.
6611624Smax.romanov@nginx.com      */
6621624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL);
6631624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
6641624Smax.romanov@nginx.com         nxt_unit_req_error(http->req,
6651624Smax.romanov@nginx.com                            "Python failed to call 'future.result()'");
6661624Smax.romanov@nginx.com         nxt_python_print_exception();
6671624Smax.romanov@nginx.com 
6681624Smax.romanov@nginx.com         rc = NXT_UNIT_ERROR;
6691624Smax.romanov@nginx.com 
6701624Smax.romanov@nginx.com     } else {
6711624Smax.romanov@nginx.com         Py_DECREF(res);
6721624Smax.romanov@nginx.com 
6731624Smax.romanov@nginx.com         rc = NXT_UNIT_OK;
6741624Smax.romanov@nginx.com     }
6751624Smax.romanov@nginx.com 
6761624Smax.romanov@nginx.com     nxt_unit_request_done(http->req, rc);
6771624Smax.romanov@nginx.com 
6781624Smax.romanov@nginx.com     Py_RETURN_NONE;
6791624Smax.romanov@nginx.com }
6801624Smax.romanov@nginx.com 
6811624Smax.romanov@nginx.com 
6821624Smax.romanov@nginx.com #endif /* NXT_HAVE_ASGI */
683