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