Skip to content

Commit 8deb39c

Browse files
fix: Handle client disconnection for HTTP and WebSocket according to ASGI spec
For HTTP connections: - If the app is sending data using the send callable, according to the ASGI spec, it should throw an exception in case of client disconnection. Previously, even if we processed the client_error message and set the http->closed state, it wouldn't throw an error because it wasn't handled. This change ensures that the exception is raised as per the ASGI spec. For WebSocket connections: - If the app is awaiting on receive, it would get a 'websocket.disconnect' event. However, if the app continues to send data using the send callable after receiving this event, it wouldn't raise an error because ws->state = NXT_WS_DISCONNECTED was never set in that case. According to the ASGI spec, if send is called after receiving a 'websocket.disconnect' event or on a closed client, it should raise an exception. This change ensures that the exception is raised as per the ASGI spec.
1 parent 87431eb commit 8deb39c

File tree

2 files changed

+13
-8
lines changed

2 files changed

+13
-8
lines changed

src/python/nxt_python_asgi_http.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,11 @@ nxt_py_asgi_http_response_body(nxt_py_asgi_http_t *http, PyObject *dict)
368368
"sent, after response already completed");
369369
}
370370

371+
if (nxt_slow_path(http->closed)) {
372+
return PyErr_Format(PyExc_ConnectionResetError,
373+
"Connection Closed ");
374+
}
375+
371376
if (nxt_slow_path(http->send_future != NULL)) {
372377
return PyErr_Format(PyExc_RuntimeError, "Concurrent send");
373378
}

src/python/nxt_python_asgi_websocket.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,10 @@ nxt_py_asgi_websocket_accept(nxt_py_asgi_websocket_t *ws, PyObject *dict)
273273
return PyErr_Format(PyExc_RuntimeError, "WebSocket already accepted");
274274

275275
case NXT_WS_DISCONNECTED:
276-
return PyErr_Format(PyExc_RuntimeError, "WebSocket disconnected");
276+
return PyErr_Format(PyExc_ConnectionResetError, "WebSocket disconnected");
277277

278278
case NXT_WS_CLOSED:
279-
return PyErr_Format(PyExc_RuntimeError, "WebSocket already closed");
279+
return PyErr_Format(PyExc_ConnectionResetError, "WebSocket already closed");
280280
}
281281

282282
if (nxt_slow_path(nxt_unit_response_is_websocket(ws->req))) {
@@ -368,11 +368,11 @@ nxt_py_asgi_websocket_close(nxt_py_asgi_websocket_t *ws, PyObject *dict)
368368
}
369369

370370
if (nxt_slow_path(ws->state == NXT_WS_DISCONNECTED)) {
371-
return PyErr_Format(PyExc_RuntimeError, "WebSocket disconnected");
371+
return PyErr_Format(PyExc_ConnectionResetError, "WebSocket disconnected");
372372
}
373373

374374
if (nxt_slow_path(ws->state == NXT_WS_CLOSED)) {
375-
return PyErr_Format(PyExc_RuntimeError, "WebSocket already closed");
375+
return PyErr_Format(PyExc_ConnectionResetError, "WebSocket already closed");
376376
}
377377

378378
if (nxt_unit_response_is_websocket(ws->req)) {
@@ -433,11 +433,11 @@ nxt_py_asgi_websocket_send_frame(nxt_py_asgi_websocket_t *ws, PyObject *dict)
433433
}
434434

435435
if (nxt_slow_path(ws->state == NXT_WS_DISCONNECTED)) {
436-
return PyErr_Format(PyExc_RuntimeError, "WebSocket disconnected");
436+
return PyErr_Format(PyExc_ConnectionResetError, "WebSocket disconnected");
437437
}
438438

439439
if (nxt_slow_path(ws->state == NXT_WS_CLOSED)) {
440-
return PyErr_Format(PyExc_RuntimeError, "WebSocket already closed");
440+
return PyErr_Format(PyExc_ConnectionResetError, "WebSocket already closed");
441441
}
442442

443443
bytes = PyDict_GetItem(dict, nxt_py_bytes_str);
@@ -984,9 +984,9 @@ nxt_py_asgi_websocket_close_handler(nxt_unit_request_info_t *req)
984984
return;
985985
}
986986

987-
if (ws->receive_future == NULL) {
988-
ws->state = NXT_WS_DISCONNECTED;
987+
ws->state = NXT_WS_DISCONNECTED;
989988

989+
if (ws->receive_future == NULL) {
990990
return;
991991
}
992992

0 commit comments

Comments
 (0)