Skip to content

Commit 5173a7f

Browse files
committed
Implement shutdown_gracefully
1 parent de94e8f commit 5173a7f

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

tests/test_server.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66

77
def test_send_close(client_session):
8-
"Ensure client stops receiving data once we send_close (socket is still open)"
8+
"""
9+
Ensure client stops receiving data once we send_close (socket is still open)
10+
"""
911
client, server = client_session
1012
assert client.received_messages == []
1113

@@ -21,6 +23,20 @@ def test_send_close(client_session):
2123
assert client.received_messages == ["test1"]
2224

2325

26+
def test_shutdown_gracefully(client_session):
27+
client, server = client_session
28+
assert client.ws.sock and client.ws.sock.connected
29+
assert server.socket.fileno() > 0
30+
31+
server.shutdown_gracefully()
32+
sleep(0.5)
33+
34+
# Ensure all parties disconnected
35+
assert not client.ws.sock
36+
assert server.socket.fileno() == -1
37+
assert not server.clients
38+
39+
2440
def test_client_closes_gracefully(session):
2541
client, server = session
2642
assert client.connected

websocket_server/websocket_server.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ def send_message(self, client, msg):
8686
def send_message_to_all(self, msg):
8787
self._multicast(msg)
8888

89+
def shutdown_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
90+
"""
91+
Close with a websocket handshake
92+
93+
1. Send CLOSE to all clients
94+
2. Close TCP
95+
"""
96+
self.keep_alive = False
97+
for client in self.clients:
98+
client["handler"].send_close(CLOSE_STATUS_NORMAL, reason)
99+
self.server_close()
100+
89101

90102
class WebsocketServer(ThreadingMixIn, TCPServer, API):
91103
"""
@@ -258,7 +270,16 @@ def send_close(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
258270
"""
259271
if status < CLOSE_STATUS_NORMAL or status > 1015:
260272
raise Exception(f"CLOSE status must be between 1000 and 1015, got {status}")
261-
self.request.send(struct.pack('!H', status) + reason, OPCODE_CLOSE_CONN)
273+
274+
header = bytearray()
275+
payload = struct.pack('!H', status) + reason
276+
payload_length = len(payload)
277+
assert payload_length <= 125, "We only support short closing reasons at the moment"
278+
279+
# Send CLOSE with status & reason
280+
header.append(FIN | OPCODE_CLOSE_CONN)
281+
header.append(payload_length)
282+
self.request.send(header + payload)
262283

263284
def send_text(self, message, opcode=OPCODE_TEXT):
264285
"""

0 commit comments

Comments
 (0)