Warp is a composable, web server in Rust. It's code is very small and focus on speed.
The fundamental building block of warp
is the Filter
, they can be combined and composed to express rich requirements on requests.
But it's actually not easy to use if you are not familiar with it's concept, and the type system will also scare some beginners.
For example, I actually spend some time to figure out how to use Cookie for authorization.
Suppose we have defined a Struct to represent the User:
#[derive(Debug, Deserialize)]
pub struct User {
username: String,
password: String,
}
And we have a logic to authorize whether the login request has a valid username and password, the detail implementation depends on your code:
pub fn verify_user(user: &User) -> bool {
....
}
The question is how to set a Cookie in response and how to verify each request after authorization.
Set a cookie
When a user send login request and passed the authorization, we use reply
with with_header
to set a cookie to store a token, which will be used for later requests:
let login = warp::path!("api" / "login")
.and(warp::post())
.and(warp::body::json())
.map(|user: auth::User| {
if auth::verify_user(&user) {
let token = auth::gen_token();
warp::reply::with_header(
token.clone(),
"set-cookie",
format!("token={}; Path=/; HttpOnly; Max-Age=1209600", token),
)
.into_response()
} else {
warp::reply::with_status("failed", http::StatusCode::UNAUTHORIZED).into_response()
}
});
let routes = routes.or(login);
Use cookie for Authorization
To authorize request, we need to implement a filter
in Warp, and use it like this, here we use Filter::untuple_one
to unroll nested tuple layers from extractions.
let verify = warp::path!("api" / "verify")
.and(warp::get())
.and(auth_validation())
.untuple_one()
.map(|| warp::reply::reply().into_response());
let routes = routes.or(verify);
And the auth_validation
will call another built-in filter warp::cookie
to extract the token
from request header:
struct Unauthorized;
impl reject::Reject for Unauthorized {}
fn auth_validation() -> impl Filter<Extract = ((),), Error = Rejection> + Copy {
warp::cookie::<String>("token").and_then(|token: String| async move {
println!("token: {}", token);
if let Some(true) = auth::verify_token(&token) {
Ok(())
} else {
Err(warp::reject::custom(Unauthorized))
}
})
}
Some thoughts
Even I have spent some time writing code in Rust, I still need to learn some new concepts or some details in a Web framework such as Warp. From my experience, there are some unnatural part for users who used to some other programming languages. For example, the return type of a filter:
impl Filter<Extract = ((),), Error = Rejection> + Copy
It's just not so easy to say what this mean? And why we need to call untuple_one
with the filter?
I know we must obey the rules of type system, and we need to add more annotations when writing Rust code, it's just not so easy for learning as other programming languages.
Even so, I'm still having fun with it for it bringing some new things for programming.
Top comments (0)