1
2 /*
3 * Copyright (C) NGINX, Inc.
4 */
5
6
7 #include <python/nxt_python.h>
8
9 #if (NXT_HAVE_ASGI)
10
11 #include <nxt_main.h>
12 #include <python/nxt_python_asgi.h>
13 #include <python/nxt_python_asgi_str.h>
14
15 #include <structmember.h>
16
17
18 typedef struct {
19 PyObject_HEAD
20 nxt_py_asgi_ctx_data_t *ctx_data;
21 int disabled;
22 int startup_received;
23 int startup_sent;
24 int shutdown_received;
25 int shutdown_sent;
26 int shutdown_called;
27 PyObject *startup_future;
28 PyObject *shutdown_future;
29 PyObject *receive_future;
30 PyObject *state;
31 } nxt_py_asgi_lifespan_t;
32
33 static PyObject *nxt_py_asgi_lifespan_target_startup(
34 nxt_py_asgi_ctx_data_t *ctx_data, nxt_python_target_t *target);
35 static int nxt_py_asgi_lifespan_target_shutdown(
36 nxt_py_asgi_lifespan_t *lifespan);
37 static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none);
38 static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict);
39 static PyObject *nxt_py_asgi_lifespan_send_startup(
40 nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict);
41 static PyObject *nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan,
42 int v, int *sent, PyObject **future);
43 static PyObject *nxt_py_asgi_lifespan_send_shutdown(
44 nxt_py_asgi_lifespan_t *lifespan, int v, PyObject *dict);
45 static PyObject *nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan);
46 static PyObject *nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future);
47 static void nxt_py_asgi_lifespan_dealloc(PyObject *self);
48
49
50 static PyMethodDef nxt_py_asgi_lifespan_methods[] = {
51 { "receive", nxt_py_asgi_lifespan_receive, METH_NOARGS, 0 },
52 { "send", nxt_py_asgi_lifespan_send, METH_O, 0 },
53 { "_done", nxt_py_asgi_lifespan_done, METH_O, 0 },
54 { NULL, NULL, 0, 0 }
55 };
56
57 static PyMemberDef nxt_py_asgi_lifespan_members[] = {
58 {
59 #if PY_VERSION_HEX >= NXT_PYTHON_VER(3, 7)
60 .name = "state",
61 #else
62 .name = (char *)"state",
63 #endif
64 .type = T_OBJECT_EX,
65 .offset = offsetof(nxt_py_asgi_lifespan_t, state),
66 .flags = READONLY,
67 #if PY_VERSION_HEX >= NXT_PYTHON_VER(3, 7)
68 .doc = PyDoc_STR("lifespan.state")
69 #else
70 .doc = (char *)PyDoc_STR("lifespan.state")
71 #endif
72 },
73
74 { NULL, 0, 0, 0, NULL }
75 };
76
77 static PyAsyncMethods nxt_py_asgi_async_methods = {
78 .am_await = nxt_py_asgi_await,
79 };
80
81 static PyTypeObject nxt_py_asgi_lifespan_type = {
82 PyVarObject_HEAD_INIT(NULL, 0)
83
84 .tp_name = "unit._asgi_lifespan",
85 .tp_basicsize = sizeof(nxt_py_asgi_lifespan_t),
86 .tp_dealloc = nxt_py_asgi_lifespan_dealloc,
87 .tp_as_async = &nxt_py_asgi_async_methods,
88 .tp_flags = Py_TPFLAGS_DEFAULT,
89 .tp_doc = "unit ASGI Lifespan object",
90 .tp_iter = nxt_py_asgi_iter,
91 .tp_iternext = nxt_py_asgi_next,
92 .tp_methods = nxt_py_asgi_lifespan_methods,
93 .tp_members = nxt_py_asgi_lifespan_members,
94 };
95
96
97 int
nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t * ctx_data)98 nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data)
99 {
100 size_t size;
101 PyObject *lifespan;
102 PyObject **target_lifespans;
103 nxt_int_t i;
104 nxt_python_target_t *target;
105
106 size = nxt_py_targets->count * sizeof(PyObject*);
107
108 target_lifespans = nxt_unit_malloc(NULL, size);
109 if (nxt_slow_path(target_lifespans == NULL)) {
110 nxt_unit_alert(NULL, "Failed to allocate lifespan data");
111 return NXT_UNIT_ERROR;
112 }
113
114 memset(target_lifespans, 0, size);
115
116 for (i = 0; i < nxt_py_targets->count; i++) {
117 target = &nxt_py_targets->target[i];
118
119 lifespan = nxt_py_asgi_lifespan_target_startup(ctx_data, target);
120 if (nxt_slow_path(lifespan == NULL)) {
121 return NXT_UNIT_ERROR;
122 }
123
124 target_lifespans[i] = lifespan;
125 }
126
127 ctx_data->target_lifespans = target_lifespans;
128
129 return NXT_UNIT_OK;
130 }
131
132
133 static PyObject *
nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t * ctx_data,nxt_python_target_t * target)134 nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data,
135 nxt_python_target_t *target)
136 {
137 PyObject *scope, *res, *py_task, *receive, *send, *done;
138 PyObject *stage2;
139 nxt_py_asgi_lifespan_t *lifespan, *ret;
140
141 if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) {
142 nxt_unit_alert(NULL,
143 "Python failed to initialize the 'asgi_lifespan' type object");
144 return NULL;
145 }
146
147 lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type);
148 if (nxt_slow_path(lifespan == NULL)) {
149 nxt_unit_alert(NULL, "Python failed to create lifespan object");
150 return NULL;
151 }
152
153 ret = NULL;
154
155 receive = PyObject_GetAttrString((PyObject *) lifespan, "receive");
156 if (nxt_slow_path(receive == NULL)) {
157 nxt_unit_alert(NULL, "Python failed to get 'receive' method");
158 goto release_lifespan;
159 }
160
161 send = PyObject_GetAttrString((PyObject *) lifespan, "send");
162 if (nxt_slow_path(receive == NULL)) {
163 nxt_unit_alert(NULL, "Python failed to get 'send' method");
164 goto release_receive;
165 }
166
167 done = PyObject_GetAttrString((PyObject *) lifespan, "_done");
168 if (nxt_slow_path(receive == NULL)) {
169 nxt_unit_alert(NULL, "Python failed to get '_done' method");
170 goto release_send;
171 }
172
173 lifespan->startup_future = PyObject_CallObject(ctx_data->loop_create_future,
174 NULL);
175 if (nxt_slow_path(lifespan->startup_future == NULL)) {
176 nxt_unit_alert(NULL, "Python failed to create Future object");
177 nxt_python_print_exception();
178
179 goto release_done;
180 }
181
182 lifespan->ctx_data = ctx_data;
183 lifespan->disabled = 0;
184 lifespan->startup_received = 0;
185 lifespan->startup_sent = 0;
186 lifespan->shutdown_received = 0;
187 lifespan->shutdown_sent = 0;
188 lifespan->shutdown_called = 0;
189 lifespan->shutdown_future = NULL;
190 lifespan->receive_future = NULL;
191 lifespan->state = NULL;
192
193 scope = nxt_py_asgi_new_scope(NULL, nxt_py_lifespan_str, nxt_py_2_0_str);
194 if (nxt_slow_path(scope == NULL)) {
195 goto release_future;
196 }
197
198 lifespan->state = PyDict_New();
199 if (nxt_slow_path(lifespan->state == NULL)) {
200 nxt_unit_req_error(NULL,
201 "Python failed to create 'state' dict");
202 goto release_future;
203 }
204
205 if (nxt_slow_path(PyDict_SetItem(scope, nxt_py_state_str,
206 lifespan->state) == -1))
207 {
208 nxt_unit_req_error(NULL,
209 "Python failed to set 'scope.state' item");
210 Py_CLEAR(lifespan->state);
211 goto release_future;
212 }
213
214 if (!target->asgi_legacy) {
215 nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application");
216
217 res = PyObject_CallFunctionObjArgs(target->application,
218 scope, receive, send, NULL);
219
220 } else {
221 nxt_unit_req_debug(NULL, "Python call legacy application");
222
223 res = PyObject_CallFunctionObjArgs(target->application, scope, NULL);
224 if (nxt_slow_path(res == NULL)) {
225 nxt_unit_log(NULL, NXT_UNIT_LOG_INFO,
226 "ASGI Lifespan processing exception");
227 nxt_python_print_exception();
228
229 lifespan->disabled = 1;
230
231 Py_INCREF(lifespan);
232 ret = lifespan;
233
234 goto release_scope;
235 }
236
237 if (nxt_slow_path(PyCallable_Check(res) == 0)) {
238 nxt_unit_req_error(NULL,
239 "Legacy ASGI application returns not a callable");
240
241 Py_DECREF(res);
242
243 goto release_scope;
244 }
245
246 stage2 = res;
247
248 res = PyObject_CallFunctionObjArgs(stage2, receive, send, NULL);
249
250 Py_DECREF(stage2);
251 }
252
253 if (nxt_slow_path(res == NULL)) {
254 nxt_unit_error(NULL, "Python failed to call the application");
255 nxt_python_print_exception();
256 goto release_scope;
257 }
258
259 if (nxt_slow_path(!PyCoro_CheckExact(res))) {
260 nxt_unit_error(NULL, "Application result type is not a coroutine");
261 Py_DECREF(res);
262 goto release_scope;
263 }
264
265 py_task = PyObject_CallFunctionObjArgs(ctx_data->loop_create_task, res,
266 NULL);
267 if (nxt_slow_path(py_task == NULL)) {
268 nxt_unit_alert(NULL, "Python failed to call the create_task");
269 nxt_python_print_exception();
270 Py_DECREF(res);
271 goto release_scope;
272 }
273
274 Py_DECREF(res);
275
276 res = PyObject_CallMethodObjArgs(py_task, nxt_py_add_done_callback_str,
277 done, NULL);
278 if (nxt_slow_path(res == NULL)) {
279 nxt_unit_alert(NULL, "Python failed to call 'task.add_done_callback'");
280 nxt_python_print_exception();
281 goto release_task;
282 }
283
284 Py_DECREF(res);
285
286 res = PyObject_CallFunctionObjArgs(ctx_data->loop_run_until_complete,
287 lifespan->startup_future, NULL);
288 if (nxt_slow_path(res == NULL)) {
289 nxt_unit_alert(NULL, "Python failed to call loop.run_until_complete");
290 nxt_python_print_exception();
291 goto release_task;
292 }
293
294 Py_DECREF(res);
295
296 if (lifespan->startup_sent == 1 || lifespan->disabled) {
297 Py_INCREF(lifespan);
298
299 ret = lifespan;
300 }
301
302 release_task:
303 Py_DECREF(py_task);
304 release_scope:
305 Py_DECREF(scope);
306 release_future:
307 Py_CLEAR(lifespan->startup_future);
308 release_done:
309 Py_DECREF(done);
310 release_send:
311 Py_DECREF(send);
312 release_receive:
313 Py_DECREF(receive);
314 release_lifespan:
315 Py_DECREF(lifespan);
316
317 return (PyObject *) ret;
318 }
319
320
321 int
nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t * ctx)322 nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx)
323 {
324 nxt_int_t i, ret;
325 nxt_py_asgi_lifespan_t *lifespan;
326 nxt_py_asgi_ctx_data_t *ctx_data;
327
328 ctx_data = ctx->data;
329
330 for (i = 0; i < nxt_py_targets->count; i++) {
331 lifespan = (nxt_py_asgi_lifespan_t *)ctx_data->target_lifespans[i];
332
333 ret = nxt_py_asgi_lifespan_target_shutdown(lifespan);
334 if (nxt_slow_path(ret != NXT_UNIT_OK)) {
335 return NXT_UNIT_ERROR;
336 }
337 }
338
339 nxt_unit_free(NULL, ctx_data->target_lifespans);
340
341 return NXT_UNIT_OK;
342 }
343
344
345 static int
nxt_py_asgi_lifespan_target_shutdown(nxt_py_asgi_lifespan_t * lifespan)346 nxt_py_asgi_lifespan_target_shutdown(nxt_py_asgi_lifespan_t *lifespan)
347 {
348 PyObject *msg, *future, *res;
349 nxt_py_asgi_ctx_data_t *ctx_data;
350
351 ctx_data = lifespan->ctx_data;
352
353 if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) {
354 return NXT_UNIT_OK;
355 }
356
357 lifespan->shutdown_called = 1;
358
359 if (lifespan->receive_future != NULL) {
360 future = lifespan->receive_future;
361 lifespan->receive_future = NULL;
362
363 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str);
364
365 if (nxt_fast_path(msg != NULL)) {
366 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
367 msg, NULL);
368 Py_XDECREF(res);
369 Py_DECREF(msg);
370 }
371
372 Py_DECREF(future);
373 }
374
375 if (lifespan->shutdown_sent) {
376 return NXT_UNIT_OK;
377 }
378
379 lifespan->shutdown_future = PyObject_CallObject(ctx_data->loop_create_future,
380 NULL);
381 if (nxt_slow_path(lifespan->shutdown_future == NULL)) {
382 nxt_unit_alert(NULL, "Python failed to create Future object");
383 nxt_python_print_exception();
384 return NXT_UNIT_ERROR;
385 }
386
387 res = PyObject_CallFunctionObjArgs(ctx_data->loop_run_until_complete,
388 lifespan->shutdown_future, NULL);
389 if (nxt_slow_path(res == NULL)) {
390 nxt_unit_alert(NULL, "Python failed to call loop.run_until_complete");
391 nxt_python_print_exception();
392 return NXT_UNIT_ERROR;
393 }
394
395 Py_DECREF(res);
396 Py_CLEAR(lifespan->shutdown_future);
397
398 return NXT_UNIT_OK;
399 }
400
401
402 static PyObject *
nxt_py_asgi_lifespan_receive(PyObject * self,PyObject * none)403 nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none)
404 {
405 PyObject *msg, *future;
406 nxt_py_asgi_lifespan_t *lifespan;
407 nxt_py_asgi_ctx_data_t *ctx_data;
408
409 lifespan = (nxt_py_asgi_lifespan_t *) self;
410 ctx_data = lifespan->ctx_data;
411
412 nxt_unit_debug(NULL, "asgi_lifespan_receive");
413
414 future = PyObject_CallObject(ctx_data->loop_create_future, NULL);
415 if (nxt_slow_path(future == NULL)) {
416 nxt_unit_alert(NULL, "Python failed to create Future object");
417 nxt_python_print_exception();
418
419 return PyErr_Format(PyExc_RuntimeError,
420 "failed to create Future object");
421 }
422
423 if (!lifespan->startup_received) {
424 lifespan->startup_received = 1;
425
426 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_startup_str);
427
428 return nxt_py_asgi_set_result_soon(NULL, ctx_data, future, msg);
429 }
430
431 if (lifespan->shutdown_called && !lifespan->shutdown_received) {
432 lifespan->shutdown_received = 1;
433
434 msg = nxt_py_asgi_new_msg(NULL, nxt_py_lifespan_shutdown_str);
435
436 return nxt_py_asgi_set_result_soon(NULL, ctx_data, future, msg);
437 }
438
439 Py_INCREF(future);
440 lifespan->receive_future = future;
441
442 return future;
443 }
444
445
446 static PyObject *
nxt_py_asgi_lifespan_send(PyObject * self,PyObject * dict)447 nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict)
448 {
449 PyObject *type, *msg;
450 const char *type_str;
451 Py_ssize_t type_len;
452 nxt_py_asgi_lifespan_t *lifespan;
453
454 static const nxt_str_t startup_complete
455 = nxt_string("lifespan.startup.complete");
456 static const nxt_str_t startup_failed
457 = nxt_string("lifespan.startup.failed");
458 static const nxt_str_t shutdown_complete
459 = nxt_string("lifespan.shutdown.complete");
460 static const nxt_str_t shutdown_failed
461 = nxt_string("lifespan.shutdown.failed");
462
463 lifespan = (nxt_py_asgi_lifespan_t *) self;
464
465 type = PyDict_GetItem(dict, nxt_py_type_str);
466 if (nxt_slow_path(type == NULL || !PyUnicode_Check(type))) {
467 nxt_unit_error(NULL,
468 "asgi_lifespan_send: 'type' is not a unicode string");
469 return PyErr_Format(PyExc_TypeError,
470 "'type' is not a unicode string");
471 }
472
473 type_str = PyUnicode_AsUTF8AndSize(type, &type_len);
474
475 nxt_unit_debug(NULL, "asgi_lifespan_send type is '%.*s'",
476 (int) type_len, type_str);
477
478 if (type_len == (Py_ssize_t) startup_complete.length
479 && memcmp(type_str, startup_complete.start, type_len) == 0)
480 {
481 return nxt_py_asgi_lifespan_send_startup(lifespan, 0, NULL);
482 }
483
484 if (type_len == (Py_ssize_t) startup_failed.length
485 && memcmp(type_str, startup_failed.start, type_len) == 0)
486 {
487 msg = PyDict_GetItem(dict, nxt_py_message_str);
488 return nxt_py_asgi_lifespan_send_startup(lifespan, 1, msg);
489 }
490
491 if (type_len == (Py_ssize_t) shutdown_complete.length
492 && memcmp(type_str, shutdown_complete.start, type_len) == 0)
493 {
494 return nxt_py_asgi_lifespan_send_shutdown(lifespan, 0, NULL);
495 }
496
497 if (type_len == (Py_ssize_t) shutdown_failed.length
498 && memcmp(type_str, shutdown_failed.start, type_len) == 0)
499 {
500 msg = PyDict_GetItem(dict, nxt_py_message_str);
501 return nxt_py_asgi_lifespan_send_shutdown(lifespan, 1, msg);
502 }
503
504 return nxt_py_asgi_lifespan_disable(lifespan);
505 }
506
507
508 static PyObject *
nxt_py_asgi_lifespan_send_startup(nxt_py_asgi_lifespan_t * lifespan,int v,PyObject * message)509 nxt_py_asgi_lifespan_send_startup(nxt_py_asgi_lifespan_t *lifespan, int v,
510 PyObject *message)
511 {
512 const char *message_str;
513 Py_ssize_t message_len;
514
515 if (nxt_slow_path(v != 0)) {
516 nxt_unit_error(NULL, "Application startup failed");
517
518 if (nxt_fast_path(message != NULL && PyUnicode_Check(message))) {
519 message_str = PyUnicode_AsUTF8AndSize(message, &message_len);
520
521 nxt_unit_error(NULL, "%.*s", (int) message_len, message_str);
522 }
523 }
524
525 return nxt_py_asgi_lifespan_send_(lifespan, v,
526 &lifespan->startup_sent,
527 &lifespan->startup_future);
528 }
529
530
531 static PyObject *
nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t * lifespan,int v,int * sent,PyObject ** pfuture)532 nxt_py_asgi_lifespan_send_(nxt_py_asgi_lifespan_t *lifespan, int v, int *sent,
533 PyObject **pfuture)
534 {
535 PyObject *future, *res;
536
537 if (*sent) {
538 return nxt_py_asgi_lifespan_disable(lifespan);
539 }
540
541 *sent = 1 + v;
542
543 if (*pfuture != NULL) {
544 future = *pfuture;
545 *pfuture = NULL;
546
547 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
548 Py_None, NULL);
549 if (nxt_slow_path(res == NULL)) {
550 nxt_unit_alert(NULL, "Failed to call 'future.set_result'");
551 nxt_python_print_exception();
552
553 return nxt_py_asgi_lifespan_disable(lifespan);
554 }
555
556 Py_DECREF(res);
557 Py_DECREF(future);
558 }
559
560 Py_INCREF(lifespan);
561
562 return (PyObject *) lifespan;
563 }
564
565
566 static PyObject *
nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t * lifespan)567 nxt_py_asgi_lifespan_disable(nxt_py_asgi_lifespan_t *lifespan)
568 {
569 nxt_unit_warn(NULL, "Got invalid state transition on lifespan protocol");
570
571 lifespan->disabled = 1;
572
573 return PyErr_Format(PyExc_AssertionError,
574 "Got invalid state transition on lifespan protocol");
575 }
576
577
578 static PyObject *
nxt_py_asgi_lifespan_send_shutdown(nxt_py_asgi_lifespan_t * lifespan,int v,PyObject * message)579 nxt_py_asgi_lifespan_send_shutdown(nxt_py_asgi_lifespan_t *lifespan, int v,
580 PyObject *message)
581 {
582 return nxt_py_asgi_lifespan_send_(lifespan, v,
583 &lifespan->shutdown_sent,
584 &lifespan->shutdown_future);
585 }
586
587
588 static PyObject *
nxt_py_asgi_lifespan_done(PyObject * self,PyObject * future)589 nxt_py_asgi_lifespan_done(PyObject *self, PyObject *future)
590 {
591 PyObject *res;
592 nxt_py_asgi_lifespan_t *lifespan;
593
594 nxt_unit_debug(NULL, "asgi_lifespan_done");
595
596 lifespan = (nxt_py_asgi_lifespan_t *) self;
597
598 if (lifespan->startup_sent == 0) {
599 lifespan->disabled = 1;
600 }
601
602 /*
603 * Get Future.result() and it raises an exception, if coroutine exited
604 * with exception.
605 */
606 res = PyObject_CallMethodObjArgs(future, nxt_py_result_str, NULL);
607 if (nxt_slow_path(res == NULL)) {
608 nxt_unit_log(NULL, NXT_UNIT_LOG_INFO,
609 "ASGI Lifespan processing exception");
610 nxt_python_print_exception();
611 }
612
613 Py_XDECREF(res);
614
615 if (lifespan->startup_future != NULL) {
616 future = lifespan->startup_future;
617 lifespan->startup_future = NULL;
618
619 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
620 Py_None, NULL);
621 if (nxt_slow_path(res == NULL)) {
622 nxt_unit_alert(NULL, "Failed to call 'future.set_result'");
623 nxt_python_print_exception();
624 }
625
626 Py_XDECREF(res);
627 Py_DECREF(future);
628 }
629
630 if (lifespan->shutdown_future != NULL) {
631 future = lifespan->shutdown_future;
632 lifespan->shutdown_future = NULL;
633
634 res = PyObject_CallMethodObjArgs(future, nxt_py_set_result_str,
635 Py_None, NULL);
636 if (nxt_slow_path(res == NULL)) {
637 nxt_unit_alert(NULL, "Failed to call 'future.set_result'");
638 nxt_python_print_exception();
639 }
640
641 Py_XDECREF(res);
642 Py_DECREF(future);
643 }
644
645 Py_RETURN_NONE;
646 }
647
648
649 static void
nxt_py_asgi_lifespan_dealloc(PyObject * self)650 nxt_py_asgi_lifespan_dealloc(PyObject *self)
651 {
652 nxt_py_asgi_lifespan_t *lifespan = (nxt_py_asgi_lifespan_t *)self;
653
654 Py_CLEAR(lifespan->state);
655 PyObject_Del(self);
656 }
657
658
659 #endif /* NXT_HAVE_ASGI */
660