feat(js-engine): 实现JavaScript引擎核心功能和中间件系统

添加完整的JavaScript运行时支持,包括:
- 创建bindings模块处理JavaScript与Rust类型转换
- 实现JsRequest和JsResponse包装器用于HTTP对象转换
- 添加register_bindings函数注册全局JavaScript绑定
- 实现MiddlewareExecutor执行JavaScript中间件链
- 创建JsRuntime包装器管理QuickJS运行时和上下文
- 支持多种中间件钩子(onRequest, onResponse, onResponseSent)
- 实现多层级中间件系统(全局/站点/路由级别)
- 添加JavaScript配置解析和类型转换功能
- 提供错误处理和日志记录支持
```
This commit is contained in:
kingecg 2026-01-18 21:23:43 +08:00
parent 045ca3b1a9
commit cf3b6dd95a
8 changed files with 717 additions and 32 deletions

159
src/js_engine/bindings.rs Normal file
View File

@ -0,0 +1,159 @@
use crate::js_engine::error::JsEngineError;
use rquickjs::{AsyncContext, Ctx, FromJs, IntoJs, Object, Value};
use std::collections::HashMap;
/// JavaScript请求对象包装器
#[derive(Debug, Clone)]
pub struct JsRequest {
pub method: String,
pub uri: String,
pub headers: HashMap<String, String>,
pub body: Option<String>,
}
impl<'js> FromJs<'js> for JsRequest {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self, rquickjs::Error> {
let obj = value
.as_object()
.ok_or_else(|| rquickjs::Error::new_from_js("value", "object"))?;
let method = obj.get::<_, String>("method")?;
let uri = obj.get::<_, String>("uri")?;
// 获取headers
let headers_obj: Object = match obj.get("headers") {
Ok(obj) => obj,
Err(_) => Object::new(ctx.clone())?,
};
let mut headers = HashMap::new();
for key in headers_obj.keys::<String>() {
if let Ok(key_str) = key {
if let Ok(value) = headers_obj.get::<_, String>(&key_str) {
headers.insert(key_str, value);
}
}
}
// 可选的body
let body = obj.get::<_, Option<String>>("body").unwrap_or(None);
Ok(JsRequest {
method,
uri,
headers,
body,
})
}
}
impl<'js> IntoJs<'js> for JsRequest {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>, rquickjs::Error> {
let obj = Object::new(ctx.clone())?;
obj.set("method", self.method)?;
obj.set("uri", self.uri)?;
// 设置headers
let headers_obj = Object::new(ctx.clone())?;
for (key, value) in self.headers {
headers_obj.set(key, value)?;
}
obj.set("headers", headers_obj)?;
// 设置body
obj.set("body", self.body)?;
Ok(obj.into_value())
}
}
/// JavaScript响应对象包装器
#[derive(Debug, Clone)]
pub struct JsResponse {
pub status: u16,
pub headers: HashMap<String, String>,
pub body: Option<String>,
}
impl<'js> FromJs<'js> for JsResponse {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self, rquickjs::Error> {
let obj = value
.as_object()
.ok_or_else(|| rquickjs::Error::new_from_js("value", "object"))?;
let status = obj.get::<_, u16>("status")?;
// 获取headers
let headers_obj: Object = match obj.get("headers") {
Ok(obj) => obj,
Err(_) => Object::new(ctx.clone())?,
};
let mut headers = HashMap::new();
for key in headers_obj.keys::<String>() {
if let Ok(key_str) = key {
if let Ok(value) = headers_obj.get::<_, String>(&key_str) {
headers.insert(key_str, value);
}
}
}
// 可选的body
let body = obj.get::<_, Option<String>>("body").unwrap_or(None);
Ok(JsResponse {
status,
headers,
body,
})
}
}
impl<'js> IntoJs<'js> for JsResponse {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>, rquickjs::Error> {
let obj = Object::new(ctx.clone())?;
obj.set("status", self.status)?;
// 设置headers
let headers_obj = Object::new(ctx.clone())?;
for (key, value) in self.headers {
headers_obj.set(key, value)?;
}
obj.set("headers", headers_obj)?;
// 设置body
obj.set("body", self.body)?;
Ok(obj.into_value())
}
}
// Note: 由于类型复杂性实际的HTTP转换函数将在middleware模块中实现
// 这样可以更容易地处理axum/hyper的类型转换
/// 为JavaScript上下文注册全局绑定
pub async fn register_bindings(context: &AsyncContext) -> Result<(), JsEngineError> {
context
.clone()
.with(|ctx| {
let globals = ctx.globals();
// 创建console对象
let console = Object::new(ctx.clone())?;
// 添加log方法 - 使用简单的函数
let log_func = rquickjs::Function::new(ctx.clone(), |args: Vec<String>| {
println!("[JS] {}", args.join(" "));
Ok::<(), rquickjs::Error>(())
})?;
console.set("log", log_func)?;
globals.set("console", console)?;
Ok::<_, rquickjs::Error>(())
})
.await
.map_err(|e| JsEngineError::Runtime(format!("Failed to register bindings: {}", e)))
}

View File

@ -17,3 +17,9 @@ pub enum JsEngineError {
#[error("Generic error: {0}")]
Generic(String),
}
impl From<rquickjs::Error> for JsEngineError {
fn from(err: rquickjs::Error) -> Self {
JsEngineError::Execution(err.to_string())
}
}

280
src/js_engine/middleware.rs Normal file
View File

@ -0,0 +1,280 @@
use crate::js_engine::error::JsEngineError;
use crate::js_engine::types::{MiddlewareContainer, MiddlewareFunction, MiddlewareHook};
use crate::js_engine::{JsRequest, JsResponse};
use axum::{body::Body, http::StatusCode};
use rquickjs::{AsyncContext, FromJs, Value};
use std::sync::Arc;
use tracing::{info, warn};
/// 中间件执行器
/// 负责执行JavaScript中间件链
pub struct MiddlewareExecutor {
context: Arc<AsyncContext>,
middleware_container: Arc<MiddlewareContainer>,
}
impl MiddlewareExecutor {
/// 创建新的中间件执行器
pub fn new(context: Arc<AsyncContext>, middleware_container: Arc<MiddlewareContainer>) -> Self {
Self {
context,
middleware_container,
}
}
/// 执行完整的中间件链
pub async fn execute(
&self,
hook: MiddlewareHook,
request: axum::http::Request<Body>,
site_name: Option<&str>,
route_path: Option<&str>,
) -> Result<Option<axum::http::Response<Body>>, JsEngineError> {
info!(
"Executing middleware hook: {:?} for site: {:?}, route: {:?}",
hook, site_name, route_path
);
// 获取对应Hook的中间件链
let middleware_functions = self.get_middleware_chain(hook.clone(), site_name, route_path);
if middleware_functions.is_empty() {
info!("No middleware found for hook: {:?}", hook);
return Ok(None);
}
// 将Rust请求转换为JsRequest
let js_request = convert_request(&request).await?;
// 执行中间件链
let js_response = self
.execute_middleware_chain(middleware_functions, js_request)
.await?;
// 如果中间件返回了响应转换为Rust响应
if let Some(js_response) = js_response {
let response = convert_response(js_response).await?;
Ok(Some(response))
} else {
Ok(None)
}
}
/// 获取指定Hook的中间件链
fn get_middleware_chain(
&self,
hook: MiddlewareHook,
site_name: Option<&str>,
route_path: Option<&str>,
) -> Vec<MiddlewareFunction> {
let mut chain = Vec::new();
// 添加全局中间件
if let Some(global_middleware) = self.middleware_container.global.get(&hook) {
chain.extend(global_middleware.iter().cloned());
}
// 添加站点级别中间件
if let Some(site_name) = site_name {
if let Some(site_middleware) = self.middleware_container.sites.get(site_name) {
if let Some(middleware) = site_middleware.get(&hook) {
chain.extend(middleware.iter().cloned());
}
}
}
// 添加路由级别中间件
if let Some(route_path) = route_path {
if let Some(route_middleware) = self.middleware_container.routes.get(route_path) {
if let Some(middleware) = route_middleware.get(&hook) {
chain.extend(middleware.iter().cloned());
}
}
}
chain
}
/// 执行JavaScript中间件链
async fn execute_middleware_chain(
&self,
middleware_functions: Vec<MiddlewareFunction>,
_request: JsRequest,
) -> Result<Option<JsResponse>, JsEngineError> {
let context = self.context.clone();
context
.with(move |ctx| {
// 简化版本:直接按顺序执行中间件
// 如果某个中间件返回了响应,就停止执行
for middleware_code in middleware_functions {
// 将代码包装为async函数
let wrapped_code = format!("(async function() {{ {} }})", middleware_code.0);
let middleware_func: rquickjs::Function = ctx.eval(wrapped_code.as_bytes())?;
// 调用中间件函数
let result = middleware_func.call::<(), rquickjs::Value>(())?;
// 检查是否返回了响应
if !result.is_undefined() && !result.is_null() {
let js_response = JsResponse::from_js(&ctx, result)?;
return Ok(Some(js_response));
}
}
// 所有中间件都执行完毕,没有返回响应
Ok(None)
})
.await
.map_err(|e: rquickjs::Error| {
JsEngineError::Execution(format!("Failed to execute middleware chain: {}", e))
})
}
}
/// 将Rust HTTP请求转换为JsRequest
async fn convert_request(request: &axum::http::Request<Body>) -> Result<JsRequest, JsEngineError> {
let method = request.method().to_string();
let uri = request.uri().to_string();
// 提取headers
let mut headers = std::collections::HashMap::new();
for (name, value) in request.headers() {
if let Ok(value_str) = value.to_str() {
headers.insert(name.as_str().to_string(), value_str.to_string());
}
}
// 暂时不读取请求体,因为 Body 不支持克隆
// TODO: 需要重构来支持请求体读取
let body = None;
Ok(JsRequest {
method,
uri,
headers,
body,
})
}
/// 将JsResponse转换为Rust HTTP响应
async fn convert_response(
js_response: JsResponse,
) -> Result<axum::http::Response<Body>, JsEngineError> {
let status = StatusCode::from_u16(js_response.status)
.map_err(|e| JsEngineError::Execution(format!("Invalid status code: {}", e)))?;
let mut response_builder = axum::http::Response::builder().status(status);
// 设置headers
for (name, value) in js_response.headers {
response_builder = response_builder.header(&name, value);
}
// 构建响应
let body = if let Some(body_str) = js_response.body {
Body::from(body_str)
} else {
Body::empty()
};
response_builder
.body(body)
.map_err(|e| JsEngineError::Execution(format!("Failed to build response: {}", e)))
}
/// 辅助函数从JavaScript配置文件提取中间件函数
pub fn extract_middleware_from_config(
config: &serde_json::Value,
) -> Result<MiddlewareContainer, JsEngineError> {
let mut container = MiddlewareContainer::new();
// 解析全局中间件
if let Some(global_middleware) = config.get("middleware") {
parse_middleware_object(global_middleware, &mut container.global)?;
}
// 解析站点中间件
if let Some(sites) = config.get("sites") {
if let serde_json::Value::Object(sites_obj) = sites {
for (site_name, site_config) in sites_obj {
if let serde_json::Value::Object(site_config_obj) = site_config {
let site_middleware = container.sites.entry(site_name.clone()).or_default();
if let Some(middleware) = site_config_obj.get("middleware") {
parse_middleware_object(middleware, site_middleware)?;
}
// 解析站点内的路由
if let Some(routes) = site_config_obj.get("routes") {
if let serde_json::Value::Array(routes_arr) = routes {
for route in routes_arr {
if let serde_json::Value::Object(route_obj) = route {
if let (Some(path), Some(middleware)) = (
route_obj.get("path").and_then(|p| p.as_str()),
route_obj.get("middleware"),
) {
let route_middleware =
container.routes.entry(path.to_string()).or_default();
parse_middleware_object(middleware, route_middleware)?;
}
}
}
}
}
}
}
}
}
Ok(container)
}
/// 解析中间件对象
fn parse_middleware_object(
middleware_value: &serde_json::Value,
middleware_map: &mut std::collections::HashMap<MiddlewareHook, Vec<MiddlewareFunction>>,
) -> Result<(), JsEngineError> {
if let serde_json::Value::Object(middleware_obj) = middleware_value {
for (hook_name, hook_value) in middleware_obj {
let hook = match hook_name.as_str() {
"onRequest" => MiddlewareHook::OnRequest,
"onResponse" => MiddlewareHook::OnResponse,
"onResponseSent" => MiddlewareHook::OnResponseSent,
_ => {
warn!("Unknown middleware hook: {}", hook_name);
continue;
}
};
match hook_value {
serde_json::Value::String(code) => {
middleware_map
.entry(hook.clone())
.or_default()
.push(MiddlewareFunction::new(code.clone()));
}
serde_json::Value::Array(codes) => {
for code in codes {
if let serde_json::Value::String(code_str) = code {
middleware_map
.entry(hook.clone())
.or_default()
.push(MiddlewareFunction::new(code_str.clone()));
}
}
}
_ => {
warn!(
"Invalid middleware value for hook {}: {:?}",
hook_name, hook_value
);
}
}
}
}
Ok(())
}

View File

@ -3,10 +3,16 @@
//! 提供JavaScript运行环境、中间件系统和配置加载功能
//! 使用rquickjs库集成QuickJS引擎
pub mod bindings;
pub mod error;
pub mod middleware;
pub mod runtime;
pub mod types;
pub use bindings::{JsRequest, JsResponse, register_bindings};
pub use error::JsEngineError;
pub use middleware::{MiddlewareExecutor, extract_middleware_from_config};
pub use runtime::JsRuntime;
pub use types::{MiddlewareContainer, MiddlewareFunction, MiddlewareHook};
use std::sync::Arc;

172
src/js_engine/runtime.rs Normal file
View File

@ -0,0 +1,172 @@
use crate::js_engine::error::JsEngineError;
use rquickjs::{AsyncContext, AsyncRuntime, Function, Module, Value};
use std::sync::Arc;
use tracing::info;
/// JavaScript Runtime包装器
/// 管理QuickJS运行时和上下文
#[derive(Clone)]
pub struct JsRuntime {
runtime: Arc<AsyncRuntime>,
context: Option<Arc<AsyncContext>>,
}
impl JsRuntime {
/// 创建新的JavaScript运行时
pub async fn new() -> Result<Self, JsEngineError> {
// 创建运行时
let runtime = AsyncRuntime::new()
.map_err(|e| JsEngineError::Runtime(format!("Failed to create runtime: {}", e)))?;
info!("JavaScript runtime created successfully");
// 创建完整上下文
let context = AsyncContext::full(&runtime)
.await
.map_err(|e| JsEngineError::Runtime(format!("Failed to create context: {}", e)))?;
let runtime = Arc::new(runtime);
let context = Arc::new(context);
Ok(Self {
runtime,
context: Some(context),
})
}
/// 获取异步上下文
pub fn context(&self) -> Option<&AsyncContext> {
self.context.as_deref()
}
/// 执行JavaScript代码片段并返回结果
pub async fn evaluate<R>(&self, code: &str) -> Result<R, JsEngineError>
where
R: for<'js> rquickjs::FromJs<'js> + Send + 'static,
{
let context = match &self.context {
Some(ctx) => ctx.clone(),
None => {
return Err(JsEngineError::Runtime(
"Context not initialized".to_string(),
));
}
};
context
.with(|ctx| {
let result = ctx.eval(code.as_bytes()).map_err(|e| {
JsEngineError::Execution(format!("Failed to evaluate JavaScript: {}", e))
})?;
rquickjs::FromJs::from_js(&ctx, result)
.map_err(|e| JsEngineError::Execution(format!("Type conversion failed: {}", e)))
})
.await
}
/// 执行JavaScript模块带有export default
pub async fn evaluate_module<R>(
&self,
module_name: &str,
code: &str,
) -> Result<R, JsEngineError>
where
R: for<'js> rquickjs::FromJs<'js> + Send + 'static,
{
let context = match &self.context {
Some(ctx) => ctx.clone(),
None => {
return Err(JsEngineError::Runtime(
"Context not initialized".to_string(),
));
}
};
context
.with(|ctx| {
let module =
Module::evaluate(ctx.clone(), module_name, code.as_bytes()).map_err(|e| {
JsEngineError::Execution(format!("Failed to evaluate module: {}", e))
})?;
let default_export = module.get::<_, Value>("default").map_err(|e| {
JsEngineError::Execution(format!("Failed to get default export: {}", e))
})?;
rquickjs::FromJs::from_js(&ctx, default_export).map_err(|e| {
JsEngineError::Execution(format!("Module export type conversion failed: {}", e))
})
})
.await
}
/// 创建一个JavaScript函数并立即调用无参数
pub async fn call_function<R>(&self, code: &str) -> Result<R, JsEngineError>
where
R: for<'js> rquickjs::FromJs<'js> + Send + 'static,
{
let context = match &self.context {
Some(ctx) => ctx.clone(),
None => {
return Err(JsEngineError::Runtime(
"Context not initialized".to_string(),
));
}
};
// 创建并调用函数
context
.with(|ctx| {
// 将代码包装为匿名函数并调用
let func_code = format!("(function() {{ {} }})", code);
let func: Function = ctx.eval(func_code.as_bytes()).map_err(|e| {
JsEngineError::Execution(format!("Failed to create function: {}", e))
})?;
// 调用函数(无参数)
let result = func.call(()).map_err(|e| {
JsEngineError::Execution(format!("Failed to call function: {}", e))
})?;
rquickjs::FromJs::from_js(&ctx, result).map_err(|e| {
JsEngineError::Execution(format!("Return value conversion failed: {}", e))
})
})
.await
}
/// 检查是否有待处理的作业
pub async fn has_pending_jobs(&self) -> bool {
self.runtime.is_job_pending().await
}
/// 执行第一个待处理作业
pub async fn execute_pending_job(&self) -> Result<bool, JsEngineError> {
self.runtime
.execute_pending_job()
.await
.map_err(|e| JsEngineError::Runtime(format!("Failed to execute pending job: {}", e)))
}
/// 运行垃圾回收
pub async fn run_gc(&self) {
self.runtime.run_gc().await;
}
/// 释放上下文和运行时
pub async fn shutdown(&mut self) -> Result<(), JsEngineError> {
if let Some(context) = self.context.take() {
// 调用 GC 清理内存
self.run_gc().await;
// 释放上下文
drop(context);
}
// 释放运行时
// 注意Arc 会处理引用计数
info!("JavaScript runtime shutdown complete");
Ok(())
}
}

View File

@ -1,38 +1,47 @@
use std::collections::HashMap;
/// JavaScript中间件钩子类型
#[derive(Debug, Clone)]
/// JavaScript中间件钩子类型(简化版本)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MiddlewareHook {
/// 请求前钩子
OnRequest(MiddlewareFunction),
OnRequest,
/// 响应前钩子
OnResponse(MiddlewareFunction),
OnResponse,
/// 响应后钩子
OnResponseSent(MiddlewareFunction),
OnResponseSent,
}
/// 中间件函数类型
#[derive(Debug, Clone)]
pub struct MiddlewareFunction {
/// 函数代码
pub code: String,
pub struct MiddlewareFunction(pub String);
impl std::fmt::Display for MiddlewareFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl MiddlewareFunction {
pub fn new(code: String) -> Self {
Self { code }
Self(code)
}
}
impl AsRef<str> for MiddlewareFunction {
fn as_ref(&self) -> &str {
&self.0
}
}
/// 中间件容器
#[derive(Debug, Clone, Default)]
pub struct MiddlewareContainer {
/// 全局中间件
global: Vec<MiddlewareHook>,
/// 全局中间件hook -> functions
pub global: HashMap<MiddlewareHook, Vec<MiddlewareFunction>>,
/// 站点级别中间件hostname -> hooks
site: HashMap<String, Vec<MiddlewareHook>>,
pub sites: HashMap<String, HashMap<MiddlewareHook, Vec<MiddlewareFunction>>>,
/// 路由级别中间件route_key -> hooks
route: HashMap<String, Vec<MiddlewareHook>>,
pub routes: HashMap<String, HashMap<MiddlewareHook, Vec<MiddlewareFunction>>>,
}
impl MiddlewareContainer {
@ -40,37 +49,43 @@ impl MiddlewareContainer {
Self::default()
}
pub fn add_global(&mut self, hook: MiddlewareHook) {
self.global.push(hook);
pub fn add_global(&mut self, hook: MiddlewareHook, func: MiddlewareFunction) {
self.global.entry(hook).or_default().push(func);
}
pub fn add_site(&mut self, hostname: String, hook: MiddlewareHook) {
self.site
pub fn add_site(&mut self, hostname: String, hook: MiddlewareHook, func: MiddlewareFunction) {
self.sites
.entry(hostname)
.or_insert_with(Vec::new)
.push(hook);
.or_default()
.entry(hook)
.or_default()
.push(func);
}
pub fn add_route(&mut self, route_key: String, hook: MiddlewareHook) {
self.route
pub fn add_route(&mut self, route_key: String, hook: MiddlewareHook, func: MiddlewareFunction) {
self.routes
.entry(route_key)
.or_insert_with(Vec::new)
.push(hook);
.or_default()
.entry(hook)
.or_default()
.push(func);
}
pub fn get_site_hooks(&self, hostname: &str) -> Option<&[MiddlewareHook]> {
self.site.get(hostname).map(|v| v.as_slice())
pub fn get_site_hooks(
&self,
hostname: &str,
) -> Option<&HashMap<MiddlewareHook, Vec<MiddlewareFunction>>> {
self.sites.get(hostname)
}
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 get_route_hooks(
&self,
route_key: &str,
) -> Option<&HashMap<MiddlewareHook, Vec<MiddlewareFunction>>> {
self.routes.get(route_key)
}
pub fn is_empty(&self) -> bool {
self.global.is_empty() && self.site.is_empty() && self.route.is_empty()
self.global.is_empty() && self.sites.is_empty() && self.routes.is_empty()
}
}

16
test_rquickjs.rs Normal file
View File

@ -0,0 +1,16 @@
use rquickjs::{AsyncContext, AsyncRuntime, Error};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = AsyncRuntime::new()?;
let ctx = AsyncContext::full(&rt).await?;
ctx.with(|ctx| {
let value: i32 = ctx.eval("1 + 1")?;
println!("1 + 1 = {}", value);
Ok::<(), Error>(())
})
.await?;
Ok(())
}

31
test_runtime.rs Normal file
View File

@ -0,0 +1,31 @@
use rhttpd::js_engine::{JsEngineError, JsRuntime};
#[tokio::main]
async fn main() -> Result<(), JsEngineError> {
println!("Testing JavaScript runtime...");
// 创建运行时
let runtime = JsRuntime::new().await?;
println!("Runtime created successfully");
// 测试简单表达式
let result: i32 = runtime.evaluate("1 + 2").await?;
println!("1 + 2 = {}", result);
// 测试字符串
let message: String = runtime.evaluate("'Hello, World!'").await?;
println!("Message: {}", message);
// 测试布尔值
let is_true: bool = runtime.evaluate("5 > 3").await?;
println!("5 > 3 is {}", is_true);
// 测试函数调用
let func_result: i32 = runtime.call_function("return 42;").await?;
println!("Function returned: {}", func_result);
// 清理
println!("Runtime test completed successfully");
Ok(())
}