0%

The websockets Library, Without asyncio

What the Library Did for You

Your echo handler is a of a few lines, and your client is a few more. Chapters 2 to 5 took 618 lines, across three files, to do the same work. None of that work disappeared: every still gets a header, every client frame is still masked, every close is still answered. The library does it now. This lesson lays the two side by side, so that each thing the library does has a name you know, and then looks at what the library does that your code never did.

What the library took over

On the wireWhere you wrote itWhere the library does it
The server’s half of the : read the request, check it, answer 101 with the accept 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 valuemake_client_key, build_upgrade_request, check_accept_response, client_handshake (Chapter 2)inside connect, before it returns
Frames: the header, the three length forms, for textencode_frame, decode_frame, read_message (Chapters 3 and 4)inside send and recv
, and refusing a frame masked the wrong way for its directionapply_mask, client_frame, and the require_mask keyword of decode_frame (Chapters 4 and 5)inside send and recv, by role
Answering a with a pong carrying the same payloadreply_to_ping (Chapter 4), server_reply (Chapter 5)as each ping arrives
The closing handshakeclose_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 1009failure_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.

. Every 20 seconds the library sends a ping (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 .