携手创作,共同成长!这是我参与「掘金日新计划 · 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")
]
}
从官方例子中,可以总结出响应数据的类型主要以下几种:
- 空响应 类型为()
- 纯文本 类型为
&str和String - 字节序列 类型为
Vec<xxx> - JSON 响应 类型为
Json<XXX> - HTML 响应 类型为
Html<xxx> - 状态码 类型为StatusCode
- 带响应头的响应 类型为HeaderMap
- 数组 类型为HeaderMap中的值
- 任意响应 类型为
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如何处理响应数据,就像如何把大象放进冰箱一样。
主要分为三步:
- 定义一个struct
- 为struct实现IntoResponse特质
- 返回数据