diff --git a/plugins/wasm-rust/Dockerfile b/plugins/wasm-rust/Dockerfile index c4ef13f26..745b186b4 100644 --- a/plugins/wasm-rust/Dockerfile +++ b/plugins/wasm-rust/Dockerfile @@ -11,5 +11,5 @@ RUN cargo build --target wasm32-wasip1 $BUILD_OPTS \ && cp target/wasm32-wasip1/release/*.wasm /main.wasm \ && cargo clean -FROM scratch +FROM scratch AS output COPY --from=builder /main.wasm plugin.wasm diff --git a/plugins/wasm-rust/Makefile b/plugins/wasm-rust/Makefile index 3d18e3d6c..6d9d73078 100644 --- a/plugins/wasm-rust/Makefile +++ b/plugins/wasm-rust/Makefile @@ -20,6 +20,15 @@ build: @echo "" @echo "output wasm file: extensions/${PLUGIN_NAME}/plugin.wasm" +build-image: + DOCKER_BUILDKIT=1 docker build \ + --build-arg PLUGIN_NAME=${PLUGIN_NAME} \ + --build-arg BUILDER=${BUILDER} \ + -t ${IMG} \ + . + @echo "" + @echo "image: ${IMG}" + lint-base: cargo fmt --all --check cargo clippy --workspace --all-features --all-targets diff --git a/plugins/wasm-rust/README.md b/plugins/wasm-rust/README.md index 764fcff7d..e8dbd6389 100644 --- a/plugins/wasm-rust/README.md +++ b/plugins/wasm-rust/README.md @@ -101,6 +101,7 @@ make lint PLUGIN_NAME=say-hello 当前 Makefile 包含以下可用目标: - `build` - 构建插件(默认插件为 say-hello) +- `build-image` - 构建插件对应镜像(默认插件为 say-hello) - `lint-base` - 对所有代码进行 lint 检查 - `lint` - 对指定插件进行 lint 检查 - `test-base` - 运行所有测试 @@ -384,7 +385,7 @@ docker build -t my-custom-plugin:latest --build-arg PLUGIN_NAME=my-custom-plugin # 进入项目目录 cd plugins/wasm-rust/ -# 构建插件(会自动推送到官方仓库) +# 构建插件 make build PLUGIN_NAME=my-plugin PLUGIN_VERSION=1.0.0 # 构建构建器镜像 @@ -414,7 +415,7 @@ spec: 不同命令需要在不同的目录下执行: -- **Makefile 命令**(如 `make build`、`make test`、`make lint`):在 `plugins/wasm-rust/` 目录下执行 +- **Makefile 命令**(如 `make build`、`make build-image`、`make test`、`make lint`):在 `plugins/wasm-rust/` 目录下执行 - **Cargo 命令**(如 `cargo build`、`cargo test`):在具体的插件目录下执行(如 `plugins/wasm-rust/extensions/my-plugin/`) - **Docker 命令**:在 `plugins/wasm-rust/` 目录下执行,需要指定 `PLUGIN_NAME` 参数 diff --git a/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs b/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs index ed432e69a..04c1c1a93 100644 --- a/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs +++ b/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs @@ -1,7 +1,7 @@ -use higress_wasm_rust::cluster_wrapper::{DnsCluster, StaticIpCluster}; +use higress_wasm_rust::cluster_wrapper::{DnsCluster, FQDNCluster}; use higress_wasm_rust::log::Log; use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper}; -use higress_wasm_rust::redis_wrapper::{RedisClient, RedisClientBuilder, RedisClientConfig}; +use higress_wasm_rust::redis_wrapper::{RedisClient, RedisClientConfig}; use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher}; use http::Method; use multimap::MultiMap; @@ -23,12 +23,45 @@ proxy_wasm::main! {{ const PLUGIN_NAME: &str = "demo-wasm"; +#[derive(Debug, Deserialize, Clone)] +struct RedisConfig { + service_name: String, + #[serde(default)] + service_port: Option, + #[serde(default)] + username: Option, + #[serde(default)] + password: Option, + #[serde(default = "default_redis_timeout")] + timeout: u64, // 超时(默认1000ms) + #[serde(default)] + database: Option, +} + +impl Default for RedisConfig { + fn default() -> Self { + Self { + service_name: String::new(), + service_port: None, + username: None, + password: None, + timeout: default_redis_timeout(), + database: None, + } + } +} + +// 超时时间默认值 +fn default_redis_timeout() -> u64 { + 1000 +} + #[derive(Default, Debug, Deserialize, Clone)] struct DemoWasmConfig { // 配置文件结构体 test: String, - #[serde(default)] - password: Option, + #[serde(rename = "redis")] + redis_config: RedisConfig, } fn format_body(body: Option>) -> String { @@ -74,50 +107,49 @@ impl HttpContextWrapper for DemoWasm { // 请求header获取完成回调 self.log .info(&format!("on_http_request_complete_headers {:?}", headers)); - if let Some(config) = &self.config { - let _redis_client = RedisClientBuilder::new( - &StaticIpCluster::new("redis", 80, ""), - Duration::from_secs(5), - ) - .password(config.password.as_ref()) - .build(); - - let redis_client = RedisClient::new( - RedisClientConfig::new( - &StaticIpCluster::new("redis", 80, ""), - Duration::from_secs(5), - ) - .password(config.password.as_ref()), - ); - + // 验证配置是否存在 + let config = match &self.config { + Some(cfg) => cfg, + None => { + self.log.error("Plugin configuration is missing"); + return HeaderAction::Continue; + } + }; + // 初始化redis client + let redis_client = match self.initialize_redis_client(config) { + Ok(client) => client, + Err(err) => { + self.log + .error(&format!("Failed to initialize Redis client: {}", err)); + return HeaderAction::Continue; + } + }; + self.redis_client = Some(redis_client); + // 调用redis + if let Some(redis_client) = &self.redis_client { if let Some(self_rc) = self.weak.upgrade() { - let init_res = redis_client.init(); - self.log.info(&format!("redis init {:?}", init_res)); - if init_res.is_ok() { - let incr_res = redis_client.incr( - "connect", - Box::new(move |res, status, token_id| { - self_rc.borrow().log().info(&format!( - "redis incr finish value_res:{:?}, status: {}, token_id: {}", - res, status, token_id - )); - if let Some(this) = self_rc.borrow_mut().downcast_mut::() { - if let Ok(Value::Int(value)) = res { - this.cid = *value; - } + let incr_res = redis_client.incr( + "connect", + Box::new(move |res, status, token_id| { + self_rc.borrow().log().info(&format!( + "redis incr finish value_res:{:?}, status: {}, token_id: {}", + res, status, token_id + )); + if let Some(this) = self_rc.borrow_mut().downcast_mut::() { + if let Ok(Value::Int(value)) = res { + this.cid = *value; } - self_rc.borrow().resume_http_request(); - }), - ); - match incr_res { - Ok(s) => { - self.log.info(&format!("redis incr ok {}", s)); - return HeaderAction::StopAllIterationAndBuffer; } - Err(e) => self.log.info(&format!("redis incr error {:?}", e)), - }; - } - self.redis_client = Some(redis_client); + self_rc.borrow().resume_http_request(); + }), + ); + match incr_res { + Ok(s) => { + self.log.info(&format!("redis incr ok {}", s)); + return HeaderAction::StopAllIterationAndBuffer; + } + Err(e) => self.log.info(&format!("redis incr error {:?}", e)), + }; } else { self.log.error("self_weak upgrade error"); } @@ -233,6 +265,48 @@ impl HttpContextWrapper for DemoWasm { DataAction::Continue } } + +impl DemoWasm { + fn initialize_redis_client(&self, config: &DemoWasmConfig) -> Result { + let redis_cfg = &config.redis_config; + + if redis_cfg.service_name.is_empty() { + return Err("Redis service_name is required but missing".to_string()); + } + + let service_port = redis_cfg.service_port.unwrap_or_else(|| { + if redis_cfg.service_name.ends_with(".static") { + 80 + } else { + 6379 + } + }); + + let cluster = FQDNCluster::new(&redis_cfg.service_name, "", service_port); + let timeout = Duration::from_millis(redis_cfg.timeout); + let mut client_config = RedisClientConfig::new(&cluster, timeout); + if let Some(username) = &redis_cfg.username { + client_config.username(Some(username)); + } + if let Some(password) = &redis_cfg.password { + client_config.password(Some(password)); + } + if let Some(database) = redis_cfg.database { + client_config.database(Some(database)); + } + let redis_client = RedisClient::new(&client_config); + + // 初始化redis client + match redis_client.init() { + Ok(_) => { + self.log.info("Redis client initialized successfully"); + Ok(redis_client) + } + Err(e) => Err(format!("Failed to initialize Redis client: {:?}", e)), + } + } +} + struct DemoWasmRoot { log: Log, rule_matcher: SharedRuleMatcher, diff --git a/plugins/wasm-rust/src/redis_wrapper.rs b/plugins/wasm-rust/src/redis_wrapper.rs index 50d30a1f5..345bce6c8 100644 --- a/plugins/wasm-rust/src/redis_wrapper.rs +++ b/plugins/wasm-rust/src/redis_wrapper.rs @@ -1,10 +1,9 @@ use std::{collections::HashMap, time::Duration}; +use crate::{cluster_wrapper::Cluster, internal}; use proxy_wasm::{hostcalls::RedisCallbackFn, types::Status}; use redis::{Cmd, ToRedisArgs, Value}; -use crate::{cluster_wrapper::Cluster, internal}; - pub type RedisValueCallbackFn = dyn FnOnce(&Result, usize, u32); fn gen_callback(call_fn: Box) -> Box { @@ -25,6 +24,7 @@ pub struct RedisClientBuilder { username: Option, password: Option, timeout: Duration, + database: Option, } impl RedisClientBuilder { @@ -34,6 +34,7 @@ impl RedisClientBuilder { username: None, password: None, timeout, + database: None, } } @@ -47,9 +48,24 @@ impl RedisClientBuilder { self } + pub fn database(mut self, database: Option) -> Self { + self.database = database; + self + } + pub fn build(self) -> RedisClient { + let upstream = if let Some(db) = self.database { + if db != 0 { + format!("{}?db={}", self.upstream, db) + } else { + self.upstream + } + } else { + self.upstream + }; + RedisClient { - upstream: self.upstream, + upstream, username: self.username, password: self.password, timeout: self.timeout, @@ -62,6 +78,7 @@ pub struct RedisClientConfig { username: Option, password: Option, timeout: Duration, + database: Option, } impl RedisClientConfig { @@ -71,6 +88,7 @@ impl RedisClientConfig { username: None, password: None, timeout, + database: None, } } @@ -83,6 +101,11 @@ impl RedisClientConfig { self.password = password.map(|p| p.as_ref().to_string()); self } + + pub fn database(&mut self, database: Option) -> &Self { + self.database = database; + self + } } #[derive(Debug, Clone)] @@ -95,8 +118,18 @@ pub struct RedisClient { impl RedisClient { pub fn new(config: &RedisClientConfig) -> Self { + let upstream = if let Some(db) = config.database { + if db != 0 { + format!("{}?db={}", config.upstream, db) + } else { + config.upstream.clone() + } + } else { + config.upstream.clone() + }; + RedisClient { - upstream: config.upstream.clone(), + upstream, username: config.username.clone(), password: config.password.clone(), timeout: config.timeout,