Frames, Byte by Byte · practice
Encode a Small Frame
The RFC’s simplest example is a whole text message, “Hello”, in one unmasked
81 05 48 65 6c 6c 6f
You can read the first two already: 81 is FIN set with 05 is
Text and binary
struct. On the wire both are just a payload of bytes; the opcode tells the receiver how to treat them.
encode_frame(opcode, payload) accepts either kind of payload. When payload is a str, the function encodes it with UTF-8 first; when it is already bytes, it uses it as it is. isinstance(payload, str) tells the two apart. So encode_frame(OP_TEXT, "Hello") and encode_frame(OP_TEXT, b"Hello") return the same frame.
Count bytes, not characters
The length in byte 1 counts payload bytes, not characters. For “Hello” the two numbers agree, but, as Networks with Python Chapter 2 showed, UTF-8 uses more than one byte for many characters. é takes two bytes and ✓ takes three, so "café ✓" is 6 characters and 9 bytes:
The frame starts 81 09: the second byte is 9, the length of what is actually sent. A frame that said 6 would leave three bytes behind, and the receiver would read them as the start of the next frame.
encode_frame(OP_TEXT, "café ✓") builds a frame. What is its second byte?
Build byte 0 and byte 1
The first byte combines the FIN flag and the opcode, with the tools from Lesson 1. Start from the opcode, then set the top bit with | 0x80 when fin is true. The opcode stays in the low four bits, where parse_header_bytes looks for it.
fin is a keyword-only True, so an ordinary call such as encode_frame(OP_TEXT, "Hello") builds a final frame, and only a caller that writes fin=False gets a frame that is not final. You will not need fin=False in this chapter, but it shows the bit is really yours to set: encode_frame(OP_TEXT, "Hel", fin=False) is 01 03 48 65 6c, the first fragment of a message the RFC sends in two frames.
What does this print?
print(hex(0x80 | 0x2), hex(0x0 | 0x1))
The second byte is the payload’s length. bytes([b0, length]) turns a bytes + payload adds the payload after it. That is the whole frame.
Only small frames, for now
A 7-bit length reaches 127, and as Lesson 2 said, 126 and 127 have another meaning. So this lesson’s encode_frame handles payloads of 0 to 125 bytes and refuses anything longer with a KNOWN_OPCODES, because no reader would accept the frame.
Your task
In ws_frames.py, write encode_frame(opcode, payload, *, fin=True):
raise
ValueErrorifopcodeis not inKNOWN_OPCODES;if
payloadis astr, encode it with UTF-8;raise
ValueErrorif the payload is longer than 125 bytes, with a message that names Lesson 4;build the first byte from the opcode, with
0x80added by|whenfinis true;return the first byte, the length byte and the payload as one
bytesvalue.
Leave parse_header_bytes and the stubs for later lessons as they are.
Press Run. The Lesson 3 section prints your frame for “Hello”, and it should match the RFC’s bytes exactly. The Lesson 4 section now runs your function too, on a 65536-byte payload, and your own refusal appears there, after ValueError:. That is expected: frames_demo.py catches the ValueError and prints its message. With the reference wording, the output from Lesson 3 on reads:
Lesson 3: a text frame for Hello
81 05 48 65 6c 6c 6f
Lesson 4: the start of a 65536-byte frame
ValueError: payloads over 125 bytes need a longer length form (Lesson 4)
Lesson 5: Hello decoded one byte at a time
not written yet
Then press Submit. The check compares your frames with bytes it builds itself, for a text you have not seen with characters that take more than one byte, a 125-byte binary payload, an empty text, a frame with fin=False, and a ValueError for two reserved opcodes and a 126-byte payload.
Your frames stop at 125 bytes. The next lesson removes that ceiling with the two longer length forms.
Task
Write encode_frame(opcode, payload, *, fin=True) in ws_frames.py. It returns one unmasked bytes:
a first byte holding the
in its low four bits, with the top bit 0x80set whenfinis true;a second byte holding the payload’s length in bytes;
the payload, encoded with
first when it is a str, and used as it is when it isbytes.
Raise KNOWN_OPCODES, and for a payload longer than 125 bytes, with a message that names Lesson 4.
Press Run: the Lesson 3 section should print 81 05 48 65 6c 6c 6f, and the Lesson 4 section prints your ValueError message. Then press Submit. The check compares your frames with bytes it builds itself: “Hello” as str and as bytes, a text you have not seen whose UTF-8 is longer than its character count, a 125-byte binary payload, an empty text, fin=False, and a ValueError for opcode 3, opcode 0xB and a 126-byte payload. Any way of joining the bytes is fine.