Skip to main content

aiway_plugin/
lib.rs

1//! # aiway-plugin
2//!
3//! 网关插件 SDK,用于实现自定义插件。
4
5pub mod wasm_types;
6
7mod macros;
8mod plugin_ctx;
9mod wasm_ctx;
10
11pub use crate::plugin_ctx::{
12    FormPart, HttpRequest, HttpRequestBuilder, HttpResponse, LOG_DEBUG, LOG_ERROR, LOG_INFO,
13    LOG_TRACE, LOG_WARN, PluginContext, PluginContextExt,
14};
15pub use async_trait::async_trait;
16pub use bincode;
17pub use bytes::Bytes;
18pub use http;
19pub use semver::Version;
20use serde::de::DeserializeOwned;
21pub use serde_json;
22use serde_json::Value;
23pub use wasm_ctx::{respond_to_host, WasmHttpContext};
24
25/// 插件信息
26#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
27pub struct PluginInfo {
28    /// 插件版本
29    pub version: Version,
30    /// 默认配置
31    pub default_config: Value,
32    /// 插件描述,用于简要描述插件的功能
33    pub description: String,
34    /// 插件使用手册,通常是一个内容为 `markdown` 格式的字符串
35    pub readme: Option<String>,
36}
37
38/// 插件控制流
39///
40/// 插件阶段返回此枚举,决定网关是继续执行后续插件/流程,还是由插件主动响应并终止。
41#[derive(Debug)]
42pub enum Outcome {
43    /// 继续执行下一个插件或后续流程
44    Continue,
45    /// 主动响应,会终止后续流程,当某个插件处理后,不想继续后续流程时,返回该值
46    Respond(Response),
47}
48
49/// 插件主动响应
50///
51/// 当插件需要直接返回响应时使用(如预检、缓存命中、mock 等场景)。
52#[derive(Debug)]
53pub struct Response {
54    /// HTTP 状态码
55    pub status: u16,
56    /// 响应头
57    pub headers: Vec<(String, String)>,
58    /// 响应体
59    pub body: Vec<u8>,
60}
61
62/// 插件错误类型
63#[derive(Debug)]
64pub enum PluginError {
65    /// 执行插件业务逻辑时的错误
66    ExecuteError(String),
67    /// 插件不存在
68    NotFound(String),
69    /// 从磁盘或网络加载插件时错误
70    LoadError(String),
71    /// 序列化/反序列化错误
72    SerdeError(String),
73    /// HTTP 错误(发起HTTP调用错误)
74    HttpError(String),
75}
76
77pub type PluginResult = Result<Outcome, PluginError>;
78
79impl Outcome {
80    /// 继续执行,等价于 `Ok(Outcome::Continue)`
81    pub fn goon() -> PluginResult {
82        Ok(Outcome::Continue)
83    }
84
85    /// 主动响应,会终止后续流程
86    pub fn respond(status: u16, headers: Vec<(String, String)>, body: Vec<u8>) -> PluginResult {
87        Ok(Outcome::Respond(Response {
88            status,
89            headers,
90            body,
91        }))
92    }
93
94    /// 拒绝请求,等价于 `respond(status, vec![], msg)`
95    ///
96    /// 用于限流(429)、鉴权失败(403)、参数校验(400) 等场景。
97    pub fn reject(status: u16, msg: impl Into<String>) -> PluginResult {
98        Ok(Outcome::Respond(Response {
99            status,
100            headers: Vec::new(),
101            body: msg.into().into_bytes(),
102        }))
103    }
104
105    pub fn execute_error(msg: impl Into<String>) -> PluginResult {
106        Err(PluginError::ExecuteError(msg.into()))
107    }
108
109    pub fn not_found(msg: impl Into<String>) -> PluginResult {
110        Err(PluginError::NotFound(msg.into()))
111    }
112}
113
114impl std::fmt::Display for PluginError {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        match self {
117            PluginError::ExecuteError(msg) => write!(f, "{}", msg),
118            PluginError::NotFound(msg) => write!(f, "{}", msg),
119            PluginError::LoadError(msg) => write!(f, "{}", msg),
120            PluginError::SerdeError(msg) => write!(f, "{}", msg),
121            PluginError::HttpError(msg) => write!(f, "{}", msg),
122        }
123    }
124}
125
126/// 插件定义
127///
128/// 插件开发者实现此 trait。
129#[async_trait]
130pub trait Plugin: Send + Sync {
131    /// 插件名称
132    fn name(&self) -> &str;
133    /// 插件信息
134    fn info(&self) -> PluginInfo;
135
136    /// 请求阶段,可改写请求头
137    ///
138    /// 插件配置通过 `ctx.config()` 获取,请求头通过 `ctx` 读写。
139    async fn on_request(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
140        Ok(Outcome::Continue)
141    }
142
143    /// 请求体阶段,可改写请求体
144    ///
145    /// 请求体通过 `ctx.request_body()` 读取、`ctx.set_request_body()` 覆盖。
146    async fn on_request_body(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
147        Ok(Outcome::Continue)
148    }
149
150    /// 响应阶段,可改写响应头
151    async fn on_response(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
152        Ok(Outcome::Continue)
153    }
154
155    /// 响应体阶段,可改写响应体
156    ///
157    /// 响应体通过 `ctx.response_body()` 读取、`ctx.set_response_body()` 覆盖。
158    async fn on_response_body(&self, _ctx: &mut dyn PluginContext) -> PluginResult {
159        Ok(Outcome::Continue)
160    }
161
162    /// 日志阶段
163    async fn on_logging(&self, _: &mut dyn PluginContext) {}
164}
165
166pub trait PluginConfigExt: Plugin {
167    /// 解析插件配置,这个只是方便调用,手动使用`serde_json`转换也可
168    fn parse_config<T>(&self, config: &Value) -> Result<T, PluginError>
169    where
170        T: DeserializeOwned,
171    {
172        serde_json::from_value(config.clone()).map_err(|e| {
173            PluginError::SerdeError(format!("[{}] pase plugin config error: {}", self.name(), e))
174        })
175    }
176}
177
178impl<T: Plugin> PluginConfigExt for T {}
179
180/// 简易 block_on,用于在同步上下文(WASM 内部)中执行 async 函数。
181///
182/// WASM 环境无真正异步 I/O,插件 future 必须立即返回 `Ready`。
183/// 若返回 `Pending` 说明插件误用了异步 I/O(如网络请求),直接 panic 露问题。
184pub fn block_on<F: Future>(f: F) -> F::Output {
185    use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
186
187    fn noop_clone(_: *const ()) -> RawWaker {
188        RawWaker::new(std::ptr::null(), &VTABLE)
189    }
190    fn noop(_: *const ()) {}
191    static VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop);
192
193    let raw_waker = RawWaker::new(std::ptr::null(), &VTABLE);
194    let waker = unsafe { Waker::from_raw(raw_waker) };
195    let mut cx = Context::from_waker(&waker);
196
197    let mut f = std::pin::pin!(f);
198    match f.as_mut().poll(&mut cx) {
199        Poll::Ready(val) => val,
200        Poll::Pending => panic!(
201            "plugin future returned Pending in WASM context; \
202             WASM plugins must not perform real async I/O"
203        ),
204    }
205}