Skip to content

Instantly share code, notes, and snippets.

@max-itzpapalotl
Last active February 25, 2024 14:43
Show Gist options
  • Save max-itzpapalotl/e0943bc83ef48ea0a7fa1abf3b9b553e to your computer and use it in GitHub Desktop.
Save max-itzpapalotl/e0943bc83ef48ea0a7fa1abf3b9b553e to your computer and use it in GitHub Desktop.
24. An HTTP server using warp I

24. An HTTP server using warp I

In this video I show how to set up an HTTP server with the warp crate.

Our first GET route

We need the following dependencies in Cargo.toml (we might not need all listed features):

[dependencies]
tokio = { version = "1.36.0", features = ["full"] }
warp = { version = "0.3.6", features = ["tls", "tokio-rustls"] }

Remember that you can add this by doing

cargo add tokio --features full
cargo add warp --features tls,tokio-rustls

Here is our first server:

use std::net::IpAddr;
use warp::{Filter, http::Response};

#[tokio::main]
async fn main() {
    let api = warp::path!("api" / "name" / String)
        .and(warp::get())
        .map(|name| {
            let s = format!("Hello {}!", name);
            println!("Got request, answering with: {}", s);
            Response::builder().body(s)
        });
    let ip_addr: IpAddr = "0.0.0.0".parse().unwrap();
    warp::serve(api).run((ip_addr, 8000)).await;
}

Query with:

curl http://localhost:8000/api/name/hugo

Returning a JSON body

Using the serde_json crate, we can return a JSON body.

Use

[dependencies]
serde_json = "1.0.113"
tokio = { version = "1.36.0", features = ["full"] }
warp = { version = "0.3.6", features = ["tls", "tokio-rustls"] }

we can do this:

use serde_json::json;
use std::net::IpAddr;
use warp::Filter;

#[tokio::main]
async fn main() {
    let api = warp::path!("api" / "name" / String)
        .and(warp::get())
        .map(|name| {
            let j = json!({"version":1, "name": name});
            println!("Got request, answering with: {}", j);
            warp::reply::json(&j)
        });
    let ip_addr: IpAddr = "0.0.0.0".parse().unwrap();
    warp::serve(api).run((ip_addr, 8000)).await;
}

Query with:

curl http://localhost:8000/api/name/hugo

JSON parsing into a struct

Using yet more dependencies:

[dependencies]
bytes = { version = "1.5.0", features = ["serde"] }
serde = { version = "1.0.196", features = ["serde_derive", "derive"] }
serde_json = "1.0.113"
tokio = { version = "1.36.0", features = ["full"] }
warp = { version = "0.3.6", features = ["tls", "tokio-rustls"] }

we can work with structs for our JSON parsing and sending:

use bytes::Bytes;
use serde::{Serialize, Deserialize};
use std::net::IpAddr;
use warp::Filter;

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    age: i32,
}

#[tokio::main]
async fn main() {
    let api = warp::path!("api" / "person")
        .and(warp::post())
        .and(warp::body::bytes())
        .map(|bodybytes: Bytes| {
            let p : Person = serde_json::from_slice(&bodybytes[..]).unwrap();
            println!("Got Person: {:?}", p);
            warp::reply::json(&p)
        });
    let ip_addr: IpAddr = "0.0.0.0".parse().unwrap();
    warp::serve(api).run((ip_addr, 8000)).await;
}

Query with:

curl http://localhost:8000/api/person -d '{"name":"Max", "age": 54}'

Setting up TLS

For this, we need these files:

  • ca.pem: Certificate of a CA (Certificate Authority)
  • cert.pem: Server certificate
  • key.pem: Server key

These files can be generated with the following openssl script:

#!/bin/bash
openssl genrsa -aes256 -passout pass:abcd1234 -out ca-key.pem 2048
openssl req -x509 -new -nodes -extensions v3_ca -key ca-key.pem -days 1024 -out ca.pem -sha512 -subj "/C=DE/ST=NRW/L=Kerpen/O=Neunhoeffer/OU=Max/CN=Max Neunhoeffer/emailAddress=max@9hoeffer.de/" -passin pass:abcd1234
openssl genrsa -passout pass:abcd1234 -out key.pem 2048
cat > ssl.conf <<EOF
[req]
prompt = no
distinguished_name = myself

[myself]
C = de
ST = NRW
L = Kerpen
O = Neunhoeffer
OU = Max
CN = xeo.9hoeffer.de

[req_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = xeo.9hoeffer.de
DNS.3 = 127.0.0.1
EOF

openssl req -new -key key.pem -out key-csr.pem -sha512 -config ssl.conf -subj "/C=DE/ST=NRW/L=Kerpen/O=Neunhoeffer/OU=Labor/CN=xeo.9hoeffer.de/"

openssl x509 -req -in key-csr.pem -CA ca.pem -days 3650 -CAkey ca-key.pem -out cert.pem -extensions req_ext -extfile ssl.conf -passin pass:abcd1234 -CAcreateserial

Here is the code:

use bytes::Bytes;
use serde::{Serialize, Deserialize};
use std::net::IpAddr;
use warp::Filter;

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    age: i32,
}

#[tokio::main]
async fn main() {
    let api = warp::path!("api" / "person")
        .and(warp::post())
        .and(warp::body::bytes())
        .map(|bodybytes: Bytes| {
            let p : Person = serde_json::from_slice(&bodybytes[..]).unwrap();
            println!("Got Person: {:?}", p);
            warp::reply::json(&p)
        });
    let ip_addr: IpAddr = "0.0.0.0".parse().unwrap();
    warp::serve(api)
        .tls()
        .cert_path("cert.pem")
        .key_path("key.pem")
        .run((ip_addr, 8000)).await;
}

Query with:

curl --cacert ca.pem https://localhost:8000/api/person -d '{"name":"Max", "age": 54}' -v

Using tokio::spawn

We can also start the server as follows:

use bytes::Bytes;
use serde::{Serialize, Deserialize};
use std::net::IpAddr;
use warp::Filter;

#[derive(Debug, Serialize, Deserialize)]
struct Person {
    name: String,
    age: i32,
}

#[tokio::main]
async fn main() {
    let api = warp::path!("api" / "person")
        .and(warp::post())
        .and(warp::body::bytes())
        .map(|bodybytes: Bytes| {
            let p : Person = serde_json::from_slice(&bodybytes[..]).unwrap();
            println!("Got Person: {:?}", p);
            warp::reply::json(&p)
        });
    let ip_addr: IpAddr = "0.0.0.0".parse().unwrap();
    let server = warp::serve(api)
        .tls()
        .cert_path("cert.pem")
        .key_path("key.pem")
        .bind((ip_addr, 8000));
    let j = tokio::task::spawn(server);
    j.await.unwrap();
}

References:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment