Inspect text protocols
Send an HTTP request by hand
Type a complete HTTP/1.1 request through a Telnet client and see the protocol message carried inside TCP.
Create http-server.mjs so we have a local HTTP server to talk to:
import http from 'node:http'
http.createServer((request, response) => {
response.end('Hello from HTTP\n')
}).listen(8000, '127.0.0.1')
Run it with node http-server.mjs. Same rules as the Telnet lab: loopback only, port above 1024, nothing exposed to your network.
Now open the port with the Telnet client:
telnet 127.0.0.1 8000
You get the usual Connected to 127.0.0.1. line, and then silence. HTTP servers wait for the client to speak first. That is different from SMTP, which you will see in the next lesson.
Connect to a local HTTP server or a host you control, then type a complete request followed by a blank line:
GET / HTTP/1.1
Host: localhost
Connection: close
Read the status line, headers, blank line, and body. Telnet does not understand HTTP here. It only carries the bytes you type. Repeat with curl -v and compare the exact request and response boundaries.
What comes back
Type carefully, because a typo in the request line gets you a 400 Bad Request and a closed connection. The blank line is not optional. It is how HTTP marks the end of the headers, so the server does nothing until it arrives. Press Enter twice after the last header.
The Node server answers like this:
HTTP/1.1 200 OK
Date: Tue, 08 Sep 2026 16:20:00 GMT
Connection: close
Content-Length: 16
Hello from HTTP
Connection closed by foreign host.
Same shape as the request: a status line, headers, a blank line, then the body. Content-Length: 16 tells the client exactly how many body bytes to read. Count them: fifteen characters plus the newline. That header is how HTTP frames a message inside a TCP stream that has no boundaries of its own.
The last line is the Telnet client talking, not the server. We sent Connection: close, so the server hung up after the response, and the client noticed. Without that header the server would keep the connection open for another request, and you would sit there wondering whether it was done.
Compare with curl
Now let curl do the same thing, with -v so it shows the request and response it exchanged:
curl -v http://127.0.0.1:8000/
Lines starting with > are what curl sent. Lines starting with < are what came back. You will recognize every one of them, because you just typed the same request by hand. curl adds a User-Agent and an Accept header, and it does not send Connection: close, but the grammar is identical.
That is the point of this exercise. The request grammar came from HTTP. TCP carried the bytes. The Telnet program only gave you an interactive way to type into the socket. Three layers, and now you have seen each one on its own.
Lesson completed