1*1624Smax.romanov@nginx.com 
2*1624Smax.romanov@nginx.com /*
3*1624Smax.romanov@nginx.com  * Copyright (C) NGINX, Inc.
4*1624Smax.romanov@nginx.com  */
5*1624Smax.romanov@nginx.com 
6*1624Smax.romanov@nginx.com 
7*1624Smax.romanov@nginx.com #include <python/nxt_python.h>
8*1624Smax.romanov@nginx.com 
9*1624Smax.romanov@nginx.com #if (NXT_HAVE_ASGI)
10*1624Smax.romanov@nginx.com 
11*1624Smax.romanov@nginx.com #include <nxt_main.h>
12*1624Smax.romanov@nginx.com #include <nxt_unit.h>
13*1624Smax.romanov@nginx.com #include <nxt_unit_request.h>
14*1624Smax.romanov@nginx.com #include <python/nxt_python_asgi.h>
15*1624Smax.romanov@nginx.com #include <python/nxt_python_asgi_str.h>
16*1624Smax.romanov@nginx.com 
17*1624Smax.romanov@nginx.com 
18*1624Smax.romanov@nginx.com typedef struct {
19*1624Smax.romanov@nginx.com     PyObject_HEAD
20*1624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
21*1624Smax.romanov@nginx.com     nxt_queue_link_t         link;
22*1624Smax.romanov@nginx.com     PyObject                 *receive_future;
23*1624Smax.romanov@nginx.com     PyObject                 *send_future;
24*1624Smax.romanov@nginx.com     uint64_t                 content_length;
25*1624Smax.romanov@nginx.com     uint64_t                 bytes_sent;
26*1624Smax.romanov@nginx.com     int                      complete;
27*1624Smax.romanov@nginx.com     PyObject                 *send_body;
28*1624Smax.romanov@nginx.com     Py_ssize_t               send_body_off;
29*1624Smax.romanov@nginx.com } nxt_py_asgi_http_t;
30*1624Smax.romanov@nginx.com 
31*1624Smax.romanov@nginx.com 
32*1624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_receive(PyObject *self, PyObject *none);
33*1624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_read_msg(nxt_py_asgi_http_t *http);
34*1624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_send(PyObject *self, PyObject *dict);
35*1624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_response_start(nxt_py_asgi_http_t *http,
36*1624Smax.romanov@nginx.com     PyObject *dict);
37*1624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http,
38*1624Smax.romanov@nginx.com     PyObject *dict);
39*1624Smax.romanov@nginx.com static PyObject *nxt_py_asgi_http_done(PyObject *self, PyObject *future);
40*1624Smax.romanov@nginx.com 
41*1624Smax.romanov@nginx.com 
42*1624Smax.romanov@nginx.com static PyMethodDef nxt_py_asgi_http_methods[] = {
43*1624Smax.romanov@nginx.com     { "receive",   nxt_py_asgi_http_receive, METH_NOARGS, 0 },
44*1624Smax.romanov@nginx.com     { "send",      nxt_py_asgi_http_send,    METH_O,      0 },
45*1624Smax.romanov@nginx.com     { "_done",     nxt_py_asgi_http_done,    METH_O,      0 },
46*1624Smax.romanov@nginx.com     { NULL, NULL, 0, 0 }
47*1624Smax.romanov@nginx.com };
48*1624Smax.romanov@nginx.com 
49*1624Smax.romanov@nginx.com static PyAsyncMethods nxt_py_asgi_async_methods = {
50*1624Smax.romanov@nginx.com     .am_await = nxt_py_asgi_await,
51*1624Smax.romanov@nginx.com };
52*1624Smax.romanov@nginx.com 
53*1624Smax.romanov@nginx.com static PyTypeObject nxt_py_asgi_http_type = {
54*1624Smax.romanov@nginx.com     PyVarObject_HEAD_INIT(NULL, 0)
55*1624Smax.romanov@nginx.com 
56*1624Smax.romanov@nginx.com     .tp_name      = "unit._asgi_http",
57*1624Smax.romanov@nginx.com     .tp_basicsize = sizeof(nxt_py_asgi_http_t),
58*1624Smax.romanov@nginx.com     .tp_dealloc   = nxt_py_asgi_dealloc,
59*1624Smax.romanov@nginx.com     .tp_as_async  = &nxt_py_asgi_async_methods,
60*1624Smax.romanov@nginx.com     .tp_flags     = Py_TPFLAGS_DEFAULT,
61*1624Smax.romanov@nginx.com     .tp_doc       = "unit ASGI HTTP request object",
62*1624Smax.romanov@nginx.com     .tp_iter      = nxt_py_asgi_iter,
63*1624Smax.romanov@nginx.com     .tp_iternext  = nxt_py_asgi_next,
64*1624Smax.romanov@nginx.com     .tp_methods   = nxt_py_asgi_http_methods,
65*1624Smax.romanov@nginx.com };
66*1624Smax.romanov@nginx.com 
67*1624Smax.romanov@nginx.com static Py_ssize_t  nxt_py_asgi_http_body_buf_size = 32 * 1024 * 1024;
68*1624Smax.romanov@nginx.com 
69*1624Smax.romanov@nginx.com 
70*1624Smax.romanov@nginx.com nxt_int_t
71*1624Smax.romanov@nginx.com nxt_py_asgi_http_init(nxt_task_t *task)
72*1624Smax.romanov@nginx.com {
73*1624Smax.romanov@nginx.com     if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_http_type) != 0)) {
74*1624Smax.romanov@nginx.com         nxt_alert(task, "Python failed to initialize the 'http' type object");
75*1624Smax.romanov@nginx.com         return NXT_ERROR;
76*1624Smax.romanov@nginx.com     }
77*1624Smax.romanov@nginx.com 
78*1624Smax.romanov@nginx.com     return NXT_OK;
79*1624Smax.romanov@nginx.com }
80*1624Smax.romanov@nginx.com 
81*1624Smax.romanov@nginx.com 
82*1624Smax.romanov@nginx.com PyObject *
83*1624Smax.romanov@nginx.com nxt_py_asgi_http_create(nxt_unit_request_info_t *req)
84*1624Smax.romanov@nginx.com {
85*1624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
86*1624Smax.romanov@nginx.com 
87*1624Smax.romanov@nginx.com     http = PyObject_New(nxt_py_asgi_http_t, &nxt_py_asgi_http_type);
88*1624Smax.romanov@nginx.com 
89*1624Smax.romanov@nginx.com     if (nxt_fast_path(http != NULL)) {
90*1624Smax.romanov@nginx.com         http->req = req;
91*1624Smax.romanov@nginx.com         http->receive_future = NULL;
92*1624Smax.romanov@nginx.com         http->send_future = NULL;
93*1624Smax.romanov@nginx.com         http->content_length = -1;
94*1624Smax.romanov@nginx.com         http->bytes_sent = 0;
95*1624Smax.romanov@nginx.com         http->complete = 0;
96*1624Smax.romanov@nginx.com         http->send_body = NULL;
97*1624Smax.romanov@nginx.com         http->send_body_off = 0;
98*1624Smax.romanov@nginx.com     }
99*1624Smax.romanov@nginx.com 
100*1624Smax.romanov@nginx.com     return (PyObject *) http;
101*1624Smax.romanov@nginx.com }
102*1624Smax.romanov@nginx.com 
103*1624Smax.romanov@nginx.com 
104*1624Smax.romanov@nginx.com static PyObject *
105*1624Smax.romanov@nginx.com nxt_py_asgi_http_receive(PyObject *self, PyObject *none)
106*1624Smax.romanov@nginx.com {
107*1624Smax.romanov@nginx.com     PyObject                 *msg, *future;
108*1624Smax.romanov@nginx.com     nxt_py_asgi_http_t       *http;
109*1624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
110*1624Smax.romanov@nginx.com 
111*1624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
112*1624Smax.romanov@nginx.com     req = http->req;
113*1624Smax.romanov@nginx.com 
114*1624Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_receive");
115*1624Smax.romanov@nginx.com 
116*1624Smax.romanov@nginx.com     msg = nxt_py_asgi_http_read_msg(http);
117*1624Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
118*1624Smax.romanov@nginx.com         return NULL;
119*1624Smax.romanov@nginx.com     }
120*1624Smax.romanov@nginx.com 
121*1624Smax.romanov@nginx.com     future = PyObject_CallObject(nxt_py_loop_create_future, NULL);
122*1624Smax.romanov@nginx.com     if (nxt_slow_path(future == NULL)) {
123*1624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "Python failed to create Future object");
124*1624Smax.romanov@nginx.com         nxt_python_print_exception();
125*1624Smax.romanov@nginx.com 
126*1624Smax.romanov@nginx.com         Py_DECREF(msg);
127*1624Smax.romanov@nginx.com 
128*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
129*1624Smax.romanov@nginx.com                             "failed to create Future object");
130*1624Smax.romanov@nginx.com     }
131*1624Smax.romanov@nginx.com 
132*1624Smax.romanov@nginx.com     if (msg != Py_None) {
133*1624Smax.romanov@nginx.com         return nxt_py_asgi_set_result_soon(req, future, msg);
134*1624Smax.romanov@nginx.com     }
135*1624Smax.romanov@nginx.com 
136*1624Smax.romanov@nginx.com     http->receive_future = future;
137*1624Smax.romanov@nginx.com     Py_INCREF(http->receive_future);
138*1624Smax.romanov@nginx.com 
139*1624Smax.romanov@nginx.com     Py_DECREF(msg);
140*1624Smax.romanov@nginx.com 
141*1624Smax.romanov@nginx.com     return future;
142*1624Smax.romanov@nginx.com }
143*1624Smax.romanov@nginx.com 
144*1624Smax.romanov@nginx.com 
145*1624Smax.romanov@nginx.com static PyObject *
146*1624Smax.romanov@nginx.com nxt_py_asgi_http_read_msg(nxt_py_asgi_http_t *http)
147*1624Smax.romanov@nginx.com {
148*1624Smax.romanov@nginx.com     char                     *body_buf;
149*1624Smax.romanov@nginx.com     ssize_t                  read_res;
150*1624Smax.romanov@nginx.com     PyObject                 *msg, *body;
151*1624Smax.romanov@nginx.com     Py_ssize_t               size;
152*1624Smax.romanov@nginx.com     nxt_unit_request_info_t  *req;
153*1624Smax.romanov@nginx.com 
154*1624Smax.romanov@nginx.com     req = http->req;
155*1624Smax.romanov@nginx.com 
156*1624Smax.romanov@nginx.com     size = req->content_length;
157*1624Smax.romanov@nginx.com 
158*1624Smax.romanov@nginx.com     if (size > nxt_py_asgi_http_body_buf_size) {
159*1624Smax.romanov@nginx.com         size = nxt_py_asgi_http_body_buf_size;
160*1624Smax.romanov@nginx.com     }
161*1624Smax.romanov@nginx.com 
162*1624Smax.romanov@nginx.com     if (size > 0) {
163*1624Smax.romanov@nginx.com         body = PyBytes_FromStringAndSize(NULL, size);
164*1624Smax.romanov@nginx.com         if (nxt_slow_path(body == NULL)) {
165*1624Smax.romanov@nginx.com             nxt_unit_req_alert(req, "Python failed to create body byte string");
166*1624Smax.romanov@nginx.com             nxt_python_print_exception();
167*1624Smax.romanov@nginx.com 
168*1624Smax.romanov@nginx.com             return PyErr_Format(PyExc_RuntimeError,
169*1624Smax.romanov@nginx.com                                 "failed to create Bytes object");
170*1624Smax.romanov@nginx.com         }
171*1624Smax.romanov@nginx.com 
172*1624Smax.romanov@nginx.com         body_buf = PyBytes_AS_STRING(body);
173*1624Smax.romanov@nginx.com 
174*1624Smax.romanov@nginx.com         read_res = nxt_unit_request_read(req, body_buf, size);
175*1624Smax.romanov@nginx.com 
176*1624Smax.romanov@nginx.com     } else {
177*1624Smax.romanov@nginx.com         body = NULL;
178*1624Smax.romanov@nginx.com         read_res = 0;
179*1624Smax.romanov@nginx.com     }
180*1624Smax.romanov@nginx.com 
181*1624Smax.romanov@nginx.com     if (read_res > 0 || read_res == size) {
182*1624Smax.romanov@nginx.com         msg = nxt_py_asgi_new_msg(req, nxt_py_http_request_str);
183*1624Smax.romanov@nginx.com         if (nxt_slow_path(msg == NULL)) {
184*1624Smax.romanov@nginx.com             Py_XDECREF(body);
185*1624Smax.romanov@nginx.com 
186*1624Smax.romanov@nginx.com             return NULL;
187*1624Smax.romanov@nginx.com         }
188*1624Smax.romanov@nginx.com 
189*1624Smax.romanov@nginx.com #define SET_ITEM(dict, key, value) \
190*1624Smax.romanov@nginx.com     if (nxt_slow_path(PyDict_SetItem(dict, nxt_py_ ## key ## _str, value)      \
191*1624Smax.romanov@nginx.com                         == -1))                                                \
192*1624Smax.romanov@nginx.com     {                                                                          \
193*1624Smax.romanov@nginx.com         nxt_unit_req_alert(req,                                                \
194*1624Smax.romanov@nginx.com                            "Python failed to set '" #dict "." #key "' item");  \
195*1624Smax.romanov@nginx.com         PyErr_SetString(PyExc_RuntimeError,                                    \
196*1624Smax.romanov@nginx.com                         "Python failed to set '" #dict "." #key "' item");     \
197*1624Smax.romanov@nginx.com         goto fail;                                                             \
198*1624Smax.romanov@nginx.com     }
199*1624Smax.romanov@nginx.com 
200*1624Smax.romanov@nginx.com         if (body != NULL) {
201*1624Smax.romanov@nginx.com             SET_ITEM(msg, body, body)
202*1624Smax.romanov@nginx.com         }
203*1624Smax.romanov@nginx.com 
204*1624Smax.romanov@nginx.com         if (req->content_length > 0) {
205*1624Smax.romanov@nginx.com             SET_ITEM(msg, more_body, Py_True)
206*1624Smax.romanov@nginx.com         }
207*1624Smax.romanov@nginx.com 
208*1624Smax.romanov@nginx.com #undef SET_ITEM
209*1624Smax.romanov@nginx.com 
210*1624Smax.romanov@nginx.com         Py_XDECREF(body);
211*1624Smax.romanov@nginx.com 
212*1624Smax.romanov@nginx.com         return msg;
213*1624Smax.romanov@nginx.com     }
214*1624Smax.romanov@nginx.com 
215*1624Smax.romanov@nginx.com     Py_XDECREF(body);
216*1624Smax.romanov@nginx.com 
217*1624Smax.romanov@nginx.com     Py_RETURN_NONE;
218*1624Smax.romanov@nginx.com 
219*1624Smax.romanov@nginx.com fail:
220*1624Smax.romanov@nginx.com 
221*1624Smax.romanov@nginx.com     Py_DECREF(msg);
222*1624Smax.romanov@nginx.com     Py_XDECREF(body);
223*1624Smax.romanov@nginx.com 
224*1624Smax.romanov@nginx.com     return NULL;
225*1624Smax.romanov@nginx.com }
226*1624Smax.romanov@nginx.com 
227*1624Smax.romanov@nginx.com 
228*1624Smax.romanov@nginx.com static PyObject *
229*1624Smax.romanov@nginx.com nxt_py_asgi_http_send(PyObject *self, PyObject *dict)
230*1624Smax.romanov@nginx.com {
231*1624Smax.romanov@nginx.com     PyObject            *type;
232*1624Smax.romanov@nginx.com     const char          *type_str;
233*1624Smax.romanov@nginx.com     Py_ssize_t          type_len;
234*1624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
235*1624Smax.romanov@nginx.com 
236*1624Smax.romanov@nginx.com     static const nxt_str_t  response_start = nxt_string("http.response.start");
237*1624Smax.romanov@nginx.com     static const nxt_str_t  response_body = nxt_string("http.response.body");
238*1624Smax.romanov@nginx.com 
239*1624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
240*1624Smax.romanov@nginx.com 
241*1624Smax.romanov@nginx.com     type = PyDict_GetItem(dict, nxt_py_type_str);
242*1624Smax.romanov@nginx.com     if (nxt_slow_path(type == NULL || !PyUnicode_Check(type))) {
243*1624Smax.romanov@nginx.com         nxt_unit_req_error(http->req, "asgi_http_send: "
244*1624Smax.romanov@nginx.com                                       "'type' is not a unicode string");
245*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'type' is not a unicode string");
246*1624Smax.romanov@nginx.com     }
247*1624Smax.romanov@nginx.com 
248*1624Smax.romanov@nginx.com     type_str = PyUnicode_AsUTF8AndSize(type, &type_len);
249*1624Smax.romanov@nginx.com 
250*1624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_send type is '%.*s'",
251*1624Smax.romanov@nginx.com                        (int) type_len, type_str);
252*1624Smax.romanov@nginx.com 
253*1624Smax.romanov@nginx.com     if (type_len == (Py_ssize_t) response_start.length
254*1624Smax.romanov@nginx.com         && memcmp(type_str, response_start.start, type_len) == 0)
255*1624Smax.romanov@nginx.com     {
256*1624Smax.romanov@nginx.com         return nxt_py_asgi_http_response_start(http, dict);
257*1624Smax.romanov@nginx.com     }
258*1624Smax.romanov@nginx.com 
259*1624Smax.romanov@nginx.com     if (type_len == (Py_ssize_t) response_body.length
260*1624Smax.romanov@nginx.com         && memcmp(type_str, response_body.start, type_len) == 0)
261*1624Smax.romanov@nginx.com     {
262*1624Smax.romanov@nginx.com         return nxt_py_asgi_http_response_body(http, dict);
263*1624Smax.romanov@nginx.com     }
264*1624Smax.romanov@nginx.com 
265*1624Smax.romanov@nginx.com     nxt_unit_req_error(http->req, "asgi_http_send: unexpected 'type': '%.*s'",
266*1624Smax.romanov@nginx.com                        (int) type_len, type_str);
267*1624Smax.romanov@nginx.com 
268*1624Smax.romanov@nginx.com     return PyErr_Format(PyExc_AssertionError, "unexpected 'type': '%U'", type);
269*1624Smax.romanov@nginx.com }
270*1624Smax.romanov@nginx.com 
271*1624Smax.romanov@nginx.com 
272*1624Smax.romanov@nginx.com static PyObject *
273*1624Smax.romanov@nginx.com nxt_py_asgi_http_response_start(nxt_py_asgi_http_t *http, PyObject *dict)
274*1624Smax.romanov@nginx.com {
275*1624Smax.romanov@nginx.com     int                          rc;
276*1624Smax.romanov@nginx.com     PyObject                     *status, *headers, *res;
277*1624Smax.romanov@nginx.com     nxt_py_asgi_calc_size_ctx_t  calc_size_ctx;
278*1624Smax.romanov@nginx.com     nxt_py_asgi_add_field_ctx_t  add_field_ctx;
279*1624Smax.romanov@nginx.com 
280*1624Smax.romanov@nginx.com     status = PyDict_GetItem(dict, nxt_py_status_str);
281*1624Smax.romanov@nginx.com     if (nxt_slow_path(status == NULL || !PyLong_Check(status))) {
282*1624Smax.romanov@nginx.com         nxt_unit_req_error(http->req, "asgi_http_response_start: "
283*1624Smax.romanov@nginx.com                                       "'status' is not an integer");
284*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'status' is not an integer");
285*1624Smax.romanov@nginx.com     }
286*1624Smax.romanov@nginx.com 
287*1624Smax.romanov@nginx.com     calc_size_ctx.fields_size = 0;
288*1624Smax.romanov@nginx.com     calc_size_ctx.fields_count = 0;
289*1624Smax.romanov@nginx.com 
290*1624Smax.romanov@nginx.com     headers = PyDict_GetItem(dict, nxt_py_headers_str);
291*1624Smax.romanov@nginx.com     if (headers != NULL) {
292*1624Smax.romanov@nginx.com         res = nxt_py_asgi_enum_headers(headers, nxt_py_asgi_calc_size,
293*1624Smax.romanov@nginx.com                                        &calc_size_ctx);
294*1624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
295*1624Smax.romanov@nginx.com             return NULL;
296*1624Smax.romanov@nginx.com         }
297*1624Smax.romanov@nginx.com 
298*1624Smax.romanov@nginx.com         Py_DECREF(res);
299*1624Smax.romanov@nginx.com     }
300*1624Smax.romanov@nginx.com 
301*1624Smax.romanov@nginx.com     rc = nxt_unit_response_init(http->req, PyLong_AsLong(status),
302*1624Smax.romanov@nginx.com                                 calc_size_ctx.fields_count,
303*1624Smax.romanov@nginx.com                                 calc_size_ctx.fields_size);
304*1624Smax.romanov@nginx.com     if (nxt_slow_path(rc != NXT_UNIT_OK)) {
305*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
306*1624Smax.romanov@nginx.com                             "failed to allocate response object");
307*1624Smax.romanov@nginx.com     }
308*1624Smax.romanov@nginx.com 
309*1624Smax.romanov@nginx.com     add_field_ctx.req = http->req;
310*1624Smax.romanov@nginx.com     add_field_ctx.content_length = -1;
311*1624Smax.romanov@nginx.com 
312*1624Smax.romanov@nginx.com     if (headers != NULL) {
313*1624Smax.romanov@nginx.com         res = nxt_py_asgi_enum_headers(headers, nxt_py_asgi_add_field,
314*1624Smax.romanov@nginx.com                                        &add_field_ctx);
315*1624Smax.romanov@nginx.com         if (nxt_slow_path(res == NULL)) {
316*1624Smax.romanov@nginx.com             return NULL;
317*1624Smax.romanov@nginx.com         }
318*1624Smax.romanov@nginx.com 
319*1624Smax.romanov@nginx.com         Py_DECREF(res);
320*1624Smax.romanov@nginx.com     }
321*1624Smax.romanov@nginx.com 
322*1624Smax.romanov@nginx.com     http->content_length = add_field_ctx.content_length;
323*1624Smax.romanov@nginx.com 
324*1624Smax.romanov@nginx.com     Py_INCREF(http);
325*1624Smax.romanov@nginx.com     return (PyObject *) http;
326*1624Smax.romanov@nginx.com }
327*1624Smax.romanov@nginx.com 
328*1624Smax.romanov@nginx.com 
329*1624Smax.romanov@nginx.com static PyObject *
330*1624Smax.romanov@nginx.com nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict)
331*1624Smax.romanov@nginx.com {
332*1624Smax.romanov@nginx.com     int         rc;
333*1624Smax.romanov@nginx.com     char        *body_str;
334*1624Smax.romanov@nginx.com     ssize_t     sent;
335*1624Smax.romanov@nginx.com     PyObject    *body, *more_body, *future;
336*1624Smax.romanov@nginx.com     Py_ssize_t  body_len, body_off;
337*1624Smax.romanov@nginx.com 
338*1624Smax.romanov@nginx.com     body = PyDict_GetItem(dict, nxt_py_body_str);
339*1624Smax.romanov@nginx.com     if (nxt_slow_path(body != NULL && !PyBytes_Check(body))) {
340*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'body' is not a byte string");
341*1624Smax.romanov@nginx.com     }
342*1624Smax.romanov@nginx.com 
343*1624Smax.romanov@nginx.com     more_body = PyDict_GetItem(dict, nxt_py_more_body_str);
344*1624Smax.romanov@nginx.com     if (nxt_slow_path(more_body != NULL && !PyBool_Check(more_body))) {
345*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_TypeError, "'more_body' is not a bool");
346*1624Smax.romanov@nginx.com     }
347*1624Smax.romanov@nginx.com 
348*1624Smax.romanov@nginx.com     if (nxt_slow_path(http->complete)) {
349*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError,
350*1624Smax.romanov@nginx.com                             "Unexpected ASGI message 'http.response.body' "
351*1624Smax.romanov@nginx.com                             "sent, after response already completed");
352*1624Smax.romanov@nginx.com     }
353*1624Smax.romanov@nginx.com 
354*1624Smax.romanov@nginx.com     if (nxt_slow_path(http->send_future != NULL)) {
355*1624Smax.romanov@nginx.com         return PyErr_Format(PyExc_RuntimeError, "Concurrent send");
356*1624Smax.romanov@nginx.com     }
357*1624Smax.romanov@nginx.com 
358*1624Smax.romanov@nginx.com     if (body != NULL) {
359*1624Smax.romanov@nginx.com         body_str = PyBytes_AS_STRING(body);
360*1624Smax.romanov@nginx.com         body_len = PyBytes_GET_SIZE(body);
361*1624Smax.romanov@nginx.com 
362*1624Smax.romanov@nginx.com         nxt_unit_req_debug(http->req, "asgi_http_response_body: %d, %d",
363*1624Smax.romanov@nginx.com                            (int) body_len, (more_body == Py_True) );
364*1624Smax.romanov@nginx.com 
365*1624Smax.romanov@nginx.com         if (nxt_slow_path(http->bytes_sent + body_len
366*1624Smax.romanov@nginx.com                               > http->content_length))
367*1624Smax.romanov@nginx.com         {
368*1624Smax.romanov@nginx.com             return PyErr_Format(PyExc_RuntimeError,
369*1624Smax.romanov@nginx.com                                 "Response content longer than Content-Length");
370*1624Smax.romanov@nginx.com         }
371*1624Smax.romanov@nginx.com 
372*1624Smax.romanov@nginx.com         body_off = 0;
373*1624Smax.romanov@nginx.com 
374*1624Smax.romanov@nginx.com         while (body_len > 0) {
375*1624Smax.romanov@nginx.com             sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
376*1624Smax.romanov@nginx.com             if (nxt_slow_path(sent < 0)) {
377*1624Smax.romanov@nginx.com                 return PyErr_Format(PyExc_RuntimeError, "failed to send body");
378*1624Smax.romanov@nginx.com             }
379*1624Smax.romanov@nginx.com 
380*1624Smax.romanov@nginx.com             if (nxt_slow_path(sent == 0)) {
381*1624Smax.romanov@nginx.com                 nxt_unit_req_debug(http->req, "asgi_http_response_body: "
382*1624Smax.romanov@nginx.com                                    "out of shared memory, %d",
383*1624Smax.romanov@nginx.com                                    (int) body_len);
384*1624Smax.romanov@nginx.com 
385*1624Smax.romanov@nginx.com                 future = PyObject_CallObject(nxt_py_loop_create_future, NULL);
386*1624Smax.romanov@nginx.com                 if (nxt_slow_path(future == NULL)) {
387*1624Smax.romanov@nginx.com                     nxt_unit_req_alert(http->req,
388*1624Smax.romanov@nginx.com                                        "Python failed to create Future object");
389*1624Smax.romanov@nginx.com                     nxt_python_print_exception();
390*1624Smax.romanov@nginx.com 
391*1624Smax.romanov@nginx.com                     return PyErr_Format(PyExc_RuntimeError,
392*1624Smax.romanov@nginx.com                                         "failed to create Future object");
393*1624Smax.romanov@nginx.com                 }
394*1624Smax.romanov@nginx.com 
395*1624Smax.romanov@nginx.com                 http->send_body = body;
396*1624Smax.romanov@nginx.com                 Py_INCREF(http->send_body);
397*1624Smax.romanov@nginx.com                 http->send_body_off = body_off;
398*1624Smax.romanov@nginx.com 
399*1624Smax.romanov@nginx.com                 nxt_queue_insert_tail(&nxt_py_asgi_drain_queue, &http->link);
400*1624Smax.romanov@nginx.com 
401*1624Smax.romanov@nginx.com                 http->send_future = future;
402*1624Smax.romanov@nginx.com                 Py_INCREF(http->send_future);
403*1624Smax.romanov@nginx.com 
404*1624Smax.romanov@nginx.com                 return future;
405*1624Smax.romanov@nginx.com             }
406*1624Smax.romanov@nginx.com 
407*1624Smax.romanov@nginx.com             body_str += sent;
408*1624Smax.romanov@nginx.com             body_len -= sent;
409*1624Smax.romanov@nginx.com             body_off += sent;
410*1624Smax.romanov@nginx.com             http->bytes_sent += sent;
411*1624Smax.romanov@nginx.com         }
412*1624Smax.romanov@nginx.com 
413*1624Smax.romanov@nginx.com     } else {
414*1624Smax.romanov@nginx.com         nxt_unit_req_debug(http->req, "asgi_http_response_body: 0, %d",
415*1624Smax.romanov@nginx.com                            (more_body == Py_True) );
416*1624Smax.romanov@nginx.com 
417*1624Smax.romanov@nginx.com         if (!nxt_unit_response_is_sent(http->req)) {
418*1624Smax.romanov@nginx.com             rc = nxt_unit_response_send(http->req);
419*1624Smax.romanov@nginx.com             if (nxt_slow_path(rc != NXT_UNIT_OK)) {
420*1624Smax.romanov@nginx.com                 return PyErr_Format(PyExc_RuntimeError,
421*1624Smax.romanov@nginx.com                                     "failed to send response");
422*1624Smax.romanov@nginx.com             }
423*1624Smax.romanov@nginx.com         }
424*1624Smax.romanov@nginx.com     }
425*1624Smax.romanov@nginx.com 
426*1624Smax.romanov@nginx.com     if (more_body == NULL || more_body == Py_False) {
427*1624Smax.romanov@nginx.com         http->complete = 1;
428*1624Smax.romanov@nginx.com     }
429*1624Smax.romanov@nginx.com 
430*1624Smax.romanov@nginx.com     Py_INCREF(http);
431*1624Smax.romanov@nginx.com     return (PyObject *) http;
432*1624Smax.romanov@nginx.com }
433*1624Smax.romanov@nginx.com 
434*1624Smax.romanov@nginx.com 
435*1624Smax.romanov@nginx.com void
436*1624Smax.romanov@nginx.com nxt_py_asgi_http_data_handler(nxt_unit_request_info_t *req)
437*1624Smax.romanov@nginx.com {
438*1624Smax.romanov@nginx.com     PyObject            *msg, *future, *res;
439*1624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
440*1624Smax.romanov@nginx.com 
441*1624Smax.romanov@nginx.com     http = req->data;
442*1624Smax.romanov@nginx.com 
443*1624Smax.romanov@nginx.com     nxt_unit_req_debug(req, "asgi_http_data_handler");
444*1624Smax.romanov@nginx.com 
445*1624Smax.romanov@nginx.com     if (http->receive_future == NULL) {
446*1624Smax.romanov@nginx.com         return;
447*1624Smax.romanov@nginx.com     }
448*1624Smax.romanov@nginx.com 
449*1624Smax.romanov@nginx.com     msg = nxt_py_asgi_http_read_msg(http);
450*1624Smax.romanov@nginx.com     if (nxt_slow_path(msg == NULL)) {
451*1624Smax.romanov@nginx.com         return;
452*1624Smax.romanov@nginx.com     }
453*1624Smax.romanov@nginx.com 
454*1624Smax.romanov@nginx.com     if (msg == Py_None) {
455*1624Smax.romanov@nginx.com         Py_DECREF(msg);
456*1624Smax.romanov@nginx.com         return;
457*1624Smax.romanov@nginx.com     }
458*1624Smax.romanov@nginx.com 
459*1624Smax.romanov@nginx.com     future = http->receive_future;
460*1624Smax.romanov@nginx.com     http->receive_future = NULL;
461*1624Smax.romanov@nginx.com 
462*1624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, msg, NULL);
463*1624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
464*1624Smax.romanov@nginx.com         nxt_unit_req_alert(req, "'set_result' call failed");
465*1624Smax.romanov@nginx.com         nxt_python_print_exception();
466*1624Smax.romanov@nginx.com     }
467*1624Smax.romanov@nginx.com 
468*1624Smax.romanov@nginx.com     Py_XDECREF(res);
469*1624Smax.romanov@nginx.com     Py_DECREF(future);
470*1624Smax.romanov@nginx.com 
471*1624Smax.romanov@nginx.com     Py_DECREF(msg);
472*1624Smax.romanov@nginx.com }
473*1624Smax.romanov@nginx.com 
474*1624Smax.romanov@nginx.com 
475*1624Smax.romanov@nginx.com int
476*1624Smax.romanov@nginx.com nxt_py_asgi_http_drain(nxt_queue_link_t *lnk)
477*1624Smax.romanov@nginx.com {
478*1624Smax.romanov@nginx.com     char                *body_str;
479*1624Smax.romanov@nginx.com     ssize_t             sent;
480*1624Smax.romanov@nginx.com     PyObject            *future, *exc, *res;
481*1624Smax.romanov@nginx.com     Py_ssize_t          body_len;
482*1624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
483*1624Smax.romanov@nginx.com 
484*1624Smax.romanov@nginx.com     http = nxt_container_of(lnk, nxt_py_asgi_http_t, link);
485*1624Smax.romanov@nginx.com 
486*1624Smax.romanov@nginx.com     body_str = PyBytes_AS_STRING(http->send_body) + http->send_body_off;
487*1624Smax.romanov@nginx.com     body_len = PyBytes_GET_SIZE(http->send_body) - http->send_body_off;
488*1624Smax.romanov@nginx.com 
489*1624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_drain: %d", (int) body_len);
490*1624Smax.romanov@nginx.com 
491*1624Smax.romanov@nginx.com     while (body_len > 0) {
492*1624Smax.romanov@nginx.com         sent = nxt_unit_response_write_nb(http->req, body_str, body_len, 0);
493*1624Smax.romanov@nginx.com         if (nxt_slow_path(sent < 0)) {
494*1624Smax.romanov@nginx.com             goto fail;
495*1624Smax.romanov@nginx.com         }
496*1624Smax.romanov@nginx.com 
497*1624Smax.romanov@nginx.com         if (nxt_slow_path(sent == 0)) {
498*1624Smax.romanov@nginx.com             return NXT_UNIT_AGAIN;
499*1624Smax.romanov@nginx.com         }
500*1624Smax.romanov@nginx.com 
501*1624Smax.romanov@nginx.com         body_str += sent;
502*1624Smax.romanov@nginx.com         body_len -= sent;
503*1624Smax.romanov@nginx.com 
504*1624Smax.romanov@nginx.com         http->send_body_off += sent;
505*1624Smax.romanov@nginx.com         http->bytes_sent += sent;
506*1624Smax.romanov@nginx.com     }
507*1624Smax.romanov@nginx.com 
508*1624Smax.romanov@nginx.com     Py_CLEAR(http->send_body);
509*1624Smax.romanov@nginx.com 
510*1624Smax.romanov@nginx.com     future = http->send_future;
511*1624Smax.romanov@nginx.com     http->send_future = NULL;
512*1624Smax.romanov@nginx.com 
513*1624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str, Py_None,
514*1624Smax.romanov@nginx.com                                      NULL);
515*1624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
516*1624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "'set_result' call failed");
517*1624Smax.romanov@nginx.com         nxt_python_print_exception();
518*1624Smax.romanov@nginx.com     }
519*1624Smax.romanov@nginx.com 
520*1624Smax.romanov@nginx.com     Py_XDECREF(res);
521*1624Smax.romanov@nginx.com     Py_DECREF(future);
522*1624Smax.romanov@nginx.com 
523*1624Smax.romanov@nginx.com     return NXT_UNIT_OK;
524*1624Smax.romanov@nginx.com 
525*1624Smax.romanov@nginx.com fail:
526*1624Smax.romanov@nginx.com 
527*1624Smax.romanov@nginx.com     exc = PyObject_CallFunctionObjArgs(PyExc_RuntimeError,
528*1624Smax.romanov@nginx.com                                        nxt_py_failed_to_send_body_str,
529*1624Smax.romanov@nginx.com                                        NULL);
530*1624Smax.romanov@nginx.com     if (nxt_slow_path(exc == NULL)) {
531*1624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "RuntimeError create failed");
532*1624Smax.romanov@nginx.com         nxt_python_print_exception();
533*1624Smax.romanov@nginx.com 
534*1624Smax.romanov@nginx.com         exc = Py_None;
535*1624Smax.romanov@nginx.com         Py_INCREF(exc);
536*1624Smax.romanov@nginx.com     }
537*1624Smax.romanov@nginx.com 
538*1624Smax.romanov@nginx.com     future = http->send_future;
539*1624Smax.romanov@nginx.com     http->send_future = NULL;
540*1624Smax.romanov@nginx.com 
541*1624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_set_exception_str, exc,
542*1624Smax.romanov@nginx.com                                      NULL);
543*1624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
544*1624Smax.romanov@nginx.com         nxt_unit_req_alert(http->req, "'set_exception' call failed");
545*1624Smax.romanov@nginx.com         nxt_python_print_exception();
546*1624Smax.romanov@nginx.com     }
547*1624Smax.romanov@nginx.com 
548*1624Smax.romanov@nginx.com     Py_XDECREF(res);
549*1624Smax.romanov@nginx.com     Py_DECREF(future);
550*1624Smax.romanov@nginx.com     Py_DECREF(exc);
551*1624Smax.romanov@nginx.com 
552*1624Smax.romanov@nginx.com     return NXT_UNIT_ERROR;
553*1624Smax.romanov@nginx.com }
554*1624Smax.romanov@nginx.com 
555*1624Smax.romanov@nginx.com 
556*1624Smax.romanov@nginx.com static PyObject *
557*1624Smax.romanov@nginx.com nxt_py_asgi_http_done(PyObject *self, PyObject *future)
558*1624Smax.romanov@nginx.com {
559*1624Smax.romanov@nginx.com     int                 rc;
560*1624Smax.romanov@nginx.com     PyObject            *res;
561*1624Smax.romanov@nginx.com     nxt_py_asgi_http_t  *http;
562*1624Smax.romanov@nginx.com 
563*1624Smax.romanov@nginx.com     http = (nxt_py_asgi_http_t *) self;
564*1624Smax.romanov@nginx.com 
565*1624Smax.romanov@nginx.com     nxt_unit_req_debug(http->req, "asgi_http_done");
566*1624Smax.romanov@nginx.com 
567*1624Smax.romanov@nginx.com     /*
568*1624Smax.romanov@nginx.com      * Get Future.result() and it raises an exception, if coroutine exited
569*1624Smax.romanov@nginx.com      * with exception.
570*1624Smax.romanov@nginx.com      */
571*1624Smax.romanov@nginx.com     res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL);
572*1624Smax.romanov@nginx.com     if (nxt_slow_path(res == NULL)) {
573*1624Smax.romanov@nginx.com         nxt_unit_req_error(http->req,
574*1624Smax.romanov@nginx.com                            "Python failed to call 'future.result()'");
575*1624Smax.romanov@nginx.com         nxt_python_print_exception();
576*1624Smax.romanov@nginx.com 
577*1624Smax.romanov@nginx.com         rc = NXT_UNIT_ERROR;
578*1624Smax.romanov@nginx.com 
579*1624Smax.romanov@nginx.com     } else {
580*1624Smax.romanov@nginx.com         Py_DECREF(res);
581*1624Smax.romanov@nginx.com 
582*1624Smax.romanov@nginx.com         rc = NXT_UNIT_OK;
583*1624Smax.romanov@nginx.com     }
584*1624Smax.romanov@nginx.com 
585*1624Smax.romanov@nginx.com     nxt_unit_request_done(http->req, rc);
586*1624Smax.romanov@nginx.com 
587*1624Smax.romanov@nginx.com     Py_RETURN_NONE;
588*1624Smax.romanov@nginx.com }
589*1624Smax.romanov@nginx.com 
590*1624Smax.romanov@nginx.com 
591*1624Smax.romanov@nginx.com #endif /* NXT_HAVE_ASGI */
592