HTTP Server in C++

HTTP Server in C++

C and C++ are powerful languages for anyone curious about the inner workings of computers. But have you ever wondered how something as common as an HTTP server actually works under the hood? When we use frameworks like Flask in Python or Express in Node.js, most of the complexity is hidden from us. In reality, though, HTTP is just a plain text protocol where your browser sends a request for a resource, and the server replies with a properly formatted response. By peeling back the abstractions and exploring this in C or C++, you can see the web in its rawest form and truly understand how these everyday interactions take place.

Basics of HTTP

HTTP (HyperText Transfer Protocol) is all about exchanging requests and responses.

  • A request contains information in the form of headers (like which page is being requested, which browser is being used, what content types are acceptable, etc.), and sometimes a body (when sending data from the browser to the server).
  • A response also has headers and a body. Headers describe the response (status code, content type, length, etc.), while the body contains the actual data (like HTML).

Everything here is just text, but structured in a fixed format so both the browser and server can understand it.

Where C/C++ Comes In:

When you build a server in Python (Flask/Django) or JavaScript (Node.js), a lot of the low-level work is hidden from you. But in C/C++, you can interact directly with the operating system using system calls. This gives you full control over how your server is created and how it talks to the network.

At the heart of this is socket programming. A socket is like a communication endpoint — think of it as a “plug” your program uses to send and receive data over the network. TCP (Transmission Control Protocol) uses sockets to establish reliable communication between two machines.

Here’s the process of building an HTTP server over TCP in C++ step by step:

1. Creating a socket

We start by calling the socket() function. This tells the OS: “Hey, I want to open a door to the network.”

  • It returns a socket descriptor (like a file handle/descriptor).

Example: int server_socket = socket(AF_INET, SOCK_STREAM, 0);        

2. Bind it to an IP and port

Next, we attach (bind) this socket to an IP address and port so that clients know where to connect.

  • Example: binding to 127.0.0.1:8080 means the server runs on your local machine, listening on port 8080.
  • This is done using the bind() function with a sockaddr_in structure.

3. Listen for incoming connections

After binding, we tell the OS: “I’m ready to accept clients.”

  • We use listen(server_socket, backlog) to mark it as a passive socket that waits for requests.
  • Backlog defines how many requests can queue up.

4. Accept a client connection

When a browser or another client connects, we call accept().

  • This creates a new socket just for that client.
  • The original server socket keeps listening for new clients.

5. Receive the request

Now that a client is connected, we read the data it sends using read().

  • If the client is a web browser, the request it sends to the server will usually look something like this: a GET method on the / route, using the HTTP/1.1 protocol. The headers include information such as the host (in this case localhost:8080), the types of content the browser can accept, the user-agent string, and more.

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9,hi;q=0.8,fr;q=0.7        

6. Send back a response

We build an HTTP response string (headers + body) and send it using send(). For example:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 44
Connection: close

<h1>Your C++ server is live and running</h1>        

This makes the browser render the message.

This is how we make a basic TCP server. Since browsers use TCP under the hood, once we bind our process to localhost:8080, every request made to that port will be handled by our server.

Demo: Minimal HTTP Server in C++

In my basic implementation, I’ve made a C++ server for linux that always returns the same message for any request: "Your C++ server is live and running". Once you compile and run the program, just open your browser and visit http://localhost:8080 — you’ll see the message rendered on the page.

#include <string>
#include <cstring>
#include <vector>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <bits/stdc++.h>

using namespace std;

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // Reuse address and port
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;  // Listen on all interfaces
    address.sin_port = htons(8080);        // Port 8080

    // Bind socket to IP:Port
    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // Start listening
    if (listen(server_fd, 4) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    cout << "Server running on http://localhost:8080\n";

    while (true) {
        int client_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
        if (client_socket < 0) {
            perror("accept");
            continue;
        }

        char buffer[3000] = {0};
        int valread = read(client_socket, buffer, sizeof(buffer) - 1);

        cout << buffer << endl;

        string body = "<h1>Your C++ server is live and running</h1>";
        string response =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/html\r\n"
            "Content-Length: " + to_string(body.size()) + "\r\n"
            "Connection: close\r\n"
            "\r\n" +
            body;

        send(client_socket, response.c_str(), response.size(), 0);


        close(client_socket); // closing connection
    }

    close(server_fd); //closing server
    return 0;
}
        

This is a very basic implementation, but using the same concept and handling the incoming buffer string, we can send different types of responses. In the next part, I will implement route handling for GET, POST, and DELETE requests, save data in a text file as a simple database, and use threads for concurrency and efficiency — all through building a basic To-Do app.

Of course, this network program is still quite vulnerable, but it serves as a simple and practical way to understand how an HTTP server and socket programming work. Feel free to explore it and see if you can spot potential vulnerabilities. If you’d like to dive even deeper: https://beej.us/guide/bgnet/ https://www.geeksforgeeks.org/html/what-is-http/ https://app.codecrafters.io/courses/http-server/overview


To view or add a comment, sign in

More articles by Harsh Ranjan

Others also viewed

Explore content categories