HTTP Headers and Content-Length in C++¶
A received TCP chunk is not an HTTP response. Find the end of the headers, validate the body's declared byte count and stop when those bytes have arrived—even while the peer is still connected.
Course examples on GitHub
Before you start¶
Complete Your First HTTP Request. Use the same local file fixture and two Pi terminals in examples/network.
Outcome: extract exactly the declared HTTP body, retain split header bytes and distinguish an incomplete response from a completed one.
This client deliberately supports a small response profile. It is not a general HTTP implementation or a replacement for a maintained HTTP library.
Two boundaries, two different jobs¶
| Boundary | How this client finds it |
|---|---|
| End of response headers | Search accumulated bytes for \r\n\r\n. |
| End of the selected response body | Count Content-Length bytes after that header terminator. |
For a declared length of five, the body could arrive as he, then llo. Header bytes might also arrive in pieces, and the read containing the terminator may already contain part of the body.
The length counts body bytes, not characters and not the entire response. A UTF-8 character may occupy multiple bytes. The empty line itself is not part of the body count.
The parts shown are logical byte ranges, not promised recv() boundaries. The sample uses a 16-byte receive buffer so a normal header necessarily spans reads.
State the supported HTTP profile first¶
This lesson accepts responses to its own bodyless GET with:
| Supported choice | Limit or rule |
|---|---|
| Response version | HTTP/1.0 or HTTP/1.1. |
| Status | 200 or 404 only. |
| Header section | At most 4096 bytes, including the terminator. |
| Body | One Content-Length, decimal 0–4096 bytes. |
| Header spelling | Case-insensitive field names and optional surrounding spaces/tabs in values. |
| Other header syntax | Valid ASCII token names and visible ASCII/tab values, no folded lines. |
| Transfer/content coding | Neither Transfer-Encoding nor Content-Encoding is accepted. |
| Connection handling | One response, then close our socket; no reuse or pipelining. |
Rejecting identical duplicate lengths, encoded responses and some otherwise valid HTTP messages is deliberately stricter than full HTTP. It makes the accepted profile explicit.
Content-Length is not the universal HTTP body rule
Responses to HEAD, informational responses, 204 and 304 have special rules. Chunked transfer coding and close-delimited bodies have different framing. This exercise rejects those cases instead of applying this simplified length algorithm to them.
Content-Encoding, such as gzip, concerns representation coding; it is not the same as Transfer-Encoding. This lesson implements neither decoding path. Do not use this small parser as an internet-facing client, proxy or security boundary.
Build and run the length-aware client¶
Restart the same server in terminal A:
In terminal B:
Expected output:
The summary line is printed by our client. The following bytes are the file body, without response headers.
Try a missing path:
Expect status=404 bytes=... followed by Python's error body. A valid 404 is still an HTTP response; exit status zero here means the response matched the supported profile and its complete body arrived, not that the resource existed.
Follow the parser in five passes¶
- Accumulate headers. Search after each read, not just in the newest chunk. Enforce the header cap even if the terminator never arrives.
- Validate the status and fields. Check the response version and this lesson's status set. Reject malformed names, folded lines and control characters.
- Read one length. Normalize field names, trim optional spaces/tabs, then require a complete unsigned decimal value within the body cap. Reject duplicate lengths and unsupported encodings.
- Keep body bytes already read. Set
body_startjust after the terminator. Do not throw away the bytes that followed it in the same read. - Collect only what remains. Once the length is known, limit each read to the remaining body count and stop when it reaches zero.
The header parser uses temporary string_view values only while the response string is unchanged. Later reads can reallocate that string, so it does not retain views across appends.
A single fixed receive deadline covers both header and body collection. Incoming bytes do not restart it. Setup calls retain separate Linux per-call timeouts, so this is not a whole-request deadline.
Read the complete standalone file¶
Complete file: app/http-length/client.cpp. Setup and sending match the preceding lesson; the added work is header validation and body framing. All socket operations and cleanup remain in the file.
Full standalone client: app/http-length/client.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
The client ignores any surplus bytes already read beyond the selected body's length and closes the connection. It never interprets them as a second response. This is not a connection-reuse parser.
To compile without CMake:
No custom network header or additional library is needed.
Test the cases that EOF hides¶
Run this lesson's independent checks:
| Fixture | Expected result |
|---|---|
| Complete body, peer still open | Return the body before peer EOF. |
| Zero-length body, peer still open | Finish after the headers. |
| EOF during headers | Report truncated headers. |
Length 5, EOF after only he |
Report truncated body, not a two-byte success. |
| Length over 4096 or integer overflow | Reject from the headers without waiting for a body. |
| Duplicate length or transfer coding | Reject this unsupported response profile. |
| Slowly arriving incomplete data | The original receive deadline still expires. |
The checks also cover a real Python server, mixed-case names, whitespace around values, binary bodies, exact size limits, invalid syntax and ignored surplus bytes. They do not claim general RFC conformance.
For comparison, the previous raw client waits for EOF even after a complete length-delimited response. These tests keep their peer sockets open until the parsed client has returned, so the change is observable rather than just a code comment.
Troubleshooting¶
- Missing Content-Length: the message is outside this lesson's profile. A close-delimited response can be valid HTTP, but this client intentionally does not decode it.
- Encoded response outside profile: do not ignore
Transfer-Encodingand trust a length field anyway. Use the correct decoding path in a complete implementation. - Truncated body: the peer ended its sending direction before all declared bytes arrived. Do not silently lower the length.
- Receive deadline: the response did not complete within the fixed budget. The optional third argument after port and path sets 1–10000 ms.
- Unsupported status: body framing rules depend on request method and status. This client intentionally accepts only the two statuses used in the exercise.
Keep the server bound to loopback and serving only the fixture directory, as in the preceding lesson. Its 30-second timeout is separate from the client's receive budget.
Check your understanding¶
A header declares Content-Length: 5. The receiver has only he, and the next read returns zero. May it report a completed body because TCP ended cleanly?
Compare your reasoning
No. TCP EOF tells you no more bytes will arrive in that direction. The declared HTTP body is still three bytes short, so the response is incomplete.
Continue the course¶
Previous: Your First HTTP Request · Next: A Small HTTP Server · Course overview
References: HTTP/1.1 field parsing, message body length rules, Content-Length semantics.