mirror of
https://github.com/DefectingCat/phthonus
synced 2025-07-15 16:41:32 +00:00
Merge commit 'da92ec0'
This commit is contained in:
@ -6,8 +6,11 @@ Web framework comparison in my stack.
|
||||
|
||||
- Read environment variables from `.env` file.
|
||||
- Log request info to std.
|
||||
- Loggin request http method, host, uri, and UA
|
||||
- Loggin response status code, latency
|
||||
- Serialize json and plain text.
|
||||
- JWT support.
|
||||
- Graceful shutdown.
|
||||
|
||||
## Frameworks
|
||||
|
||||
@ -19,3 +22,4 @@ Web framework comparison in my stack.
|
||||
- Nodejs
|
||||
- Deno
|
||||
- Bunjs
|
||||
- [ ] Elysia
|
||||
|
42
frameworks/Bunjs/elysia/.gitignore
vendored
Normal file
42
frameworks/Bunjs/elysia/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
**/*.trace
|
||||
**/*.zip
|
||||
**/*.tar.gz
|
||||
**/*.tgz
|
||||
**/*.log
|
||||
package-lock.json
|
||||
**/*.bun
|
15
frameworks/Bunjs/elysia/README.md
Normal file
15
frameworks/Bunjs/elysia/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Elysia with Bun runtime
|
||||
|
||||
## Getting Started
|
||||
To get started with this template, simply paste this command into your terminal:
|
||||
```bash
|
||||
bun create elysia ./elysia-example
|
||||
```
|
||||
|
||||
## Development
|
||||
To start the development server run:
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Open http://localhost:3000/ with your browser to see the result.
|
BIN
frameworks/Bunjs/elysia/bun.lockb
Executable file
BIN
frameworks/Bunjs/elysia/bun.lockb
Executable file
Binary file not shown.
15
frameworks/Bunjs/elysia/package.json
Normal file
15
frameworks/Bunjs/elysia/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "elysia",
|
||||
"version": "1.0.50",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "bun run --watch src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"elysia": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
},
|
||||
"module": "src/index.js"
|
||||
}
|
7
frameworks/Bunjs/elysia/src/index.ts
Normal file
7
frameworks/Bunjs/elysia/src/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Elysia } from "elysia";
|
||||
|
||||
const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
|
||||
|
||||
console.log(
|
||||
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
|
||||
);
|
103
frameworks/Bunjs/elysia/tsconfig.json
Normal file
103
frameworks/Bunjs/elysia/tsconfig.json
Normal file
@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "ES2022", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
1281
frameworks/Rust/axum-xitca/Cargo.lock
generated
Normal file
1281
frameworks/Rust/axum-xitca/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
frameworks/Rust/axum-xitca/Cargo.toml
Normal file
30
frameworks/Rust/axum-xitca/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "phthonus"
|
||||
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 = { version = "0.4.0", features = ["io-uring"] }
|
||||
xitca-http = { version = "0.6.0", features = ["io-uring"] }
|
||||
xitca-io = { version = "0.4.1", features = ["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"
|
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"
|
@ -1,4 +1,4 @@
|
||||
use std::{borrow::Cow, fmt::Display};
|
||||
use std::fmt::Display;
|
||||
|
||||
use axum::{
|
||||
extract::rejection::{FormRejection, JsonRejection},
|
||||
@ -20,13 +20,12 @@ pub enum AppError {
|
||||
AxumFormRejection(#[from] FormRejection),
|
||||
#[error(transparent)]
|
||||
AxumJsonRejection(#[from] JsonRejection),
|
||||
|
||||
// route
|
||||
// 路由通常错误 错误信息直接返回用户
|
||||
#[error("{0}")]
|
||||
AuthorizeFailed(Cow<'static, str>),
|
||||
#[error("{0}")]
|
||||
UserConflict(Cow<'static, str>),
|
||||
// #[error("{0}")]
|
||||
// AuthorizeFailed(Cow<'static, str>),
|
||||
// #[error("{0}")]
|
||||
// UserConflict(Cow<'static, str>),
|
||||
}
|
||||
|
||||
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
|
||||
@ -81,12 +80,11 @@ impl IntoResponse for AppError {
|
||||
ParameterIncorrect,
|
||||
self.to_string(),
|
||||
),
|
||||
|
||||
// route
|
||||
AppError::AuthorizeFailed(err) => {
|
||||
(StatusCode::UNAUTHORIZED, AuthorizeFailed, err.to_string())
|
||||
}
|
||||
AppError::UserConflict(err) => (StatusCode::CONFLICT, UserConflict, err.to_string()),
|
||||
// AppError::AuthorizeFailed(err) => {
|
||||
// (StatusCode::UNAUTHORIZED, AuthorizeFailed, err.to_string())
|
||||
// }
|
||||
// AppError::UserConflict(err) => (StatusCode::CONFLICT, UserConflict, err.to_string()),
|
||||
};
|
||||
let body = Json(json!({
|
||||
"code": code,
|
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
|
||||
}
|
@ -12,7 +12,10 @@ use tower_http::classify::ServerErrorsFailureClass;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tracing::{error, info, info_span, Span};
|
||||
|
||||
use crate::error::AppResult;
|
||||
use crate::{
|
||||
consts::{NAME, VERSION},
|
||||
error::AppResult,
|
||||
};
|
||||
|
||||
/// Middleware for adding version information to each response's headers.
|
||||
///
|
||||
@ -27,11 +30,8 @@ pub async fn add_version(
|
||||
) -> AppResult<impl IntoResponse> {
|
||||
let mut res = next.run(req).await;
|
||||
let headers = res.headers_mut();
|
||||
headers.append("Server", HeaderValue::from_static(env!("CARGO_PKG_NAME")));
|
||||
headers.append(
|
||||
"Phthonus-Version",
|
||||
HeaderValue::from_static(env!("CARGO_PKG_VERSION")),
|
||||
);
|
||||
headers.append("Server", HeaderValue::from_static(NAME));
|
||||
headers.append("Phthonus-Version", HeaderValue::from_static(VERSION));
|
||||
Ok(res)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use axum::{
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::{compression::CompressionLayer, cors::CorsLayer, timeout::TimeoutLayer};
|
||||
use tower_http::timeout::TimeoutLayer;
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
@ -37,9 +37,7 @@ pub fn routes() -> Router {
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.layer(middleware::from_fn(add_version))
|
||||
.layer(CorsLayer::permissive())
|
||||
.layer(TimeoutLayer::new(Duration::from_secs(15)))
|
||||
.layer(CompressionLayer::new()),
|
||||
.layer(TimeoutLayer::new(Duration::from_secs(15))),
|
||||
)
|
||||
.fallback(fallback);
|
||||
logging_route(router)
|
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)))
|
||||
}
|
||||
}
|
@ -2,20 +2,6 @@ use tokio::signal;
|
||||
use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter};
|
||||
|
||||
/// 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() {
|
||||
let formatting_layer = fmt::layer()
|
||||
// .pretty()
|
2
frameworks/Rust/axum/.cargo/config.toml
Normal file
2
frameworks/Rust/axum/.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = ["-C", "target-cpu=native", "-Z", "threads=8"]
|
15
frameworks/Rust/axum/.editorconfig
Normal file
15
frameworks/Rust/axum/.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/.env.example
Normal file
1
frameworks/Rust/axum/.env.example
Normal file
@ -0,0 +1 @@
|
||||
PHTHONUS_PORT=4000
|
2
frameworks/Rust/axum/.gitignore
vendored
Normal file
2
frameworks/Rust/axum/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target/
|
||||
.env
|
@ -43,9 +43,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.88"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
@ -82,9 +82,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@ -108,7 +108,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower 0.5.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@ -116,9 +116,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@ -129,7 +129,7 @@ dependencies = [
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 0.1.2",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@ -152,9 +152,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@ -393,6 +393,8 @@ dependencies = [
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -856,18 +858,18 @@ checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -939,7 +941,6 @@ dependencies = [
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -948,15 +949,21 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64",
|
||||
@ -976,7 +983,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower 0.4.13",
|
||||
"tower 0.5.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
@ -3,20 +3,17 @@ name = "phthonus"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[build]
|
||||
rustflags = ["-C", "target-cpu=native"]
|
||||
|
||||
[dependencies]
|
||||
# server
|
||||
axum = "0.7.5"
|
||||
axum = "0.7.6"
|
||||
tokio = { version = "1.40.0", features = ["full"] }
|
||||
tower = "0.5.1"
|
||||
tower-http = { version = "0.5.2", features = ["full"] }
|
||||
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.88"
|
||||
thiserror = "1.0.63"
|
||||
anyhow = "1.0.89"
|
||||
thiserror = "1.0.64"
|
||||
# tools
|
||||
dotenvy = "0.15.7"
|
||||
serde = { version = "1.0.210", features = ["derive", "serde_derive"] }
|
62
frameworks/Rust/axum/Makefile
Normal file
62
frameworks/Rust/axum/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/rust-toolchain.toml
Normal file
2
frameworks/Rust/axum/rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
3
frameworks/Rust/axum/src/consts.rs
Normal file
3
frameworks/Rust/axum/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/src/error.rs
Normal file
98
frameworks/Rust/axum/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>;
|
69
frameworks/Rust/axum/src/middlewares/mod.rs
Normal file
69
frameworks/Rust/axum/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);
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
24
frameworks/Rust/axum/src/routes/json.rs
Normal file
24
frameworks/Rust/axum/src/routes/json.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use axum::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::ErrorCode;
|
||||
|
||||
use super::{RouteResponse, RouteResult};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct JsonData {
|
||||
pub name: Cow<'static, str>,
|
||||
}
|
||||
|
||||
pub async fn json() -> RouteResult<JsonData> {
|
||||
let data = JsonData { name: "xfy".into() };
|
||||
let res = RouteResponse {
|
||||
code: ErrorCode::Normal,
|
||||
message: None,
|
||||
data,
|
||||
};
|
||||
|
||||
Ok(Json(res))
|
||||
}
|
102
frameworks/Rust/axum/src/routes/mod.rs
Normal file
102
frameworks/Rust/axum/src/routes/mod.rs
Normal file
@ -0,0 +1,102 @@
|
||||
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},
|
||||
};
|
||||
|
||||
pub mod json;
|
||||
|
||||
#[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))
|
||||
.route("/json", get(json::json).post(json::json))
|
||||
.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()),
|
||||
}
|
||||
}
|
||||
}
|
81
frameworks/Rust/axum/src/utils/mod.rs
Normal file
81
frameworks/Rust/axum/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()
|
||||
},
|
||||
}
|
||||
}
|
5
oha.md
Normal file
5
oha.md
Normal file
@ -0,0 +1,5 @@
|
||||
disable keepalive
|
||||
|
||||
```sh
|
||||
oha -z 10sec -c 50 -j --latency-correction --disable-keepalive http://localhost:4001/json
|
||||
```
|
Submodule web/Bunjs/elysia deleted from de891a6187
Reference in New Issue
Block a user