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