In this video I show how to set up an HTTP server with the warp crate.
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
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
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}'
For this, we need these files:
ca.pem
: Certificate of a CA (Certificate Authority)cert.pem
: Server certificatekey.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/[email protected]/" -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
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();
}
- Warp crate: https://github.com/seanmonstar/warp
- Warp documentation: https://docs.rs/warp/latest/warp/
- JSON serde: https://docs.rs/serde_json/latest/serde_json/
- Serde: https://github.com/serde-rs/serde
- Video: https://youtu.be/CMminhJdfuE
- Overview: https://gist.github.com/max-itzpapalotl/18f7675a60f6f9603250367bcb63992e