系列文章目錄
第一章 axum學(xué)習(xí)使用
前言
本職java開發(fā),兼架構(gòu)設(shè)計(jì)。空閑時(shí)間學(xué)習(xí)了rust,目前還不熟練掌握。想著用urst開發(fā)個(gè)web服務(wù),正好熟悉一下rust語言開發(fā)。
目前rust 語言web開發(fā)相關(guān)的框架已經(jīng)有很多,但還是和java,go語言比不了。
這個(gè)系列想完整走一遍web開發(fā),后續(xù)有時(shí)間就出orm,還有一些別的web用到的庫教程。
言歸正傳,開始學(xué)習(xí)axum框架
老規(guī)矩先看官方文檔介紹
Axum是一個(gè)專注于人體工程學(xué)和模塊化的Web應(yīng)用程序框架。
高級功能
使用無宏 API 將請求路由到處理程序。
使用提取程序以聲明方式分析請求。
簡單且可預(yù)測的錯(cuò)誤處理模型。
使用最少的樣板生成響應(yīng)。
充分利用塔和塔-http生態(tài)系統(tǒng) 中間件、服務(wù)和實(shí)用程序。
特別是,最后一點(diǎn)是與其他框架的區(qū)別。 沒有自己的中間件系統(tǒng),而是使用tower::Service。這意味著獲得超時(shí)、跟蹤、壓縮、 授權(quán)等等,免費(fèi)。它還使您能夠與 使用 hyper 或 tonic 編寫的應(yīng)用程序。axumaxumaxum
兼容性
Axum旨在與Tokio和Hyper配合使用。運(yùn)行時(shí)和 傳輸層獨(dú)立性不是目標(biāo),至少目前是這樣。
tokio框架在rust異步當(dāng)中相當(dāng)流行。axum能很好地搭配tokio實(shí)現(xiàn)異步web
二、hello world
看看官方例子
use axum::{
routing::get,
Router,
};
#[tokio::main]
async fn main() {
// 構(gòu)建router
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
// 運(yùn)行hyper http服務(wù) localhost:3000
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
要想使用還需要引入庫
[dependencies]
axum = "0.6.19"
tokio = { version = "1.29.1", features = ["full"] }
tower = "0.4.13"
這時(shí)候就可以運(yùn)行了,訪問localhost:3000此時(shí)就能在頁面看到Hello, World!
三、路由
路由設(shè)置路徑有哪些handler去處理
handler可以理解為springboot開發(fā)當(dāng)中的controller里面的方法
use axum::{Router, routing::get};
// our router
let app = Router::new()
.route("/", get(root)) //路徑對應(yīng)handler
.route("/foo", get(get_foo).post(post_foo))
.route("/foo/bar", get(foo_bar));
// 一個(gè)個(gè)handler
async fn root() {}
async fn get_foo() {}
async fn post_foo() {}
async fn foo_bar() {}
創(chuàng)建路由
Router::new()
說一些常用方法
nest方法可以嵌套一些別的路由
use axum::{
routing::{get, post},
Router,
};
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);
//此時(shí)有兩個(gè)路徑
// - GET /api/users/:id
// - POST /api/teams
其實(shí)就大致相當(dāng)于springboot當(dāng)中在controller類上設(shè)置總路徑。
merge方法將兩個(gè)路由器合并為一個(gè)
use axum::{
routing::get,
Router,
};
// user路由
let user_routes = Router::new()
.route("/users", get(users_list))
.route("/users/:id", get(users_show));
// team路由
let team_routes = Router::new()
.route("/teams", get(teams_list));
// 合并
let app = Router::new()
.merge(user_routes)
.merge(team_routes);
// 此時(shí)接受請求
// - GET /users
// - GET /users/:id
// - GET /teams
router可以接受多個(gè)handler方法,對于不同的請求方式
use axum::{Router, routing::{get, delete}, extract::Path};
let app = Router::new().route(
"/",
get(get_root).post(post_root).delete(delete_root),
);
async fn get_root() {}
async fn post_root() {}
async fn delete_root() {}
如果你之前用過go語言中的gin框架,那么上手這個(gè)會簡單很多
四,handler和提取器
handler是一個(gè)異步函數(shù),它接受零個(gè)或多個(gè)“提取器”作為參數(shù)并返回一些 可以轉(zhuǎn)換為響應(yīng)。
處理程序是應(yīng)用程序邏輯所在的位置,也是構(gòu)建 axum 應(yīng)用程序的位置 通過在處理程序之間路由。
它采用任意數(shù)量的 “提取器”作為參數(shù)。提取器是實(shí)現(xiàn) FromRequest 或 FromRequestPart 的類型
例如,Json 提取器,它使用請求正文和 將其反序列化為 JSON 為某種目標(biāo)類型,可以用來解析json格式
use axum::{
extract::Json,
routing::post,
handler::Handler,
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateUser {
email: String,
password: String,
}
async fn create_user(Json(payload): Json<CreateUser>) {
// 這里payload參數(shù)類型為CreateUser結(jié)構(gòu)體,并且字段參數(shù)已經(jīng)被賦值
}
let app = Router::new().route("/users", post(create_user));
注意需要引入serde 依賴
serde = { version = "1.0.176", features = ["derive"] }
serde_json = "1.0.104"
還有一些其他的常用的提取器,用于解析不同類型參數(shù)
use axum::{
extract::{Json, TypedHeader, Path, Extension, Query},
routing::post,
headers::UserAgent,
http::{Request, header::HeaderMap},
body::{Bytes, Body},
Router,
};
use serde_json::Value;
use std::collections::HashMap;
// `Path`用于解析路徑上的參數(shù),比如/path/:user_id,這時(shí)候請求路徑/path/100,那么user_id的值就是100,類似springboot當(dāng)中@PathVariable注解
async fn path(Path(user_id): Path<u32>) {}
// 查詢路徑請求參數(shù)值,這里轉(zhuǎn)換成hashmap對象了,類似springboot當(dāng)中@RequestParam注解
async fn query(Query(params): Query<HashMap<String, String>>) {}
// `HeaderMap`可以獲取所有請求頭的值
async fn headers(headers: HeaderMap) {}
//TypedHeader可以用于提取單個(gè)標(biāo)頭(header),請注意這需要您啟用了axum的headers功能
async fn user_agent(TypedHeader(user_agent): TypedHeader<UserAgent>) {}
//獲得請求體中的數(shù)據(jù),按utf-8編碼
async fn string(body: String) {}
//獲得請求體中的數(shù)據(jù),字節(jié)類型
async fn bytes(body: Bytes) {}
//這個(gè)使json類型轉(zhuǎn)換成結(jié)構(gòu)體,上面的例子講了
async fn json(Json(payload): Json<Value>) {}
// 這里可以獲取Request,可以自己去實(shí)現(xiàn)更多功能
async fn request(request: Request<Body>) {}
//Extension從"請求擴(kuò)展"中提取數(shù)據(jù)。這里可以獲得共享狀態(tài)
async fn extension(Extension(state): Extension<State>) {}
//程序的共享狀態(tài),需要實(shí)現(xiàn)Clone
#[derive(Clone)]
struct State { /* ... */ }
let app = Router::new()
.route("/path/:user_id", post(path))
.route("/query", post(query))
.route("/user_agent", post(user_agent))
.route("/headers", post(headers))
.route("/string", post(string))
.route("/bytes", post(bytes))
.route("/json", post(json))
.route("/request", post(request))
.route("/extension", post(extension));
每個(gè)handler參數(shù)可以使用多個(gè)提取器提取參數(shù)
use axum::{
extract::{Path, Query},
routing::get,
Router,
};
use uuid::Uuid;
use serde::Deserialize;
let app = Router::new().route("/users/:id/things", get(get_user_things));
#[derive(Deserialize)]
struct Pagination {
page: usize,
per_page: usize,
}
impl Default for Pagination {
fn default() -> Self {
Self { page: 1, per_page: 30 }
}
}
async fn get_user_things(
Path(user_id): Path<Uuid>,
pagination: Option<Query<Pagination>>,
) {
let Query(pagination) = pagination.unwrap_or_default();
// ...
}
提取器的順序
提取程序始終按函數(shù)參數(shù)的順序運(yùn)行,從左到右。
請求正文是只能使用一次的異步流。 因此,只能有一個(gè)使用請求正文的提取程序
例如
use axum::Json;
use serde::Deserialize;
#[derive(Deserialize)]
struct Payload {}
async fn handler(
// 這種是不被允許的,body被處理了兩次
string_body: String,
json_body: Json<Payload>,
) {
// ...
}
那么如果參數(shù)是可選的需要這么多,使用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 {
} else {
}
}
let app = Router::new().route("/users", post(create_user));
五,響應(yīng)
響應(yīng)內(nèi)容只要是實(shí)現(xiàn) IntoResponse就能返回
use axum::{
Json,
response::{Html, IntoResponse},
http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}},
};
// 空的
async fn empty() {}
// 返回string,此時(shí)`text/plain; charset=utf-8` content-type
async fn plain_text(uri: Uri) -> String {
format!("Hi from {}", uri.path())
}
// 返回bytes`application/octet-stream` content-type
async fn bytes() -> Vec<u8> {
vec![1, 2, 3, 4]
}
// 返回json格式
async fn json() -> Json<Vec<String>> {
Json(vec!["foo".to_owned(), "bar".to_owned()])
}
// 返回html網(wǎng)頁格式`text/html` content-type
async fn html() -> Html<&'static str> {
Html("<p>Hello, World!</p>")
}
// 返回響應(yīng)碼,返回值空
async fn status() -> StatusCode {
StatusCode::NOT_FOUND
}
// 返回值的響應(yīng)頭
async fn headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(header::SERVER, "axum".parse().unwrap());
headers
}
// 數(shù)組元組設(shè)置響應(yīng)頭
async fn array_headers() -> [(HeaderName, &'static str); 2] {
[
(header::SERVER, "axum"),
(header::CONTENT_TYPE, "text/plain")
]
}
// 只要是實(shí)現(xiàn)IntoResponse 都可以返回
async fn impl_trait() -> impl IntoResponse {
[
(header::SERVER, "axum"),
(header::CONTENT_TYPE, "text/plain")
]
}
關(guān)于自定義IntoResponse,看看ai怎么說
要自定義實(shí)現(xiàn)IntoResponse,按照以下步驟進(jìn)行:
創(chuàng)建一個(gè)實(shí)現(xiàn)http::Response的結(jié)構(gòu)體,該結(jié)構(gòu)體將承載您的自定義響應(yīng)對象。
創(chuàng)建一個(gè)impl塊,實(shí)現(xiàn)IntoResponse trait。
在into_response方法中,根據(jù)需要生成您的自定義響應(yīng)。
use axum::{http::{Response, StatusCode}, into_response::IntoResponse, response::Html};
// 創(chuàng)建一個(gè)自定義響應(yīng)對象
struct MyResponse(String);
// 創(chuàng)建一個(gè)impl塊,實(shí)現(xiàn)`IntoResponse` trait
impl IntoResponse for MyResponse {
type Body = Html<String>;
type Error = std::convert::Infallible;
fn into_response(self) -> Response<Self::Body> {
// 根據(jù)需要生成您的自定義響應(yīng)
Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "text/html")
.body(Html(self.0))
.unwrap()
}
}
在上面的代碼中,我們實(shí)現(xiàn)了一個(gè)名為MyResponse的自定義響應(yīng)對象,并為其實(shí)現(xiàn)了IntoResponse trait。在into_response方法中,我們將自定義響應(yīng)對象轉(zhuǎn)換為一個(gè)HTML響應(yīng),并返回。文章來源:http://www.zghlxwxcb.cn/news/detail-656030.html
您可以像下面這樣使用這個(gè)自定義響應(yīng)對象:文章來源地址http://www.zghlxwxcb.cn/news/detail-656030.html
async fn my_handler() -> impl IntoResponse {
MyResponse("<h1>Hello, Axum!</h1>".to_string())
}
到了這里,關(guān)于【從零開始的rust web開發(fā)之路 一】axum學(xué)習(xí)使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!