Your First HTTP Request in C++¶
TCP delivers bytes; HTTP gives those bytes a request and response format. Send a GET request through raw socket calls and inspect the reply from a local HTTP server.
Course examples on GitHub
Before you start¶
Complete Multiple TCP Connections. Use Raspberry Pi OS with Python 3.11 or later, C++20 and CMake. Run both terminals on the Pi in examples/network.
Outcome: identify the request line, headers, empty line and response body in an actual HTTP exchange.
The server is Python's standard-library test server, serving only the checked-in fixture directory. The client is standalone C++, not a wrapper around curl or an HTTP library.
What an HTTP GET sends over TCP¶
This is the request used on port 8080. Each displayed \r\n represents the two actual carriage-return and line-feed bytes in the C++ string:
| Part | Purpose in this exchange |
|---|---|
GET |
Ask for the selected resource. |
/hello.txt |
Origin-form request target, not a local C++ file path. |
HTTP/1.1 |
The request's protocol version. |
Host |
The target authority, including this non-default port. |
| Empty line | End of the header section. This GET has no request body. |
Connection: close |
Ask to close this connection after the response. |
The Host field does not connect a socket or perform DNS. Our connect() still explicitly targets IPv4 loopback. Different HTTP host authorities can share an address, so the application needs this field too.
We do not call shutdown(SHUT_WR) to finish the request. The empty header line ends this bodyless GET. A server can reply while the TCP connection remains open.
This shows logical message parts, not a fixed number of send(), recv() calls or TCP packets.
Start a temporary local HTTP server¶
Build the client:
In terminal A:
The directory contains only hello.txt, whose content is Hello from Raspberry Pi. followed by a newline. The process stops after 30 seconds; an exit status of 124 from timeout then means the time limit was reached. Restart it when needed.
Local test server only
Keep --bind 127.0.0.1 and the explicit fixture directory. Python's http.server is not a production server. Do not point this exercise at a directory containing private files or expose it to the internet.
Within 30 seconds, run in terminal B:
The output begins with HTTP/1.1 200 OK. It contains headers, an empty line and this body:
Expect Content-Length: 25 for the checked-in file. Server, Date and Last-Modified vary with the Python version, clock and checkout. Do not compare the entire header block against a fixed timestamp.
Separate the response parts¶
| Part | What to inspect |
|---|---|
| Status line | Version and status code, such as 200. |
| Response headers | Metadata, including content type and length. |
| Empty line | Header/body boundary, encoded as \r\n\r\n. |
| Body | File bytes, not the headers themselves. |
Try an absent resource while the same server is running:
The server returns a 404 response with an error body. That is an HTTP response, not a failed TCP connection. This raw-capture client exits successfully if it collected a non-empty reply through EOF; it does not interpret the status code as success or failure.
Read the standalone client¶
Complete file: app/http-wire/client.cpp. Read it in three passes: construct the HTTP string, send all its bytes, then collect the response.
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 | |
The port and path checks stop malformed arguments from adding request-header lines. Paths in this small example are limited to 200 visible ASCII characters, start with / and contain no spaces or fragment marker. They are not a complete URL parser.
The receive phase has a fixed three-second budget and an 8192-byte response cap. Linux setup waits have separate per-call timeouts. No project-local header hides the socket operations or descriptor cleanup.
To build just this file:
Why this is not an HTTP parser yet¶
The client prints the response unchanged. It does not validate a status line, decode chunked transfer coding or prove that a declared body length arrived.
For this first capture, we ask the server to close and read until EOF. A complete response with a Content-Length does not generally require EOF, and a premature EOF can leave an incomplete body. The next lesson adds those distinctions rather than mistaking a successful socket read for a valid HTTP message.
If a test peer sends the complete response but keeps the connection open, this raw client reaches its receive deadline. That limitation is deliberate and tested.
This exchange is plain HTTP, without encryption or server authentication. HTTPS will add TLS in the next chapter; it is not achieved by changing a port number.
Troubleshooting and checks¶
- Connection refused: restart terminal A after its 30-second window and check the same port in both commands.
- Port already in use: choose another unprivileged port and update both commands. The C++ client builds the matching
Hostfield. - The existing file returns 404: run the server from
examples/networkwith the exact--directoryargument. - Receive deadline: the peer did not close before the budget. The raw capture has not yet learned HTTP body framing.
- Different headers: normal for generated dates and version strings. Inspect the status and file content first.
Run the lesson's checks:
They include a real Python HTTP/1.1 server, a 404 response, exact request bytes, split replies, binary capture, input validation, a response-size cap and a non-renewing receive deadline. No public internet server is used.
Check your understanding¶
The C++ client has sent the final \r\n\r\n for this bodyless GET. Must it close its TCP sending direction before the server can reply?
Compare your reasoning
No. HTTP message syntax already identifies this request's end. TCP EOF is not required. The request and response each have their own HTTP framing rules.
Continue the course¶
Previous: Multiple TCP Connections · Next: HTTP Headers and Body Length · Course overview
References: HTTP/1.1 message syntax, HTTP semantics, Python's test HTTP server.