# mock-server-plus **Repository Path**: chaoxa/mock-server-plus ## Basic Information - **Project Name**: mock-server-plus - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-03-12 - **Last Updated**: 2025-03-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # mock-server-plus #### 介绍 {**以下是 Gitee 平台说明,您可以替换此简介** Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} #### 软件架构 软件架构说明 #### 安装教程 1. xxxx 2. xxxx 3. xxxx #### 使用说明 1. xxxx 2. xxxx 3. xxxx #### 参与贡献 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request #### 特技 1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md 2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) 3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) 6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) src/ ├── main.rs # 程序入口 ├── app_state.rs # 应用状态管理 ├── handlers/ # 处理函数模块 │ ├── users.rs # 用户相关处理 │ ├── templates.rs # 模板相关处理 │ └── form.rs # 表单处理 ├── middleware/ # 中间件模块 │ └── request.rs # 请求响应中间件 ├── models/ # 数据模型 │ ┌── user.rs # 用户模型 │ └── error.rs # 错误处理 ├── routes.rs # 路由配置 ├── templates/ # 模板相关函数 │ └── functions.rs # Tera 自定义函数 └── time_library.rs # 时间库模拟 ```rust //! Example showing how to convert errors into responses. //! //! Run with //! //! ```not_rust //! cargo run -p example-error-handling //! ``` //! //! For successful requests the log output will be //! //! ```ignore //! DEBUG request{method=POST uri=/users matched_path="/users"}: tower_http::trace::on_request: started processing request //! DEBUG request{method=POST uri=/users matched_path="/users"}: tower_http::trace::on_response: finished processing request latency=0 ms status=200 //! ``` //! //! For failed requests the log output will be //! //! ```ignore //! DEBUG request{method=POST uri=/users matched_path="/users"}: tower_http::trace::on_request: started processing request //! ERROR request{method=POST uri=/users matched_path="/users"}: example_error_handling: error from time_library err=failed to get time //! DEBUG request{method=POST uri=/users matched_path="/users"}: tower_http::trace::on_response: finished processing request latency=0 ms status=500 //! ``` mod app_state; mod routes; use axum::extract::{DefaultBodyLimit, Multipart}; use axum::http::{header, HeaderValue}; use axum::middleware::Next; use axum::response::Html; use axum::routing::get; use axum::{ body::{Body, Bytes}, extract::{rejection::JsonRejection, MatchedPath, Request, State}, http, http::StatusCode, response::{IntoResponse, Response}, routing::post, Router, }; use axum_macros::{debug_handler, FromRequest}; use http_body_util::BodyExt; use serde::{Deserialize, Serialize}; use serde_json::{json, Number, Value}; use std::time::{Duration, SystemTime}; use std::{ collections::HashMap, sync::{ atomic::{AtomicU64, Ordering}, Arc, Mutex, }, }; use tera::Tera; use time_library::Timestamp; use tokio::signal; use tokio::time::sleep; use tower_http::limit::RequestBodyLimitLayer; use tower_http::timeout::TimeoutLayer; use tower_http::trace::TraceLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::main] async fn main() { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { format!("{}=debug,tower_http=debug", env!("CARGO_CRATE_NAME")).into() }), ) .with(tracing_subscriber::fmt::layer()) .init(); let state = AppState::default(); let app = Router::new() // A dummy route that accepts some JSON but sometimes fails .route("/", get(handler)) .route("/users", post(users_create)) .route("/slow", get(|| sleep(Duration::from_secs(5)))) .route("/forever", get(std::future::pending::<()>)) .route("/get-head", get(get_head_handler)) .route("/form", get(show_form).post(accept_form)) .route("/reader_template", get(create_template)) .layer(( TraceLayer::new_for_http() // Create our own span for the request and include the matched path. The matched // path is useful for figuring out which handler the request was routed to. .on_request(|req: &Request, _span: &tracing::Span| { tracing::debug!( "started processing request {:#?}", req.extensions().is_empty() ); }) .make_span_with(|req: &Request| { let method = req.method(); let uri = req.uri(); // axum automatically adds this extension. let matched_path = req .extensions() .get::() .map(|matched_path| matched_path.as_str()); tracing::debug_span!("request", %method, %uri, matched_path) }) // By default `TraceLayer` will log 5xx responses but we're doing our specific // logging of errors so disable that .on_failure(()), TimeoutLayer::new(Duration::from_secs(10)), )) .layer(DefaultBodyLimit::disable()) .layer(RequestBodyLimitLayer::new( 250 * 1024 * 1024, /* 250mb */ )) .layer(axum::middleware::from_fn(print_request_response)) .with_state(state); let app = app.fallback(handler_404); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); tracing::debug!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await .unwrap(); } async fn shutdown_signal() { 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 => {}, _ = terminate => {}, } } #[derive(Default, Clone)] struct AppState { next_id: Arc, users: Arc>>, } #[derive(Deserialize)] struct UserParams { name: String, } #[derive(Serialize, Clone)] struct User { id: u64, name: String, created_at: Timestamp, updated_at: SystemTime, } #[debug_handler] async fn users_create( State(state): State, // Make sure to use our own JSON extractor so we get input errors formatted in a way that // matches our application AppJson(params): AppJson, ) -> Result, AppError> { let id = state.next_id.fetch_add(1, Ordering::SeqCst); // We have implemented `From for AppError` which allows us to use `?` to // automatically convert the error let created_at = Timestamp::now()?; let user = User { id, name: params.name, created_at, updated_at: SystemTime::now(), }; state.users.lock().unwrap().insert(id, user.clone()); Ok(AppJson(user)) } fn add_one(args: &HashMap) -> Result { tracing::debug!("add_one {:?}", serde_json::to_string(args)); if let Some(Value::Number(num)) = args.get("num") { if let Some(int_num) = num.as_i64() { Ok(json!(int_num + 1)) } else { Err(tera::Error::msg("Expected integer")) } } else { Err(tera::Error::msg("Invalid argument")) } // match value { // Value::Number(num) => { // if let Some(int_num) = num.as_i64() { // Ok(tera::Value::Number(tera::Number::from(int_num + 1))) // } else { // Err(tera::Error::msg("Expected an integer")) // } // } // _ => Err(tera::Error::msg("Expected a number")), // } } pub fn add_two(value: &Value, _: &HashMap) -> tera::Result { tracing::debug!("add_two {:?}", serde_json::to_string(value)); Ok(Value::Number(Number::from(29))) } #[derive(Debug, Serialize, Deserialize)] struct DataResult { data: T, } static HELLO_TPL: &str = r#" {% set add_two = 25 %} { "name": "{{data.name | lower}}", "time_at": "{{data.time}}" "no": {{ add_one(num=28) + 1 }}, "rand": {{ get_random(start=0, end=100) }} }"#; #[derive(Debug, Serialize)] struct TplData { name: String, time: String, no: i64, } #[debug_handler] async fn create_template() -> Result { let mut tera = Tera::new("templates/**/*")?; tera.register_function("add_one", add_one); tera.add_raw_template("hello", HELLO_TPL)?; // Prepare the context with some data let mut context = tera::Context::new(); context.insert( "data", &TplData { name: "World".into(), time: "2023-01-01".to_owned(), no: 12, }, ); let rendered = tera.render("hello", &context)?; Ok(( [( header::CONTENT_TYPE, HeaderValue::from_static("application/json; charset=utf-8"), )], DataResult { data: rendered }, ) .into_response()) } impl IntoResponse for DataResult { fn into_response(self) -> Response { let body = self.data; ( [( header::CONTENT_TYPE, HeaderValue::from_static("application/json; charset=utf-8"), )], body, ) .into_response() } } async fn show_form() -> Html<&'static str> { Html( r#"
"#, ) } async fn accept_form(mut multipart: Multipart) { while let Some(field) = multipart.next_field().await.unwrap() { let name = field.name().unwrap().to_string(); let file_name = field.file_name().unwrap().to_string(); let content_type = field.content_type().unwrap().to_string(); let data = field.bytes().await.unwrap(); println!( "Length of `{name}` (`{file_name}`: `{content_type}`) is {} bytes", data.len() ); } } async fn get_head_handler(method: http::Method) -> Response { tracing::debug!("got a head request"); if method == http::Method::HEAD { return ([("x-some-header", "header from HEAD")]).into_response(); } do_some_computing_task(); ([("x-some-header", "header from GET")], "body from GET").into_response() } fn do_some_computing_task() { // TODO } async fn print_request_response( req: Request, next: Next, ) -> Result { let (parts, body) = req.into_parts(); let bytes = buffer_and_print("request", body).await?; let req = Request::from_parts(parts, Body::from(bytes)); let res = next.run(req).await; let (parts, body) = res.into_parts(); let bytes = buffer_and_print("response", body).await?; let res = Response::from_parts(parts, Body::from(bytes)); Ok(res) } async fn buffer_and_print(direction: &str, body: B) -> Result where B: axum::body::HttpBody, B::Error: std::fmt::Display, { let bytes = match body.collect().await { Ok(collected) => collected.to_bytes(), Err(err) => { return Err(( StatusCode::BAD_REQUEST, format!("failed to read {direction} body: {err}"), )); } }; if let Ok(body) = std::str::from_utf8(&bytes) { tracing::debug!("{direction} body = {body:?}"); } Ok(bytes) } async fn handler() -> Html<&'static str> { Html("

Hello, World!

") } async fn handler_404() -> impl IntoResponse { (StatusCode::NOT_FOUND, "nothing to see here") } // Create our own JSON extractor by wrapping `axum::Json`. This makes it easy to override the // rejection and provide our own which formats errors to match our application. // // `axum::Json` responds with plain text if the input is invalid. #[derive(FromRequest)] #[from_request(via(axum::Json), rejection(AppError))] struct AppJson(T); impl IntoResponse for AppJson where axum::Json: IntoResponse, { fn into_response(self) -> Response { axum::Json(self.0).into_response() } } // The kinds of errors we can hit in our application. enum AppError { // The request body contained invalid JSON JsonRejection(JsonRejection), // Some error from a third party library we're using TimeError(time_library::Error), TeraError(tera::Error), } // Tell axum how `AppError` should be converted into a response. // // This is also a convenient place to log errors. impl IntoResponse for AppError { fn into_response(self) -> Response { // How we want errors responses to be serialized #[derive(Serialize)] struct ErrorResponse { message: String, error_code: String, } let (status, message) = match self { AppError::JsonRejection(rejection) => { // This error is caused by bad user input so don't log it (rejection.status(), rejection.body_text()) } AppError::TimeError(err) => { // Because `TraceLayer` wraps each request in a span that contains the request // method, uri, etc we don't need to include those details here tracing::error!(%err, "error from time_library"); // Don't expose any details about the error to the client ( StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong".to_owned(), ) } AppError::TeraError(err) => { // Because `TraceLayer` wraps each request in a span that contains the request // method, uri, etc we don't need to include those details here tracing::debug!(%err, "error from tera"); // Don't expose any details about the error to the client ( StatusCode::INTERNAL_SERVER_ERROR, "Tera template went wrong".to_owned(), ) } }; ( status, AppJson(ErrorResponse { message, error_code: "E500001".to_owned(), }), ) .into_response() } } impl From for AppError { fn from(rejection: JsonRejection) -> Self { Self::JsonRejection(rejection) } } impl From for AppError { fn from(error: time_library::Error) -> Self { Self::TimeError(error) } } impl From for AppError { fn from(error: tera::Error) -> Self { tracing::error!(%error, "error from tera"); Self::TeraError(error) } } // Imagine this is some third party library that we're using. It sometimes returns errors which we // want to log. mod time_library { use std::sync::atomic::{AtomicU64, Ordering}; use serde::Serialize; #[derive(Serialize, Clone)] pub struct Timestamp(u64); impl Timestamp { pub fn now() -> Result { static COUNTER: AtomicU64 = AtomicU64::new(0); // Fail on every third call just to simulate errors if COUNTER.fetch_add(1, Ordering::SeqCst) % 3 == 0 { Err(Error::FailedToGetTime) } else { Ok(Self(1337)) } } } #[derive(Debug)] pub enum Error { FailedToGetTime, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "failed to get time") } } } ```