简介
warp是一个超级便捷、可组合、速度极快的异步Web框架。目前最新版本为v0.2.3,尚未稳定。内部主要基于hyper框架实现,同一个作者,而hyper主要基于tokio。因此hyper的大部分特性都可以在warp中得到了继承:
- HTTP/1
- HTTP/2
- Asynchronous
warp的基本组成部分是filter,他们可以通过组合满足各种不同的需求,个人不太喜欢这种编程风格,warp提供以下开箱即用的功能:
- Path routing and parameter extraction
- Header requirements and extraction
- Query string deserialization
- JSON and Form bodies
- Multipart form data
- Static Files and Directories
- Websockets
- Access logging
- Gzip, Deflate, and Brotli compression
快速开始
- 创建项目
cargo new warp-demo
- 在cargo.toml中添加依赖
[dependencies]
tokio = { version = "0.2", features = ["macros"] }
warp = "0.2"
- 修改main.js
use warp::Filter;
#[tokio::main]
async fn main() {
// GET /hello/warp => 200 OK with body "Hello, warp!"
let hello = warp::path!("hello" / String)
.map(|name| format!("Hello, {}!", name));
warp::serve(hello)
.run(([127, 0, 0, 1], 8080))
.await;
}
- 启动
cargo run
- 访问
打开浏览器访问http://localhost:8080/hello/warp
Request和Response
从path和body中获取参数
#![deny(warnings)]
use serde_derive::{Deserialize, Serialize};
use warp::Filter;
#[derive(Deserialize, Serialize)]
struct Employee {
name: String,
rate: u32,
}
#[tokio::main]
async fn main() {
pretty_env_logger::init();
// POST /employees/:rate {"name":"Sean","rate":2}
let promote = warp::post()
.and(warp::path("employees"))
.and(warp::path::param::<u32>())
// Only accept bodies smaller than 16kb...
.and(warp::body::content_length_limit(1024 * 16))
.and(warp::body::json())
.map(|rate, mut employee: Employee| {
employee.rate = rate;
warp::reply::json(&employee)
});
warp::serve(promote).run(([127, 0, 0, 1], 3030)).await
}
从query中获取参数、设置状态码
cargo.toml
tokio = { version = "0.2", features = ["macros"] }
warp = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0.55"}
log = "0.4"
pretty_env_logger = "0.3"
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use warp::{
http::{Response, StatusCode},
Filter,
};
#[derive(Deserialize, Serialize)]
struct MyObject {
key1: String,
key2: u32,
}
#[tokio::main]
async fn main() {
pretty_env_logger::init();
// get /example1?key=value
// demonstrates an optional parameter.
let example1 = warp::get()
.and(warp::path("example1"))
.and(warp::query::<HashMap<String, String>>())
.map(|p: HashMap<String, String>| match p.get("key") {
Some(key) => Response::builder().body(format!("key = {}", key)),
None => Response::builder().body(String::from("No \"key\" param in query.")),
});
// get /example2?key1=value,key2=42
// uses the query string to populate a custom object
let example2 = warp::get()
.and(warp::path("example2"))
.and(warp::query::<MyObject>())
.map(|p: MyObject| {
Response::builder().body(format!("key1 = {}, key2 = {}", p.key1, p.key2))
});
let opt_query = warp::query::<MyObject>()
.map(Some)
.or_else(|_| async { Ok::<(Option<MyObject>,), std::convert::Infallible>((None,)) });
// get /example3?key1=value,key2=42
// builds on example2 but adds custom error handling
let example3 =
warp::get()
.and(warp::path("example3"))
.and(opt_query)
.map(|p: Option<MyObject>| match p {
Some(obj) => {
Response::builder().body(format!("key1 = {}, key2 = {}", obj.key1, obj.key2))
}
None => Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(String::from("Failed to decode query param.")),
});
warp::serve(example1.or(example2).or(example3))
.run(([127, 0, 0, 1], 3030))
.await
}
静态文件、目录
#![deny(warnings)]
use warp::Filter;
#[tokio::main]
async fn main() {
pretty_env_logger::init();
let readme = warp::get()
.and(warp::path::end())
.and(warp::fs::file("./README.md"));
// dir already requires GET...
let examples = warp::path("ex").and(warp::fs::dir("./examples/"));
// GET / => README.md
// GET /ex/... => ./examples/..
let routes = readme.or(examples);
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
websocket
#![deny(warnings)]
use futures::{FutureExt, StreamExt};
use warp::Filter;
#[tokio::main]
async fn main() {
pretty_env_logger::init();
let routes = warp::path("echo")
// The `ws()` filter will prepare the Websocket handshake.
.and(warp::ws())
.map(|ws: warp::ws::Ws| {
// And then our closure will be called when it completes...
ws.on_upgrade(|websocket| {
// Just echo all messages back...
let (tx, rx) = websocket.split();
rx.forward(tx).map(|result| {
if let Err(e) = result {
eprintln!("websocket error: {:?}", e);
}
})
})
});
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
}
重定向
#![deny(warnings)]
use warp::{http::Uri, Filter};
#[tokio::test]
async fn redirect_uri() {
let over_there = warp::any().map(|| warp::redirect(Uri::from_static("/over-there")));
let req = warp::test::request();
let resp = req.reply(&over_there).await;
assert_eq!(resp.status(), 301);
assert_eq!(resp.headers()["location"], "/over-there");
}
tls
#![deny(warnings)]
// Don't copy this `cfg`, it's only needed because this file is within
// the warp repository.
#[cfg(feature = "tls")]
#[tokio::main]
async fn main() {
use warp::Filter;
// Match any request and return hello world!
let routes = warp::any().map(|| "Hello, World!");
warp::serve(routes)
.tls()
.cert_path("examples/tls/cert.pem")
.key_path("examples/tls/key.rsa")
.run(([127, 0, 0, 1], 3030))
.await;
}
#[cfg(not(feature = "tls"))]
fn main() {
eprintln!("Requires the `tls` feature.");
}