rust:axum+sqlx实战学习笔记2

1,666 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

书接上回,上一章节中。我们学习了axum的基本使用方法,了解到路由上的handler函数至关重要。在handler函数中接受路由的参数,在函数体内经过处理,并最后返回请求的响应结果。

我们知道rust是种强类型语言。今天,我们就来学习一下handler函数要如何才能返回各种各样的响应结果,不同类型的响应结果要怎么进行处理。

照例,先看一下axum官方例子:

use axum::{
    Json,
    response::{Html, IntoResponse},
    http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}},
};

// `()` gives an empty response
async fn empty() {}

// String will get a `text/plain; charset=utf-8` content-type
async fn plain_text(uri: Uri) -> String {
    format!("Hi from {}", uri.path())
}

// Bytes will get a `application/octet-stream` content-type
async fn bytes() -> Vec<u8> {
    vec![1, 2, 3, 4]
}

// `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()])
}

// `Html` will get a `text/html` content-type
async fn html() -> Html<&'static str> {
    Html("<p>Hello, World!</p>")
}

// `StatusCode` gives an empty response with that status code
async fn status() -> StatusCode {
    StatusCode::NOT_FOUND
}

// `HeaderMap` gives an empty response with some headers
async fn headers() -> HeaderMap {
    let mut headers = HeaderMap::new();
    headers.insert(header::SERVER, "axum".parse().unwrap());
    headers
}

// An array of tuples also gives headers
async fn array_headers() -> [(HeaderName, &'static str); 2] {
    [
        (header::SERVER, "axum"),
        (header::CONTENT_TYPE, "text/plain")
    ]
}

// Use `impl IntoResponse` to avoid writing the whole type
async fn impl_trait() -> impl IntoResponse {
    [
        (header::SERVER, "axum"),
        (header::CONTENT_TYPE, "text/plain")
    ]
}

从官方例子中,可以总结出响应数据的类型主要以下几种:

  1. 空响应 类型为()
  2. 纯文本 类型为&strString
  3. 字节序列 类型为Vec<xxx>
  4. JSON 响应 类型为Json<XXX>
  5. HTML 响应 类型为Html<xxx>
  6. 状态码 类型为StatusCode
  7. 带响应头的响应 类型为HeaderMap
  8. 数组 类型为HeaderMap中的值
  9. 任意响应 类型为impl IntoResponse

一眼看过去,大部分的响应类型都不是前后端常用的数据类型。那我们只能创建自己的响应数据类型,同时实现IntoResponse的特质即可。

方法一:定义一个struct,用json进行包装

use axum::{http::StatusCode, response::Response};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
//常量
pub const CODE_SUCCESS: StatusCode = StatusCode::OK;
pub const CODE_FAIL: StatusCode = StatusCode::BAD_REQUEST;

/// http接口返回模型结构,提供基础的 code,msg,data 等json数据结构
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RespVO<T> {
    pub code: Option<u16>,
    pub msg: Option<String>,
    pub data: Option<T>,
}

impl<T> RespVO<T>
where
    T: Serialize + DeserializeOwned + Clone,
{
    pub fn from_result(arg: &T) -> Self {
        Self {
            code: Some(CODE_SUCCESS.as_u16()),
            msg: Some("操作成功".to_string()),
            data: Some(arg.clone()),
        }
    }

    pub fn from_error(arg: &str) -> Self {
        Self {
            code: Some(CODE_FAIL.as_u16()),
            msg: Some(arg.to_string()),
            data: None,
        }
    }

    pub fn from_error_info(code: StatusCode, info: &str) -> Self {
        Self {
            code: Some(code.as_u16()),
            msg: Some(info.to_string()),
            data: None,
        }
    }
}

这里先定义好通用的http接口返回的数据模型以及几个附属的方法,以后在响应中统一用这个struct进行处理。 比如:

pub async fn update_user() -> impl IntoResponse {
     let msg = "更新成功!".to_string();
     Json(RespVO::<String>::from_result(&msg))
}

方法二:定义一个struct,为该结构体实现IntoResponse特质

use axum::{
    body,
    routing::get,
    response::{IntoResponse, Response},
    Router,
};
use http_body::Body;
use http::HeaderMap;
use bytes::Bytes;
use std::{
    convert::Infallible,
    task::{Poll, Context},
    pin::Pin,
};

struct MyBody;

// First implement `Body` for `MyBody`. This could for example use
// some custom streaming protocol.
impl Body for MyBody {
    type Data = Bytes;
    type Error = Infallible;

    fn poll_data(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>
    ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
        // ...
    }

    fn poll_trailers(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>
    ) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
        // ...
    }
}

// Now we can implement `IntoResponse` directly for `MyBody`
impl IntoResponse for MyBody {
    fn into_response(self) -> Response {
        Response::new(body::boxed(self))
    }
}

第一种方法,我是在项目中使用的。第二种是axum官方例子。大家觉得哪种方法更好呢?

细心的朋友可能会发现,我自己定义的响应模型,响应码似乎不是直接用官方自带的StatusCode,而是把官方的StatusCode转换为数字u16,为什么呢?这里先留个悬念。。。。

最后,照例总结一下:今天我们学习了axum如何处理响应数据,就像如何把大象放进冰箱一样。

主要分为三步:

  1. 定义一个struct
  2. 为struct实现IntoResponse特质
  3. 返回数据