Skip to content

Building HTTP service with Rust

Cargo

Cargo is the package manager and build system for Rust programming language. It is used to manage dependencies, build, and run Rust projects. Here are a few frequently used cargo commands:

txt
cargo new: Creates a new Rust project.
cargo build: Builds the project and its dependencies.
cargo run: Builds and runs the project.
cargo test: Runs the tests in the project.
cargo check: Checks the syntax and type correctness without producing an executable.
cargo update: Updates dependencies in the project.
cargo clean: Cleans the project files and directories.

Create a project with cargo

shell
mkdir rust-study && cd rust-study
cargo init

Then we will have a project with this layout

shell
lujunjie@mac:/Users/lujunjie/code/github/jaylu/rust-study > tree
.
├── Cargo.toml
└── src
    └── main.rs

More folder structure info can be seen here

When can then try to run the project via cargo run

shell
lujunjie@mac:/Users/lujunjie/code/github/jaylu/rust-study > cargo run 
    Blocking waiting for file lock on package cache
   Compiling rust-study v0.1.0 (/Users/lujunjie/code/github/jaylu/rust-study)
    Finished dev [unoptimized + debuginfo] target(s) in 10.62s
     Running `target/debug/rust-study`
Hello, world!

Here we are, the project skeleton is set up!!

Building a simple web server with Actix

I try do some quick study of the Rust HTTP lib:

LibraryAsync SupportPerformanceStabilityEase of UseFeatures
Actix WebYesHighStableModerateMiddlewares, WebSocket
RocketYesModerateStableEasyMacros, Form Handling
WarpYesHighExperimentalEasyFilters, WebSockets
HyperYesHighStableModerateLow-level, Flexibility
TideYesHighExperimentalEasyMiddleware Chain, Async
IronNoModerateUnmaintainedModerateMiddleware
GothamYesHighExperimentalEasyRouter, Middlewares

And Actix seems to be supporting Middleware and the API is quite fit for my intuition. Below is what I had done to start up a simple http server:

  1. Adding dependencies in cargo.toml

    txt
    [dev-dependencies]
    actix-web = "4"
    actix-files = "0.6"
    env_logger = "0.9.0"
    log = "0.4"
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1"
    futures = "0.3"
  2. Add code in examples/actix_web.rs

    rust
    use actix_files::Files;
    use actix_web::{App, get, HttpServer, middleware::Logger, Responder, Result, web};
    use serde::Deserialize;
    
    #[get("/hello/{name}")]
    async fn greet(name: web::Path<String>) -> impl Responder {
        format!("Hello {name}!")
    }
    
    #[derive(Deserialize)]
    struct Info {
        username: String,
    }
    
    /// extract `Info` using serde
    async fn do_post(info: web::Json<Info>) -> Result<String> {
        println!("received request");
        Ok(format!("Welcome {}!", info.username))
    }
    
    
    #[actix_web::main] // or #[tokio::main]
    async fn main() -> std::io::Result<()> {
        env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
        log::info!("starting HTTP server at http://localhost:8080");
    
        HttpServer::new(|| {
            App::new()
                .route("/hello", web::get().to(|| async { "Hello World!" }))
                .route("/post", web::post().to(do_post))
                .service(greet)
                .service(Files::new("/web", "./examples/static")
                    .show_files_listing()
                    .use_last_modified(true)
                )
                .wrap(Logger::default())
        })
            .bind(("127.0.0.1", 8080))?
            .run()
            .await
    }
  3. Run command cargo run --example actix_web

source code here

Summary

It's simple enough to kick-start, but I might want a more low level HTTP server to build a web proxy in the near future, maybe let's look at using Hyper next time to read the request body in a streaming way.

Reference