merge: 合并热重载功能分支
合并 feature-ms-v0.0.1-SNAPSHOOT-20260319 的热重载实现: - 使用 notify-debouncer-mini 监听 mocks 目录变化 - AppState 使用 RwLock<MockRouter> 支持并发读写 - 200ms 防抖避免编辑器保存时的多次触发 保留当前分支的功能: - 文件上传功能 (upload.rs) - 请求体 body 匹配支持 - 完整的测试覆盖 (43个测试) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
Cargo.lock
generated
102
Cargo.lock
generated
@@ -105,6 +105,12 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
@@ -210,6 +216,15 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -432,6 +447,26 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
@@ -448,6 +483,26 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||||
|
dependencies = [
|
||||||
|
"kqueue-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue-sys"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -512,6 +567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -524,6 +580,8 @@ dependencies = [
|
|||||||
"axum-extra",
|
"axum-extra",
|
||||||
"chrono",
|
"chrono",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"notify",
|
||||||
|
"notify-debouncer-mini",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
@@ -553,6 +611,42 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "8.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"kqueue",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"notify-types",
|
||||||
|
"walkdir",
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-debouncer-mini"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a689eb4262184d9a1727f9087cd03883ea716682ab03ed24efec57d7716dccb8"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"notify",
|
||||||
|
"notify-types",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify-types"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -664,7 +758,7 @@ version = "0.5.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -673,7 +767,7 @@ version = "1.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -1159,7 +1253,7 @@ version = "0.244.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"semver",
|
"semver",
|
||||||
@@ -1380,7 +1474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags 2.10.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ tracing-subscriber = "0.3.22"
|
|||||||
# 性能优化:快速哈希(可选,用于路由匹配)
|
# 性能优化:快速哈希(可选,用于路由匹配)
|
||||||
#dashmap = "7.0.0-rc2"
|
#dashmap = "7.0.0-rc2"
|
||||||
# 热加载支持(扩展功能)
|
# 热加载支持(扩展功能)
|
||||||
#notify = "8.2.0"
|
notify = "8.2.0"
|
||||||
|
notify-debouncer-mini = "0.6.0"
|
||||||
# 路径处理
|
# 路径处理
|
||||||
#pathdiff = "0.2.3"
|
#pathdiff = "0.2.3"
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ response:
|
|||||||
"msg": "success"
|
"msg": "success"
|
||||||
}
|
}
|
||||||
settings:
|
settings:
|
||||||
delay_ms: 200 # 模拟真实网络延迟
|
delay_ms: 2000 # 模拟真实网络延迟
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ use axum::{
|
|||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, RwLock};
|
||||||
use tokio_util::io::ReaderStream; // 需在 Cargo.toml 确认有 tokio-util
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use crate::router::MockRouter;
|
use crate::router::MockRouter;
|
||||||
|
|
||||||
/// 共享的应用状态
|
/// 共享的应用状态,router 由 RwLock 保护以支持热重载
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub router: MockRouter,
|
pub router: RwLock<MockRouter>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 全局统一请求处理函数
|
/// 全局统一请求处理函数
|
||||||
@@ -26,7 +26,7 @@ pub async fn mock_handler(
|
|||||||
let path = req.uri().path().to_string();
|
let path = req.uri().path().to_string();
|
||||||
let method_str = method.as_str().to_string();
|
let method_str = method.as_str().to_string();
|
||||||
|
|
||||||
// 将 Axum HeaderMap 转换为简单的 HashMap 供 Router 使用
|
// 1. 将需要的数据克隆出来,断开与 req 的借用关系
|
||||||
let body_bytes = match axum::body::to_bytes(req.into_body(), 10 * 1024 * 1024).await {
|
let body_bytes = match axum::body::to_bytes(req.into_body(), 10 * 1024 * 1024).await {
|
||||||
Ok(bytes) => bytes,
|
Ok(bytes) => bytes,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -39,40 +39,41 @@ pub async fn mock_handler(
|
|||||||
|
|
||||||
let incoming_json: Option<serde_json::Value> = serde_json::from_slice(&body_bytes).ok();
|
let incoming_json: Option<serde_json::Value> = serde_json::from_slice(&body_bytes).ok();
|
||||||
|
|
||||||
|
// 2. 将 Axum HeaderMap 转换为简单的 HashMap
|
||||||
let mut req_headers = HashMap::new();
|
let mut req_headers = HashMap::new();
|
||||||
for (name, value) in headers.iter() {
|
for (name, value) in headers.iter() {
|
||||||
if let Ok(v) = value.to_str() {
|
if let Ok(v) = value.to_str() {
|
||||||
req_headers.insert(name.as_str().to_string(), v.to_string());
|
req_headers.insert(name.as_str().to_string(), v.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2. 执行匹配逻辑
|
|
||||||
if let Some(rule) =
|
// 3. 执行匹配逻辑:先获取读锁 (Read Lock)
|
||||||
state
|
let maybe_rule = {
|
||||||
.router
|
let router = state.router.read().expect("Failed to acquire read lock");
|
||||||
.match_rule(&method_str, &path, ¶ms, &req_headers, &incoming_json)
|
router.match_rule(&method_str, &path, ¶ms, &req_headers, &incoming_json).cloned()
|
||||||
{
|
// 使用 .cloned() 以便尽早释放读锁,避免阻塞热重载写锁
|
||||||
// 3. 处理模拟延迟
|
};
|
||||||
|
|
||||||
|
if let Some(rule) = maybe_rule {
|
||||||
|
// 4. 处理模拟延迟
|
||||||
if let Some(ref settings) = rule.settings {
|
if let Some(ref settings) = rule.settings {
|
||||||
if let Some(delay) = settings.delay_ms {
|
if let Some(delay) = settings.delay_ms {
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 构建响应基础信息
|
// 5. 构建响应
|
||||||
let status = StatusCode::from_u16(rule.response.status).unwrap_or(StatusCode::OK);
|
let status = StatusCode::from_u16(rule.response.status).unwrap_or(StatusCode::OK);
|
||||||
let mut response_builder = Response::builder().status(status);
|
let mut response_builder = Response::builder().status(status);
|
||||||
|
|
||||||
// 注入 YAML 定义的 Header
|
|
||||||
if let Some(ref h) = rule.response.headers {
|
if let Some(ref h) = rule.response.headers {
|
||||||
for (k, v) in h {
|
for (k, v) in h {
|
||||||
// println!("{}:{}",k.clone(), v.clone());
|
|
||||||
response_builder = response_builder.header(k, v);
|
response_builder = response_builder.header(k, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 执行 Smart Body 协议逻辑
|
// 6. Smart Body 逻辑
|
||||||
if let Some(file_path) = rule.response.get_file_path() {
|
if let Some(file_path) = rule.response.get_file_path() {
|
||||||
// A. 文件模式:异步打开文件并转换为流,实现低内存占用
|
|
||||||
match tokio::fs::File::open(file_path).await {
|
match tokio::fs::File::open(file_path).await {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
let stream = ReaderStream::new(file);
|
let stream = ReaderStream::new(file);
|
||||||
@@ -88,13 +89,13 @@ pub async fn mock_handler(
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// B. 内联模式:直接返回字符串内容
|
// 内联模式:直接返回字符串内容
|
||||||
response_builder
|
response_builder
|
||||||
.body(Body::from(rule.response.body.clone()))
|
.body(Body::from(rule.response.body.clone()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("请求头{:?}", req_headers.clone());
|
println!("请求头{:?}", req_headers);
|
||||||
// 匹配失败返回 404
|
// 匹配失败返回 404
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(StatusCode::NOT_FOUND)
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
|||||||
54
src/main.rs
54
src/main.rs
@@ -1,7 +1,10 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::time::Duration;
|
||||||
use axum::{routing::{any, post}, Router};
|
use axum::{routing::{any, post}, Router};
|
||||||
|
use notify_debouncer_mini::{new_debouncer, notify::*};
|
||||||
|
|
||||||
use mock_server::loader::MockLoader;
|
use mock_server::loader::MockLoader;
|
||||||
use mock_server::router::MockRouter;
|
use mock_server::router::MockRouter;
|
||||||
use mock_server::handler::{mock_handler, AppState};
|
use mock_server::handler::{mock_handler, AppState};
|
||||||
@@ -9,34 +12,58 @@ use mock_server::upload::upload_handler;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// 1. 初始化日志(建议添加,方便观察加载情况)
|
|
||||||
// 需要在 Cargo.toml 添加 tracing-subscriber = "0.3"
|
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
// 2. 递归加载所有的 YAML 配置文件
|
// 1. 确保 mocks 目录存在
|
||||||
let mocks_dir = Path::new("./mocks");
|
let mocks_dir = Path::new("./mocks");
|
||||||
if !mocks_dir.exists() {
|
if !mocks_dir.exists() {
|
||||||
println!("Warning: 'mocks/' directory not found. Creating it...");
|
println!("Warning: 'mocks/' directory not found. Creating it...");
|
||||||
std::fs::create_dir_all(mocks_dir).unwrap();
|
std::fs::create_dir_all(mocks_dir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Scanning mocks directory...");
|
// 2. 确保 storage 目录存在
|
||||||
let index = MockLoader::load_all_from_dir(mocks_dir);
|
|
||||||
|
|
||||||
// 3. 确保 storage 目录存在
|
|
||||||
let storage_dir = Path::new("./storage");
|
let storage_dir = Path::new("./storage");
|
||||||
if !storage_dir.exists() {
|
if !storage_dir.exists() {
|
||||||
println!("Creating storage directory...");
|
println!("Creating storage directory...");
|
||||||
std::fs::create_dir_all(storage_dir).unwrap();
|
std::fs::create_dir_all(storage_dir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 构建路由引擎并包装为共享状态
|
// 3. 初始加载 Mock 配置
|
||||||
let router_engine = MockRouter::new(index);
|
println!("Scanning mocks directory...");
|
||||||
|
let index = MockLoader::load_all_from_dir(mocks_dir);
|
||||||
|
|
||||||
|
// 4. 构建共享状态(使用 RwLock 支持热重载)
|
||||||
let shared_state = Arc::new(AppState {
|
let shared_state = Arc::new(AppState {
|
||||||
router: router_engine,
|
router: RwLock::new(MockRouter::new(index)),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5. 配置 Axum 路由
|
// 5. 设置热加载监听器
|
||||||
|
let state_for_watcher = shared_state.clone();
|
||||||
|
let watch_path = mocks_dir.to_path_buf();
|
||||||
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
// 200ms 防抖,防止编辑器保存文件时产生多次干扰
|
||||||
|
let mut debouncer = new_debouncer(Duration::from_millis(200), tx).unwrap();
|
||||||
|
debouncer.watcher().watch(&watch_path, RecursiveMode::Recursive).unwrap();
|
||||||
|
|
||||||
|
// 启动异步任务监听文件变动
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Ok(res) = rx.recv() {
|
||||||
|
match res {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("🔄 Detecting changes in mocks/, reloading...");
|
||||||
|
let new_index = MockLoader::load_all_from_dir(&watch_path);
|
||||||
|
// 获取写锁 (Write Lock) 更新索引
|
||||||
|
let mut writer = state_for_watcher.router.write().expect("Failed to acquire write lock");
|
||||||
|
*writer = MockRouter::new(new_index);
|
||||||
|
println!("✅ Mocks reloaded successfully.");
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("Watcher error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. 配置 Axum 路由
|
||||||
// 文件上传路由:POST /api/upload
|
// 文件上传路由:POST /api/upload
|
||||||
// 其他所有请求由 mock_handler 处理
|
// 其他所有请求由 mock_handler 处理
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
@@ -44,10 +71,11 @@ async fn main() {
|
|||||||
.fallback(any(mock_handler))
|
.fallback(any(mock_handler))
|
||||||
.with_state(shared_state);
|
.with_state(shared_state);
|
||||||
|
|
||||||
// 6. 启动服务
|
// 7. 启动服务
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||||
println!("🚀 Rust Mock Server is running on http://{}", addr);
|
println!("🚀 Rust Mock Server is running on http://{}", addr);
|
||||||
println!("📁 File upload endpoint: POST http://{}/api/upload", addr);
|
println!("📁 File upload endpoint: POST http://{}/api/upload", addr);
|
||||||
|
println!("🔄 Hot reload enabled for mocks/ directory");
|
||||||
println!("Ready to handle requests based on your YAML definitions.");
|
println!("Ready to handle requests based on your YAML definitions.");
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user