Prerequisites

Before we start, ensure that you have the following installed on your machine:

  • Rust (latest stable version)
  • Cargo (Rust's package manager and build system)

You can install Rust by following the instructions at rustup.rs.

Setting Up Your Project

First, create a new Rust project using Cargo. Open your terminal and run:

cargo new rust_web_server
cd rust_web_server

This command creates a new directory named rust_web_server with a basic Rust project structure.

Adding Dependencies

Next, we need to add the hyper crate to our project. Open Cargo.toml in your project directory and add the following lines under [dependencies]:

[dependencies]
hyper = "0.14"
tokio = { version = "1", features = ["full"] }

The hyper crate will handle HTTP requests and responses, while tokio provides an asynchronous runtime.

Writing the Server Code

Now, let's write the code for our web server. Open src/main.rs and replace its contents with the following code:

use hyper::{Body, Request, Response, Server, StatusCode};
use hyper::service::{make_service_fn, service_fn};
use std::convert::Infallible;

async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
    match req.uri().path() {
        "/" => Ok(Response::new(Body::from("Welcome to the Rust Web Server!"))),
        "/hello" => Ok(Response::new(Body::from("Hello, World!"))),
        _ => {
            let mut not_found = Response::new(Body::from("404 Not Found"));
            *not_found.status_mut() = StatusCode::NOT_FOUND;
            Ok(not_found)
        }
    }
}

#[tokio::main]
async fn main() {
    let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle_request)) });

    let addr = ([127, 0, 0, 1], 3000).into();
    let server = Server::bind(&addr).serve(make_svc);

    println!("Listening on http://{}", addr);

    if let Err(e) = server.await {
        eprintln!("Server error: {}", e);
    }
}

Code Explanation

  1. Imports: We import necessary modules from hyper, including Body, Request, Response, and others for handling HTTP requests.
  1. Request Handler: The handle_request function takes an HTTP request and returns a response. It matches the request URI to determine the appropriate response:
  • For the root path (/), it returns a welcome message.
  • For the /hello path, it returns a "Hello, World!" message.
  • For any other path, it returns a 404 Not Found response.
  1. Main Function: The main function initializes the server. It binds to 127.0.0.1:3000 and starts listening for incoming requests.

Running the Server

To run your web server, execute the following command in your terminal:

cargo run

You should see the output:

Listening on http://127.0.0.1:3000

Now, open your web browser or use a tool like curl to test your server:

  • Navigate to http://127.0.0.1:3000/ to see the welcome message.
  • Navigate to http://127.0.0.1:3000/hello to see "Hello, World!".
  • Navigate to a non-existent path, like http://127.0.0.1:3000/test, to see the 404 response.

Error Handling and Best Practices

While the above implementation is functional, there are several best practices to consider:

  1. Error Handling: Implement proper error handling to manage potential issues gracefully. For instance, you can log errors and return user-friendly messages.
  1. Asynchronous Programming: Leverage Rust's asynchronous capabilities to handle multiple requests concurrently. The tokio runtime allows you to scale your server efficiently.
  1. Configuration: Use environment variables or configuration files to manage server settings, such as the host and port.
  1. Logging: Consider adding logging to monitor server activity and errors. The log and env_logger crates are excellent choices for this purpose.
  1. Testing: Write unit tests for your request handler to ensure it behaves as expected. You can use the hyper testing utilities to simulate requests.

Conclusion

In this tutorial, we built a simple web server using Rust and the hyper crate. We covered the setup, code structure, and best practices for developing a robust server application. This foundational knowledge will help you explore more complex web applications in Rust.


Learn more with useful resources