Skip to Content
Course content

177: Building a Basic HTTP Client from Sockets

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How do I actually "talk" HTTP over a raw socket?

The biggest secret about HTTP is that, at its core, it's just plain text sent over a TCP connection. You don't need a fancy library to start; you just need to send a string that follows the HTTP specification. If you're building a basic client, you're essentially just writing a formatted string to the socket using send().

For a basic GET request, you need the method (GET), the path (/), the version (HTTP/1.1), and a few mandatory headers. I usually recommend keeping it simple. Here is how I'd structure the request string in C++:

std::string request = 
    "GET / HTTP/1.1\r\n"
    "Host: example.com\r\n"
    "Connection: close\r\n"
    "\r\n";

if (send(sock, request.c_str(), request.length(), 0) == -1) {
    perror("send failed");
    // handle error
}

Notice those \r\n sequences? That's Carriage Return and Line Feed. HTTP is very picky about this. If you just use \n, some servers will ignore you or throw a 400 Bad Request error. Also, that double \r\n at the end is critical—it tells the server, "I'm done sending headers, please process the request."

Why do I need that Host header? Isn't the IP address enough?

This trips up almost everyone the first time they do this. You'd think that since you connected your socket to a specific IP address, the server would know who you're looking for. But in the real world, one single IP address often hosts hundreds of different websites (this is called virtual hosting).

When the request hits the server, the web server software (like Apache or Nginx) looks at the Host header to decide which directory on the disk to serve you. If you omit Host: example.com\r\n, the server will likely return a 404 or a default page because it has no idea which specific site you're requesting from its pool of hosted domains.

How do I know when the server is finished sending the response?

Since sockets are stream-oriented, recv() doesn't magically know where an HTTP response ends. It just gives you whatever bytes have arrived in the buffer. You have two main choices here: you can either parse the Content-Length header to know exactly how many bytes to expect, or you can tell the server to close the connection as soon as it's done.

In the example above, I used Connection: close. This is the "easy mode" for beginners. It tells the server, "Once you've sent the data, hang up the phone." This means your recv() loop will eventually return 0, which is your signal that the transmission is complete. I've written a typical read loop for this below:

char buffer[4096];
std::string response;
int bytes_received;

while ((bytes_received = recv(sock, buffer, sizeof(buffer) - 1, 0)) > 0) {
    buffer[bytes_received] = '\0'; // Null-terminate for safety
    response += buffer;
}

if (bytes_received == -1) {
    perror("recv failed");
}

Just a heads up: in a production environment, you'd avoid Connection: close and use keep-alive to reuse the socket for multiple requests, but that requires a much more complex parser to handle the Content-Length or Transfer-Encoding: chunked headers.




📋 Practical Task

Exercise: Building a Page-Length Counter for example.com

Your task is to create a C++ program that connects to example.com (IP: 93.184.216.34) on port 80 and retrieves the home page. Instead of printing the entire HTML body to the console, your program must calculate and print only the total number of bytes received in the response (including the headers).

  • Use socket(), connect(), send(), and recv().
  • Ensure you use the Connection: close header to simplify your reading loop.
  • The final output should be a single line: "Total bytes received: [number]".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.