将单页应用(SPA)嵌入到AXUM应用中
本章我们将讨论如何把单页应用(SPA)嵌入到AXUM二进制文件中。
目标
我们的目标是把一个使用VUE3构建的SPA嵌入到AXUM二进制文件中。
[dependencies]
tokio = { version = "1", features = ["full"] }
axum = { version = "0.7" }
serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
rust-embed = "8.5"
mime_guess = "2"
Rust 代码
资源模块
// src/asset.rs
use axum::{
http::{header, StatusCode, Uri},
response::{Html, IntoResponse, Response},
};
use rust_embed::Embed;
pub const INDEX_HTML: &'static str = "index.html";
#[derive(Embed)]
#[folder = "dist/"]
pub struct Assets;
pub async fn static_handler(uri: Uri) -> impl IntoResponse {
let path = uri.path().trim_start_matches("/");
if path.is_empty() || path == INDEX_HTML {
return index_html().await;
}
match Assets::get(path) {
Some(content) => {
let mime = mime_guess::from_path(path).first_or_octet_stream();
([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response()
}
None => {
if path.contains('.') {
return not_found().await;
}
index_html().await
}
}
}
pub async fn index_html() -> Response {
match Assets::get(INDEX_HTML) {
Some(content) => Html(content.data).into_response(),
None => not_found().await,
}
}
pub async fn not_found() -> Response {
(StatusCode::NOT_FOUND, "404").into_response()
}
资源结构体
#[derive(Embed)]
#[folder = "dist/"]
pub struct Assets;
我们的资源结构体将把 dist/
目录里面的资源嵌入到二进制文件中。
注意:为了避免编译错误,请手动在项目根目录下创建
dist
目录。等编写好SPA之后,会自动生成该目录和资源文件
静态文件处理
pub async fn static_handler(uri: Uri) ...
-
通过请求的URI判断是否是首页,如果是的话,渲染首页:
if path.is_empty() || path == INDEX_HTML { return index_html().await; }
-
如果不是首页,如果资源文件不存在,渲染
not_found()
;否则,猜测其MIME,然后进行渲染match Assets::get(path) { Some(content) => { let mime = mime_guess::from_path(path).first_or_octet_stream(); ([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response() } None => { if path.contains('.') { return not_found().await; } index_html().await } }
首页渲染
pub async fn index_html() -> Response {
match Assets::get(INDEX_HTML) {
Some(content) => Html(content.data).into_response(),
None => not_found().await,
}
}
- 如果静态资源
index.html
存在,则返回该文件的内容 - 否则,渲染
not_found()
404错误
pub async fn not_found() -> Response {
(StatusCode::NOT_FOUND, "404").into_response()
}
- 返回
StatusCode::NOT_FOUND
响应 - 内容为
404
。你可以写详细点,比如:你请求的资源不存在
#[tokio::main]
async fn main() {
let tcp_listener = TcpListener::bind("0.0.0.0:9527").await.unwrap();
let app = Router::new()
.route("/now", get(now_handler))
.fallback(asset::static_handler);
axum::serve(tcp_listener, app).await.unwrap();
}
- 绑定了
/
路由,交由now_handler
来处理 - 其他路由,通过
asset::static_handler
处理
路由处理函数
async fn now_handler() -> String {
Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
}
知识简单的返回当前时间。
前端
创建前端项目
# 创建项目
yarn create vite spa-ui --template vue-ts
cd spa-ui
# 安装依赖
yarn
# 安装tailwindcss
# 参见官方文档 https://tailwindcss.com/docs/guides/vite#vue
# 安装dayjs
yarn add dayjs
<!-- spa-ui/src/components/Now.vue -->
<script setup lang="ts">
import dayjs from "dayjs";
import { onMounted, ref } from "vue";
const remoteNow = ref<string>("正在获取");
const loadData = () => {
fetch("/now", {
method: "GET",
})
.then((r) => r.text())
.then((r) => (remoteNow.value = r));
};
onMounted(() => {
loadData();
});
</script>
<template>
<ul>
<li>服务器时间:{{ remoteNow }}</li>
<li>客户端时间:{{ dayjs().format("YYYY-MM-DD HH:mm:ss") }}</li>
</ul>
</template>
渲染组件
# 在spa-ui目录下进行
rm -rf ../dist && yarn build --outDir ../dist
至此,我们的SPA已经嵌入的AXUM二进制文件中了,只需要发布、部署一个 cargo build --release
生成的二进制文件即可,无需额外发布、部署SPA页面和文件。
本章代码位于spa
目录。