The websockets Library, Without asyncio
What the Library Did for You
Your echo handler is a
What the library took over
| On the wire | Where you wrote it | Where the library does it |
|---|---|---|
| The server’s half of the | serve_handshake, check_upgrade, accept_value (Chapter 2) | inside serve, before your handler is called |
| The client’s half: a fresh key, the request, the check of the accept value | make_client_key, build_upgrade_request, check_accept_response, client_handshake (Chapter 2) | inside connect, before it returns |
| Frames: the header, the three length forms, | encode_frame, decode_frame, read_message (Chapters 3 and 4) | inside send and recv |
apply_mask, client_frame, and the require_mask keyword decode_frame (Chapters 4 and 5) | inside send and recv, by role | |
| Answering a | reply_to_ping (Chapter 4), server_reply (Chapter 5) | as each ping arrives |
| The closing handshake | close_frame, wait_for_close (Chapter 5) | inside close, and at the end of a client’s with block |
| Failing the connection with 1002, 1007 or 1009 | failure_frame (Chapter 5) | as the faulty frame arrives, with the same codes |
The last row is worth a closer look, because it is where the two could have disagreed. In local tests against the library’s protocol code, an unmasked frame sent to a library server was answered with close 1002 and the reason incorrect masking, a close reason that was not valid UTF-8 with 1007, and a frame over the size limit with 1009. Those are the codes your failure_frame sends for the same faults. The closing handshake agrees byte for byte too: when the library answers a close, it sends back the payload it received, unchanged, as your close_frame does. A comment in its source explains why: rebuilding an empty close would fail, because an empty close is reported as 1005, a code that may not be sent.
What it does that yours did not
The learning codec was honest about its limits, and the library fills each one.
Fragments. Your codec refused a fragmented message with 1003. A conforming endpoint must accept fragments, and the library does. In a local test, the Chapter 5 codec sent a library server Hel as an unfinished text fragment, then a ping, then lo as the final fragment. The server answered the ping with a pong, and its handler received Hello whole and echoed it.
A client sends Hel and lo as two fragments to a library server. What does the handler's recv return?
Compression. By default the library offers permessage-deflate, an extension that compresses each message, during the handshake (compression="deflate"). Compressed frames set RSV1, a bit your codec refuses. That was safe only because your Chapter 5 server never agreed to the extension, so the interop run in Lesson 1 used none. This course leaves compression out.
ping_interval=20), and if the pong does not arrive within 20 seconds (ping_timeout=20), it closes the connection. Your client sent one ping at the start of a conversation, never one on a timer, so a peer that vanished without a word went unnoticed until a read timed out. Chapter 8 is about this.
A default size limit. The library refuses a message over 1 max_size=1048576 bytes, and closes the connection with 1009. Your codec’s MAX_SIZE was 1,000,000 bytes, so the two limits are close but not equal.
These defaults belong to version 17.4, the one you pinned. Another version may change them, which is one more reason for the pin.
With default settings, a client sends a message of 2,000,000 bytes to a library server. What happens?
A convenience this course skips
A connection can also be read with a for loop:
for message in connection:
connection.send(message)
The loop reads one message per pass and ends quietly when the connection closes normally. It reads well, and it has no timeout: a client that connects and says nothing holds the loop, and its thread, for as long as the connection stays open. Every read in this course is bounded, so handlers loop on recv(timeout=...) and catch ConnectionClosed instead.
Why does this course not write for message in connection:?
Everything you need to rebuild the Chapter 5 echo service is now in echo_library.py. The project puts it together into a program with a