A Conversation on Loopback · practice
Fail the Connection
Two endings work now: either side can close, and both report 1000. The rude client in Run still gets the old treatment. After Lesson 2 its lines read:
A rude client:
client: the server closed the connection without a close frame
server: stopped by ValueError: (1002, 'the frame breaks the masking rule')
The codec did its job: it refused an unmasked serve_conversation, ended the server’s thread, and the client never learned what it did wrong. This lesson turns that exception into a close frame.
Closing versus failing
A close is a conversation both sides finish together. RFC 6455 section 7.1.7 describes the other ending, failing the connection, for when a peer has broken the protocol. The endpoint that notices “SHOULD send a Close frame with an appropriate
So a failing server does three things: it sends one close frame carrying the code for the broken rule, it reads nothing more, and it returns. This course always sends that close when the
Codes from the codec
Since Chapter 4’s project, every refusal in read_message is a ValueError(code, reason). The code is exc.args[0] and the reason exc.args[1]:
1002, protocol error: an unmasked frame to a server, reserved bits or
, a control frame that is too long or not final, a that may not travel; 1003, a message this codec cannot take: any fragment, because the learning codec does not reassemble fragmented messages;
1007, a text message or close reason that is not valid
; 1009, message too big: a frame whose header announces more than
MAX_SIZEbytes, refused before a single payload byte is read.
failure_frame(exc) builds the close from those two values: ws_frames.close_payload(exc.args[0], exc.args[1]) in an unmasked close frame. For ValueError(1009, "x") that is:
88 03 03 f1 78
03 f1 is 1009, and 78 is “x”.
A masked binary frame announces 2,000,000 bytes, over MAX_SIZE. What does the server send?
A peer that vanishes
Two other exceptions can end a read, and neither is a broken rule:
ConnectionError:read_exactgotb""because the peer closed thewithout a close frame, as Lesson 1’s client did; TimeoutError: the peer sent nothing for 2 seconds, the timeout the wiring sets on every accepted socket.
In both cases there is no one to send a close to, or no reason to believe a close would be read. The server sends nothing and reports the code 1006, which RFC 6455 reserves for exactly this: the connection ended without a close frame. 1006 only ever appears in reports. Like 1005, it may never travel in a frame.
The client's connection drops in the middle of a conversation. What does the server send?
Your task
Write failure_frame(exc). Then wrap the serve_conversation, the closing steps after it included, in try, with two handlers:
def serve_conversation(sock, read_exact, *, max_messages=MAX_MESSAGES):
count = 0
try:
... # Lesson 2's loop and closing steps, unchanged
except ValueError as exc:
sock.sendall(failure_frame(exc))
return {"messages": count, "code": exc.args[0]}
except (ConnectionError, TimeoutError):
return {"messages": count, "code": 1006}
count is whatever name your loop uses for the messages echoed so far. Two separate except ConnectionError and except TimeoutError clauses work just as well. Keep the handlers to these exception except Exception would treat a broken rule like a vanished peer and send no close at all.
Why not wait
After a close in Lesson 2, the server waited for the answer. After a failure, it does not. Waiting would mean trusting the peer one more time. A client that sent an unmasked frame may be a broken program or something that is not a
Why does the server not wait for the client's close after failing the connection?
Run
Press Run. After this lesson, the rude client’s lines become:
A rude client:
client: {'type': 'close', 'code': 1002, 'reason': 'the frame breaks the masking rule'}
server: {'messages': 0, 'code': 1002}
server thread still running: False
The rude client now reads a close frame that names its mistake, and the server’s thread ends with a result instead of an exception. The first conversation still reports 1000 on both sides.
Then press Submit. It feeds serve_conversation frames that break each rule above, a header that announces too many bytes with nothing behind it, and clients that vanish or go silent after one message, and checks the close you send, the code you return, and that nothing is read after a failure.
Your server now has an answer for every way a conversation can end. So far you have seen that in Run and Submit has checked it; the next lesson writes tests of your own that prove it, over two connected sockets with no
Task
Write one loopback_demo.py.
failure_frame(exc): return the unmasked closefor a ValueError(code, reason):ws_frames.close_payload(exc.args[0], exc.args[1])insidews_frames.encode_frame(ws_frames.OP_CLOSE, ...).serve_conversation: wrap theand the closing steps after it in try. OnValueError as exc, sendfailure_frame(exc)and return{"messages": n, "code": exc.args[0]}at once, without reading again. OnConnectionErrororTimeoutError, send nothing and return{"messages": n, "code": 1006}. Catch only these, not Exception.
Press Run: the rude client should now receive {'type': 'close', 'code': 1002, ...}, and the first conversation should still report 1000 on both sides.
Then press Submit. The check calls failure_frame directly, and gives serve_conversation stand-in clients that send an unmasked text, a text that is not