🚀 Day 32: Crafting a Single-Threaded Web Server in Rust! 🕸 💻✨
Tags: rust, 100daysofcode, programming, learning
Published: 2024-02-02 19:53:51

Greetings, fellow Rustaceans! Today, I embarked on a journey to channel my inner web developer spirit by crafting a single-threaded web server using Rust. Because let's face it, what's more exciting than building a web server from scratch? So, grab your favorite beverage and let's dive into the delightful world of Rust web development! ☕🦀

> Web development in Rust? Absolutely! As someone with a fervent love for web development, I've decided to explore the untamed lands of Rust and see how it handles the mighty task of serving web pages. This is just the beginning; we're laying the foundation for future explorations into Rust web frameworks and more intricate projects. So, fasten your seatbelts, and let's roll with our single-threaded web server!

---

## 🏁🎬Setting the Stage🎬🏁

Before we dive into the code, let's set the stage. We're going to build a simple, single-threaded web server. It's like the "Hello, World!" of web servers - simple, but a great way to get your feet wet.😉

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

Here, we're just importing the necessary modules. `fs` for file handling, `BufReader` for buffered reading of our `TcpStream`, and `TcpListener` and `TcpStream` for network programming. It's like gathering our ingredients before we start cooking. 🍳

---

## 🏃‍♂️🚦Starting the Server🚦🏃‍♂️

```rust
fn main() {
    if let Err(err) = run_server() {
        eprintln!("Error: {}", err);
    }
}
```

`main` function is the entry point of our server, in our `main` function, we're calling the `run_server` function. We're using the `if let` construct to handle any potential errors. If `run_server` encounters an error, it handles it by printing them to the standard error output.


```rust
fn run_server() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:7878")?;
    println!("Server running on http://127.0.0.1:7878/");

    for stream in listener.incoming() {
        let stream = stream?;
        handle_connection(stream)?;
    }

    Ok(())
}
```

In `run_server`, we create a `TcpListener` that will listen for incoming TCP connections at the specified address and port (127.0.0.1:7878). This is like setting up a shop and putting out an open sign. Here, we're looping over the `listener.incoming()`, which creates an iterator over the incoming `TcpStream`. For each incoming stream (connection), we call `handle_connection`.

---

## 🤝🔌Handling Connections🔌🤝

```rust
fn handle_connection(mut stream: TcpStream) -> std::io::Result<()> {
    let buf_reader = BufReader::new(&mut stream);
    let request_line = match buf_reader.lines().next() {
        Some(line) => line?,
        None => return Ok(()), // Ignore empty requests
    };
...

```

In `handle_connection`, we wrap our `stream` in a `BufReader`, which gives us handy methods for reading. We then attempt to read the first line of the request.



```rust
let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
        ("HTTP/1.1 200 OK", "src/hello.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND", "src/404.html")
    };
```

We check if the request line is asking for the root path ("/"). If it is, we prepare a 200 status and the `hello.html` file. If it's not, we prepare a 404 status and the `404.html` file.



```rust
let contents = match fs::read_to_string(filename) {
        Ok(content) => content,
        Err(_) => {
            return Ok(());
        }
    };
```

We attempt to read the contents of the chosen file. If we succeed, we store the contents. If we fail, we simply return from the function.



```rust
let length = contents.len();
    let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
```

We calculate the length of the response and then format our response. This response includes the status line, the content length, and the contents of our file.



```rust
stream.write_all(response.as_bytes())?;
    println!("Connection established!");

    Ok(())
}
```

Finally, we write our response back to the stream, print a message to the console, and return `Ok(())` to signify that everything went well.

---

## Complete Code

Filename: **main.rs**

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

fn main() {
    if let Err(err) = run_server() {
        eprintln!("Error: {}", err);
    }
}

fn run_server() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:7878")?;
    println!("Server running on http://127.0.0.1:7878/");

    for stream in listener.incoming() {
        let stream = stream?;
        handle_connection(stream)?;
    }

    Ok(())
}

fn handle_connection(mut stream: TcpStream) -> std::io::Result<()> {
    let buf_reader = BufReader::new(&mut stream);
    let request_line = match buf_reader.lines().next() {
        Some(line) => line?,
        None => return Ok(()), // Ignore empty requests
    };

    let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
        ("HTTP/1.1 200 OK", "src/hello.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND", "src/404.html")
    };

    let contents = match fs::read_to_string(filename) {
        Ok(content) => content,
        Err(_) => {
            return Ok(());
        }
    };

    let length = contents.len();
    let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");

    stream.write_all(response.as_bytes())?;
    println!("Connection established!");

    Ok(())
}
```

---

Filename: **hello.html**

```rust
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust Web Server</title>
</head>
<body>
    <h1>Hello, Rustaceans!</h1>
    <p>This is my single threaded web server using Rust✨🥳</p>
</body>
</html>
```

---

Filename: **404.html**

```rust
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Page Not Found</title>
</head>

<body>
    <h1>Oops!</h1>
    <p>Sorry, I don't know what are you asking for?</p>
</body>

</html>
```

---

### Output

```bash
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/http_server`
Server running on http://127.0.0.1:7878/
```

---


![Client side output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wes9brgnvu2rw4tzy76y.png)

---

## 🎉🏁And... Cut!🏁🎉

Today's performance was nothing short of thrilling! We've set up a basic, single-threaded web server in Rust, ready to conquer the world of web development. As we continue this Rust web odyssey, we'll explore frameworks, build projects, and bring more excitement to the stage. Stay tuned, fellow developers; the best is yet to come! 🌐🚀