Rust LogoBuilding a Single-Threaded Web Server

Building a single-threaded web server is a fundamental exercise for understanding network programming and the HTTP protocol. In essence, a web server listens for incoming network requests (typically HTTP requests over TCP), processes them, and sends back a response. A 'single-threaded' server means it handles one client connection at a time. When a new request arrives, the server fully processes it, sends a response, and then closes the connection before it can accept or process another incoming request.

How it Works (Single-Threaded Model):
1. Binding and Listening: The server first binds to a specific network address and port (e.g., `127.0.0.1:7878`). It then enters a listening state, waiting for incoming client connections.
2. Accepting a Connection: When a client attempts to connect, the server accepts the connection. In a single-threaded model, this `accept` operation is blocking; the server pauses execution until a connection is established.
3. Reading the Request: Once connected, the server reads data from the client's connection stream. This data typically constitutes an HTTP request, which includes a request line (e.g., `GET / HTTP/1.1`), headers (e.g., `Host`, `User-Agent`), and an optional body.
4. Processing the Request: The server parses the received request to understand what the client is asking for (e.g., which resource to retrieve, what action to perform). For a simple server, this might involve checking the requested path (`/`) and preparing a corresponding response.
5. Sending the Response: The server constructs an HTTP response, which also includes a status line (e.g., `HTTP/1.1 200 OK`), headers (e.g., `Content-Type`, `Content-Length`), and the response body (e.g., HTML content).
6. Closing the Connection: After sending the complete response, the server typically closes the connection with the client.
7. Looping: The server then returns to the 'Accepting a Connection' step, waiting for the next client. While one client is being served, all other incoming connection attempts will either be queued by the operating system (up to a limit) or rejected until the current connection is fully handled.

Limitations of Single-Threaded Servers:
* Blocking Nature: Each step (accepting, reading, writing) is blocking. If reading a request or writing a response takes a long time (e.g., due to slow network or large file transfer), the server becomes unresponsive to all other clients during that period.
* Poor Performance Under Load: As only one request can be processed at a time, throughput is very low. This architecture is unsuitable for real-world applications that need to handle many concurrent users.

Why Build One?
Despite its limitations, building a single-threaded web server is an excellent learning exercise. It helps developers understand:
* The fundamentals of TCP/IP sockets.
* The basics of the HTTP protocol (request/response structure).
* Input/output operations over network streams.
* Error handling in network applications.

It lays the groundwork for understanding more complex, high-performance server architectures, such as multi-threaded, asynchronous, or event-driven models.

Example Code

use std::net::{TcpListener, TcpStream};
use std::io::{prelude::*, BufReader};
use std::fs;

fn main() {
    // 1. Bind to an address and port
    // unwrap() is used for simplicity in this example. In real applications, handle the Result.
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    println!("Server listening on http://127.0.0.1:7878");

    // 2. Loop indefinitely to accept and handle connections
    for stream in listener.incoming() {
        let stream = stream.unwrap(); // Handle errors in a real app
        handle_connection(stream);
    }
}

fn handle_connection(mut stream: TcpStream) {
    // Create a buffered reader for the TCP stream to easily read lines
    let buf_reader = BufReader::new(&mut stream);

    // 3. Read the incoming HTTP request line by line
    // We only need the first line to determine the request path for this simple server.
    let request_line = buf_reader.lines().next().unwrap().unwrap();

    // Log the request line for debugging
    println!("Request: {}", request_line);

    // 4. Simple request processing
    let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
        ("HTTP/1.1 200 OK", "hello.html")
    } else if request_line == "GET /sleep HTTP/1.1" {
        // Simulate a slow operation for demonstration of single-threaded blocking
        std::thread::sleep(std::time::Duration::from_secs(5));
        ("HTTP/1.1 200 OK", "hello.html") // Still serve hello.html after delay
    } else {
        // For any other request, return a 404 Not Found error
        ("HTTP/1.1 404 NOT FOUND", "404.html")
    };

    // Read the content of the file into a string
    let contents = fs::read_to_string(filename).unwrap();
    let content_length = contents.len();

    // 5. Construct and send the HTTP response
    let response = format!(
        "{}\r\nContent-Length: {}\r\n\r\n{}",
        status_line,
        content_length,
        contents
    );

    stream.write_all(response.as_bytes()).unwrap();
    stream.flush().unwrap(); // Ensure all buffered data is sent immediately

    // 6. The connection is implicitly closed when `stream` goes out of scope
    // after `handle_connection` returns.
}

// To make this example runnable, create two simple HTML files in the same directory as your src/main.rs:
//
// 1. `hello.html`:
//    ```html
//    <!DOCTYPE html>
//    <html lang="en">
//    <head>
//        <meta charset="utf-8">
//        <title>Hello!</title>
//    </head>
//    <body>
//        <h1>Hello from Rust!</h1>
//        <p>This is a simple single-threaded web server example.</p>
//    </body>
//    </html>
//    ```
//
// 2. `404.html`:
//    ```html
//    <!DOCTYPE html>
//    <html lang="en">
//    <head>
//        <meta charset="utf-8">
//        <title>Not Found</title>
//    </head>
//    <body>
//        <h1>404</h1>
//        <p>Oops! The page you requested could not be found.</p>
//    </body>
//    </html>
//    ```

// How to run:
// 1. Save the Rust code as `src/main.rs` in a new Cargo project.
// 2. Create the `hello.html` and `404.html` files in the root of your Cargo project.
// 3. Run `cargo run` in your terminal.
// 4. Open your web browser and navigate to `http://127.0.0.1:7878` or `http://127.0.0.1:7878/sleep`.
//    You can also try `http://127.0.0.1:7878/nonexistent` to see the 404 page.