【从零开始的rust web开发之路 一】axum学习使用

系列文章目录

第一章 axum学习使用

文章目录


前言

本职java开发,兼架构设计。空闲时间学习了rust,目前还不熟练掌握。想着用urst开发个web服务,正好熟悉一下rust语言开发。

目前rust 语言web开发相关的框架已经有很多,但还是和java,go语言比不了。

这个系列想完整走一遍web开发,后续有时间就出orm,还有一些别的web用到的库教程。

言归正传,开始学习axum框架

老规矩先看官方文档介绍

Axum是一个专注于人体工程学和模块化的Web应用程序框架。

高级功能

使用无宏 API 将请求路由到处理程序。

使用提取程序以声明方式分析请求。

简单且可预测的错误处理模型。

使用最少的样板生成响应。

充分利用塔和塔-http生态系统 中间件、服务和实用程序。

特别是,最后一点是与其他框架的区别。 没有自己的中间件系统,而是使用tower::Service。这意味着获得超时、跟踪、压缩、 授权等等,免费。它还使您能够与 使用 hyper 或 tonic 编写的应用程序。axumaxumaxum

兼容性

Axum旨在与Tokio和Hyper配合使用。运行时和 传输层独立性不是目标,至少目前是这样。

tokio框架在rust异步当中相当流行。axum能很好地搭配tokio实现异步web

二、hello world

看看官方例子

rust 复制代码
use axum::{
    routing::get,
    Router,
};

#[tokio::main]
async fn main() {
    // 构建router
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    // 运行hyper  http服务 localhost:3000
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

要想使用还需要引入库

rust 复制代码
[dependencies]
axum = "0.6.19"
tokio = { version = "1.29.1", features = ["full"] }
tower = "0.4.13"

这时候就可以运行了,访问localhost:3000此时就能在页面看到Hello, World!

三、路由

路由设置路径有哪些handler去处理

handler可以理解为springboot开发当中的controller里面的方法

rust 复制代码
use axum::{Router, routing::get};

// our router
let app = Router::new()
    .route("/", get(root))  //路径对应handler
    .route("/foo", get(get_foo).post(post_foo))
    .route("/foo/bar", get(foo_bar));

// 一个个handler
async fn root() {}
async fn get_foo() {}
async fn post_foo() {}
async fn foo_bar() {}

创建路由

rust 复制代码
Router::new()

说一些常用方法

nest方法可以嵌套一些别的路由

rust 复制代码
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);
//此时有两个路径
// - GET /api/users/:id
// - POST /api/teams

其实就大致相当于springboot当中在controller类上设置总路径。

merge方法将两个路由器合并为一个

rust 复制代码
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);

//  此时接受请求
// - GET /users
// - GET /users/:id
// - GET /teams

router可以接受多个handler方法,对于不同的请求方式

rust 复制代码
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框架,那么上手这个会简单很多

四,handler和提取器

handler是一个异步函数,它接受零个或多个"提取器"作为参数并返回一些 可以转换为响应。

处理程序是应用程序逻辑所在的位置,也是构建 axum 应用程序的位置 通过在处理程序之间路由。

它采用任意数量的 "提取器"作为参数。提取器是实现 FromRequest 或 FromRequestPart 的类型

例如,Json 提取器,它使用请求正文和 将其反序列化为 JSON 为某种目标类型,可以用来解析json格式

rust 复制代码
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参数类型为CreateUser结构体,并且字段参数已经被赋值
}
let app = Router::new().route("/users", post(create_user));

注意需要引入serde 依赖

rust 复制代码
serde = { version = "1.0.176", features = ["derive"] }
serde_json = "1.0.104"

还有一些其他的常用的提取器,用于解析不同类型参数

rust 复制代码
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`用于解析路径上的参数,比如/path/:user_id,这时候请求路径/path/100,那么user_id的值就是100,类似springboot当中@PathVariable注解
async fn path(Path(user_id): Path<u32>) {}

// 查询路径请求参数值,这里转换成hashmap对象了,类似springboot当中@RequestParam注解
async fn query(Query(params): Query<HashMap<String, String>>) {}

// `HeaderMap`可以获取所有请求头的值
async fn headers(headers: HeaderMap) {}

//TypedHeader可以用于提取单个标头(header),请注意这需要您启用了axum的headers功能
async fn user_agent(TypedHeader(user_agent): TypedHeader<UserAgent>) {}

//获得请求体中的数据,按utf-8编码
async fn string(body: String) {}

//获得请求体中的数据,字节类型
async fn bytes(body: Bytes) {}

//这个使json类型转换成结构体,上面的例子讲了
async fn json(Json(payload): Json<Value>) {}

// 这里可以获取Request,可以自己去实现更多功能
async fn request(request: Request<Body>) {}

//Extension从"请求扩展"中提取数据。这里可以获得共享状态
async fn extension(Extension(state): Extension<State>) {}

//程序的共享状态,需要实现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));

每个handler参数可以使用多个提取器提取参数

rust 复制代码
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();

    // ...
}

提取器的顺序

提取程序始终按函数参数的顺序运行,从左到右。

请求正文是只能使用一次的异步流。 因此,只能有一个使用请求正文的提取程序

例如

rust 复制代码
use axum::Json;
use serde::Deserialize;

#[derive(Deserialize)]
struct Payload {}

async fn handler(
    // 这种是不被允许的,body被处理了两次
    string_body: String,
    json_body: Json<Payload>,
) {
    // ...
}

那么如果参数是可选的需要这么多,使用Option包裹

rust 复制代码
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));

五,响应

响应内容只要是实现 IntoResponse就能返回

rust 复制代码
use axum::{
    Json,
    response::{Html, IntoResponse},
    http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}},
};

// 空的
async fn empty() {}

// 返回string,此时`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网页格式`text/html` content-type
async fn html() -> Html<&'static str> {
    Html("<p>Hello, World!</p>")
}

// 返回响应码,返回值空
async fn status() -> StatusCode {
    StatusCode::NOT_FOUND
}

// 返回值的响应头
async fn headers() -> HeaderMap {
    let mut headers = HeaderMap::new();
    headers.insert(header::SERVER, "axum".parse().unwrap());
    headers
}

// 数组元组设置响应头
async fn array_headers() -> [(HeaderName, &'static str); 2] {
    [
        (header::SERVER, "axum"),
        (header::CONTENT_TYPE, "text/plain")
    ]
}

// 只要是实现IntoResponse 都可以返回
async fn impl_trait() -> impl IntoResponse {
    [
        (header::SERVER, "axum"),
        (header::CONTENT_TYPE, "text/plain")
    ]
}

关于自定义IntoResponse,看看ai怎么说

要自定义实现IntoResponse,按照以下步骤进行:

创建一个实现http::Response的结构体,该结构体将承载您的自定义响应对象。

创建一个impl块,实现IntoResponse trait。

在into_response方法中,根据需要生成您的自定义响应。

rust 复制代码
use axum::{http::{Response, StatusCode}, into_response::IntoResponse, response::Html};

// 创建一个自定义响应对象
struct MyResponse(String);

// 创建一个impl块,实现`IntoResponse` trait
impl IntoResponse for MyResponse {
    type Body = Html<String>;
    type Error = std::convert::Infallible;

    fn into_response(self) -> Response<Self::Body> {
        // 根据需要生成您的自定义响应
        Response::builder()
            .status(StatusCode::OK)
            .header("Content-Type", "text/html")
            .body(Html(self.0))
            .unwrap()
    }
}
 

在上面的代码中,我们实现了一个名为MyResponse的自定义响应对象,并为其实现了IntoResponse trait。在into_response方法中,我们将自定义响应对象转换为一个HTML响应,并返回。

您可以像下面这样使用这个自定义响应对象:

rust 复制代码
async fn my_handler() -> impl IntoResponse {
   MyResponse("<h1>Hello, Axum!</h1>".to_string())
}
相关推荐
uzong1 小时前
技术故障复盘模版
后端
GetcharZp1 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程2 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研2 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi2 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy3 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack4 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9654 小时前
pip install 已经不再安全
后端
寻月隐君5 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github