This commit is contained in:
rustmailer
2026-05-24 02:37:22 +08:00
parent 575f851cfb
commit 0be2670600
3 changed files with 77 additions and 66 deletions
+5 -1
View File
@@ -3,6 +3,10 @@ name = "bichon-server"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
[features]
default = ["embed-web"]
embed-web = ["dep:rust-embed"]
[dependencies] [dependencies]
bichon-core = { path = "../core", features = ["web-api"] } bichon-core = { path = "../core", features = ["web-api"] }
@@ -17,7 +21,7 @@ poem-openapi = { version = "5.1.16", features = [
"swagger-ui", "swagger-ui",
"email", "email",
] } ] }
rust-embed = "8.11.0" rust-embed = { version = "8.11.0", optional = true }
email_address.workspace = true email_address.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
+9 -22
View File
@@ -1,24 +1,11 @@
// #[cfg(feature = "embed-web")]
// Copyright (c) 2025-2026 rustmailer.com (https://rustmailer.com) mod inner {
// use rust_embed::RustEmbed;
// This file is part of the Bichon Email Archiving Project
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#[derive(RustEmbed)]
#[folder = "../../web/dist/"]
pub struct FrontEndAssets;
}
use rust_embed::RustEmbed; #[cfg(feature = "embed-web")]
pub use inner::FrontEndAssets;
#[derive(RustEmbed)]
#[folder = "../../web/dist/"]
pub struct FrontEndAssets;
+63 -43
View File
@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::common::auth::ApiGuard;
use crate::common::error::ErrorCapture; use crate::common::error::ErrorCapture;
use crate::common::log::Tracing; use crate::common::log::Tracing;
use crate::common::tls::rustls_config; use crate::common::tls::rustls_config;
@@ -29,25 +30,31 @@ use bichon_core::error::BichonResult;
use bichon_core::raise_error; use bichon_core::raise_error;
use bichon_core::settings::cli::SETTINGS; use bichon_core::settings::cli::SETTINGS;
use super::error::ApiErrorResponse;
use crate::common::auth::ApiGuard;
use api::create_openapi_service; use api::create_openapi_service;
use assets::FrontEndAssets; use http::Method;
use http::{HeaderValue, Method};
use poem::endpoint::EmbeddedFilesEndpoint;
use poem::listener::{Listener, TcpListener}; use poem::listener::{Listener, TcpListener};
use poem::middleware::{CatchPanic, Compression, Cors, SetHeader}; use poem::middleware::{CatchPanic, Compression, Cors};
use poem::{get, handler, post, Endpoint, EndpointExt, IntoResponse, Route, Server}; use poem::{get, post, Endpoint, EndpointExt, Route, Server};
use public::oauth2::oauth2_callback; use public::oauth2::oauth2_callback;
use std::collections::HashSet; use std::collections::HashSet;
use std::time::Duration; use std::time::Duration;
#[cfg(feature = "embed-web")]
use {
assets::FrontEndAssets,
http::HeaderValue,
poem::{handler, endpoint::EmbeddedFilesEndpoint, IntoResponse},
poem::middleware::SetHeader,
};
pub mod api; pub mod api;
pub mod assets; pub mod assets;
pub mod public; pub mod public;
pub type ApiResult<T, E = ApiErrorResponse> = std::result::Result<T, E>; pub type ApiResult<T, E = ApiErrorResponse> = std::result::Result<T, E>;
use super::error::ApiErrorResponse;
/// Build the community route tree. Pro/Enterprise servers can call this /// Build the community route tree. Pro/Enterprise servers can call this
/// and then add their own routes before passing the tree to the server. /// and then add their own routes before passing the tree to the server.
pub fn build_routes() -> impl Endpoint { pub fn build_routes() -> impl Endpoint {
@@ -95,13 +102,6 @@ pub fn build_routes() -> impl Endpoint {
.expose_headers(vec!["Accept"]) .expose_headers(vec!["Accept"])
.max_age(SETTINGS.bichon_cors_max_age); .max_age(SETTINGS.bichon_cors_max_age);
let cache_static = || {
SetHeader::new().overriding(
http::header::CACHE_CONTROL,
HeaderValue::from_static("max-age=86400"),
)
};
let app_logic = Route::new() let app_logic = Route::new()
.nest("/api-docs/swagger", swagger) .nest("/api-docs/swagger", swagger)
.nest("/api-docs/redoc", redoc) .nest("/api-docs/redoc", redoc)
@@ -112,12 +112,9 @@ pub fn build_routes() -> impl Endpoint {
.nest("/oauth2/callback", get(oauth2_callback)) .nest("/oauth2/callback", get(oauth2_callback))
.nest("/api/status", get(get_status)) .nest("/api/status", get(get_status))
.nest("/api/login", post(login)) .nest("/api/login", post(login))
.nest_no_strip("/api/v1", open_api_route) .nest_no_strip("/api/v1", open_api_route);
.nest_no_strip(
"/assets", let app_logic = add_web_assets(app_logic);
EmbeddedFilesEndpoint::<FrontEndAssets>::new().with(cache_static()),
)
.at("/*", serve_index_with_base);
Route::new() Route::new()
.nest(&SETTINGS.bichon_base_url, app_logic) .nest(&SETTINGS.bichon_base_url, app_logic)
@@ -126,6 +123,52 @@ pub fn build_routes() -> impl Endpoint {
.with(CatchPanic::new()) .with(CatchPanic::new())
} }
#[cfg(feature = "embed-web")]
fn add_web_assets(route: Route) -> impl Endpoint {
let cache_static = || {
SetHeader::new().overriding(
http::header::CACHE_CONTROL,
HeaderValue::from_static("max-age=86400"),
)
};
route
.nest_no_strip(
"/assets",
EmbeddedFilesEndpoint::<FrontEndAssets>::new().with(cache_static()),
)
.at("/*", serve_index_with_base)
}
#[cfg(not(feature = "embed-web"))]
fn add_web_assets(route: Route) -> Route {
route
}
#[cfg(feature = "embed-web")]
#[handler]
async fn serve_index_with_base() -> impl IntoResponse {
let mut html =
String::from_utf8_lossy(&FrontEndAssets::get("index.html").unwrap().data).to_string();
let raw_base = &SETTINGS.bichon_base_url;
let base_href = if raw_base.ends_with('/') {
raw_base.clone()
} else {
format!("{}/", raw_base)
};
let inject_content = format!(
r#"<base href="{}"><script>window.__BICHON_BASE__ = '{}';</script>"#,
base_href, raw_base
);
html = html.replace("<head>", &format!("<head>{}", inject_content));
poem::Response::builder()
.content_type("text/html; charset=utf-8")
.body(html)
}
pub async fn start_http_server() -> BichonResult<()> { pub async fn start_http_server() -> BichonResult<()> {
let listener = TcpListener::bind(( let listener = TcpListener::bind((
SETTINGS.bichon_bind_ip.clone().unwrap_or("0.0.0.0".into()), SETTINGS.bichon_bind_ip.clone().unwrap_or("0.0.0.0".into()),
@@ -160,26 +203,3 @@ pub async fn start_http_server() -> BichonResult<()> {
.await .await
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError)) .map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))
} }
#[handler]
async fn serve_index_with_base() -> impl IntoResponse {
let mut html =
String::from_utf8_lossy(&FrontEndAssets::get("index.html").unwrap().data).to_string();
let raw_base = &SETTINGS.bichon_base_url;
let base_href = if raw_base.ends_with('/') {
raw_base.clone()
} else {
format!("{}/", raw_base)
};
let inject_content = format!(
r#"<base href="{}"><script>window.__BICHON_BASE__ = '{}';</script>"#,
base_href, raw_base
);
html = html.replace("<head>", &format!("<head>{}", inject_content));
poem::Response::builder()
.content_type("text/html; charset=utf-8")
.body(html)
}