【从零开始的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())
}
相关推荐
好奇的菜鸟几秒前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
Alive~o.09 分钟前
Go语言进阶&依赖管理
开发语言·后端·golang
许苑向上15 分钟前
Dubbo集成SpringBoot实现远程服务调用
spring boot·后端·dubbo
Code_Artist1 小时前
使用Portainer来管理并编排Docker容器
docker·云原生·容器
郑祎亦1 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
本当迷ya1 小时前
💖2025年不会Stream流被同事排挤了┭┮﹏┭┮(强烈建议实操)
后端·程序员
计算机毕设指导62 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
paopaokaka_luck2 小时前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
捂月4 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
瓜牛_gn4 小时前
依赖注入注解
java·后端·spring