main.rs
use axum::{
routing::get,
Router,
};
// 属性宏,将此函数标记为异步程序的入口点,启动一个异步运行时(Tokio 运行时)来执行这个异步函数
#[tokio::main]
async fn main() {
// 创建路由实例
let app = Router::new().route("/hello", get(|| async { "Hello, Axum!"}));
// 使用hyper监听所有地址的9090端口,.await等待异步完成,绑定成功返回TcpListener实例,失败panic并打印错误信息
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await.unwrap();
println!("->>LISTENING on {:?}",listener);
// 使用serve函数启动一个异步服务器,监听TcpListener实例,并使用app作为处理函数
axum::serve(listener, app).await.unwrap();
}
使用postman
请求0.0.0.0:8080/hello
可以看到返回了数据
Hello, Axum!
0.0.0.0表示所有ipv4地址,但不能被ping通
127.0.0.1表示回环地址。所有网络号为127的地址都是回环地址
Rust项目源代码发生变化时自动运行 Cargo 命令
# 安装(关闭杀毒软件)
cargo install cargo-watch
监听src/目录,更改代码自动重新执行cargo run
cargo watch -q -c -w src/ -x run
若要监听tests/目录,更改代码自动重新执行
cargo test -q test_dev -- --nocapture
测试并显示所有输出
cargo watch -q -c -w tests/ -x "test -q test_dev -- --nocapture"
axum::Router
路由
闭包传递路由
闭包可以捕获调用者作用域中的值
use axum::{
routing::get,
Router,
extract::Path,
};
// use tracing::info;
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello, Rust!" }))
.route("/hello", get(|| async { "Hello, World!" }))
.route("/tokio/:name", get(|name:Path<String>| async move{ format!("Hello,{:?}",name) }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
还可以将路由函数提取出来,相同路由可以的不同处理可以通过.
添加处理器并添加自定义方法,如.get().post().patch().delete()
use axum::{
routing::get,
Router,
};
use tokio::net::TcpListener;
// 获取
async fn get_handler() -> String {
"Hello, world!".to_string()
}
// 创建
async fn post_handler() -> String {
"post".to_string()
}
// 更新
async fn patch_handler() -> String {
"update".to_string()
}
// 删除
async fn delete_handler() -> String {
"delete".to_string()
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(get_handler))
.route("/hello", get(get_handler).post(post_handler).patch(patch_handler).delete(delete_handler));
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
幂等:一个操作被多次重复执行多次,其结果与第一次执行的结果相同
同样的请求被执行一次与连续执行多次,对服务器的预期影响是相同的,那么称这个 HTTP 方法是幂等的,如
PUT
、DELETE
所有的安全方法都是幂等的,如
GET
、HEAD
、OPTIONS
安全:一个 HTTP 方法是
安全
的,是指这是个方法不会修改服务器的数据,即只读的方法,如GET
、HEAD
、OPTIONS
- GET(获取资源):请求资源
- GET 请求是安全、幂等的
- POST(创建资源):通常用于向服务器提交数据以创建新的资源
- POST 请求是不安全、不幂等的
- POST 请求的主体可以包含任意格式的数据,例如表单数据、JSON 或 XML
- PUT(更新资源):更新服务器上的现有资源,客户端将完整的资源表示发送到服务器,服务器用这个表示替换现有的资源
- PUT请求是不安全、幂等的
- PATCH(部分更新资源): PATCH 只需要提供资源的部分,服务器只更新指定的部分
- PATCH请求是不安全、不幂等的
- DELETE(删除资源):删除指定的资源
- DELETE请求是不安全、幂等的
- HEAD(获取资源头信息):只返回资源的头部信息,不返回资源的主体内容,用于检查资源的存在性、获取资源的大小、最后修改时间等信息,而不需要下载整个资源
- OPTIONS(获取服务器支持的方法):获取服务器支持的 HTTP 方法和其他选项信息,客户端发送 OPTIONS 请求以了解服务器对特定资源的支持情况
- OPTIONS请求的响应通常包含一个
Allow
头部,列出服务器支持的方法 - OPTIONS 请求可以用于客户端在发送实际请求之前了解服务器的能力和限制
- OPTIONS请求的响应通常包含一个
路由匹配
:
创建动态路由,可作为传递的值,必须有值才能匹配到
/hello/:id
匹配/hello/12
/:id/hello
匹配/12/hello
*
创建通配符路由
/hello/*file
匹配/hello/sssssssfile
- 特殊
/hello/*key
不匹配/hello/
但会匹配/hello/
下的所有路由,如/hello/cci/cci/cci/
多个参数传递使用axum::extract::Path
提取
路径只包含一个参数时,可以省略元组
use axum::{
extract::Path,
routing::get,
Router,
};
use tokio::net::TcpListener;
// 获取
async fn get_handler() -> String {
"Hello, world!".to_string()
}
async fn show_user(Path((user_id,team_id)):Path<(String,String)>)-> String {
format!("{}_{}", user_id, team_id)
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(get_handler))
.route("/users/:user_id/team/:team_id", get(show_user));
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
nest 嵌套路由
将路由嵌套在另一个路由下,例如将用户相关的路由嵌套在users
下,请求路径必须包含users
- 嵌套路由不会看到原始请求 URI,而是会删除匹配的前缀
- 使用原始URI请使用
axum::extract::OriginalUri
- 嵌套路由和通配符路由功能类似,嵌套路由会删除前缀,通配符路由保留完整路由
use axum::{
extract::Path,
routing::{get,post},
Router,
};
use tokio::net::TcpListener;
async fn show_user(Path(id): Path<String>) -> String {
format!("id: {:?}", id)
}
async fn post_user() -> String {
"post_user".to_string()
}
#[tokio::main]
async fn main() {
let user_routes = Router::new().route("/:id", get(show_user));
let team_routes = Router::new().route("/", post(post_user));
let api_routes = Router::new()
.nest("/users", user_routes)// GET /api/users/145632
.nest("/teams", team_routes);// POST /api/teams/
let app = Router::new().nest("/api", api_routes);
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
fallback 后备路由
fallback:后备,备用方案或回退机制,主要的操作或功能无法正常执行时,程序可以使用预先定义的 fallback来处理情况,以确保程序不会完全失败或崩溃
后背路由仅适用于路由中任何内容均不匹配的路由,创建路由后使用.fallback()
添加后备路由
callback:区分回调函数
如果嵌套路由没有自己的后备,那么将继承外部路由的后备,以下例子
- 当请求
:8080/api/users/145632
时可以匹配成功 - 当请求
:8080/test
时,由于没有路由可以匹配,会执行/api
定义的后备 - 当请求
:8080/api/users/
时,由于没有路由可以匹配,会执行/users
定义的后备 - 当请求
:8080/api/users/145632/test
时,由于没有路由可以匹配,/:id
路由没有定义后备路由,会执行外部的/users
定义的后备
use axum::{
http::Uri,
extract::OriginalUri,
routing::get,
Router,
http::StatusCode
};
use tokio::net::TcpListener;
async fn show_user(uri: Uri, OriginalUri(original_uri): OriginalUri) -> String {
format!("uri: {:?}\noriginal_uri: {:?}\n", uri,original_uri)
}
async fn fallback_api() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "Not Found /api")
}
async fn fallback_users() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "Not Found /users")
}
#[tokio::main]
async fn main() {
let user_routes = Router::new().route("/:id", get(show_user));
let api_routes = Router::new()
.nest("/users", user_routes).fallback(fallback_users);
let app = Router::new().nest("/api", api_routes).fallback(fallback_api);
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
axum::extract::OriginalUri
获取原始URI
use axum::{
http::Uri,
extract::OriginalUri,
routing::{get,post},
Router,
};
use tokio::net::TcpListener;
async fn show_user(uri: Uri, OriginalUri(original_uri): OriginalUri) -> String {
//uri: /145632
//original_uri: /api/users/145632
format!("uri: {:?}\noriginal_uri: {:?}\n", uri,original_uri)
}
async fn post_user() -> String {
"post_user".to_string()
}
#[tokio::main]
async fn main() {
let user_routes = Router::new().route("/:id", get(show_user));
let team_routes = Router::new().route("/", post(post_user));
let api_routes = Router::new()
.nest("/users", user_routes)// GET \api\users\145632
.nest("/teams", team_routes);// POST \api\teams\
let app = Router::new().nest("/api", api_routes);
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
merge 合并路由
将多个独立的路由组合到一起统一处理,例如两个模块中定义了路由,在主应用中合并为一个路由
use axum::{
routing::get,
Router,
extract::Path,
};
use tokio::net::TcpListener;
async fn users_list() -> String {
"users list".to_string()
}
async fn users_show(Path(id):Path<String>) -> String {
format!("user show: {:?}",id)
}
async fn teams_list() -> String {
"teams list".to_string()
}
#[tokio::main]
async fn main() {
let user_routes = Router::new()
.route("/users", get(users_list)) // GET :8080/users
.route("/users/:id", get(users_show)); // GET :8080/users/145632
let team_routes = Router::new()
.route("/teams", get(teams_list)); // GET :8080/teams
let app = Router::new()
.merge(user_routes)
.merge(team_routes);
let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
路由还可以携带状态,如数据库连接、context,使用.with_state(state)
,案例见https://github.com/SeaQL/sea-orm/blob/master/examples/axum_example/api/src/lib.rs