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 if (nxt_slow_path(http->complete)) {
3661624Smax.romanov@nginx.com return PyErr_Format(PyExc_RuntimeError,
3671624Smax.romanov@nginx.com "Unexpected ASGI message 'http.response.body' "
3681624Smax.romanov@nginx.com "sent, after response already completed");
3691624Smax.romanov@nginx.com }
3701624Smax.romanov@nginx.com
3711624Smax.romanov@nginx.com if (nxt_slow_path(http->send_future != NULL)) {
3721624Smax.romanov@nginx.com return PyErr_Format(PyExc_RuntimeError, "Concurrent send");
3731624Smax.romanov@nginx.com }
3741624Smax.romanov@nginx.com
375*2674Szelenkov@nginx.com more_body = PyDict_GetItem(dict, nxt_py_more_body_str);
376*2674Szelenkov@nginx.com if (nxt_slow_path(more_body != NULL && !PyBool_Check(more_body))) {
377*2674Szelenkov@nginx.com return PyErr_Format(PyExc_TypeError, "'more_body' is not a bool");
378*2674Szelenkov@nginx.com }
379*2674Szelenkov@nginx.com
380*2674Szelenkov@nginx.com body = PyDict_GetItem(dict, nxt_py_body_str);
381*2674Szelenkov@nginx.com
3821624Smax.romanov@nginx.com if (body != NULL) {
383*2674Szelenkov@nginx.com if (PyBytes_Check(body)) {
384*2674Szelenkov@nginx.com body_str = PyBytes_AS_STRING(body);
385*2674Szelenkov@nginx.com body_len = PyBytes_GET_SIZE(body);
386*2674Szelenkov@nginx.com
387*2674Szelenkov@nginx.com } else if (PyByteArray_Check(body)) {
388*2674Szelenkov@nginx.com body_str = PyByteArray_AS_STRING(body);
389*2674Szelenkov@nginx.com body_len = PyByteArray_GET_SIZE(body);
390*2674Szelenkov@nginx.com
391*2674Szelenkov@nginx.com } else {
392*2674Szelenkov@nginx.com return PyErr_Format(PyExc_TypeError,
393*2674Szelenkov@nginx.com "'body' is not a byte string or bytearray");
394*2674Szelenkov@nginx.com }
3951624Smax.romanov@nginx.com
3961624Smax.romanov@nginx.com nxt_unit_req_debug(http->req, "asgi_http_response_body: %d, %d",
3971624Smax.romanov@nginx.com (int) body_len, (more_body == Py_True) );
3981624Smax.romanov@nginx.com
3991624Smax.romanov@nginx.com if (nxt_slow_path(http->bytes_sent + body_len
4001624Smax.romanov@nginx.com > http->content_length))
4011624Smax.romanov@nginx.com {
4021624Smax.romanov@nginx.com return PyErr_Format(PyExc_RuntimeError,
4031624Smax.romanov@nginx.com "Response content longer than Content-Length");
4041624Smax.romanov@nginx.com }
4051624Smax.romanov@nginx.com
4061624Smax.romanov@nginx.com body_off = 0;
4071624Smax.romanov@nginx.com
4081681Smax.romanov@nginx.com ctx_data = http->req->ctx->data;
4091681Smax.romanov@nginx.com
4101624Smax.romanov@nginx.com while (body_len > 0) {
4111624Smax.romanov@nginx.com sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
4121624Smax.romanov@nginx.com if (nxt_slow_path(sent < 0)) {
4131624Smax.romanov@nginx.com return PyErr_Format(PyExc_RuntimeError, "failed to send body");
4141624Smax.romanov@nginx.com }
4151624Smax.romanov@nginx.com
4161624Smax.romanov@nginx.com if (nxt_slow_path(sent == 0)) {
4171624Smax.romanov@nginx.com nxt_unit_req_debug(http->req, "asgi_http_response_body: "
4181624Smax.romanov@nginx.com "out of shared memory, %d",
4191624Smax.romanov@nginx.com (int) body_len);
4201624Smax.romanov@nginx.com
4211681Smax.romanov@nginx.com future = PyObject_CallObject(ctx_data->loop_create_future,
4221681Smax.romanov@nginx.com NULL);
4231624Smax.romanov@nginx.com if (nxt_slow_path(future == NULL)) {
4241624Smax.romanov@nginx.com nxt_unit_req_alert(http->req,
4251624Smax.romanov@nginx.com "Python failed to create Future object");
4261624Smax.romanov@nginx.com nxt_python_print_exception();
4271624Smax.romanov@nginx.com
4281624Smax.romanov@nginx.com return PyErr_Format(PyExc_RuntimeError,
4291624Smax.romanov@nginx.com "failed to create Future object");
4301624Smax.romanov@nginx.com }
4311624Smax.romanov@nginx.com
4321624Smax.romanov@nginx.com http->send_body = body;
4331624Smax.romanov@nginx.com Py_INCREF(http->send_body);
4341624Smax.romanov@nginx.com http->send_body_off = body_off;
4351624Smax.romanov@nginx.com
4361681Smax.romanov@nginx.com nxt_py_asgi_drain_wait(http->req, &http->link);
4371624Smax.romanov@nginx.com
4381624Smax.romanov@nginx.com http->send_future = future;
4391624Smax.romanov@nginx.com Py_INCREF(http->send_future);
4401624Smax.romanov@nginx.com
4411624Smax.romanov@nginx.com return future;
4421624Smax.romanov@nginx.com }
4431624Smax.romanov@nginx.com
4441624Smax.romanov@nginx.com body_str += sent;
4451624Smax.romanov@nginx.com body_len -= sent;
4461624Smax.romanov@nginx.com body_off += sent;
4471624Smax.romanov@nginx.com http->bytes_sent += sent;
4481624Smax.romanov@nginx.com }
4491624Smax.romanov@nginx.com
4501624Smax.romanov@nginx.com } else {
4511624Smax.romanov@nginx.com nxt_unit_req_debug(http->req, "asgi_http_response_body: 0, %d",
4521624Smax.romanov@nginx.com (more_body == Py_True) );
4531624Smax.romanov@nginx.com
4541624Smax.romanov@nginx.com if (!nxt_unit_response_is_sent(http->req)) {
4551624Smax.romanov@nginx.com rc = nxt_unit_response_send(http->req);
4561624Smax.romanov@nginx.com if (nxt_slow_path(rc != NXT_UNIT_OK)) {
4571624Smax.romanov@nginx.com return PyErr_Format(PyExc_RuntimeError,
4581624Smax.romanov@nginx.com "failed to send response");
4591624Smax.romanov@nginx.com }
4601624Smax.romanov@nginx.com }
4611624Smax.romanov@nginx.com }
4621624Smax.romanov@nginx.com
4631624Smax.romanov@nginx.com if (more_body == NULL || more_body == Py_False) {
4641624Smax.romanov@nginx.com http->complete = 1;
4651916Smax.romanov@nginx.com
4661916Smax.romanov@nginx.com nxt_py_asgi_http_emit_disconnect(http);
4671624Smax.romanov@nginx.com }
4681624Smax.romanov@nginx.com
4691624Smax.romanov@nginx.com Py_INCREF(http);
4701624Smax.romanov@nginx.com return (PyObject *) http;
4711624Smax.romanov@nginx.com }
4721624Smax.romanov@nginx.com
4731624Smax.romanov@nginx.com
4741916Smax.romanov@nginx.com static void
nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t * http)4751916Smax.romanov@nginx.com nxt_py_asgi_http_emit_disconnect(nxt_py_asgi_http_t *http)
4761916Smax.romanov@nginx.com {
4771917Smax.romanov@nginx.com PyObject *msg, *future;
4781916Smax.romanov@nginx.com
4791916Smax.romanov@nginx.com if (http->receive_future == NULL) {
4801916Smax.romanov@nginx.com return;
4811916Smax.romanov@nginx.com }
4821916Smax.romanov@nginx.com
4831916Smax.romanov@nginx.com msg = nxt_py_asgi_new_msg(http->req, nxt_py_http_disconnect_str);
4841916Smax.romanov@nginx.com if (nxt_slow_path(msg == NULL)) {
4851916Smax.romanov@nginx.com return;
4861916Smax.romanov@nginx.com }
4871916Smax.romanov@nginx.com
4881916Smax.romanov@nginx.com if (msg == Py_None) {
4891916Smax.romanov@nginx.com Py_DECREF(msg);
4901916Smax.romanov@nginx.com return;
4911916Smax.romanov@nginx.com }
4921916Smax.romanov@nginx.com
4931916Smax.romanov@nginx.com future = http->receive_future;
4941916Smax.romanov@nginx.com http->receive_future = NULL;
4951916Smax.romanov@nginx.com
4961917Smax.romanov@nginx.com nxt_py_asgi_http_set_result(http, future, msg);
4971917Smax.romanov@nginx.com
4981917Smax.romanov@nginx.com Py_DECREF(msg);
4991917Smax.romanov@nginx.com }
5001917Smax.romanov@nginx.com
5011917Smax.romanov@nginx.com
5021917Smax.romanov@nginx.com static void
nxt_py_asgi_http_set_result(nxt_py_asgi_http_t * http,PyObject * future,PyObject * msg)5031917Smax.romanov@nginx.com nxt_py_asgi_http_set_result(nxt_py_asgi_http_t *http, PyObject *future,
5041917Smax.romanov@nginx.com PyObject *msg)
5051917Smax.romanov@nginx.com {
5061917Smax.romanov@nginx.com PyObject *res;
5071917Smax.romanov@nginx.com
5081917Smax.romanov@nginx.com res = PyObject_CallMethodObjArgs(future, nxt_py_done_str, NULL);
5091916Smax.romanov@nginx.com if (nxt_slow_path(res == NULL)) {
5101917Smax.romanov@nginx.com nxt_unit_req_alert(http->req, "'done' call failed");
5111916Smax.romanov@nginx.com nxt_python_print_exception();
5121916Smax.romanov@nginx.com }
5131916Smax.romanov@nginx.com
5141917Smax.romanov@nginx.com if (nxt_fast_path(res == Py_False)) {
5151917Smax.romanov@nginx.com res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg,
5161917Smax.romanov@nginx.com NULL);
5171917Smax.romanov@nginx.com if (nxt_slow_path(res == NULL)) {
5181917Smax.romanov@nginx.com nxt_unit_req_alert(http->req, "'set_result' call failed");
5191917Smax.romanov@nginx.com nxt_python_print_exception();
5201917Smax.romanov@nginx.com }
5211917Smax.romanov@nginx.com
5221917Smax.romanov@nginx.com } else {
5231917Smax.romanov@nginx.com res = NULL;
5241917Smax.romanov@nginx.com }
5251917Smax.romanov@nginx.com
5261916Smax.romanov@nginx.com Py_XDECREF(res);
5271916Smax.romanov@nginx.com Py_DECREF(future);
5281916Smax.romanov@nginx.com }
5291916Smax.romanov@nginx.com
5301916Smax.romanov@nginx.com
5311624Smax.romanov@nginx.com void
nxt_py_asgi_http_data_handler(nxt_unit_request_info_t * req)5321624Smax.romanov@nginx.com nxt_py_asgi_http_data_handler(nxt_unit_request_info_t *req)
5331624Smax.romanov@nginx.com {
5341917Smax.romanov@nginx.com PyObject *msg, *future;
5351624Smax.romanov@nginx.com nxt_py_asgi_http_t *http;
5361624Smax.romanov@nginx.com
5371624Smax.romanov@nginx.com http = req->data;
5381624Smax.romanov@nginx.com
5391624Smax.romanov@nginx.com nxt_unit_req_debug(req, "asgi_http_data_handler");
5401624Smax.romanov@nginx.com
5411624Smax.romanov@nginx.com if (http->receive_future == NULL) {
5421624Smax.romanov@nginx.com return;
5431624Smax.romanov@nginx.com }
5441624Smax.romanov@nginx.com
5451624Smax.romanov@nginx.com msg = nxt_py_asgi_http_read_msg(http);
5461624Smax.romanov@nginx.com if (nxt_slow_path(msg == NULL)) {
5471624Smax.romanov@nginx.com return;
5481624Smax.romanov@nginx.com }
5491624Smax.romanov@nginx.com
5501624Smax.romanov@nginx.com if (msg == Py_None) {
5511624Smax.romanov@nginx.com Py_DECREF(msg);
5521624Smax.romanov@nginx.com return;
5531624Smax.romanov@nginx.com }
5541624Smax.romanov@nginx.com
5551624Smax.romanov@nginx.com future = http->receive_future;
5561624Smax.romanov@nginx.com http->receive_future = NULL;
5571624Smax.romanov@nginx.com
5581917Smax.romanov@nginx.com nxt_py_asgi_http_set_result(http, future, msg);
5591624Smax.romanov@nginx.com
5601624Smax.romanov@nginx.com Py_DECREF(msg);
5611624Smax.romanov@nginx.com }
5621624Smax.romanov@nginx.com
5631624Smax.romanov@nginx.com
5641624Smax.romanov@nginx.com int
nxt_py_asgi_http_drain(nxt_queue_link_t * lnk)5651624Smax.romanov@nginx.com nxt_py_asgi_http_drain(nxt_queue_link_t *lnk)
5661624Smax.romanov@nginx.com {
5671624Smax.romanov@nginx.com char *body_str;
5681624Smax.romanov@nginx.com ssize_t sent;
5691624Smax.romanov@nginx.com PyObject *future, *exc, *res;
5701624Smax.romanov@nginx.com Py_ssize_t body_len;
5711624Smax.romanov@nginx.com nxt_py_asgi_http_t *http;
5721624Smax.romanov@nginx.com
5731624Smax.romanov@nginx.com http = nxt_container_of(lnk, nxt_py_asgi_http_t, link);
5741624Smax.romanov@nginx.com
5751624Smax.romanov@nginx.com body_str = PyBytes_AS_STRING(http->send_body) + http->send_body_off;
5761624Smax.romanov@nginx.com body_len = PyBytes_GET_SIZE(http->send_body) - http->send_body_off;
5771624Smax.romanov@nginx.com
5781624Smax.romanov@nginx.com nxt_unit_req_debug(http->req, "asgi_http_drain: %d", (int) body_len);
5791624Smax.romanov@nginx.com
5801624Smax.romanov@nginx.com while (body_len > 0) {
5811624Smax.romanov@nginx.com sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
5821624Smax.romanov@nginx.com if (nxt_slow_path(sent < 0)) {
5831624Smax.romanov@nginx.com goto fail;
5841624Smax.romanov@nginx.com }
5851624Smax.romanov@nginx.com
5861624Smax.romanov@nginx.com if (nxt_slow_path(sent == 0)) {
5871624Smax.romanov@nginx.com return NXT_UNIT_AGAIN;
5881624Smax.romanov@nginx.com }
5891624Smax.romanov@nginx.com
5901624Smax.romanov@nginx.com body_str += sent;
5911624Smax.romanov@nginx.com body_len -= sent;
5921624Smax.romanov@nginx.com
5931624Smax.romanov@nginx.com http->send_body_off += sent;
5941624Smax.romanov@nginx.com http->bytes_sent += sent;
5951624Smax.romanov@nginx.com }
5961624Smax.romanov@nginx.com
5971624Smax.romanov@nginx.com Py_CLEAR(http->send_body);
5981624Smax.romanov@nginx.com
5991624Smax.romanov@nginx.com future = http->send_future;
6001624Smax.romanov@nginx.com http->send_future = NULL;
6011624Smax.romanov@nginx.com
6021917Smax.romanov@nginx.com nxt_py_asgi_http_set_result(http, future, Py_None);
6031624Smax.romanov@nginx.com
6041624Smax.romanov@nginx.com return NXT_UNIT_OK;
6051624Smax.romanov@nginx.com
6061624Smax.romanov@nginx.com fail:
6071624Smax.romanov@nginx.com
6081624Smax.romanov@nginx.com exc = PyObject_CallFunctionObjArgs(PyExc_RuntimeError,
6091624Smax.romanov@nginx.com nxt_py_failed_to_send_body_str,
6101624Smax.romanov@nginx.com NULL);
6111624Smax.romanov@nginx.com if (nxt_slow_path(exc == NULL)) {
6121624Smax.romanov@nginx.com nxt_unit_req_alert(http->req, "RuntimeError create failed");
6131624Smax.romanov@nginx.com nxt_python_print_exception();
6141624Smax.romanov@nginx.com
6151624Smax.romanov@nginx.com exc = Py_None;
6161624Smax.romanov@nginx.com Py_INCREF(exc);
6171624Smax.romanov@nginx.com }
6181624Smax.romanov@nginx.com
6191624Smax.romanov@nginx.com future = http->send_future;
6201624Smax.romanov@nginx.com http->send_future = NULL;
6211624Smax.romanov@nginx.com
6221624Smax.romanov@nginx.com res = PyObject_CallMethodObjArgs(future, nxt_py_set_exception_str, exc,
6231624Smax.romanov@nginx.com NULL);
6241624Smax.romanov@nginx.com if (nxt_slow_path(res == NULL)) {
6251624Smax.romanov@nginx.com nxt_unit_req_alert(http->req, "'set_exception' call failed");
6261624Smax.romanov@nginx.com nxt_python_print_exception();
6271624Smax.romanov@nginx.com }
6281624Smax.romanov@nginx.com
6291624Smax.romanov@nginx.com Py_XDECREF(res);
6301624Smax.romanov@nginx.com Py_DECREF(future);
6311624Smax.romanov@nginx.com Py_DECREF(exc);
6321624Smax.romanov@nginx.com
6331624Smax.romanov@nginx.com return NXT_UNIT_ERROR;
6341624Smax.romanov@nginx.com }
6351624Smax.romanov@nginx.com
6361624Smax.romanov@nginx.com
6371715Smax.romanov@nginx.com void
nxt_py_asgi_http_close_handler(nxt_unit_request_info_t * req)6381715Smax.romanov@nginx.com nxt_py_asgi_http_close_handler(nxt_unit_request_info_t *req)
6391715Smax.romanov@nginx.com {
6401715Smax.romanov@nginx.com nxt_py_asgi_http_t *http;
6411715Smax.romanov@nginx.com
6421715Smax.romanov@nginx.com http = req->data;
6431715Smax.romanov@nginx.com
6441715Smax.romanov@nginx.com nxt_unit_req_debug(req, "asgi_http_close_handler");
6451715Smax.romanov@nginx.com
6461980Smax.romanov@nginx.com if (nxt_fast_path(http != NULL)) {
6471980Smax.romanov@nginx.com http->closed = 1;
6481715Smax.romanov@nginx.com
6491980Smax.romanov@nginx.com nxt_py_asgi_http_emit_disconnect(http);
6501980Smax.romanov@nginx.com }
6511715Smax.romanov@nginx.com }
6521715Smax.romanov@nginx.com
6531715Smax.romanov@nginx.com
6541624Smax.romanov@nginx.com static PyObject *
nxt_py_asgi_http_done(PyObject * self,PyObject * future)6551624Smax.romanov@nginx.com nxt_py_asgi_http_done(PyObject *self, PyObject *future)
6561624Smax.romanov@nginx.com {
6571624Smax.romanov@nginx.com int rc;
6581624Smax.romanov@nginx.com PyObject *res;
6591624Smax.romanov@nginx.com nxt_py_asgi_http_t *http;
6601624Smax.romanov@nginx.com
6611624Smax.romanov@nginx.com http = (nxt_py_asgi_http_t *) self;
6621624Smax.romanov@nginx.com
6631624Smax.romanov@nginx.com nxt_unit_req_debug(http->req, "asgi_http_done");
6641624Smax.romanov@nginx.com
6651624Smax.romanov@nginx.com /*
6661624Smax.romanov@nginx.com * Get Future.result() and it raises an exception, if coroutine exited
6671624Smax.romanov@nginx.com * with exception.
6681624Smax.romanov@nginx.com */
6691624Smax.romanov@nginx.com res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL);
6701624Smax.romanov@nginx.com if (nxt_slow_path(res == NULL)) {
6711624Smax.romanov@nginx.com nxt_unit_req_error(http->req,
6721624Smax.romanov@nginx.com "Python failed to call 'future.result()'");
6731624Smax.romanov@nginx.com nxt_python_print_exception();
6741624Smax.romanov@nginx.com
6751624Smax.romanov@nginx.com rc = NXT_UNIT_ERROR;
6761624Smax.romanov@nginx.com
6771624Smax.romanov@nginx.com } else {
6781624Smax.romanov@nginx.com Py_DECREF(res);
6791624Smax.romanov@nginx.com
6801624Smax.romanov@nginx.com rc = NXT_UNIT_OK;
6811624Smax.romanov@nginx.com }
6821624Smax.romanov@nginx.com
6831624Smax.romanov@nginx.com nxt_unit_request_done(http->req, rc);
6841624Smax.romanov@nginx.com
6851624Smax.romanov@nginx.com Py_RETURN_NONE;
6861624Smax.romanov@nginx.com }
6871624Smax.romanov@nginx.com
6881624Smax.romanov@nginx.com
6891624Smax.romanov@nginx.com #endif /* NXT_HAVE_ASGI */
690