Message framing
Send length-prefixed messages
Frame binary or multiline data with an explicit length and parse it without reading past the message.
Newline framing breaks down when the payload can contain newlines: binary data, file contents, multiline text. A length prefix removes the problem. A length prefix lets the payload contain any byte, including newlines.
The sender writes a fixed-size header holding the payload size, then the payload itself. The receiver first reads the fixed-size length, then exactly that many payload bytes. No delimiter, no escaping.
Encode one four-byte length and payload:
const payload = Buffer.from('hello')
const frame = Buffer.alloc(4 + payload.length)
frame.writeUInt32BE(payload.length, 0)
payload.copy(frame, 4)
socket.write(frame)
writeUInt32BE stores the length big-endian, most significant byte first. That’s network byte order, the convention wire protocols use for multi-byte integers. Both sides must agree: a big-endian writer paired with a little-endian reader turns length 5 into 83,886,080.
Capture the bytes and confirm:
console.log(frame.toString('hex'))
// 0000000568656c6c6f
The first four bytes represent 5 in network byte order, followed by 68 65 6c 6c 6f — “hello”.
Parse without reading past the message
The receiver accumulates a Buffer and loops. Build a parser that waits for the complete frame:
let pending = Buffer.alloc(0)
socket.on('data', chunk => {
pending = Buffer.concat([pending, chunk])
while (pending.length >= 4) {
const size = pending.readUInt32BE(0)
if (size > 1_000_000) return socket.destroy()
if (pending.length < 4 + size) break
handleMessage(pending.subarray(4, 4 + size))
pending = pending.subarray(4 + size)
}
})
The while loop matters: one chunk may contain several complete frames, and each must be handled. The break matters too: if the payload hasn’t fully arrived, stop and wait for the next data event instead of consuming a partial frame.
The size check is not decoration. Reject impossible or excessive lengths before allocating memory or waiting for payload data. A single hostile 4-byte header claiming a 4 GB payload would otherwise make your server buffer forever for a message that never comes — pick a ceiling your protocol actually needs and destroy the connection past it.
Lesson completed