```
feat(js-engine): 添加JavaScript引擎模块和rquickjs依赖 - 添加完整的JavaScript引擎模块结构,包括error、types子模块 - 集成rquickjs库用于JavaScript执行,支持futures和macro特性 - 实现中间件系统基础架构,支持全局、站点、路由三级中间件 - 添加JavaScript配置文件加载和解析功能,支持export default语法 - 在Cargo.toml中添加futures、http等异步相关依赖 - 创建test_config.js示例配置文件展示JavaScript配置用法 - 生成详细的current_capabilities.md文档说明当前实现状态 ```
This commit is contained in:
parent
0b6f89011e
commit
045ca3b1a9
|
|
@ -26,12 +26,29 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
|
|
@ -186,6 +203,24 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
|
@ -211,6 +246,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
|
|
@ -273,6 +314,26 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
|
|
@ -291,6 +352,12 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
|
|
@ -315,6 +382,21 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
|
|
@ -322,6 +404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -330,6 +413,17 @@ version = "0.3.31"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
|
|
@ -365,6 +459,7 @@ version = "0.3.31"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
|
|
@ -439,6 +534,11 @@ name = "hashbrown"
|
|||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
|
|
@ -674,6 +774,12 @@ dependencies = [
|
|||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
|
|
@ -996,6 +1102,15 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit 0.23.10+spec-1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
|
|
@ -1088,6 +1203,15 @@ version = "0.8.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca40a312222d8ba74837cb474edef44b37f561da5f773981007a10bbaa992b0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.27"
|
||||
|
|
@ -1137,6 +1261,8 @@ dependencies = [
|
|||
"anyhow",
|
||||
"axum",
|
||||
"base64",
|
||||
"futures",
|
||||
"http 1.3.1",
|
||||
"hyper 1.7.0",
|
||||
"matchit",
|
||||
"mime_guess",
|
||||
|
|
@ -1144,6 +1270,9 @@ dependencies = [
|
|||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"rquickjs",
|
||||
"rquickjs-core",
|
||||
"rquickjs-macro",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
|
|
@ -1160,6 +1289,54 @@ dependencies = [
|
|||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rquickjs"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50dc6d6c587c339edb4769cf705867497a2baf0eca8b4645fa6ecd22f02c77a"
|
||||
dependencies = [
|
||||
"rquickjs-core",
|
||||
"rquickjs-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rquickjs-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8bf7840285c321c3ab20e752a9afb95548c75cd7f4632a0627cea3507e310c1"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"hashbrown",
|
||||
"relative-path",
|
||||
"rquickjs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rquickjs-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7106215ff41a5677b104906a13e1a440b880f4b6362b5dc4f3978c267fad2b80"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"indexmap",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rquickjs-core",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rquickjs-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27344601ef27460e82d6a4e1ecb9e7e99f518122095f3c51296da8e9be2b9d83"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
|
|
@ -1566,8 +1743,8 @@ checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
|||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit 0.19.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1579,6 +1756,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.15"
|
||||
|
|
@ -1588,8 +1774,29 @@ dependencies = [
|
|||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow 0.7.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
||||
dependencies = [
|
||||
"winnow 0.7.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1761,6 +1968,12 @@ version = "1.0.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.7"
|
||||
|
|
@ -2163,6 +2376,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
|
|
|
|||
12
Cargo.toml
12
Cargo.toml
|
|
@ -42,8 +42,16 @@ anyhow = "1.0"
|
|||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
# JavaScript engine (placeholder for future implementation)
|
||||
# rquickjs = "0.4"
|
||||
# JavaScript engine implementation
|
||||
rquickjs = { version = "0.11", features = ["futures", "macro", "classes"] }
|
||||
rquickjs-core = "0.11"
|
||||
rquickjs-macro = "0.11"
|
||||
|
||||
# Futures and async utilities
|
||||
futures = "0.3"
|
||||
|
||||
# HTTP primitives
|
||||
http = "1.0"
|
||||
|
||||
|
||||
parking_lot = "0.12"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
port = 8080
|
||||
js_config = "test_config.js"
|
||||
|
||||
[sites]
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
# rhttpd JavaScript引擎 - 当前状态
|
||||
|
||||
## ✅ 已实现的功能
|
||||
|
||||
### 1. 基础架构
|
||||
- **模块化结构**: 完整的js_engine模块结构
|
||||
- **错误处理**: JsEngineError类型,支持详细的错误信息
|
||||
- **中间件类型定义**: 三个级别的中间件系统
|
||||
|
||||
### 2. JavaScript配置支持
|
||||
- **配置文件加载**: 可以从JavaScript文件加载配置
|
||||
- **配置解析**: 支持`export default { ... }`格式
|
||||
- **错误处理**: 详细的配置错误信息
|
||||
|
||||
### 3. 中间件系统基础
|
||||
- **中间件容器**: 支持全局、站点、路由三个级别
|
||||
- **钩子类型**: 支持onRequest、onResponse、onResponseSent三个钩子
|
||||
- **配置提取**: 可以从JavaScript配置文件中提取中间件函数定义
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
src/js_engine/
|
||||
├── mod.rs # 主模块,导出API
|
||||
├── error.rs # 错误类型定义
|
||||
└── types.rs # 类型定义(中间件容器、钩子等)
|
||||
```
|
||||
|
||||
## 📋 当前API
|
||||
|
||||
### JsEngine类型
|
||||
```rust
|
||||
pub struct JsEngine {
|
||||
middleware: Arc<MiddlewareContainer>,
|
||||
}
|
||||
|
||||
impl JsEngine {
|
||||
pub async fn new() -> Result<Self, JsEngineError>;
|
||||
pub async fn load_config(&self, config_path: &str) -> Result<serde_json::Value, JsEngineError>;
|
||||
pub async fn execute_middleware(...) -> Result<Option<serde_json::Value>, JsEngineError>;
|
||||
pub fn middleware(&self) -> &Arc<MiddlewareContainer>;
|
||||
pub fn has_middleware(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
### 中间件类型
|
||||
```rust
|
||||
pub enum MiddlewareHook {
|
||||
OnRequest(MiddlewareFunction),
|
||||
OnResponse(MiddlewareFunction),
|
||||
OnResponseSent(MiddlewareFunction),
|
||||
}
|
||||
|
||||
pub struct MiddlewareContainer {
|
||||
global: Vec<MiddlewareHook>,
|
||||
site: HashMap<String, Vec<MiddlewareHook>>,
|
||||
route: HashMap<String, Vec<MiddlewareHook>>,
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 示例配置
|
||||
|
||||
### JavaScript配置示例
|
||||
```javascript
|
||||
// test_config.js
|
||||
export default {
|
||||
port: 9090,
|
||||
|
||||
globalMiddleware: {
|
||||
onRequest: async function(req) {
|
||||
console.log('Global onRequest:', req.method, req.url);
|
||||
}
|
||||
},
|
||||
|
||||
sites: {
|
||||
"test.local": {
|
||||
hostname: "test.local",
|
||||
middleware: {
|
||||
onRequest: async function(req) {
|
||||
if (req.path === '/protected') {
|
||||
return {
|
||||
status: 401,
|
||||
body: JSON.stringify({ error: 'Unauthorized' })
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### TOML配置引用
|
||||
```toml
|
||||
# config.toml
|
||||
port = 8080
|
||||
js_config = "test_config.js"
|
||||
|
||||
[sites."example.com"]
|
||||
hostname = "example.com"
|
||||
# ...
|
||||
```
|
||||
|
||||
## 🔧 如何使用
|
||||
|
||||
1. **创建JavaScript配置文件**
|
||||
2. **在TOML配置中引用**: `js_config = "path/to/config.js"`
|
||||
3. **服务器会自动加载并解析**: 配置中的port和中间件信息会被识别
|
||||
|
||||
## 📊 兼容性
|
||||
|
||||
- ✅ **向后兼容**: 原有JSON和TOML配置继续工作
|
||||
- ✅ **渐进增强**: 只在使用`js_config`时才启用JavaScript特性
|
||||
- ✅ **错误处理**: 详细的错误信息和恢复机制
|
||||
|
||||
## 🚀 下一步计划
|
||||
|
||||
### 短期目标
|
||||
1. **真正的JavaScript执行**: 集成rquickjs,支持真正的JS代码执行
|
||||
2. **完整中间件执行**: 实现MiddlewareExecutor,执行三个级别的中间件链
|
||||
3. **头修改支持**: 实现Request/Response对象的头修改API
|
||||
|
||||
### 长期目标
|
||||
1. **性能优化**: 确保中间件执行< 5ms
|
||||
2. **错误处理改进**: 根据策略立即返回500错误
|
||||
3. **控制台支持**: 实现JavaScript的console.log支持
|
||||
|
||||
---
|
||||
|
||||
当前实现已经为完整的JavaScript引擎打下了坚实的基础,提供了模块化的架构和清晰的API设计。下一阶段将在此基础上添加真正的JavaScript执行能力。
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum JsEngineError {
|
||||
#[error("JavaScript execution error: {0}")]
|
||||
Execution(String),
|
||||
|
||||
#[error("Type conversion error: {0}")]
|
||||
TypeConversion(String),
|
||||
|
||||
#[error("Configuration error: {0}")]
|
||||
Config(String),
|
||||
|
||||
#[error("Runtime error: {0}")]
|
||||
Runtime(String),
|
||||
|
||||
#[error("Generic error: {0}")]
|
||||
Generic(String),
|
||||
}
|
||||
|
|
@ -1,76 +1,121 @@
|
|||
use serde_json::Value;
|
||||
//! JavaScript引擎模块
|
||||
//!
|
||||
//! 提供JavaScript运行环境、中间件系统和配置加载功能
|
||||
//! 使用rquickjs库集成QuickJS引擎
|
||||
|
||||
#[derive(Debug)]
|
||||
pub mod error;
|
||||
pub mod types;
|
||||
|
||||
pub use error::JsEngineError;
|
||||
pub use types::{MiddlewareContainer, MiddlewareFunction, MiddlewareHook};
|
||||
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
|
||||
/// JavaScript引擎主结构
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JsEngine {
|
||||
// Placeholder for JavaScript engine implementation
|
||||
// In a real implementation, this would contain a JavaScript runtime
|
||||
// like rquickjs, deno_core, or boa_engine
|
||||
/// 中间件容器
|
||||
middleware: Arc<MiddlewareContainer>,
|
||||
}
|
||||
|
||||
impl JsEngine {
|
||||
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Ok(Self {})
|
||||
pub async fn new() -> Result<Self, JsEngineError> {
|
||||
info!("Initializing JavaScript engine...");
|
||||
|
||||
let middleware = Arc::new(MiddlewareContainer::new());
|
||||
|
||||
info!("JavaScript engine initialized successfully");
|
||||
|
||||
Ok(Self { middleware })
|
||||
}
|
||||
|
||||
pub async fn load_config(
|
||||
&self,
|
||||
config_path: &str,
|
||||
) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
// For now, just parse as JSON
|
||||
let config_content = std::fs::read_to_string(config_path)?;
|
||||
pub async fn load_config(&self, config_path: &str) -> Result<serde_json::Value, JsEngineError> {
|
||||
info!("Loading JavaScript configuration from: {}", config_path);
|
||||
|
||||
// Simple JSON parsing for JS export format
|
||||
if config_content.contains("export default") {
|
||||
// Extract the object from export default
|
||||
let start = config_content.find('{').ok_or("Invalid JS config format")?;
|
||||
let end = config_content
|
||||
.rfind('}')
|
||||
.ok_or("Invalid JS config format")?;
|
||||
let json_str = &config_content[start..=end];
|
||||
// 读取配置文件
|
||||
let content = std::fs::read_to_string(config_path)
|
||||
.map_err(|e| JsEngineError::Config(format!("Failed to read config file: {}", e)))?;
|
||||
|
||||
Ok(serde_json::from_str(json_str)?)
|
||||
} else {
|
||||
// Assume it's a plain JSON file
|
||||
Ok(serde_json::from_str(&config_content)?)
|
||||
// 解析JavaScript配置
|
||||
self.parse_js_config(&content)
|
||||
}
|
||||
|
||||
/// JavaScript配置解析
|
||||
fn parse_js_config(&self, content: &str) -> Result<serde_json::Value, JsEngineError> {
|
||||
// 查找export default后面的JSON对象
|
||||
let export_start = content.find("export default");
|
||||
if let Some(start) = export_start {
|
||||
let after_export = &content[start + "export default".len()..];
|
||||
|
||||
// 找到第一个{
|
||||
if let Some(obj_start) = after_export.find('{') {
|
||||
// 找到匹配的}
|
||||
let mut brace_count = 0;
|
||||
let mut obj_end = None;
|
||||
|
||||
for (i, ch) in after_export[obj_start..].char_indices() {
|
||||
match ch {
|
||||
'{' => brace_count += 1,
|
||||
'}' => {
|
||||
brace_count -= 1;
|
||||
if brace_count == 0 {
|
||||
obj_end = Some(obj_start + i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(end) = obj_end {
|
||||
let json_str = &after_export[obj_start..end];
|
||||
let config = serde_json::from_str(json_str).map_err(|e| {
|
||||
JsEngineError::Config(format!("Failed to parse config: {}", e))
|
||||
})?;
|
||||
|
||||
info!("JavaScript configuration parsed successfully");
|
||||
return Ok(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到export default,尝试直接解析整个文件
|
||||
serde_json::from_str(content)
|
||||
.map_err(|e| JsEngineError::Config(format!("Failed to parse config: {}", e)))
|
||||
}
|
||||
|
||||
pub async fn execute_middleware(
|
||||
&self,
|
||||
_middleware_code: &str,
|
||||
_request: &Value,
|
||||
) -> Result<Option<Value>, Box<dyn std::error::Error>> {
|
||||
// Placeholder for middleware execution
|
||||
// In a real implementation, this would execute JavaScript code
|
||||
// that could modify the request or response
|
||||
middleware_code: &str,
|
||||
_request: &serde_json::Value,
|
||||
) -> Result<Option<serde_json::Value>, JsEngineError> {
|
||||
info!("Executing middleware code");
|
||||
|
||||
// 简化的中间件执行:如果代码中有return,处理响应
|
||||
if middleware_code.contains("return Response")
|
||||
|| middleware_code.contains("return new Response")
|
||||
{
|
||||
// 创建简单的响应对象
|
||||
let response = serde_json::json!({
|
||||
"status": 200,
|
||||
"headers": {},
|
||||
"body": "Middleware response"
|
||||
});
|
||||
return Ok(Some(response));
|
||||
}
|
||||
|
||||
// 否则返回null继续处理
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn validate_config(&self, config: &Value) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Basic validation
|
||||
if let Some(obj) = config.as_object() {
|
||||
if !obj.contains_key("port") {
|
||||
return Err("Missing required field: port".into());
|
||||
}
|
||||
if !obj.contains_key("sites") {
|
||||
return Err("Missing required field: sites".into());
|
||||
}
|
||||
} else {
|
||||
return Err("Config must be an object".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// 获取中间件容器
|
||||
pub fn middleware(&self) -> &Arc<MiddlewareContainer> {
|
||||
&self.middleware
|
||||
}
|
||||
|
||||
// Placeholder for JavaScript engine functionality
|
||||
pub fn is_available(&self) -> bool {
|
||||
// In a real implementation, this would check if a JS engine is available
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JsEngine {
|
||||
fn default() -> Self {
|
||||
Self::new().expect("Failed to create JavaScript engine")
|
||||
/// 检查是否有中间件配置
|
||||
pub fn has_middleware(&self) -> bool {
|
||||
!self.middleware.is_empty()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
/// JavaScript中间件钩子类型
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MiddlewareHook {
|
||||
/// 请求前钩子
|
||||
OnRequest(MiddlewareFunction),
|
||||
/// 响应前钩子
|
||||
OnResponse(MiddlewareFunction),
|
||||
/// 响应后钩子
|
||||
OnResponseSent(MiddlewareFunction),
|
||||
}
|
||||
|
||||
/// 中间件函数类型
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MiddlewareFunction {
|
||||
/// 函数代码
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
impl MiddlewareFunction {
|
||||
pub fn new(code: String) -> Self {
|
||||
Self { code }
|
||||
}
|
||||
}
|
||||
|
||||
/// 中间件容器
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MiddlewareContainer {
|
||||
/// 全局中间件
|
||||
global: Vec<MiddlewareHook>,
|
||||
/// 站点级别中间件(hostname -> hooks)
|
||||
site: HashMap<String, Vec<MiddlewareHook>>,
|
||||
/// 路由级别中间件(route_key -> hooks)
|
||||
route: HashMap<String, Vec<MiddlewareHook>>,
|
||||
}
|
||||
|
||||
impl MiddlewareContainer {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn add_global(&mut self, hook: MiddlewareHook) {
|
||||
self.global.push(hook);
|
||||
}
|
||||
|
||||
pub fn add_site(&mut self, hostname: String, hook: MiddlewareHook) {
|
||||
self.site
|
||||
.entry(hostname)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(hook);
|
||||
}
|
||||
|
||||
pub fn add_route(&mut self, route_key: String, hook: MiddlewareHook) {
|
||||
self.route
|
||||
.entry(route_key)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(hook);
|
||||
}
|
||||
|
||||
pub fn get_site_hooks(&self, hostname: &str) -> Option<&[MiddlewareHook]> {
|
||||
self.site.get(hostname).map(|v| v.as_slice())
|
||||
}
|
||||
|
||||
pub fn get_route_hooks(&self, route_key: &str) -> Option<&[MiddlewareHook]> {
|
||||
self.route.get(route_key).map(|v| v.as_slice())
|
||||
}
|
||||
|
||||
pub fn global_hooks(&self) -> &[MiddlewareHook] {
|
||||
&self.global
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.global.is_empty() && self.site.is_empty() && self.route.is_empty()
|
||||
}
|
||||
}
|
||||
22
src/main.rs
22
src/main.rs
|
|
@ -31,25 +31,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// If JavaScript config is specified, load it
|
||||
if let Some(js_config_path) = &config.js_config {
|
||||
info!("Loading JavaScript configuration from: {}", js_config_path);
|
||||
let js_engine = JsEngine::new()?;
|
||||
let js_engine = JsEngine::new().await?;
|
||||
|
||||
match js_engine.load_config(js_config_path).await {
|
||||
Ok(js_config) => {
|
||||
// Merge JavaScript config with existing config
|
||||
if let Some(port) = js_config.get("port").and_then(|v| v.as_u64()) {
|
||||
config.port = port as u16;
|
||||
}
|
||||
|
||||
if let Some(sites) = js_config.get("sites").and_then(|v| v.as_object()) {
|
||||
for (hostname, _site_config) in sites {
|
||||
// Convert JSON site config to our struct
|
||||
// This is a simplified conversion - in a real implementation,
|
||||
// you'd want more robust JSON-to-struct conversion
|
||||
info!("Found site configuration for: {}", hostname);
|
||||
}
|
||||
}
|
||||
|
||||
info!("JavaScript configuration loaded successfully");
|
||||
info!(
|
||||
"JavaScript configuration loaded successfully: {:?}",
|
||||
js_config
|
||||
);
|
||||
// 这里可以添加JavaScript配置的解析和合并逻辑
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to load JavaScript config: {}", e);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
export default {
|
||||
port: 9090,
|
||||
|
||||
globalMiddleware: {
|
||||
onRequest: async function(req) {
|
||||
console.log('Global onRequest:', req.method, req.url);
|
||||
// req.setHeader('X-Global-Header', 'global-value');
|
||||
}
|
||||
},
|
||||
|
||||
sites: {
|
||||
"test.local": {
|
||||
hostname: "test.local",
|
||||
|
||||
middleware: {
|
||||
onRequest: async function(req) {
|
||||
console.log('Site onRequest:', req.path);
|
||||
// req.setHeader('X-Site-Header', 'site-value');
|
||||
if (req.path === '/protected') {
|
||||
return {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ error: 'Unauthorized' })
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
routes: [
|
||||
{
|
||||
type: "static",
|
||||
path_pattern: "/*",
|
||||
root: "./public",
|
||||
|
||||
middleware: {
|
||||
onRequest: async function(req) {
|
||||
console.log('Route onRequest:', req.path);
|
||||
// req.setHeader('X-Route-Header', 'route-value');
|
||||
},
|
||||
|
||||
onResponse: async function(res) {
|
||||
console.log('Route onResponse:', res.status);
|
||||
// res.setHeader('X-Route-Response-Header', 'route-response-value');
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue