一文讀懂 Rust axu
先從 HTTP 說起,HTTP 是超文本傳輸協議,是一種客戶端 - 服務器協議,它是 Web 上進行任何數據交換的基礎,客戶端與服務端之間通過交換一個個獨立的消息(而非數據流)進行通信。由客戶端通常是個瀏覽器,現在也可能是 APP,發出的消息被稱作_請求_(request),由服務端發出的應答消息被稱作_響應_(response)。請求是由客戶端發起的,服務器給出響應,可以是 HTML 頁面,也可能是接口數據。例如,如果瀏覽器請求 www.example.com/a.html,那麼服務器將根據請求的 URL 獲取到路徑 a.html,然後服務器找到 a.html 資源文件,返回給瀏覽器。如果 APP 請求 www.example.com/b,那麼服務器將根據請求的 URL 獲取到路徑 b,然後服務器找到 b 對應的處理器執行,然後將執行的結果返回給 APP。服務器端的技術一直在迅猛發展,出現了很多框架,能夠很好的處理來自瀏覽器或客戶端的請求,無論是網頁還是接口數據。它們都基於 HTTP 協議。當前的這些 Web 應用框架大都由這幾大組件組成,路由,處理器,中間件。路由處於 Web 服務的最前端,它接收來自客戶端的請求,將請求提交給處理器進行處理,處理器做具體的事務,如果有中間件,它則處於路由和處理器中間,做一些通用的事務。它們協同完成整個 Web 服務。理解了 HTTP,就很容易理解 Web 應用框架。
axum 是 Rust 語言寫的一個 web 應用框架。它有以下特性:將請求路由到具有無宏 API 的處理程序,這個有別於 Rust 中其它的 web 應用框架。使用提取器解析請求。簡單且可預測的錯誤處理模塊。用最少的模板生成響應。axum 沒有自己的中間件系統,它使用了 tower::Serveice。這意味着 axum 可以免費獲得超時、跟蹤、壓縮、授權等。您還能使用 hyper 或 tonic 編寫的應用程序共享中間件。
先來一個 Hello world!看看 axum 的模樣。
use axum::{
routing::get,
Router,
};
#[tokio::main]
async fn main() {
// build our application with a single route
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
// run it with hyper on localhost:3000
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
我們將按照路由、處理器、中間件的順序介紹 axum。
一、路由 Routing
路由的作用是設置路徑 Path 和處理器 Handler 的對應關係。路由支持在一個路徑上綁定多個方法。
use axum::{Router, routing::get};
// our router
let app = Router::new()
.route("/", get(root))
.route("/foo", get(get_foo).post(post_foo))
.route("/foo/bar", get(foo_bar));
// which calls one of these handlers
async fn root() {}
async fn get_foo() {}
async fn post_foo() {}
async fn foo_bar() {}
axum 的路由很是強大,支持各種操作。
我們先從路由的路徑來看,它支持普通路由,嵌套路由,合併路由。
a、普通路由
普通路由的路徑,以所見即所得的字符串添加爲路徑。
let app = Router::new()
.route("/", get(root))
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(show_user))
.route("/api/:version/users/:id/action", delete(do_users_action))
.route("/assets/*path", get(serve_asset));
b、嵌套路由
嵌套路由的路徑,有一個相同的前綴。其他語言中可能叫分組。
let user_routes = Router::new().route("/:id", get(|| async {}));
let team_routes = Router::new().route("/", post(|| async {}));
let api_routes = Router::new()
.nest("/users", user_routes)
.nest("/teams", team_routes);
let app = Router::new().nest("/api", api_routes);
// Our app now accepts
// - GET /api/users/:id
// - POST /api/teams
說明:嵌套路由看不到原始請求 URI,因爲它去掉了匹配的前綴。如果你提供像靜態文件這樣的服務,那麼請使用 OriginalUri 獲取原始請求 URI。嵌套路由和通配符路由的區別,嵌套路由類似於通配符路由。不同之處在於,通配符路由可以看到整個 URI,而嵌套路由只能看到去掉前綴後的路徑。
c、合併路由
合併路由是將路由合併到一起,合併後的路由路徑是同級的。
// define some routes separately
let user_routes = Router::new()
.route("/users", get(users_list))
.route("/users/:id", get(users_show));
let team_routes = Router::new()
.route("/teams", get(teams_list));
// combine them into one
let app = Router::new()
.merge(user_routes)
.merge(team_routes);
// Our app now accepts
// - GET /users
// - GET /users/:id
// - GET /teams
我們再從路由的處理器 Handler 來看,它支持以下內容:
d、異步函數
具體參考下面的 handler。
e、tower 服務
let app = Router::new()
.route(
// Any request to `/` goes to a service
"/",
// Services whose response body is not `axum::body::BoxBody`
// can be wrapped in `axum::routing::any_service` (or one of the other routing filters)
// to have the response body mapped
any_service(service_fn(|_: Request<Body>| async {
let res = Response::new(Body::from("Hi from `GET /`"));
Ok::<_, Infallible>(res)
}))
)
.route_service(
"/foo",
// This service's response body is `axum::body::BoxBody` so
// it can be routed to directly.
service_fn(|req: Request<Body>| async move {
let body = Body::from(format!("Hi from `{} /foo`", req.method()));
let body = axum::body::boxed(body);
let res = Response::new(body);
Ok::<_, Infallible>(res)
})
)
.route_service(
// GET `/static/Cargo.toml` goes to a service from tower-http
"/static/Cargo.toml",
ServeFile::new("Cargo.toml"),
);
f、路由綁定狀態
將路由與 State 組合時,每個路由必須具有相同類型的狀態。下面會細講。
let routes = Router::new()
.route("/", get(|State(state): State<AppState>| async {
// use state
}))
.with_state(AppState {});
g、層級
將 tower::Layer 應用於路由器中的所有路由。這可以用於爲一組路由的請求添加額外的處理。
let app = Router::new()
.route("/foo", get(|| async {}))
.route("/bar", get(|| async {}))
.layer(TraceLayer::new_for_http());
在應用多箇中間件時,建議使用 tower::ServiceBuilder。有關更多詳細信息,請參閱中間件。
二、處理程序 Handlers
在 axum 中,處理程序是一個異步函數,它接受零個或多個提取器作爲參數,並返回可以轉換爲響應的內容。處理程序是應用程序邏輯所在的地方。對於要用作處理程序的函數,它必須實現 handler 特性。
我們講了 http,我們知道,當發過來了 http 請求時,我們要根據 http 請求中的內容,進行處理,然後返回客戶端想要的結果,即響應。而 http 請求中可包含我們的需求的地方太多,例如,請求路徑,Http 頭,請求參數,請求 Body 等。而 axum 將這些從請求中獲取內容的手段通過 Extractors 來實現。當我們獲取到請求中的內容時,我們可能會返回給客戶端錯誤,或者它想要的數據。基本流程就是這樣,具體的請參考下面的具體說明。
a、提取器 Extractors
提取器從請求 Request 中提取數據的類型和特徵,是一種實現 FromRequest 或 FromRequestParts 的類型。提取器是您挑選傳入請求以獲得處理程序所需零件的方法。
axum 已實現的提取類型有 Json,TypedHeader,Path,Extension,Query,String,Bytes,Request 等,我們這裏以案例進行一些提取器的具體說明。
1,Path 提取器,它將從 URL 獲取捕獲並使用 serde 對其進行解析。任何百分比編碼的參數都將自動解碼。解碼的參數必須是有效的 UTF-8,否則 Path 將失敗並返回 400 錯誤請求響應。axum 中的 Path 支持 3 種格式:靜態路徑,例如 / a,/a/b 等,捕獲模式,例如 / users/:id,/:key 等,通配符模式,例如 / users/*id,/*key 等,它們意如其名,捕獲和通配符的區別是捕獲隻影響到: 的該字段,而通配符影響到 * 之後的所有字段。通過 Path 提取器可以獲取到字段內容,使用方法如下:
async fn users_teams_show(
Path((user_id, team_id)): Path<(Uuid, Uuid)>,
) {
// ...
}
let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
2,Query 提取器,將查詢字符串反序列化爲實現了 serde::Deserialize 的類型。如果查詢字符串無法解析,它將以 400 錯誤請求響應拒絕該請求。
#[derive(Deserialize)]
struct Pagination {
page: usize,
per_page: usize,
}
// This will parse query strings like `?page=2&per_page=30` into `Pagination`
// structs.
async fn list_things(pagination: Query<Pagination>) {
let pagination: Pagination = pagination.0;
// ...
}
let app = Router::new().route("/list_things", get(list_things));
3,Form 提取器,Get 和 Head 方法將請求的參數反序列化,其他的方法將 application/x-www-Form-urlencoded 編碼請求體反序列化。由於解析表單數據可能需要使用請求體,因此如果一個處理程序中有多個提取器,則表單提取器必須是最後一個。具體下面有說明。
#[derive(Deserialize)]
struct SignUp {
username: String,
password: String,
}
async fn accept_form(Form(sign_up): Form<SignUp>) {
// ...
}
4,MultiPart 提取器,用於解析多部分 / 表單數據請求(通常用於文件上傳)。由於從請求中提取多部分表單數據需要消耗主體,因此如果一個處理程序中有多個提取器,則多部分提取器必須是最後一個。具體下面有說明。
use axum::{
extract::Multipart,
routing::post,
Router,
};
use futures_util::stream::StreamExt;
async fn upload(mut multipart: Multipart) {
while let Some(mut field) = multipart.next_field().await.unwrap() {
let name = field.name().unwrap().to_string();
let data = field.bytes().await.unwrap();
println!("Length of `{}` is {} bytes", name, data.len());
}
}
let app = Router::new().route("/upload", post(upload));
5,Json 提取器,用於反序列化請求 Body 中的有效 Json 數據爲目標類型。以下情況會失敗,該請求沒有 Content-Type:application/json(或類似的)頭。正文不包含語法有效的 JSON。正文包含語法有效的 JSON,但無法將其反序列化爲目標類型。緩衝請求正文失敗。由於解析 JSON 需要消耗請求體,因此如果一個處理程序中有多個提取器,JSON 提取器必須是最後一個。具體下面有說明。
#[derive(Deserialize)]
struct CreateUser {
email: String,
password: String,
}
async fn create_user(extract::Json(payload): extract::Json<CreateUser>) {
// payload is a `CreateUser`
}
let app = Router::new().route("/users", post(create_user));
6,TypedHeader 提取器,通常,建議通過 TypedHeader 僅提取所需的標頭,而使用 HeaderMap 提取所有標頭。
use axum::{
TypedHeader,
headers::UserAgent,
routing::get,
Router,
};
async fn users_teams_show(
TypedHeader(user_agent): TypedHeader<UserAgent>,
) {
// ...
}
let app = Router::new().route("/users/:user_id/team/:team_id", get(users_teams_show));
上面僅列出了常用的提取器,如果你需要更多提取器,請查閱文檔。
如果你要使用多個提取器,它是有順序的,他的位置爲函數的參數從左到右排序。請求 Body 只允許提取一次。因此 axum 框架要求這個類型的提取器必須位於函數的最後一個參數。
如果提取器定義了,當請求的內容不滿足提取器時,axum 會拒絕處理請求並返回錯誤。當參數不是必選項時,可以使用 Option 包裝設置爲可選。
use axum::{
extract::Json,
routing::post,
Router,
};
use serde_json::Value;
async fn create_user(payload: Option<Json<Value>>) {
if let Some(payload) = payload {
// We got a valid JSON payload
} else {
// Payload wasn't valid JSON
}
}
let app = Router::new().route("/users", post(create_user));
自定義提取器
如果你有需求要自定義提取器,只需要實現 FromRequestParts 或 FromRequest,它們的區別是 FromRequestParts 不需要實現請求 body。
自定義實現 FromRequestParts 的例子:
use axum::{
async_trait,
extract::FromRequestParts,
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
request::Parts,
},
};
struct ExtractUserAgent(HeaderValue);
#[async_trait]
impl<S> FromRequestParts<S> for ExtractUserAgent
where
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
if let Some(user_agent) = parts.headers.get(USER_AGENT) {
Ok(ExtractUserAgent(user_agent.clone()))
} else {
Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing"))
}
}
}
async fn handler(ExtractUserAgent(user_agent): ExtractUserAgent) {
// ...
}
自定義實現 FromRequest 的例子:
use axum::{
async_trait,
extract::FromRequest,
response::{Response, IntoResponse},
body::Bytes,
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
Request,
},
};
struct ValidatedBody(Bytes);
#[async_trait]
impl<S, B> FromRequest<S, B> for ValidatedBody
where
Bytes: FromRequest<S, B>,
B: Send + 'static,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
let body = Bytes::from_request(req, state)
.await
.map_err(IntoResponse::into_response)?;
// do validation...
Ok(Self(body))
}
}
async fn handler(ValidatedBody(body): ValidatedBody) {
// ...
}
let app = Router::new().route("/foo", get(handler));
b、響應 Responses
任何實現 IntoResponse 的內容都可以從處理程序返回。axum 默認提供了一些類型的實現:Json,Html,(),String,Vec,StatusCode,HeaderMap,impl IntoResponse 等,我們這裏以案例進行一些類型的具體說明。
1,Html,html 響應,它可以使用模板,返回的響應頭將自動獲取 Content-Type: text/html.
普通:
// `Html` will get a `text/html` content-type
async fn html() -> Html<&'static str> {
Html("<p>Hello, World!</p>")
}
使用模板:
async fn greet(extract::Path(name): extract::Path<String>) -> impl IntoResponse {
let template = HelloTemplate { name };
HtmlTemplate(template)
}
#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate {
name: String,
}
struct HtmlTemplate<T>(T);
impl<T> IntoResponse for HtmlTemplate<T>
where
T: Template,
{
fn into_response(self) -> Response {
match self.0.render() {
Ok(html) => Html(html).into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Failed to render template. Error: {}", err),
)
.into_response(),
}
}
}
2,String,返回內容爲字符串。
// String will get a `text/plain; charset=utf-8` content-type
async fn plain_text(uri: Uri) -> String {
format!("Hi from {}", uri.path())
}
3,(),返回空內容。
// `()` gives an empty response
async fn empty() {}
4,Json,返回 Json 格式的數據內容。
// `Json` will get a `application/json` content-type and work with anything that
// implements `serde::Serialize`
async fn json() -> Json<Vec<String>> {
Json(vec!["foo".to_owned(), "bar".to_owned()])
}
上面僅列出了常用的響應返回類型,如果你需要更多類型,請查閱文檔。
通常,它也支持元組返回,例如下面的例子:
// `(StatusCode, impl IntoResponse)` will override the status code of the response
async fn with_status(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
}
// Use `impl IntoResponse` to avoid having to type the whole type
async fn impl_trait(uri: Uri) -> impl IntoResponse {
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
}
// `(HeaderMap, impl IntoResponse)` to add additional headers
async fn with_headers() -> impl IntoResponse {
let mut headers = HeaderMap::new();
headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
(headers, "foo")
}
// Or an array of tuples to more easily build the headers
async fn with_array_headers() -> impl IntoResponse {
([(header::CONTENT_TYPE, "text/plain")], "foo")
}
// Use string keys for custom headers
async fn with_array_headers_custom() -> impl IntoResponse {
([("x-custom", "custom")], "foo")
}
// `(StatusCode, headers, impl IntoResponse)` to set status and add headers
// `headers` can be either a `HeaderMap` or an array of tuples
async fn with_status_and_array_headers() -> impl IntoResponse {
(
StatusCode::NOT_FOUND,
[(header::CONTENT_TYPE, "text/plain")],
"foo",
)
}
// `(Extension<_>, impl IntoResponse)` to set response extensions
async fn with_status_extensions() -> impl IntoResponse {
(
Extension(Foo("foo")),
"foo",
)
}
struct Foo(&'static str);
// Or mix and match all the things
async fn all_the_things(uri: Uri) -> impl IntoResponse {
let mut header_map = HeaderMap::new();
if uri.path() == "/" {
header_map.insert(header::SERVER, "axum".parse().unwrap());
}
(
// set status code
StatusCode::NOT_FOUND,
// headers with an array
[("x-custom", "custom")],
// some extensions
Extension(Foo("foo")),
Extension(Foo("bar")),
// more headers, built dynamically
header_map,
// and finally the body
"foo",
)
}
上面的例子,不能意外覆蓋狀態或正文,因爲 IntoResponseParts 只允許設置標頭和擴展名。
要想控制,請使用 Response。
use axum::{
Json,
response::{IntoResponse, Response},
body::{Full, Bytes},
http::StatusCode,
};
async fn response() -> Response<Full<Bytes>> {
Response::builder()
.status(StatusCode::NOT_FOUND)
.header("x-foo", "custom header")
.body(Full::from("not found"))
.unwrap()
}
如果你需要返回多個響應類型,需要調用. into_response() 將內容轉爲 axum::response::Response
use axum::{
response::{IntoResponse, Redirect, Response},
http::StatusCode,
};
async fn handle() -> Response {
if something() {
"All good!".into_response()
} else if something_else() {
(
StatusCode::INTERNAL_SERVER_ERROR,
"Something went wrong...",
).into_response()
} else {
Redirect::to("/").into_response()
}
}
fn something() -> bool {
// ...
}
fn something_else() -> bool {
// ...
}
關於使用 impl IntoResponse,當從 handler 返回很大的響應類型時,你可以使用 impl IntoResponse 替代,例如下面的例子:
use axum::http::StatusCode;
async fn handler() -> (StatusCode, [(&'static str, &'static str); 1], &'static str) {
(StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
}
上面的例子可以調整爲這樣,這樣返回值更簡單一點。
use axum::{http::StatusCode, response::IntoResponse};
async fn impl_into_response() -> impl IntoResponse {
(StatusCode::OK, [("x-foo", "bar")], "Hello, World!")
}
c、錯誤處理 Error handling
axum 旨在擁有一個簡單且可預測的錯誤處理模型。這意味着將錯誤轉換爲響應非常簡單,並且可以保證所有錯誤都得到處理。
axum 基於 tower::Service,該服務通過其關聯的錯誤類型捆綁錯誤。如果你的服務產生錯誤,並且這個錯誤傳遞到 hyper,則連接將在不發送響應的情況下終止。這通常是不可取的,因此 axum 確保你始終通過依賴類型系統來生成響應。axum 要求所有服務的錯誤類型都是 Infallible,Infallible 是指永遠不會發生錯誤的錯誤類型。
use axum::http::StatusCode;
async fn handler() -> Result<String, StatusCode> {
// ...
}
雖然它看起來可能會因 StatusCode 而失敗,但實際上這並不是一個 “錯誤”。如果此處理程序返回 Err(some_status_code),則該 Err 仍將被轉換爲響應併發送回客戶端。這是通過 StatusCode 的 IntoResponse 實現完成的。無論您是返回 Err(StatusCode::NOT_FOUND)還是 Err(StatusCode::INTERNAL_SERVER_ERROR)都無關緊要。這些在 axum 中不被視爲錯誤。與其使用直接的 StatusCode,不如使用最終可以轉換爲 Response 的中間錯誤類型。這允許使用?運算符處理程序。參見這些示例:
let app = Router::new().route("/", get(handler));
async fn handler() -> Result<(), AppError> {
try_thing()?;
Ok(())
}
fn try_thing() -> Result<(), anyhow::Error> {
anyhow::bail!("it failed!")
}
// Make our own error that wraps `anyhow::Error`.
struct AppError(anyhow::Error);
// Tell axum how to convert `AppError` into a response.
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
// `Result<_, AppError>`. That way you don't need to do that manually.
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}
d、在處理程序中共享狀態
在處理程序之間共享某些狀態是很常見的。例如:可能需要共享數據庫連接池或到其他服務的客戶端。如果瞭解 Rust 的多線程共享數據,這裏很方便理解。
最常見的三種方法是:
1,使用 State,State 是一種提取器,它更安全。缺點是它的動態性不如請求擴展。
use axum::{
extract::State,
routing::get,
Router,
};
use std::sync::Arc;
struct AppState {
// ...
}
let shared_state = Arc::new(AppState { /* ... */ });
let app = Router::new()
.route("/", get(handler))
.with_state(shared_state);
async fn handler(
State(state): State<Arc<AppState>>,
) {
// ...
}
2,使用請求擴展,這種方法的缺點是,如果您試圖提取一個不存在的擴展,可能是因爲您忘記添加中間件,或者因爲提取了錯誤的類型,則會出現運行時錯誤(特別是 500 內部服務器錯誤響應)。
use axum::{
extract::Extension,
routing::get,
Router,
};
use std::sync::Arc;
struct AppState {
// ...
}
let shared_state = Arc::new(AppState { /* ... */ });
let app = Router::new()
.route("/", get(handler))
.layer(Extension(shared_state));
async fn handler(
Extension(state): Extension<Arc<AppState>>,
) {
// ...
}
3,使用閉包捕獲,這種方法的缺點是它比使用 State 或 Extension 更冗長。
use axum::{
Json,
extract::{Extension, Path},
routing::{get, post},
Router,
};
use std::sync::Arc;
use serde::Deserialize;
struct AppState {
// ...
}
let shared_state = Arc::new(AppState { /* ... */ });
let app = Router::new()
.route(
"/users",
post({
let shared_state = Arc::clone(&shared_state);
move |body| create_user(body, shared_state)
}),
)
.route(
"/users/:id",
get({
let shared_state = Arc::clone(&shared_state);
move |path| get_user(path, shared_state)
}),
);
async fn get_user(Path(user_id): Path<String>, state: Arc<AppState>) {
// ...
}
async fn create_user(Json(payload): Json<CreateUserPayload>, state: Arc<AppState>) {
// ...
}
#[derive(Deserialize)]
struct CreateUserPayload {
// ...
}
三、中間件 Middleware
axum 的獨特之處在於,它沒有自己定製的中間件系統,它與 tower 集成。這意味着 tower 和 tower http 中間件的生態系統,axum 都可以使用。當您使用 Router::layer(或類似功能)添加中間件時,所有先前添加的路由都將封裝在中間件中。一般來說,這導致中間件從下到上執行。建議使用 tower::ServiceBuilder 添加多箇中間件,ServiceBuilder 的工作原理是將所有層組成一個層,使它們從上到下運行。因此,對於前面的代碼,layer_one 將首先接收請求,然後是 layer_2,然後是第三層,然後是處理程序,然後響應將通過第三層、第二層,最後是 layer_one 返回。從上到下執行中間件通常更容易理解和理解,這也是推薦 ServiceBuilder 的原因之一。多箇中間件的執行順序爲:
use tower::ServiceBuilder;
use axum::{routing::get, Router};
async fn handler() {}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(layer_one)
.layer(layer_two)
.layer(layer_three),
);
tower 常用的中間件有:
-
用於高級跟蹤 / 日誌記錄的 TraceLayer。
-
CorsLayer 用於處理 CORS。
-
CompressionLayer 用於自動壓縮響應。
-
RequestIdLayer 和 PropagateRequestIdLayer 設置並傳播請求 ID。
-
超時的超時層。請注意,這需要使用 HandleErrorLayer 將超時轉換爲響應。
你可以直接使用在你的應用中,使用 layer 添加即可。
當您希望執行一個小的即席操作,例如添加一個標頭。您不打算將您的中間件發佈爲供其他人使用的板條箱。那麼 tower 有幾個實用組合子,可以用來對請求或響應進行簡單的修改。
最常用的是:
ServiceBuilder::map_request
use tower::ServiceBuilder;
use tower::ServiceExt;
// Suppose we have some `Service` whose request type is `String`:
let string_svc = tower::service_fn(|request: String| async move {
println!("request: {}", request);
Ok(())
});
// ...but we want to call that service with a `usize`. What do we do?
let usize_svc = ServiceBuilder::new()
// Add a middlware that converts the request type to a `String`:
.map_request(|request: usize| format!("{}", request))
// ...and wrap the string service with that middleware:
.service(string_svc);
// Now, we can call that service with a `usize`:
usize_svc.oneshot(42).await?;
ServiceBuilder::map_response
// A service returning Result<Record, _>
let service = DatabaseService::new("127.0.0.1:8080");
// Map the response into a new response
let mut new_service = service.map_response(|record| record.name);
// Call the new service
let id = 13;
let name = new_service
.ready()
.await?
.call(id)
.await?;
ServiceBuilder::then
// A service returning Result<Record, DbError>
let service = DatabaseService::new("127.0.0.1:8080");
// An async function that attempts to recover from errors returned by the
// database.
async fn recover_from_error(error: DbError) -> Result<Record, DbError> {
// ...
}
// If the database service returns an error, attempt to recover by
// calling `recover_from_error`. Otherwise, return the successful response.
let mut new_service = service.then(|result| async move {
match result {
Ok(record) => Ok(record),
Err(e) => recover_from_error(e).await,
}
});
// Call the new service
let id = 13;
let record = new_service
.ready()
.await?
.call(id)
.await?;
ServiceBuilder::and_then
// A service returning Result<Record, _>
let service = DatabaseService::new("127.0.0.1:8080");
// Map the response into a new response
let mut new_service = service.and_then(|record: Record| async move {
let name = record.name;
avatar_lookup(name).await
});
// Call the new service
let id = 13;
let avatar = new_service.call(id).await.unwrap();
axum 提供了許多編寫中間件的方法,具有不同的抽象級別,並且有不同的優缺點。
1,在以下情況下使用 axum::middleware::from_fn 編寫中間件:
您對實現自己的 Future 感到不舒服,寧願使用熟悉的 async/await 語法。
您不打算將您的中間件發佈爲供其他人使用的板條箱。像這樣編寫的中間件只與 axum 兼容。
use axum::{
Router,
http::{self, Request},
routing::get,
response::Response,
middleware::{self, Next},
};
async fn my_middleware<B>(
request: Request<B>,
next: Next<B>,
) -> Response {
// do something with `request`...
let response = next.run(request).await;
// do something with `response`...
response
}
let app = Router::new()
.route("/", get(|| async { /* ... */ }))
.layer(middleware::from_fn(my_middleware));
2,在以下情況下使用 axum::middleware::from_extractor 編寫中間件:
您有一種類型,有時您想用作提取器,有時又想用作中間件。如果您只需要將您的類型作爲中間件,則更喜歡 middleware::from_fn。
use axum::{
extract::FromRequestParts,
middleware::from_extractor,
routing::{get, post},
Router,
http::{header, StatusCode, request::Parts},
};
use async_trait::async_trait;
// An extractor that performs authorization.
struct RequireAuth;
#[async_trait]
impl<S> FromRequestParts<S> for RequireAuth
where
S: Send + Sync,
{
type Rejection = StatusCode;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let auth_header = parts
.headers
.get(header::AUTHORIZATION)
.and_then(|value| value.to_str().ok());
match auth_header {
Some(auth_header) if token_is_valid(auth_header) => {
Ok(Self)
}
_ => Err(StatusCode::UNAUTHORIZED),
}
}
}
fn token_is_valid(token: &str) -> bool {
// ...
}
async fn handler() {
// If we get here the request has been authorized
}
async fn other_handler() {
// If we get here the request has been authorized
}
let app = Router::new()
.route("/", get(handler))
.route("/foo", post(other_handler))
// The extractor will run before all routes
.route_layer(from_extractor::<RequireAuth>());
庫集成 Axum 說明
想要提供 FromRequest、FromRequestParts 或 IntoResponse 實現的庫,應該依賴 axum-core,而不是 axum(如果可能的話)。axum-core 包含核心類型和特徵,不太可能受到破壞性變化。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/G8rS2G9xFMn_CmjmZPuw-Q