diff --git a/src/js_engine/bindings.rs b/src/js_engine/bindings.rs new file mode 100644 index 0000000..c504334 --- /dev/null +++ b/src/js_engine/bindings.rs @@ -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, + pub body: Option, +} + +impl<'js> FromJs<'js> for JsRequest { + fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { + 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::() { + 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>("body").unwrap_or(None); + + Ok(JsRequest { + method, + uri, + headers, + body, + }) + } +} + +impl<'js> IntoJs<'js> for JsRequest { + fn into_js(self, ctx: &Ctx<'js>) -> Result, 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, + pub body: Option, +} + +impl<'js> FromJs<'js> for JsResponse { + fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result { + 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::() { + 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>("body").unwrap_or(None); + + Ok(JsResponse { + status, + headers, + body, + }) + } +} + +impl<'js> IntoJs<'js> for JsResponse { + fn into_js(self, ctx: &Ctx<'js>) -> Result, 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| { + 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))) +} diff --git a/src/js_engine/error.rs b/src/js_engine/error.rs index 0c0bcdb..16ea235 100644 --- a/src/js_engine/error.rs +++ b/src/js_engine/error.rs @@ -17,3 +17,9 @@ pub enum JsEngineError { #[error("Generic error: {0}")] Generic(String), } + +impl From for JsEngineError { + fn from(err: rquickjs::Error) -> Self { + JsEngineError::Execution(err.to_string()) + } +} diff --git a/src/js_engine/middleware.rs b/src/js_engine/middleware.rs new file mode 100644 index 0000000..9f049ad --- /dev/null +++ b/src/js_engine/middleware.rs @@ -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, + middleware_container: Arc, +} + +impl MiddlewareExecutor { + /// 创建新的中间件执行器 + pub fn new(context: Arc, middleware_container: Arc) -> Self { + Self { + context, + middleware_container, + } + } + + /// 执行完整的中间件链 + pub async fn execute( + &self, + hook: MiddlewareHook, + request: axum::http::Request, + site_name: Option<&str>, + route_path: Option<&str>, + ) -> Result>, 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 { + 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, + _request: JsRequest, + ) -> Result, 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) -> Result { + 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, 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 { + 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>, +) -> 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(()) +} diff --git a/src/js_engine/mod.rs b/src/js_engine/mod.rs index bc51718..01024bf 100644 --- a/src/js_engine/mod.rs +++ b/src/js_engine/mod.rs @@ -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; diff --git a/src/js_engine/runtime.rs b/src/js_engine/runtime.rs new file mode 100644 index 0000000..1e1fcd7 --- /dev/null +++ b/src/js_engine/runtime.rs @@ -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, + context: Option>, +} + +impl JsRuntime { + /// 创建新的JavaScript运行时 + pub async fn new() -> Result { + // 创建运行时 + 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(&self, code: &str) -> Result + 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( + &self, + module_name: &str, + code: &str, + ) -> Result + 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(&self, code: &str) -> Result + 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 { + 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(()) + } +} diff --git a/src/js_engine/types.rs b/src/js_engine/types.rs index 02bb89c..3c662d4 100644 --- a/src/js_engine/types.rs +++ b/src/js_engine/types.rs @@ -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 for MiddlewareFunction { + fn as_ref(&self) -> &str { + &self.0 } } /// 中间件容器 #[derive(Debug, Clone, Default)] pub struct MiddlewareContainer { - /// 全局中间件 - global: Vec, + /// 全局中间件(hook -> functions) + pub global: HashMap>, /// 站点级别中间件(hostname -> hooks) - site: HashMap>, + pub sites: HashMap>>, /// 路由级别中间件(route_key -> hooks) - route: HashMap>, + pub routes: HashMap>>, } 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>> { + 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>> { + 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() } } diff --git a/test_rquickjs.rs b/test_rquickjs.rs new file mode 100644 index 0000000..8596adf --- /dev/null +++ b/test_rquickjs.rs @@ -0,0 +1,16 @@ +use rquickjs::{AsyncContext, AsyncRuntime, Error}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + 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(()) +} diff --git a/test_runtime.rs b/test_runtime.rs new file mode 100644 index 0000000..9227608 --- /dev/null +++ b/test_runtime.rs @@ -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(()) +}