mirror of
https://github.com/DefectingCat/phthonus
synced 2025-07-15 16:41:32 +00:00
feat(xitca): init
This commit is contained in:
@ -22,3 +22,4 @@ Web framework comparison in my stack.
|
|||||||
- Nodejs
|
- Nodejs
|
||||||
- Deno
|
- Deno
|
||||||
- Bunjs
|
- Bunjs
|
||||||
|
- [ ] Elysia
|
||||||
|
15
frameworks/Rust/axum-xitca/.editorconfig
Normal file
15
frameworks/Rust/axum-xitca/.editorconfig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_size = 4
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
1
frameworks/Rust/axum-xitca/.env.example
Normal file
1
frameworks/Rust/axum-xitca/.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
PHTHONUS_PORT=4000
|
2
frameworks/Rust/axum-xitca/.gitignore
vendored
Normal file
2
frameworks/Rust/axum-xitca/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target/
|
||||||
|
.env
|
1280
frameworks/Rust/axum-xitca/Cargo.lock
generated
Normal file
1280
frameworks/Rust/axum-xitca/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
frameworks/Rust/axum-xitca/Cargo.toml
Normal file
34
frameworks/Rust/axum-xitca/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[package]
|
||||||
|
name = "axum-xitca"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
|
axum = { version = "0.7.6", default-features = false, features = [
|
||||||
|
"json",
|
||||||
|
"query",
|
||||||
|
] }
|
||||||
|
xitca-server = "0.4.0"
|
||||||
|
xitca-http = { version = "0.6.0", features = ["io-uring"] }
|
||||||
|
xitca-io = { version = "0.4.1", features = [
|
||||||
|
"tokio-uring",
|
||||||
|
"tokio",
|
||||||
|
"runtime-uring",
|
||||||
|
] }
|
||||||
|
xitca-service = "0.2.0"
|
||||||
|
xitca-web = { version = "0.6.2", features = ["tower-http-compat"] }
|
||||||
|
tower = "0.5.1"
|
||||||
|
tower-http = { version = "0.6.1", features = ["full"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
# error
|
||||||
|
anyhow = "1.0.89"
|
||||||
|
thiserror = "1.0.64"
|
||||||
|
# tools
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
serde = { version = "1.0.210", features = ["derive", "serde_derive"] }
|
||||||
|
serde_json = { version = "1.0.128" }
|
||||||
|
serde_repr = "0.1.19"
|
||||||
|
http-body = "1.0.1"
|
||||||
|
xitca-unsafe-collection = "0.2.0"
|
62
frameworks/Rust/axum-xitca/Makefile
Normal file
62
frameworks/Rust/axum-xitca/Makefile
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
CARGO = cargo
|
||||||
|
RUSTC = rustc
|
||||||
|
CROSS = cross
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
$(CARGO) build
|
||||||
|
|
||||||
|
release: clean
|
||||||
|
$(CARGO) build --release
|
||||||
|
|
||||||
|
dev:
|
||||||
|
PHTHONUS_LOG=debug $(CARGO) watch -x run
|
||||||
|
|
||||||
|
run:
|
||||||
|
$(CARGO) run
|
||||||
|
|
||||||
|
test:
|
||||||
|
$(CARGO) test
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(CARGO) clean
|
||||||
|
|
||||||
|
clean-release:
|
||||||
|
rm -rf ./target/release/
|
||||||
|
rm -rf ./target/debug/
|
||||||
|
|
||||||
|
check:
|
||||||
|
$(CARGO) check
|
||||||
|
|
||||||
|
format:
|
||||||
|
$(CARGO) fmt
|
||||||
|
|
||||||
|
lint:
|
||||||
|
$(CARGO) clippy
|
||||||
|
|
||||||
|
fix:
|
||||||
|
$(CARGO) fix --allow-dirty --all-features && $(CARGO) fmt
|
||||||
|
|
||||||
|
linux-musl: clean-release
|
||||||
|
$(CROSS) build --release --target x86_64-unknown-linux-musl
|
||||||
|
|
||||||
|
linux-gnu: clean-release
|
||||||
|
$(CROSS) build --release --target x86_64-unknown-linux-gnu
|
||||||
|
|
||||||
|
windows-gnu: clean-release
|
||||||
|
$(CROSS) build --release --target x86_64-pc-windows-gnu
|
||||||
|
|
||||||
|
freebsd: clean-release
|
||||||
|
$(CROSS) build --release --target x86_64-unknown-freebsd
|
||||||
|
|
||||||
|
loongarch: clean-release
|
||||||
|
$(CROSS) build --release --target loongarch64-unknown-linux-gnu
|
||||||
|
|
||||||
|
deps:
|
||||||
|
python -m venv venus \
|
||||||
|
&& source venus/bin/activate \
|
||||||
|
&& pip install -r scripts/requirements.txt \
|
||||||
|
&& python scripts/download-core.py
|
||||||
|
|
||||||
|
.PHONY: all
|
2
frameworks/Rust/axum-xitca/rust-toolchain.toml
Normal file
2
frameworks/Rust/axum-xitca/rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
3
frameworks/Rust/axum-xitca/src/consts.rs
Normal file
3
frameworks/Rust/axum-xitca/src/consts.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
pub const NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
|
pub const DEFAULT_PORT: u16 = 4000;
|
98
frameworks/Rust/axum-xitca/src/error.rs
Normal file
98
frameworks/Rust/axum-xitca/src/error.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::rejection::{FormRejection, JsonRejection},
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
use serde_json::json;
|
||||||
|
use serde_repr::*;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum AppError {
|
||||||
|
#[error("{0}")]
|
||||||
|
Any(#[from] anyhow::Error),
|
||||||
|
|
||||||
|
// axum
|
||||||
|
#[error(transparent)]
|
||||||
|
AxumFormRejection(#[from] FormRejection),
|
||||||
|
#[error(transparent)]
|
||||||
|
AxumJsonRejection(#[from] JsonRejection),
|
||||||
|
// route
|
||||||
|
// 路由通常错误 错误信息直接返回用户
|
||||||
|
// #[error("{0}")]
|
||||||
|
// AuthorizeFailed(Cow<'static, str>),
|
||||||
|
// #[error("{0}")]
|
||||||
|
// UserConflict(Cow<'static, str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
Normal = 200,
|
||||||
|
InternalError = 1000,
|
||||||
|
//NotAuthorized = 1001,
|
||||||
|
AuthorizeFailed = 1002,
|
||||||
|
UserConflict = 1003,
|
||||||
|
ParameterIncorrect = 1004,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ErrorCode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use ErrorCode::*;
|
||||||
|
|
||||||
|
let res = match self {
|
||||||
|
Normal => "",
|
||||||
|
InternalError => "服务器内部错误",
|
||||||
|
//NotAuthorized => "未登录",
|
||||||
|
AuthorizeFailed => "用户名或密码错误",
|
||||||
|
UserConflict => "该用户已经存在",
|
||||||
|
ParameterIncorrect => "请求参数错误",
|
||||||
|
};
|
||||||
|
f.write_str(res)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log and return INTERNAL_SERVER_ERROR
|
||||||
|
fn log_internal_error<T: Display>(err: T) -> (StatusCode, ErrorCode, String) {
|
||||||
|
use ErrorCode::*;
|
||||||
|
|
||||||
|
error!("{err}");
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
InternalError,
|
||||||
|
"internal server error".to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell axum how to convert `AppError` into a response.
|
||||||
|
impl IntoResponse for AppError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
use ErrorCode::*;
|
||||||
|
|
||||||
|
let (status_code, code, err_message) = match self {
|
||||||
|
AppError::Any(err) => log_internal_error(err),
|
||||||
|
AppError::AxumFormRejection(_) | AppError::AxumJsonRejection(_) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
ParameterIncorrect,
|
||||||
|
self.to_string(),
|
||||||
|
),
|
||||||
|
// route
|
||||||
|
// AppError::AuthorizeFailed(err) => {
|
||||||
|
// (StatusCode::UNAUTHORIZED, AuthorizeFailed, err.to_string())
|
||||||
|
// }
|
||||||
|
// AppError::UserConflict(err) => (StatusCode::CONFLICT, UserConflict, err.to_string()),
|
||||||
|
};
|
||||||
|
let body = Json(json!({
|
||||||
|
"code": code,
|
||||||
|
"message": code.to_string(),
|
||||||
|
"error": err_message
|
||||||
|
}));
|
||||||
|
(status_code, body).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AppResult<T, E = AppError> = Result<T, E>;
|
30
frameworks/Rust/axum-xitca/src/main.rs
Normal file
30
frameworks/Rust/axum-xitca/src/main.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
use consts::{DEFAULT_PORT, NAME};
|
||||||
|
use dotenvy::dotenv;
|
||||||
|
use tower_compat::TowerHttp;
|
||||||
|
use utils::init_logger;
|
||||||
|
|
||||||
|
mod consts;
|
||||||
|
mod error;
|
||||||
|
mod middlewares;
|
||||||
|
mod routes;
|
||||||
|
mod tower_compat;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
dotenv().ok();
|
||||||
|
init_logger();
|
||||||
|
|
||||||
|
let service = TowerHttp::service(|| async { Ok(routes::routes()) });
|
||||||
|
|
||||||
|
let port = env::var("PHTHONUS_PORT")
|
||||||
|
.map(|port| port.parse::<u16>().unwrap_or(DEFAULT_PORT))
|
||||||
|
.unwrap_or(DEFAULT_PORT);
|
||||||
|
|
||||||
|
xitca_server::Builder::new()
|
||||||
|
.bind(NAME, format!("0.0.0.0:{port}"), service)?
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
}
|
69
frameworks/Rust/axum-xitca/src/middlewares/mod.rs
Normal file
69
frameworks/Rust/axum-xitca/src/middlewares/mod.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::Bytes,
|
||||||
|
extract::Request,
|
||||||
|
http::{HeaderMap, HeaderValue},
|
||||||
|
middleware::Next,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use tower_http::classify::ServerErrorsFailureClass;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
use tracing::{error, info, info_span, Span};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
consts::{NAME, VERSION},
|
||||||
|
error::AppResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Middleware for adding version information to each response's headers.
|
||||||
|
///
|
||||||
|
/// This middleware takes an incoming `Request` and a `Next` handler, which represents the
|
||||||
|
/// subsequent middleware or route in the chain. It then asynchronously runs the next handler,
|
||||||
|
/// obtaining the response. After receiving the response, it appends two headers:
|
||||||
|
/// - "Server": The name of the server extracted from the Cargo package name.
|
||||||
|
/// - "S-Version": The version of the server extracted from the Cargo package version.
|
||||||
|
pub async fn add_version(
|
||||||
|
req: Request<axum::body::Body>,
|
||||||
|
next: Next,
|
||||||
|
) -> AppResult<impl IntoResponse> {
|
||||||
|
let mut res = next.run(req).await;
|
||||||
|
let headers = res.headers_mut();
|
||||||
|
headers.append("Server", HeaderValue::from_static(NAME));
|
||||||
|
headers.append("Phthonus-Version", HeaderValue::from_static(VERSION));
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Middleware for logging each request.
|
||||||
|
///
|
||||||
|
/// This middleware will calculate each request latency
|
||||||
|
/// and add request's information to each info_span.
|
||||||
|
pub fn logging_route(router: Router) -> Router {
|
||||||
|
router.layer(
|
||||||
|
TraceLayer::new_for_http()
|
||||||
|
.make_span_with(|req: &Request<_>| {
|
||||||
|
let unknown = &HeaderValue::from_static("Unknown");
|
||||||
|
let empty = &HeaderValue::from_static("");
|
||||||
|
let headers = req.headers();
|
||||||
|
let ua = headers
|
||||||
|
.get("User-Agent")
|
||||||
|
.unwrap_or(unknown)
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or("Unknown");
|
||||||
|
let host = headers.get("Host").unwrap_or(empty).to_str().unwrap_or("");
|
||||||
|
info_span!("HTTP", method = ?req.method(), host, uri = ?req.uri(), ua)
|
||||||
|
})
|
||||||
|
.on_request(|_req: &Request<_>, _span: &Span| {})
|
||||||
|
.on_response(|res: &Response, latency: Duration, _span: &Span| {
|
||||||
|
info!("{} {}μs", res.status(), latency.as_micros());
|
||||||
|
})
|
||||||
|
.on_body_chunk(|_chunk: &Bytes, _latency: Duration, _span: &Span| {})
|
||||||
|
.on_eos(|_trailers: Option<&HeaderMap>, _stream_duration: Duration, _span: &Span| {})
|
||||||
|
.on_failure(
|
||||||
|
|error: ServerErrorsFailureClass, _latency: Duration, _span: &Span| {
|
||||||
|
error!("{}", error);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
99
frameworks/Rust/axum-xitca/src/routes/mod.rs
Normal file
99
frameworks/Rust/axum-xitca/src/routes/mod.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use std::{borrow::Cow, collections::HashMap, time::Duration};
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
async_trait,
|
||||||
|
extract::{FromRequestParts, Path},
|
||||||
|
http::{request::Parts, StatusCode, Uri},
|
||||||
|
middleware,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
routing::get,
|
||||||
|
Json, RequestPartsExt, Router,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use tower::ServiceBuilder;
|
||||||
|
use tower_http::timeout::TimeoutLayer;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{AppResult, ErrorCode},
|
||||||
|
middlewares::{add_version, logging_route},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct RouteResponse<T>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
code: ErrorCode,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
message: Option<Cow<'static, str>>,
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
pub type RouteResult<T> = AppResult<Json<RouteResponse<T>>>;
|
||||||
|
|
||||||
|
pub fn routes() -> Router {
|
||||||
|
let router = Router::new()
|
||||||
|
.route("/", get(hello).post(hello))
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(middleware::from_fn(add_version))
|
||||||
|
.layer(TimeoutLayer::new(Duration::from_secs(15))),
|
||||||
|
)
|
||||||
|
.fallback(fallback);
|
||||||
|
logging_route(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// hello world
|
||||||
|
pub async fn hello() -> String {
|
||||||
|
format!("hello {}", env!("CARGO_PKG_NAME"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback route handler for handling unmatched routes.
|
||||||
|
///
|
||||||
|
/// This asynchronous function takes a `Uri` as an argument, representing the unmatched route.
|
||||||
|
/// It logs a message indicating that the specified route is not found and returns a standard
|
||||||
|
/// "Not Found" response with a `StatusCode` of `404`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// - `uri`: The `Uri` representing the unmatched route.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns a tuple `(StatusCode, &str)` where `StatusCode` is set to `NOT_FOUND` (404),
|
||||||
|
/// indicating that the route was not found, and the string "Not found" as the response body.
|
||||||
|
pub async fn fallback(uri: Uri) -> impl IntoResponse {
|
||||||
|
info!("route {} not found", uri);
|
||||||
|
(StatusCode::NOT_FOUND, "Not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Version {
|
||||||
|
V1,
|
||||||
|
V2,
|
||||||
|
V3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<S> FromRequestParts<S> for Version
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = Response;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
let params: Path<HashMap<String, String>> =
|
||||||
|
parts.extract().await.map_err(IntoResponse::into_response)?;
|
||||||
|
|
||||||
|
let version = params
|
||||||
|
.get("version")
|
||||||
|
.ok_or_else(|| (StatusCode::NOT_FOUND, "version param missing").into_response())?;
|
||||||
|
|
||||||
|
match version.as_str() {
|
||||||
|
"v1" => Ok(Version::V1),
|
||||||
|
"v2" => Ok(Version::V2),
|
||||||
|
"v3" => Ok(Version::V3),
|
||||||
|
_ => Err((StatusCode::NOT_FOUND, "unknown version").into_response()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
frameworks/Rust/axum-xitca/src/tower_compat.rs
Normal file
73
frameworks/Rust/axum-xitca/src/tower_compat.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use core::{cell::RefCell, fmt, future::Future, marker::PhantomData};
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use http_body::Body;
|
||||||
|
use xitca_http::{
|
||||||
|
bytes::Bytes,
|
||||||
|
h1::RequestBody,
|
||||||
|
http::{Request, RequestExt, Response},
|
||||||
|
HttpServiceBuilder,
|
||||||
|
};
|
||||||
|
use xitca_io::net::io_uring::TcpStream;
|
||||||
|
use xitca_service::{
|
||||||
|
fn_build, middleware::UncheckedReady, ready::ReadyService, Service, ServiceExt,
|
||||||
|
};
|
||||||
|
use xitca_web::service::tower_http_compat::{CompatReqBody, CompatResBody};
|
||||||
|
|
||||||
|
pub struct TowerHttp<S, B> {
|
||||||
|
service: RefCell<S>,
|
||||||
|
_p: PhantomData<fn(B)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
impl<S, B> TowerHttp<S, B> {
|
||||||
|
pub fn service<F, Fut>(
|
||||||
|
func: F,
|
||||||
|
) -> impl Service<
|
||||||
|
Response = impl ReadyService + Service<(TcpStream, SocketAddr)>,
|
||||||
|
Error = impl fmt::Debug,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
F: Fn() -> Fut + Send + Sync + Clone,
|
||||||
|
Fut: Future<Output = Result<S, Error>>,
|
||||||
|
S: tower::Service<
|
||||||
|
Request<CompatReqBody<RequestExt<RequestBody>, ()>>,
|
||||||
|
Response = Response<B>,
|
||||||
|
>,
|
||||||
|
S::Error: fmt::Debug,
|
||||||
|
B: Body<Data = Bytes> + Send + 'static,
|
||||||
|
{
|
||||||
|
fn_build(move |_| {
|
||||||
|
let func = func.clone();
|
||||||
|
async move {
|
||||||
|
func().await.map(|service| TowerHttp {
|
||||||
|
service: RefCell::new(service),
|
||||||
|
_p: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.enclosed(UncheckedReady)
|
||||||
|
.enclosed(HttpServiceBuilder::h1().io_uring())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> Service<Request<RequestExt<RequestBody>>> for TowerHttp<S, B>
|
||||||
|
where
|
||||||
|
S: tower::Service<Request<CompatReqBody<RequestExt<RequestBody>, ()>>, Response = Response<B>>,
|
||||||
|
{
|
||||||
|
type Response = Response<CompatResBody<B>>;
|
||||||
|
type Error = S::Error;
|
||||||
|
|
||||||
|
async fn call(
|
||||||
|
&self,
|
||||||
|
req: Request<RequestExt<RequestBody>>,
|
||||||
|
) -> Result<Self::Response, Self::Error> {
|
||||||
|
let (parts, ext) = req.into_parts();
|
||||||
|
let req = Request::from_parts(parts, CompatReqBody::new(ext, ()));
|
||||||
|
let fut = self.service.borrow_mut().call(req);
|
||||||
|
let (parts, body) = fut.await?.into_parts();
|
||||||
|
Ok(Response::from_parts(parts, CompatResBody::new(body)))
|
||||||
|
}
|
||||||
|
}
|
81
frameworks/Rust/axum-xitca/src/utils/mod.rs
Normal file
81
frameworks/Rust/axum-xitca/src/utils/mod.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use tokio::signal;
|
||||||
|
use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter};
|
||||||
|
|
||||||
|
/// Initializes the logger for tracing.
|
||||||
|
pub fn init_logger() {
|
||||||
|
let formatting_layer = fmt::layer()
|
||||||
|
// .pretty()
|
||||||
|
.with_thread_ids(false)
|
||||||
|
.with_target(false)
|
||||||
|
.with_writer(std::io::stdout);
|
||||||
|
|
||||||
|
let env_layer = EnvFilter::try_from_env("axum").unwrap_or_else(|_| "info".into());
|
||||||
|
|
||||||
|
registry().with(env_layer).with(formatting_layer).init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously waits for a shutdown signal and executes a callback function when a signal is received.
|
||||||
|
///
|
||||||
|
/// This function listens for shutdown signals in the form of `Ctrl+C` and termination signals. When one of
|
||||||
|
/// these signals is received, it invokes the provided callback function `shutdown_cb`.
|
||||||
|
///
|
||||||
|
/// The behavior of the signal handling depends on the operating system:
|
||||||
|
///
|
||||||
|
/// - On Unix-based systems (e.g., Linux, macOS), it listens for termination signals (such as SIGTERM).
|
||||||
|
/// - On non-Unix systems (e.g., Windows), it only listens for `Ctrl+C` and ignores termination signals.
|
||||||
|
///
|
||||||
|
/// The `shutdown_cb` callback function is executed when either signal is received. This function should
|
||||||
|
/// contain the logic needed to gracefully shut down the application or perform any necessary cleanup tasks.
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// - `shutdown_cb`: A closure or function to call when a shutdown signal is received. The function should
|
||||||
|
/// have the signature `Fn()`. This callback is executed without any parameters.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// - If setting up the signal handlers fails, the function will panic with an error message.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - Panics if the setup for `Ctrl+C` or termination signal handlers fails.
|
||||||
|
///
|
||||||
|
/// # Platform-specific behavior
|
||||||
|
///
|
||||||
|
/// - On Unix-based systems, termination signals are handled using the `signal` crate for Unix signals.
|
||||||
|
/// - On non-Unix systems, only `Ctrl+C` signals are handled, and termination signals are not supported.
|
||||||
|
///
|
||||||
|
/// # Future
|
||||||
|
///
|
||||||
|
/// This function returns a future that resolves when either `Ctrl+C` or a termination signal is received
|
||||||
|
/// and the callback function has been executed.
|
||||||
|
pub async fn shutdown_signal<F>(shutdown_cb: F)
|
||||||
|
where
|
||||||
|
F: Fn(),
|
||||||
|
{
|
||||||
|
let ctrl_c = async {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install Ctrl+C handler");
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let terminate = async {
|
||||||
|
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||||
|
.expect("failed to install signal handler")
|
||||||
|
.recv()
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let terminate = std::future::pending::<()>();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = ctrl_c => {
|
||||||
|
shutdown_cb()
|
||||||
|
// let _ = stop_core().map_err(log_err);
|
||||||
|
},
|
||||||
|
_ = terminate => {
|
||||||
|
shutdown_cb()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,6 @@ type Result<T> = std::result::Result<T, Box<dyn Error>>;
|
|||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
init_logger();
|
init_logger();
|
||||||
info!("Hello, world!");
|
|
||||||
|
|
||||||
let port = env::var("PHTHONUS_PORT")
|
let port = env::var("PHTHONUS_PORT")
|
||||||
.map(|port| port.parse::<u16>().unwrap_or(DEFAULT_PORT))
|
.map(|port| port.parse::<u16>().unwrap_or(DEFAULT_PORT))
|
||||||
|
@ -2,20 +2,6 @@ use tokio::signal;
|
|||||||
use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter};
|
||||||
|
|
||||||
/// Initializes the logger for tracing.
|
/// Initializes the logger for tracing.
|
||||||
///
|
|
||||||
/// This function sets up the necessary layers for tracing using the `tracing_subscriber`
|
|
||||||
/// crate. It configures the formatting layer and environment filter based on the value
|
|
||||||
/// of the `LIMOS_LOG` environment variable (defaulting to "info" if not set).
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use utils::init_logger;
|
|
||||||
///
|
|
||||||
/// fn test() {
|
|
||||||
/// init_logger();
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn init_logger() {
|
pub fn init_logger() {
|
||||||
let formatting_layer = fmt::layer()
|
let formatting_layer = fmt::layer()
|
||||||
// .pretty()
|
// .pretty()
|
||||||
|
Reference in New Issue
Block a user