Repository: apache/dubbo-go-pixiu

Issue:
feature: support http filter plugins (Open Policy Agent)
# What does pixiu need to do with OPA ?

It supports a variety of strategies, such as JWT, and the OPA module supports it comprehensively

# PIxiu needs to be implemented in two ways (Choose one of the two)

- [ ] depends on the OPA server 
- [x] depends on the OPA rules

# introduce 

https://www.openpolicyagent.org/

## Directions for use

## start opa server

```shell
docker run -d --name opa -p 8181:8181 openpolicyagent/opa:latest run -s
```

## Create opa pollicy

```shell
curl -X PUT '127.0.0.1:8181/v1/policies/example1' \
  -H 'Content-Type: text/plain' \
  -d 'package example1

import input.request

default allow = false

allow {
    # HTTP method must GET
    request.method == "GET"
}'
```

## Query policy

```shell
curl -X POST '127.0.0.1:8181/v1/data/example1/allow' \
  -H 'Content-Type: application/json' \
  -d '{"input":{"request":{"method":"GET"}}}'
```

# OPA rules

```go
package main

import (
    "context"

    "github.com/open-policy-agent/opa/rego"
)

func main() {
    mod := `
    package test
    import future.keywords.if
    
    default allow := false
    
    allow if {
        input.x == 1
    }
    `

    pq, err := rego.New(
        rego.Query("data.test.allow"),
        rego.Module("test.rego", mod),
        rego.Input(map[string]interface{}{"x": 1})).PrepareForEval(context.Background())
    if err != nil {
        panic(err)
    }

    result, err := pq.Eval(context.Background())
    if err != nil {
        panic(err)
    }
    print(result.Allowed())
}
```



Discussion:
@Alanxtl: @sirui@baerwang: OPA Server is not yet implemented

Solution patch:
diff --git a/docs/user/filter/opa.md b/docs/user/filter/opa.md
index a72486b5..b4550293 100644
--- a/docs/user/filter/opa.md
+++ b/docs/user/filter/opa.md
@@ -7,18 +7,58 @@ English | [中文](opa_CN.md)
 ## English
 
 ### Overview
-The `dgp.filter.http.opa` filter delegates authorization decisions to Open Policy Agent (OPA) via a Rego policy. This filter evaluates requests and determines whether to allow or deny based on the policy defined in Rego. The policy is provided as an inline Rego module and evaluated using OPA's built-in query engine.
+The `dgp.filter.http.opa` filter delegates authorization decisions to Open Policy Agent (OPA) via a Rego policy. This filter evaluates requests and determines whether to allow or deny based on the policy defined in Rego.
 
-### What the filter does (current behavior)
+The filter supports two operation modes:
+- **Server Mode (Recommended)**: Evaluates policies by calling an external OPA server via HTTP API, supporting centralized policy management and hot updates.
+- **Embedded Mode**: Policies are provided as inline Rego modules and evaluated using OPA's built-in query engine.
+
+### Operation Modes
+
+#### Server Mode (Recommended)
+- Calls an external OPA server via REST API (`server_url`).
+- Supports centralized policy management and dynamic updates without restarting Pixiu.
+- Suitable for production environments and large-scale deployments.
+- Simple configuration - just specify OPA server address and decision path.
+
+#### Embedded Mode (Backward Compatible)
 - Loads a Rego **module string** from `config.policy`.
 - Builds a Rego **query** from `config.entrypoint`.
-- For each incoming request, constructs an `input` object and evaluates the query.
-- If the query result is `true`, the request is allowed. Otherwise, the request is denied.
+- Policies are evaluated within the Pixiu process; updates require restart.
+- Suitable for simple scenarios or test environments.
 
-> There is **no built-in support** for external policy files or URIs, custom HTTP status codes, or custom error bodies.
+> **Automatic Mode Selection**:
+> - If `server_url` is configured, **Server Mode** is used (priority).
+> - If `server_url` is not configured but `policy` is provided, **Embedded Mode** is used.
 
 ### Configuration schema
-Add the filter under your HTTP connection manager’s `http_filters` list.
+
+#### Server Mode Configuration (Recommended)
+
+```yaml
+filters:
+  - name: dgp.filter.httpconnectionmanager
+    config:
+      route_config:
+        # ... your routes
+      http_filters:
+        - name: dgp.filter.http.opa
+          config:
+            # OPA server address
+            server_url: "http://opa-server:8181"
+            # Decision path (OPA REST API path)
+            decision_path: "/v1/data/http/authz/allow"
+            # Request timeout in milliseconds, default 100ms
+            timeout_ms: 100
+            # Optional: Bearer token authentication
+            # bearer_token: "your-secret-token"
+        # HTTP proxy filter should be after OPA filter
+        - name: dgp.filter.http.proxy
+          config:
+            # ... proxy config
+```
+
+#### Embedded Mode Configuration (Backward Compatible)
 
 ```yaml
 filters:
@@ -42,16 +82,40 @@ filters:
         # HTTP proxy filter should be after OPA filter
         - name: dgp.filter.http.proxy
           config:
-          	# ... proxy config
+            # ... proxy config
 ```
 
 #### Fields
 
-- **`policy`** *(string, required)*
+**Server Mode Fields:**
+
+- **`server_url`** *(string, required for server mode)*
+  - **Meaning:** OPA server address, e.g., `http://opa-server:8181` or `https://opa.example.com:8181`.
+  - **Datatype:** `string`.
+  - **Example:** `"http://localhost:8181"`.
+
+- **`decision_path`** *(string, required for server mode)*
+  - **Meaning:** OPA REST API decision path, format: `/v1/data/<package>/<rule>`.
+  - **Datatype:** `string`.
+  - **Example:** `"/v1/data/http/authz/allow"`.
+
+- **`timeout_ms`** *(integer, optional)*
+  - **Meaning:** Request timeout for OPA server in milliseconds.
+  - **Datatype:** `int`.
+  - **Default:** `100` (100 milliseconds).
+
+- **`bearer_token`** *(string, optional)*
+  - **Meaning:** Bearer token for OPA server authentication.
+  - **Datatype:** `string`.
+  - **Notes:** Configure this field if OPA server requires authentication.
+
+**Embedded Mode Fields (Backward Compatible):**
+
+- **`policy`** *(string, required for embedded mode)*
   - **Meaning:** The **Rego module source code** (inline string). Loaded via `rego.Module("policy.rego", policy)`.
   - **Datatype:** `string` (multiline YAML recommended with `|`).
-  - **Notes:** File paths or bundle URIs are **not supported**.
-- **`entrypoint`** *(string, required)*
+
+- **`entrypoint`** *(string, required for embedded mode)*
   - **Meaning:** The **Rego query string** passed to `rego.Query(...)`. Should be a valid query like `data.<package>.<rule>` (e.g., `data.http.authz.allow`).
   - **Datatype:** `string`.
 
@@ -78,9 +142,170 @@ input.api          # API object (opaque)
 input.params       # route params map
 ```
 
+**Important Note: HTTP Header Canonicalization**
+
+Go's `net/http` package automatically canonicalizes HTTP header names to **Title-Case** format (e.g., `X-Api-Key`, `Content-Type`). When accessing headers in your policy, use the canonicalized key names:
+
+- ✅ Correct: `input.headers["X-Api-Key"]` or `input.headers["Content-Type"]`
+- ❌ Incorrect: `input.headers["x-api-key"]` or `input.headers["x_api_key"]`
+
+**Example:**
+```rego
+# Correct header access
+allow {
+    input.headers["X-Api-Key"][0] == "secret"
+    input.headers["Content-Type"][0] == "application/json"
+}
+```
+
+### OPA Server Deployment (Server Mode)
+
+#### Docker Deployment
+
+**1. Start OPA Server**
+
+```bash
+docker run -d \
+  --name opa-server \
+  -p 8181:8181 \
+  openpolicyagent/opa:latest \
+  run --server --addr :8181 --log-level info
+```
+
+**2. Upload Policy to OPA Server**
+
+```bash
+# Create policy file policy.rego
+cat > policy.rego <<EOF
+package http.authz
+
+default allow = false
+
+allow {
+    input.method == "GET"
+    input.path == "/status"
+}
+EOF
+
+# Upload policy
+curl -X PUT http://localhost:8181/v1/policies/authz \
+  -H "Content-Type: text/plain" \
+  --data-binary @policy.rego
+```
+
+**3. Test Policy**
+
+```bash
+curl -X POST http://localhost:8181/v1/data/http/authz/allow \
+  -H "Content-Type: application/json" \
+  -d '{
+    "input": {
+      "method": "GET",
+      "path": "/status"
+    }
+  }'
+```
+
+#### Docker Compose Deployment (Recommended)
+
+```yaml
+version: '3.8'
+
+services:
+  opa-server:
+    image: openpolicyagent/opa:latest
+    container_name: opa-server
+    ports:
+      - "8181:8181"
+    command:
+      - "run"
+      - "--server"
+      - "--addr=:8181"
+      - "--log-level=info"
+    volumes:
+      - ./opa-policies:/policies:ro
+    healthcheck:
+      test: ["CMD", "wget", "--spider", "http://localhost:8181/health"]
+      interval: 10s
+      timeout: 3s
+      retries: 3
+    restart: unless-stopped
+
+  pixiu:
+    image: apache/dubbo-go-pixiu:latest
+    depends_on:
+      opa-server:
+        condition: service_healthy
+    volumes:
+      - ./configs:/configs:ro
+    ports:
+      - "8888:8888"
+```
+
+#### Policy Hot Updates
+
+OPA server supports updating policies at runtime without restart:
+
+```bash
+# Update policy
+curl -X PUT http://localhost:8181/v1/policies/authz \
+  -H "Content-Type: text/plain" \
+  --data-binary @new-policy.rego
+```
+
 ### Minimal examples
 
-**1) Allow only GET /status**
+**Server Mode Examples**
+
+**1) Allow only GET /status (using OPA server)**
+
+```yaml
+- name: dgp.filter.http.opa
+  config:
+    server_url: "http://opa-server:8181"
+    decision_path: "/v1/data/http/authz/allow"
+    timeout_ms: 100
+```
+
+Corresponding OPA policy (uploaded to server):
+
+```rego
+package http.authz
+
+default allow = false
+
+allow {
+    input.method == "GET"
+    input.path == "/status"
+}
+```
+
+**2) Allow requests with a specific header value (using OPA server)**
+
+```yaml
+- name: dgp.filter.http.opa
+  config:
+    server_url: "http://opa-server:8181"
+    decision_path: "/v1/data/http/authz/allow"
+    timeout_ms: 100
+    bearer_token: "your-secret-token"
+```
+
+Corresponding OPA policy:
+
+```rego
+package http.authz
+
+default allow = false
+
+allow {
+    input.headers["X-Api-Key"][0] == "secret"
+}
+```
+
+**Embedded Mode Examples**
+
+**1) Allow only GET /status (embedded mode)**
 
 ```yaml
 - name: dgp.filter.http.opa
@@ -92,7 +317,7 @@ input.params       # route params map
     entrypoint: "data.http.authz.allow"
 ```
 
-**2) Allow requests with a specific header value**
+**2) Allow requests with a specific header value (embedded mode)**
 
 ```yaml
 - name: dgp.filter.http.opa
@@ -108,12 +333,71 @@ input.params       # route params map
 
 ### Limitations and notes
 
-- **Return type must be boolean**: Only `true` allows; objects (e.g., `{allow: true}`) will not be interpreted specially.
+**Response Format Support**
+
+The OPA filter supports multiple response formats and automatically recognizes them:
+
+**Format 1: Boolean Value (Simple Cases)**
+```rego
+package http.authz
+default allow = false
+allow {
+    input.method == "GET"
+}
+```
+
+**Format 2: Object with "allow" Field (OPA Common Pattern, Recommended)**
+```rego
+package http.authz
+
+default decision = {"allow": false}
+
+decision = {"allow": true, "reason": "admin user"} {
+    input.headers["X-Role"][0] == "admin"
+}
+```
+
+**Format 3: Object with "result" Field**
+```rego
+package http.authz
+
+decision = {"result": true, "metadata": {"user": "alice"}} {
+    input.headers["X-Api-Key"][0] == "secret"
+}
+```
+
+The filter extracts the decision using the following priority:
+1. Direct boolean value → use that value
+2. Object's `allow` field → use the boolean value of `allow`
+3. Object's `result` field → use the boolean value of `result`
+4. Unrecognized format → default to deny (returns 403)
+
+---
+
+**Server Mode:**
+- **Network Latency**: Introduces network latency due to external OPA server calls (typically 5-50ms).
+- **OPA Server Availability**: Ensure OPA server high availability; recommend deploying multiple instances.
+- **Timeout Configuration**: Default timeout of 100ms may be too tight in cross-service/cloud environments. Consider adjusting to 300-500ms for production based on network latency.
+
+**Embedded Mode:**
 - **No custom deny status/body**: The filter does not map policy outputs to HTTP status or body.
 - **Module-only loading**: Policies are loaded from the inline `policy` string only.
+- **Policy updates require restart**: Modifying policies requires restarting Pixiu service.
 
 ### Troubleshooting
 
-- **Denied unexpectedly**: Confirm the query is correct (e.g., `data.http.authz.allow`), and that the policy returns **`true`** for the given `input`.
-- **Policy compile errors**: Validate the Rego module with `opa eval` locally before embedding.
-- **Nil/empty results**: Re-check access to `headers`/`query` (they are maps of lists), and confirm path/method match.
\ No newline at end of file
+**Server Mode:**
+- **Connection Failed**: Check `server_url` configuration, ensure OPA server is running and accessible.
+- **Timeout Errors**: Increase `timeout_ms` value, or check OPA server performance.
+- **Authentication Failed**: If OPA server requires authentication, ensure `bearer_token` is correctly configured.
+- **Decision Path Error**: Ensure `decision_path` matches the policy path in OPA server.
+- **Denied Unexpectedly**: Use `curl` to directly test OPA server's decision API to verify policy logic.
+
+**Embedded Mode:**
+- **Denied Unexpectedly**: Confirm the query is correct (e.g., `data.http.authz.allow`), and that the policy returns **`true`** for the given `input`.
+- **Policy Compile Errors**: Validate the Rego module with `opa eval` locally before embedding.
+- **Nil/Empty Results**: Re-check access to `headers`/`query` (they are maps of lists), and confirm path/method match.
+
+**General Troubleshooting:**
+- **Check Logs**: Pixiu logs detailed OPA evaluation information, including errors and decision results.
+- **Test Policy**: Test the policy first in OPA server or locally using `opa eval` to ensure the logic is correct.
\ No newline at end of file
diff --git a/docs/user/filter/opa_CN.md b/docs/user/filter/opa_CN.md
index 7a679c98..2009e929 100644
--- a/docs/user/filter/opa_CN.md
+++ b/docs/user/filter/opa_CN.md
@@ -7,18 +7,58 @@
 ## 中文
 
 ### 概述
-`dgp.filter.http.opa` 过滤器通过 Rego 策略将授权决策委托给 Open Policy Agent (OPA)。该过滤器评估每个 HTTP 请求并根据 Rego 策略决定是否允许或拒绝请求。策略通过内联 Rego 模块加载，并使用 OPA 的内置查询引擎进行评估。
+`dgp.filter.http.opa` 过滤器通过 Rego 策略将授权决策委托给 Open Policy Agent (OPA)。该过滤器评估每个 HTTP 请求并根据 Rego 策略决定是否允许或拒绝请求。
 
-### 实际行为
+过滤器支持两种运行模式：
+- **服务器模式（推荐）**：通过 HTTP API 调用独立的 OPA 服务器进行策略评估，支持集中式策略管理和热更新。
+- **嵌入模式**：策略通过内联 Rego 模块加载，并使用 OPA 的内置查询引擎进行评估。
+
+### 运行模式
+
+#### 服务器模式（推荐）
+- 通过 REST API 调用外部 OPA 服务器 (`server_url`)。
+- 支持集中式策略管理和动态更新，无需重启 Pixiu。
+- 适合生产环境和大规模部署。
+- 配置简单，只需指定 OPA 服务器地址和决策路径。
+
+#### 嵌入模式（向后兼容）
 - 从配置项 `policy` 读取 **Rego 模块源码字符串**。
 - 从配置项 `entrypoint` 读取 **Rego 查询字符串**。
-- 每次请求构造 `input` 对象（见下），并评估该查询。
-- 如果查询结果为 `true`，则放行请求；否则拒绝请求。
+- 策略在 Pixiu 进程内评估，更新策略需要重启。
+- 适合简单场景或测试环境。
 
-> 目前过滤器**不支持**：外部文件或 URI 加载、自定义拒绝状态码或返回自定义错误体等。
+> **自动模式选择**：
+> - 如果配置了 `server_url`，则使用**服务器模式**（优先）。
+> - 如果未配置 `server_url` 但配置了 `policy`，则使用**嵌入模式**。
 
 ### 配置结构
-将过滤器添加到 HTTP 连接管理器的 `http_filters` 列表中：
+
+#### 服务器模式配置（推荐）
+
+```yaml
+filters:
+  - name: dgp.filter.httpconnectionmanager
+    config:
+      route_config:
+        # ... 你的路由
+      http_filters:
+        - name: dgp.filter.http.opa
+          config:
+            # OPA 服务器地址
+            server_url: "http://opa-server:8181"
+            # 决策路径（OPA REST API 路径）
+            decision_path: "/v1/data/http/authz/allow"
+            # 请求超时时间（毫秒），默认 100ms
+            timeout_ms: 100
+            # 可选：Bearer Token 认证
+            # bearer_token: "your-secret-token"
+        # HTTP proxy 过滤器应该在 OPA 过滤器之后
+        - name: dgp.filter.http.proxy
+          config:
+            # ... proxy config
+```
+
+#### 嵌入模式配置（向后兼容）
 
 ```yaml
 filters:
@@ -39,21 +79,45 @@ filters:
                 input.path == "/status"
               }
             entrypoint: "data.http.authz.allow"
-        # HTTP proxy 过滤器应该在OPA 过滤器之后
+        # HTTP proxy 过滤器应该在 OPA 过滤器之后
         - name: dgp.filter.http.proxy
           config:
-          	# ... proxy config
+            # ... proxy config
 ```
 
 
 
 #### 字段说明
 
-- **`policy`**（字符串，必填）
+**服务器模式字段：**
+
+- **`server_url`**（字符串，服务器模式必填）
+  - **含义：** OPA 服务器地址，如 `http://opa-server:8181` 或 `https://opa.example.com:8181`。
+  - **数据类型：** `string`。
+  - **示例：** `"http://localhost:8181"`。
+
+- **`decision_path`**（字符串，服务器模式必填）
+  - **含义：** OPA REST API 决策路径，格式为 `/v1/data/<package>/<rule>`。
+  - **数据类型：** `string`。
+  - **示例：** `"/v1/data/http/authz/allow"`。
+
+- **`timeout_ms`**（整数，可选）
+  - **含义：** 请求 OPA 服务器的超时时间（毫秒）。
+  - **数据类型：** `int`。
+  - **默认值：** `100`（100 毫秒）。
+
+- **`bearer_token`**（字符串，可选）
+  - **含义：** 用于 OPA 服务器认证的 Bearer Token。
+  - **数据类型：** `string`。
+  - **说明：** 如果 OPA 服务器需要认证，可配置此字段。
+
+**嵌入模式字段（向后兼容）：**
+
+- **`policy`**（字符串，嵌入模式必填）
   - **含义：** **Rego 模块源码**（内联字符串）。通过 `rego.Module("policy.rego", policy)` 加载。
   - **数据类型：** `string`（建议使用 YAML 多行格式 `|`）。
-  - **说明：** 当前版本不支持外部文件路径或 bundle URI。
-- **`entrypoint`**（字符串，必填）
+
+- **`entrypoint`**（字符串，嵌入模式必填）
   - **含义：** 传给 `rego.Query(...)` 的 **Rego 查询字符串**，应为合法查询，如 `data.<package>.<rule>`（如 `data.http.authz.allow`）。
   - **数据类型：** `string`。
 
@@ -80,9 +144,170 @@ input.api          # API 对象（结构可能变化）
 input.params       # 路由参数 map
 ```
 
+**重要提示：HTTP Header 规范化**
+
+Go 的 `net/http` 包会自动将 HTTP header 名称规范化为 **Title-Case** 格式（如 `X-Api-Key`、`Content-Type`）。在策略中访问 headers 时，请使用规范化后的键名：
+
+- ✅ 正确：`input.headers["X-Api-Key"]` 或 `input.headers["Content-Type"]`
+- ❌ 错误：`input.headers["x-api-key"]` 或 `input.headers["x_api_key"]`
+
+**示例：**
+```rego
+# 正确的 header 访问方式
+allow {
+    input.headers["X-Api-Key"][0] == "secret"
+    input.headers["Content-Type"][0] == "application/json"
+}
+```
+
+### OPA 服务器部署（服务器模式）
+
+#### Docker 部署
+
+**1. 启动 OPA 服务器**
+
+```bash
+docker run -d \
+  --name opa-server \
+  -p 8181:8181 \
+  openpolicyagent/opa:latest \
+  run --server --addr :8181 --log-level info
+```
+
+**2. 上传策略到 OPA 服务器**
+
+```bash
+# 创建策略文件 policy.rego
+cat > policy.rego <<EOF
+package http.authz
+
+default allow = false
+
+allow {
+    input.method == "GET"
+    input.path == "/status"
+}
+EOF
+
+# 上传策略
+curl -X PUT http://localhost:8181/v1/policies/authz \
+  -H "Content-Type: text/plain" \
+  --data-binary @policy.rego
+```
+
+**3. 测试策略**
+
+```bash
+curl -X POST http://localhost:8181/v1/data/http/authz/allow \
+  -H "Content-Type: application/json" \
+  -d '{
+    "input": {
+      "method": "GET",
+      "path": "/status"
+    }
+  }'
+```
+
+#### Docker Compose 部署（推荐）
+
+```yaml
+version: '3.8'
+
+services:
+  opa-server:
+    image: openpolicyagent/opa:latest
+    container_name: opa-server
+    ports:
+      - "8181:8181"
+    command:
+      - "run"
+      - "--server"
+      - "--addr=:8181"
+      - "--log-level=info"
+    volumes:
+      - ./opa-policies:/policies:ro
+    healthcheck:
+      test: ["CMD", "wget", "--spider", "http://localhost:8181/health"]
+      interval: 10s
+      timeout: 3s
+      retries: 3
+    restart: unless-stopped
+
+  pixiu:
+    image: apache/dubbo-go-pixiu:latest
+    depends_on:
+      opa-server:
+        condition: service_healthy
+    volumes:
+      - ./configs:/configs:ro
+    ports:
+      - "8888:8888"
+```
+
+#### 策略热更新
+
+OPA 服务器支持在运行时更新策略，无需重启：
+
+```bash
+# 更新策略
+curl -X PUT http://localhost:8181/v1/policies/authz \
+  -H "Content-Type: text/plain" \
+  --data-binary @new-policy.rego
+```
+
 ### 最小可用示例
 
-**1）仅允许 GET /status**
+**服务器模式示例**
+
+**1）仅允许 GET /status（使用 OPA 服务器）**
+
+```yaml
+- name: dgp.filter.http.opa
+  config:
+    server_url: "http://opa-server:8181"
+    decision_path: "/v1/data/http/authz/allow"
+    timeout_ms: 100
+```
+
+对应的 OPA 策略（上传到服务器）：
+
+```rego
+package http.authz
+
+default allow = false
+
+allow {
+    input.method == "GET"
+    input.path == "/status"
+}
+```
+
+**2）基于请求头校验（使用 OPA 服务器）**
+
+```yaml
+- name: dgp.filter.http.opa
+  config:
+    server_url: "http://opa-server:8181"
+    decision_path: "/v1/data/http/authz/allow"
+    timeout_ms: 100
+    bearer_token: "your-secret-token"
+```
+
+对应的 OPA 策略：
+
+```rego
+package http.authz
+
+default allow = false
+
+allow {
+    input.headers["X-Api-Key"][0] == "secret"
+}
+```
+
+**嵌入模式示例**
+
+**1）仅允许 GET /status（嵌入模式）**
 
 ```yaml
 - name: dgp.filter.http.opa
@@ -94,7 +319,7 @@ input.params       # 路由参数 map
     entrypoint: "data.http.authz.allow"
 ```
 
-**2）基于请求头校验**
+**2）基于请求头校验（嵌入模式）**
 
 ```yaml
 - name: dgp.filter.http.opa
@@ -110,12 +335,71 @@ input.params       # 路由参数 map
 
 ### 限制与说明
 
-- **返回类型必须是布尔值**：只有 `true` 会被视为放行；对象（如 `{allow: true}`）不会被特殊处理。
+**返回格式支持**
+
+OPA 策略支持多种返回格式，过滤器会自动识别：
+
+**格式 1：布尔值（简单场景）**
+```rego
+package http.authz
+default allow = false
+allow {
+    input.method == "GET"
+}
+```
+
+**格式 2：对象格式 - allow 字段（OPA 常见习惯，推荐）**
+```rego
+package http.authz
+
+default decision = {"allow": false}
+
+decision = {"allow": true, "reason": "admin user"} {
+    input.headers["X-Role"][0] == "admin"
+}
+```
+
+**格式 3：对象格式 - result 字段**
+```rego
+package http.authz
+
+decision = {"result": true, "metadata": {"user": "alice"}} {
+    input.headers["X-Api-Key"][0] == "secret"
+}
+```
+
+过滤器会按以下优先级提取决策结果：
+1. 直接布尔值 → 使用该值
+2. 对象的 `allow` 字段 → 使用 `allow` 的布尔值
+3. 对象的 `result` 字段 → 使用 `result` 的布尔值
+4. 无法识别 → 默认拒绝（返回 403）
+
+---
+
+**服务器模式：**
+- **网络延迟**：由于需要调用外部 OPA 服务器，会引入网络延迟（通常 5-50ms）。
+- **OPA 服务器可用性**：需要确保 OPA 服务器高可用，建议部署多实例。
+- **超时配置**：默认超时 100ms 可能在跨服务/云环境下偏紧，生产环境建议根据网络延迟调整（如 300-500ms）。
+
+**嵌入模式：**
 - **没有自定义拒绝响应**：过滤器不会将策略输出映射到 HTTP 状态码或响应体。
 - **仅支持内联模块加载**：策略来自配置字符串，不读取外部文件。
+- **策略更新需重启**：修改策略后需要重启 Pixiu 服务。
 
 ### 故障排查
 
+**服务器模式：**
+- **连接失败**：检查 `server_url` 配置是否正确，确保 OPA 服务器正在运行并可访问。
+- **超时错误**：增加 `timeout_ms` 值，或检查 OPA 服务器性能。
+- **认证失败**：如果 OPA 服务器需要认证，确保 `bearer_token` 配置正确。
+- **决策路径错误**：确保 `decision_path` 与 OPA 服务器中的策略路径匹配。
+- **意外拒绝**：使用 `curl` 直接测试 OPA 服务器的决策 API，验证策略逻辑。
+
+**嵌入模式：**
 - **意外拒绝**：检查查询是否正确（如 `data.http.authz.allow`），并确保策略在给定的 `input` 下返回 **`true`**。
 - **策略编译错误**：在嵌入策略之前，先使用 `opa eval` 本地验证 Rego 语法。
-- **空结果或类型不符**：请检查 `headers`/`query`，确保路径和方法匹配。
\ No newline at end of file
+- **空结果或类型不符**：请检查 `headers`/`query`，确保路径和方法匹配。
+
+**通用排查：**
+- **查看日志**：Pixiu 会记录 OPA 评估的详细日志，包括错误信息和决策结果。
+- **测试策略**：先在 OPA 服务器或本地使用 `opa eval` 测试策略，确保逻辑正确。
\ No newline at end of file
diff --git a/pkg/client/dubbo/dubbo.go b/pkg/client/dubbo/dubbo.go
index f56ea652..492806fb 100644
--- a/pkg/client/dubbo/dubbo.go
+++ b/pkg/client/dubbo/dubbo.go
@@ -48,7 +48,6 @@ import (
 import (
 	"github.com/apache/dubbo-go-pixiu/pkg/client"
 	cst "github.com/apache/dubbo-go-pixiu/pkg/common/constant"
-	"github.com/apache/dubbo-go-pixiu/pkg/config"
 	"github.com/apache/dubbo-go-pixiu/pkg/logger"
 )
 
@@ -76,7 +75,6 @@ var (
 		Organization: "dubbo-go-pixiu",
 		Name:         "Dubbogo Pixiu",
 		Module:       "dubbogo Pixiu",
-		Version:      config.Version,
 		Owner:        "Dubbogo Pixiu",
 		Environment:  "dev",
 	}
diff --git a/pkg/config/version.go b/pkg/config/version.go
index c45167df..cf1437a7 100644
--- a/pkg/config/version.go
+++ b/pkg/config/version.go
@@ -18,5 +18,6 @@
 package config
 
 const (
+	// Version TODO: delete this if not used in the future
 	Version = "0.4.0"
 )
diff --git a/pkg/filter/accesslog/access_log.go b/pkg/filter/accesslog/access_log.go
index 0b9e1cca..2aa26e0c 100644
--- a/pkg/filter/accesslog/access_log.go
+++ b/pkg/filter/accesslog/access_log.go
@@ -76,8 +76,10 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 
 // PrepareFilterChain prepare chain when http context init
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
+	// Make a shallow copy of the factory config to avoid sharing the factory's pointer.
+	cpConf := *factory.conf
 	f := &Filter{
-		conf: factory.conf,
+		conf: &cpConf,
 		alw:  factory.alw,
 	}
 	chain.AppendDecodeFilters(f)
diff --git a/pkg/filter/auth/jwt/jwt.go b/pkg/filter/auth/jwt/jwt.go
index f4ab3bec..28561aad 100644
--- a/pkg/filter/auth/jwt/jwt.go
+++ b/pkg/filter/auth/jwt/jwt.go
@@ -82,7 +82,8 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg, errMsg: factory.errMsg, providerJwks: factory.providerJwks}
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
+	f := &Filter{cfg: factory.cfg.DeepCopy(), errMsg: factory.errMsg, providerJwks: factory.providerJwks}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
@@ -225,3 +226,66 @@ func checkToken(value, prefix, providerName string, provider Provider) bool {
 func (factory *FilterFactory) Config() any {
 	return factory.cfg
 }
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *Config) DeepCopy() *Config {
+	if config == nil {
+		return nil
+	}
+	cpConfig := *config
+
+	if config.Rules != nil {
+		cpConfig.Rules = make([]Rules, 0, len(config.Rules))
+		for _, rules := range config.Rules {
+			cpRules := *rules.DeepCopy()
+			cpConfig.Rules = append(cpConfig.Rules, cpRules)
+		}
+	}
+
+	if config.Providers != nil {
+		cpConfig.Providers = make([]Providers, 0, len(config.Providers))
+		for _, providers := range config.Providers {
+			cpProviders := *providers.DeepCopy()
+			cpConfig.Providers = append(cpConfig.Providers, cpProviders)
+		}
+	}
+
+	return &cpConfig
+}
+
+func (rules *Rules) DeepCopy() *Rules {
+	if rules == nil {
+		return nil
+	}
+	cpRules := *rules
+	cpRules.Requires = *rules.Requires.DeepCopy()
+	return &cpRules
+}
+
+func (providers *Providers) DeepCopy() *Providers {
+	if providers == nil {
+		return nil
+	}
+	cpProviders := *providers
+
+	if providers.Local != nil {
+		cpLocal := *providers.Local
+		cpProviders.Local = &cpLocal
+	}
+	if providers.Remote != nil {
+		cpRemote := *providers.Remote
+		cpProviders.Remote = &cpRemote
+	}
+	return &cpProviders
+}
+
+func (requires *Requires) DeepCopy() *Requires {
+	if requires == nil {
+		return nil
+	}
+	cpRequires := *requires
+	cpRequires.RequiresAll = make([]Requirement, len(requires.RequiresAll))
+	copy(cpRequires.RequiresAll, requires.RequiresAll)
+	return &cpRequires
+}
diff --git a/pkg/filter/authority/authority.go b/pkg/filter/authority/authority.go
index cf79e05d..84223ffa 100644
--- a/pkg/filter/authority/authority.go
+++ b/pkg/filter/authority/authority.go
@@ -61,7 +61,8 @@ func (factory *FilterFactory) Apply() error {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
+	f := &Filter{cfg: factory.cfg.DeepCopy()}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
diff --git a/pkg/filter/authority/config.go b/pkg/filter/authority/config.go
index d77810c0..a5625eb8 100644
--- a/pkg/filter/authority/config.go
+++ b/pkg/filter/authority/config.go
@@ -73,3 +73,43 @@ type (
 	// LimitType the authority rule limit enum
 	LimitType int32
 )
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (rule *AuthorityRule) DeepCopy() *AuthorityRule {
+	if rule == nil {
+		return nil
+	}
+
+	cp := *rule
+
+	if rule.Items != nil {
+		cp.Items = make([]string, len(rule.Items))
+		copy(cp.Items, rule.Items)
+	} else {
+		cp.Items = nil
+	}
+
+	return &cp
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (ac *AuthorityConfiguration) DeepCopy() *AuthorityConfiguration {
+	if ac == nil {
+		return nil
+	}
+
+	cp := *ac
+
+	if ac.Rules != nil {
+		cp.Rules = make([]AuthorityRule, len(ac.Rules))
+		for i := range ac.Rules {
+			cp.Rules[i] = *ac.Rules[i].DeepCopy()
+		}
+	} else {
+		cp.Rules = nil
+	}
+
+	return &cp
+}
diff --git a/pkg/filter/cors/cors.go b/pkg/filter/cors/cors.go
index 780960f4..f56c2dc2 100644
--- a/pkg/filter/cors/cors.go
+++ b/pkg/filter/cors/cors.go
@@ -73,7 +73,8 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
+	f := &Filter{cfg: factory.cfg.DeepCopy()}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
@@ -132,3 +133,22 @@ func (factory *FilterFactory) Apply() error {
 func (factory *FilterFactory) Config() any {
 	return factory.cfg
 }
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *Config) DeepCopy() *Config {
+	if config == nil {
+		return nil
+	}
+
+	cp := *config
+
+	if config.AllowOrigin != nil {
+		cp.AllowOrigin = make([]string, len(config.AllowOrigin))
+		copy(cp.AllowOrigin, config.AllowOrigin)
+	} else {
+		cp.AllowOrigin = nil
+	}
+
+	return &cp
+}
diff --git a/pkg/filter/csrf/csrf.go b/pkg/filter/csrf/csrf.go
index efedcd20..9ad2246c 100644
--- a/pkg/filter/csrf/csrf.go
+++ b/pkg/filter/csrf/csrf.go
@@ -74,7 +74,8 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
+	f := &Filter{cfg: factory.cfg.DeepCopy()}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
@@ -137,3 +138,22 @@ func (factory *FilterFactory) Apply() error {
 func (factory *FilterFactory) Config() any {
 	return factory.cfg
 }
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *Config) DeepCopy() *Config {
+	if config == nil {
+		return nil
+	}
+
+	cp := *config
+
+	if config.IgnoreMethods != nil {
+		cp.IgnoreMethods = make([]string, len(config.IgnoreMethods))
+		copy(cp.IgnoreMethods, config.IgnoreMethods)
+	} else {
+		cp.IgnoreMethods = nil
+	}
+
+	return &cp
+}
diff --git a/pkg/filter/event/event.go b/pkg/filter/event/event.go
index 0d5cbc96..6dbd9fc4 100644
--- a/pkg/filter/event/event.go
+++ b/pkg/filter/event/event.go
@@ -63,7 +63,9 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Make a shallow copy of the factory config to avoid sharing the factory's pointer
+	cfgCopy := *factory.cfg
+	f := &Filter{cfg: &cfgCopy}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
diff --git a/pkg/filter/failinject/filter.go b/pkg/filter/failinject/filter.go
index f7a664b3..c2f9ec92 100644
--- a/pkg/filter/failinject/filter.go
+++ b/pkg/filter/failinject/filter.go
@@ -57,8 +57,9 @@ func (factory *FilterFactory) Config() any {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *contextHttp.HttpContext, chain filter.FilterChain) error {
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
 	f := Filter{
-		cfg: factory.cfg,
+		cfg: factory.cfg.DeepCopy(),
 	}
 	chain.AppendDecodeFilters(f)
 	return nil
@@ -151,3 +152,29 @@ func percentage(odds int) bool {
 	num := rand.Intn(100) + 1 // NOSONAR
 	return num <= odds
 }
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *Config) DeepCopy() *Config {
+	if config == nil {
+		return nil
+	}
+
+	cp := &Config{}
+
+	if config.Rules != nil {
+		cp.Rules = make(map[URI]*Rule, len(config.Rules))
+		for k, r := range config.Rules {
+			if r == nil {
+				cp.Rules[k] = nil
+			} else {
+				rr := *r
+				cp.Rules[k] = &rr
+			}
+		}
+	} else {
+		cp.Rules = nil
+	}
+
+	return cp
+}
diff --git a/pkg/filter/header/header.go b/pkg/filter/header/header.go
index 4b078eda..16062a72 100644
--- a/pkg/filter/header/header.go
+++ b/pkg/filter/header/header.go
@@ -56,7 +56,7 @@ func (p *Plugin) Kind() string {
 }
 
 func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
-	return &FilterFactory{}, nil
+	return &FilterFactory{cfg: &Config{}}, nil
 }
 
 func (factory *FilterFactory) Config() any {
@@ -68,7 +68,9 @@ func (factory *FilterFactory) Apply() error {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Make a shallow copy of the factory config to avoid sharing the factory's pointer
+	cfgCopy := *factory.cfg
+	f := &Filter{cfg: &cfgCopy}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
diff --git a/pkg/filter/host/host.go b/pkg/filter/host/host.go
index 233cf43c..2d7c77b4 100644
--- a/pkg/filter/host/host.go
+++ b/pkg/filter/host/host.go
@@ -55,11 +55,13 @@ func (p *Plugin) Kind() string {
 }
 
 func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
-	return &FilterFactory{}, nil
+	return &FilterFactory{cfg: &Config{}}, nil
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *contexthttp.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Make a shallow copy of the factory config to avoid sharing the factory's pointer
+	cfgCopy := *factory.cfg
+	f := &Filter{cfg: &cfgCopy}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
diff --git a/pkg/filter/http/grpcproxy/grpc.go b/pkg/filter/http/grpcproxy/grpc.go
index cfedb01f..ca43e317 100644
--- a/pkg/filter/http/grpcproxy/grpc.go
+++ b/pkg/filter/http/grpcproxy/grpc.go
@@ -152,7 +152,8 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg, descriptor: factory.descriptor, pools: factory.pools, extReg: factory.extReg, registered: factory.registered}
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
+	f := &Filter{cfg: factory.cfg.DeepCopy(), descriptor: factory.descriptor, pools: factory.pools, extReg: factory.extReg, registered: factory.registered}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
@@ -431,3 +432,34 @@ func configCheck(cfg *Config) error {
 	}
 	return nil
 }
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *Config) DeepCopy() *Config {
+	if config == nil {
+		return nil
+	}
+
+	cp := *config
+
+	if config.Rules != nil {
+		cp.Rules = make([]*Rule, len(config.Rules))
+		for i, r := range config.Rules {
+			if r == nil {
+				cp.Rules[i] = nil
+				continue
+			}
+			nr := &Rule{
+				Selector: r.Selector,
+				Match: Match{
+					Method: r.Match.Method,
+				},
+			}
+			cp.Rules[i] = nr
+		}
+	} else {
+		cp.Rules = nil
+	}
+
+	return &cp
+}
diff --git a/pkg/filter/http/loadbalancer/loadbalancer.go b/pkg/filter/http/loadbalancer/loadbalancer.go
index ffc6166f..9c4a5933 100644
--- a/pkg/filter/http/loadbalancer/loadbalancer.go
+++ b/pkg/filter/http/loadbalancer/loadbalancer.go
@@ -69,7 +69,9 @@ func (factory *FilterFactory) Apply() error {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *contexthttp.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Make a shallow copy of the factory config to avoid sharing the factory's pointer
+	cfgCopy := *factory.cfg
+	f := &Filter{cfg: &cfgCopy}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
diff --git a/pkg/filter/llm/tokenizer/tokenizer.go b/pkg/filter/llm/tokenizer/tokenizer.go
index bc9d6825..71d0c664 100644
--- a/pkg/filter/llm/tokenizer/tokenizer.go
+++ b/pkg/filter/llm/tokenizer/tokenizer.go
@@ -134,7 +134,9 @@ func (factory *FilterFactory) Config() any { return factory.cfg }
 func (factory *FilterFactory) Apply() error { return registerLLMMetrics() }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *contexthttp.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{cfg: factory.cfg}
+	// Make a shallow copy of the factory config to avoid sharing the factory's pointer
+	cfgCopy := *factory.cfg
+	f := &Filter{cfg: &cfgCopy}
 	chain.AppendDecodeFilters(f)
 	chain.AppendEncodeFilters(f)
 	return nil
diff --git a/pkg/filter/mcp/mcpserver/filter.go b/pkg/filter/mcp/mcpserver/filter.go
index 1a56cb75..9707c519 100644
--- a/pkg/filter/mcp/mcpserver/filter.go
+++ b/pkg/filter/mcp/mcpserver/filter.go
@@ -108,8 +108,9 @@ func (f *FilterFactory) PrepareFilterChain(_ *contexthttp.HttpContext, chain fil
 	sseHandler := transport.NewSSEHandler(sessionManager)
 	contentNegotiator := transport.NewContentNegotiator()
 
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
 	mcpFilter := &MCPServerFilter{
-		cfg:               f.cfg,
+		cfg:               f.cfg.DeepCopy(),
 		registry:          f.registry,
 		errorHandler:      NewErrorHandler(),
 		responseBuilder:   NewResponseBuilder(),
diff --git a/pkg/filter/opa/opa.go b/pkg/filter/opa/opa.go
index 56bae54d..59348282 100644
--- a/pkg/filter/opa/opa.go
+++ b/pkg/filter/opa/opa.go
@@ -18,7 +18,13 @@
 package opa
 
 import (
+	"bytes"
+	"context"
+	"encoding/json"
 	"fmt"
+	"io"
+	"net/http"
+	"time"
 )
 
 import (
@@ -28,7 +34,7 @@ import (
 import (
 	"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
 	"github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter"
-	"github.com/apache/dubbo-go-pixiu/pkg/context/http"
+	contextHttp "github.com/apache/dubbo-go-pixiu/pkg/context/http"
 	"github.com/apache/dubbo-go-pixiu/pkg/logger"
 )
 
@@ -44,18 +50,28 @@ type (
 	Plugin struct{}
 
 	FilterFactory struct {
-		cfg  *Config
-		rego *rego.Rego
+		cfg           *Config
+		rego          *rego.Rego              // For embedded mode (deprecated, kept for backward compat)
+		preparedQuery *rego.PreparedEvalQuery // Pre-compiled query for embedded mode
+		httpClient    *http.Client            // For server mode
 	}
 
 	Filter struct {
 		cfg           *Config
-		preparedQuery *rego.PreparedEvalQuery
+		preparedQuery *rego.PreparedEvalQuery // For embedded mode
+		httpClient    *http.Client            // For server mode
 	}
 
 	Config struct {
-		Policy     string `yaml:"policy" json:"policy" `
-		Entrypoint string `yaml:"entrypoint" json:"entrypoint" `
+		// Server mode configuration (recommended for production)
+		ServerURL    string `yaml:"server_url" json:"server_url" mapstructure:"server_url"`          // OPA Server address, e.g., http://opa-server:8181
+		DecisionPath string `yaml:"decision_path" json:"decision_path" mapstructure:"decision_path"` // Decision path, e.g., /v1/data/http/authz/allow
+		TimeoutMs    int    `yaml:"timeout_ms" json:"timeout_ms" mapstructure:"timeout_ms"`          // Request timeout in milliseconds, default 100
+		BearerToken  string `yaml:"bearer_token" json:"bearer_token" mapstructure:"bearer_token"`    // Optional authentication token
+
+		// Embedded mode configuration (for backward compatibility)
+		Policy     string `yaml:"policy" json:"policy" mapstructure:"policy"`             // Policy content
+		Entrypoint string `yaml:"entrypoint" json:"entrypoint" mapstructure:"entrypoint"` // Policy entrypoint
 	}
 )
 
@@ -73,44 +89,89 @@ func (factory *FilterFactory) Config() any {
 
 // Apply is called after the configuration is loaded and is used to prepare the OPA query.
 func (factory *FilterFactory) Apply() error {
-	policy := factory.cfg.Policy
-	if policy == "" {
-		return fmt.Errorf("OPA policy is empty in the configuration")
+	cfg := factory.cfg
+
+	// Server mode (recommended for production)
+	if cfg.ServerURL != "" {
+		if cfg.DecisionPath == "" {
+			return fmt.Errorf("decision_path is required when using OPA server mode")
+		}
+
+		timeout := 100 * time.Millisecond
+		if cfg.TimeoutMs > 0 {
+			timeout = time.Duration(cfg.TimeoutMs) * time.Millisecond
+		}
+
+		factory.httpClient = &http.Client{
+			Timeout: timeout,
+			Transport: &http.Transport{
+				MaxIdleConns:        100,
+				MaxIdleConnsPerHost: 10,
+				IdleConnTimeout:     90 * time.Second,
+			},
+		}
+
+		logger.Infof("OPA filter initialized in server mode: %s%s (timeout: %v)", cfg.ServerURL, cfg.DecisionPath, timeout)
+		return nil
 	}
 
-	r := rego.New(
-		rego.Query(factory.cfg.Entrypoint),
-		rego.Module("policy.rego", policy),
-	)
+	// Embedded mode (for backward compatibility)
+	if cfg.Policy != "" {
+		logger.Warnf("OPA filter using embedded mode. Consider using server mode for better maintainability and performance.")
 
-	factory.rego = r
+		if cfg.Entrypoint == "" {
+			return fmt.Errorf("entrypoint is required when using embedded mode")
+		}
 
-	return nil
+		r := rego.New(
+			rego.Query(cfg.Entrypoint),
+			rego.Module("policy.rego", cfg.Policy),
+		)
+
+		// Pre-compile the query once at initialization for better performance
+		preparedQuery, err := r.PrepareForEval(context.Background())
+		if err != nil {
+			return fmt.Errorf("failed to prepare OPA query: %w", err)
+		}
+
+		factory.rego = r
+		factory.preparedQuery = &preparedQuery
+
+		logger.Infof("OPA filter initialized in embedded mode")
+		return nil
+	}
+
+	return fmt.Errorf("OPA filter requires either server_url (recommended) or policy configuration")
 }
 
 // PrepareFilterChain prepares the filter chain for a new request by dynamically creating a Filter
-func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	if factory.rego == nil {
-		return fmt.Errorf("rego instance not initialized in factory")
-	}
+func (factory *FilterFactory) PrepareFilterChain(ctx *contextHttp.HttpContext, chain filter.FilterChain) error {
+	// Shallow copy cfg (copy the struct value; inner reference fields remain shared)
+	cfgCopy := *factory.cfg
+	var f *Filter
 
-	preparedQuery, err := factory.rego.PrepareForEval(ctx.Ctx)
-	if err != nil {
-		return fmt.Errorf("failed to prepare OPA query: %w", err)
+	// Server mode (priority)
+	if factory.httpClient != nil {
+		f = &Filter{
+			cfg:        &cfgCopy,
+			httpClient: factory.httpClient,
+		}
+	} else if factory.preparedQuery != nil {
+		// Embedded mode (backward compatibility) - reuse pre-compiled query
+		f = &Filter{
+			cfg:           &cfgCopy,
+			preparedQuery: factory.preparedQuery,
+		}
+	} else {
+		return fmt.Errorf("OPA filter not properly initialized")
 	}
 
-	f := &Filter{cfg: factory.cfg, preparedQuery: &preparedQuery}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
 
 // Decode is the core logic of the filter. It converts HTTP request data into a standard OPA input format and evaluates the policy.
-func (f *Filter) Decode(c *http.HttpContext) filter.FilterStatus {
-	if f.preparedQuery == nil {
-		logger.Error("OPA filter not initialized properly.")
-		return filter.Stop
-	}
-
+func (f *Filter) Decode(c *contextHttp.HttpContext) filter.FilterStatus {
 	input := map[string]any{
 		"method":      c.Request.Method,
 		"path":        c.Request.URL.Path,
@@ -125,15 +186,161 @@ func (f *Filter) Decode(c *http.HttpContext) filter.FilterStatus {
 		"params":      c.Params,
 	}
 
+	// Server mode (priority)
+	if f.httpClient != nil {
+		return f.evaluateServer(c, input)
+	}
+
+	// Embedded mode (backward compatibility)
+	if f.preparedQuery != nil {
+		return f.evaluateEmbedded(c, input)
+	}
+
+	logger.Error("OPA filter not initialized properly")
+	errResp := contextHttp.InternalError.WithError(fmt.Errorf("OPA filter not initialized"))
+	c.SendLocalReply(errResp.Status, errResp.ToJSON())
+	return filter.Stop
+}
+
+// evaluateEmbedded evaluates the policy using embedded OPA engine
+func (f *Filter) evaluateEmbedded(c *contextHttp.HttpContext, input map[string]any) filter.FilterStatus {
 	results, err := f.preparedQuery.Eval(c.Ctx, rego.EvalInput(input))
 	if err != nil {
-		logger.Error("OPA evaluation error: %v\n", err)
+		logger.Errorf("OPA embedded evaluation error: %v", err)
+		errResp := contextHttp.InternalError.WithError(err)
+		c.SendLocalReply(errResp.Status, errResp.ToJSON())
+		return filter.Stop
+	}
+
+	if len(results) == 0 {
+		logger.Debugf("OPA embedded policy returned empty result for request: %s %s", input["method"], input["path"])
+		errResp := contextHttp.Forbidden.New()
+		c.SendLocalReply(errResp.Status, errResp.ToJSON())
 		return filter.Stop
 	}
 
-	if len(results) == 0 || results[0].Expressions[0].Value != true {
+	// Extract decision from result (supports both boolean and object formats)
+	allow := extractDecision(results[0].Expressions[0].Value)
+	if !allow {
+		logger.Debugf("OPA embedded policy denied request: %s %s", input["method"], input["path"])
+		errResp := contextHttp.Forbidden.New()
+		c.SendLocalReply(errResp.Status, errResp.ToJSON())
 		return filter.Stop
 	}
 
 	return filter.Continue
 }
+
+// evaluateServer evaluates the policy by calling OPA server REST API
+func (f *Filter) evaluateServer(c *contextHttp.HttpContext, input map[string]any) filter.FilterStatus {
+	// Construct request body according to OPA REST API specification
+	requestBody := map[string]any{
+		"input": input,
+	}
+
+	jsonData, err := json.Marshal(requestBody)
+	if err != nil {
+		logger.Errorf("Failed to marshal OPA request: %v", err)
+		errResp := contextHttp.InternalError.WithError(err)
+		c.SendLocalReply(errResp.Status, errResp.ToJSON())
+		return filter.Stop
+	}
+
+	// Construct HTTP request
+	url := f.cfg.ServerURL + f.cfg.DecisionPath
+	req, err := http.NewRequestWithContext(c.Ctx, "POST", url, bytes.NewBuffer(jsonData))
+	if err != nil {
+		logger.Errorf("Failed to create OPA request: %v", err)
+		errResp := contextHttp.InternalError.WithError(err)
+		c.SendLocalReply(errResp.Status, errResp.ToJSON())
+		return filter.Stop
+	}
+
+	req.Header.Set("Content-Type", "application/json")
+	if f.cfg.BearerToken != "" {
+		req.Header.Set("Authorization", "Bearer "+f.cfg.BearerToken)
+	}
+
+	// Send request to OPA server
+	resp, err := f.httpClient.Do(req)
+	if err != nil {
+		logger.Errorf("OPA server request failed: %v", err)
+		// Check if it's a timeout error using net.Error interface
+		if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
+			errResp := contextHttp.GatewayTimeout.WithError(err)
+			c.SendLocalReply(errResp.Status, errResp.ToJSON())
+		} else {
+			errResp := contextHttp.ServiceUnavailable.WithError(err)
+			c.SendLocalReply(errResp.Status, errResp.ToJSON())
+		}
+		return filter.Stop
+	}
+	defer resp.Body.Close()
+
+	// Check HTTP status code
+	if resp.StatusCode != 200 {
+		body, _ := io.ReadAll(resp.Body)
+		logger.Errorf("OPA server returned status %d: %s", resp.StatusCode, string(body))
+		errResp := contextHttp.BadGateway.WithError(fmt.Errorf("OPA server returned status %d", resp.StatusCode))
+		c.SendLocalReply(errResp.Status, errResp.ToJSON())
+		return filter.Stop
+	}
+
+	// Parse response according to OPA REST API specification
+	var result map[string]any
+	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+		logger.Errorf("Failed to decode OPA response: %v", err)
+		errResp := contextHttp.BadGateway.WithError(err)
+		c.SendLocalReply(errResp.Status, errResp.ToJSON())
+		return filter.Stop
+	}
+
+	// Extract decision from result (supports both boolean and object formats)
+	if resultValue, exists := result["result"]; exists {
+		allow := extractDecision(resultValue)
+		if !allow {
+			logger.Debugf("OPA server policy denied request: %s %s", input["method"], input["path"])
+			errResp := contextHttp.Forbidden.New()
+			c.SendLocalReply(errResp.Status, errResp.ToJSON())
+			return filter.Stop
+		}
+		return filter.Continue
+	}
+
+	logger.Errorf("Invalid OPA response format: missing 'result' field")
+	errResp := contextHttp.BadGateway.WithError(fmt.Errorf("missing 'result' field in OPA response"))
+	c.SendLocalReply(errResp.Status, errResp.ToJSON())
+	return filter.Stop
+}
+
+// extractDecision extracts the allow decision from OPA result
+// Supports multiple formats:
+// 1. Boolean: true/false
+// 2. Object with "allow" field: {allow: true}
+// 3. Object with "result" field: {result: true}
+func extractDecision(value any) bool {
+	// Format 1: Direct boolean value
+	if boolVal, ok := value.(bool); ok {
+		return boolVal
+	}
+
+	// Format 2 & 3: Object with allow/result field
+	if objVal, ok := value.(map[string]any); ok {
+		// Try "allow" field first (common OPA pattern)
+		if allowVal, exists := objVal["allow"]; exists {
+			if boolVal, ok := allowVal.(bool); ok {
+				return boolVal
+			}
+		}
+		// Try "result" field as fallback
+		if resultVal, exists := objVal["result"]; exists {
+			if boolVal, ok := resultVal.(bool); ok {
+				return boolVal
+			}
+		}
+	}
+
+	// Default to deny if format is unrecognized
+	logger.Warnf("Unrecognized OPA decision format, defaulting to deny. Value: %v", value)
+	return false
+}
diff --git a/pkg/filter/prometheus/metric.go b/pkg/filter/prometheus/metric.go
index 08e9e081..ac830530 100644
--- a/pkg/filter/prometheus/metric.go
+++ b/pkg/filter/prometheus/metric.go
@@ -76,8 +76,10 @@ func (factory *FilterFactory) Apply() error {
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *contextHttp.HttpContext, chain filter.FilterChain) error {
 
+	// Shallow copy config to avoid pointer sharing (factory.cfg may change at runtime)
+	cpConfig := *factory.Cfg
 	f := &Filter{
-		Cfg:  factory.Cfg,
+		Cfg:  &cpConfig,
 		Prom: factory.Prom,
 	}
 	f.Prom.SetPushGatewayUrl(f.Cfg.Rules.PushGatewayURL, f.Cfg.Rules.MetricPath)
diff --git a/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go b/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go
index fd22ad88..c8ad9e55 100644
--- a/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go
+++ b/pkg/filter/sentinel/circuitbreaker/circuit_breaker.go
@@ -77,8 +77,9 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 	return &FilterFactory{cfg: &Config{}}, nil
 }
 
+// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
 func (factory *FilterFactory) PrepareFilterChain(ctx *http.HttpContext, chain filter.FilterChain) error {
-	chain.AppendDecodeFilters(&Filter{cfg: factory.cfg, matcher: factory.matcher})
+	chain.AppendDecodeFilters(&Filter{cfg: factory.cfg.DeepCopy(), matcher: factory.matcher})
 	return nil
 }
 
@@ -165,3 +166,33 @@ func loadRules(rules []*circuitbreaker.Rule) error {
 	}
 	return nil
 }
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *Config) DeepCopy() *Config {
+	if config == nil {
+		return nil
+	}
+	cpConfig := *config
+
+	if config.Resources != nil {
+		cpConfig.Resources = make([]*pkgs.Resource, len(config.Resources))
+		for index, resource := range config.Resources {
+			if resource != nil {
+				cpConfig.Resources[index] = resource.DeepCopy()
+			}
+		}
+	}
+
+	if config.Rules != nil {
+		cpConfig.Rules = make([]*circuitbreaker.Rule, len(config.Rules))
+		for index, rule := range config.Rules {
+			if rule != nil {
+				cpRule := *rule
+				cpConfig.Rules[index] = &cpRule
+			}
+		}
+	}
+
+	return &cpConfig
+}
diff --git a/pkg/filter/sentinel/config.go b/pkg/filter/sentinel/config.go
index 8fc84edc..cc672f1a 100644
--- a/pkg/filter/sentinel/config.go
+++ b/pkg/filter/sentinel/config.go
@@ -40,3 +40,22 @@ type (
 	// MatchStrategy API match strategy
 	MatchStrategy int32
 )
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (resource *Resource) DeepCopy() *Resource {
+	if resource == nil {
+		return nil
+	}
+	cpResource := *resource
+	if resource.Items != nil {
+		cpResource.Items = make([]*Item, len(resource.Items))
+		for index, orig := range resource.Items {
+			if orig != nil {
+				cp := *orig
+				cpResource.Items[index] = &cp
+			}
+		}
+	}
+	return &cpResource
+}
diff --git a/pkg/filter/sentinel/ratelimit/rate_limit.go b/pkg/filter/sentinel/ratelimit/rate_limit.go
index 5ea4ad91..ee7516ab 100644
--- a/pkg/filter/sentinel/ratelimit/rate_limit.go
+++ b/pkg/filter/sentinel/ratelimit/rate_limit.go
@@ -68,7 +68,8 @@ func (p *Plugin) CreateFilterFactory() (filter.HttpFilterFactory, error) {
 }
 
 func (factory *FilterFactory) PrepareFilterChain(ctx *contexthttp.HttpContext, chain filter.FilterChain) error {
-	f := &Filter{conf: factory.conf, matcher: factory.matcher}
+	// Deep copy config to avoid pointer sharing (factory.cfg may change at runtime)
+	f := &Filter{conf: factory.conf.DeepCopy(), matcher: factory.matcher}
 	chain.AppendDecodeFilters(f)
 	return nil
 }
@@ -131,3 +132,32 @@ func OnRulesUpdate(rules []*Rule) {
 		logger.Warnf("rate limit load rules err: %v", err)
 	}
 }
+
+// DeepCopy returns a new independent copy of Config
+// // Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *Config) DeepCopy() *Config {
+	if config == nil {
+		return nil
+	}
+	cpConfig := *config
+	if config.Resources != nil {
+		cpConfig.Resources = make([]*pkgs.Resource, len(config.Resources))
+		for index, resource := range config.Resources {
+			if resource != nil {
+				cpConfig.Resources[index] = resource.DeepCopy()
+			}
+		}
+	}
+
+	if config.Rules != nil {
+		cpConfig.Rules = make([]*Rule, len(config.Rules))
+		for index, rule := range config.Rules {
+			if rule != nil {
+				cpRule := *rule
+				cpConfig.Rules[index] = &cpRule
+			}
+		}
+	}
+
+	return &cpConfig
+}
diff --git a/pkg/model/mcpserver.go b/pkg/model/mcpserver.go
index 45f270a4..27d89732 100644
--- a/pkg/model/mcpserver.go
+++ b/pkg/model/mcpserver.go
@@ -222,3 +222,187 @@ func GetPathParameterNames(pathTemplate string) []string {
 
 	return names
 }
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *McpServerConfig) DeepCopy() *McpServerConfig {
+	if config == nil {
+		return nil
+	}
+
+	cpConfig := *config
+
+	// Deep copy Tools
+	if config.Tools != nil {
+		cpConfig.Tools = make([]ToolConfig, len(config.Tools))
+		for i := range config.Tools {
+			cpConfig.Tools[i] = *config.Tools[i].DeepCopy()
+		}
+	}
+
+	// Deep copy Resources
+	if config.Resources != nil {
+		cpConfig.Resources = make([]ResourceConfig, len(config.Resources))
+		copy(cpConfig.Resources, config.Resources)
+	}
+
+	// Deep copy ResourceTemplates
+	if config.ResourceTemplates != nil {
+		cpConfig.ResourceTemplates = make([]ResourceTemplateConfig, len(config.ResourceTemplates))
+		for i := range config.ResourceTemplates {
+			cpConfig.ResourceTemplates[i] = *config.ResourceTemplates[i].DeepCopy()
+		}
+	}
+
+	// Deep copy Prompts
+	if config.Prompts != nil {
+		cpConfig.Prompts = make([]PromptConfig, len(config.Prompts))
+		for i := range config.Prompts {
+			cpConfig.Prompts[i] = *config.Prompts[i].DeepCopy()
+		}
+	}
+
+	return &cpConfig
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (toolConfig *ToolConfig) DeepCopy() *ToolConfig {
+	if toolConfig == nil {
+		return nil
+	}
+	cpConfig := *toolConfig
+	cpConfig.Request = *toolConfig.Request.DeepCopy()
+
+	if toolConfig.Args != nil {
+		cpConfig.Args = make([]ArgConfig, len(toolConfig.Args))
+		for index := range toolConfig.Args {
+			argPtr := &toolConfig.Args[index]
+			cpConfig.Args[index] = *argPtr.DeepCopy()
+		}
+	}
+
+	return &cpConfig
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *RequestConfig) DeepCopy() *RequestConfig {
+	if config == nil {
+		return nil
+	}
+	cpConfig := *config
+	if config.Headers != nil {
+		cpConfig.Headers = make(map[string]string, len(config.Headers))
+		for k, v := range config.Headers {
+			cpConfig.Headers[k] = v
+		}
+	}
+
+	return &cpConfig
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *ArgConfig) DeepCopy() *ArgConfig {
+	if config == nil {
+		return nil
+	}
+	cpConfig := *config
+	if config.Enum != nil {
+		cpConfig.Enum = make([]string, len(config.Enum))
+		copy(cpConfig.Enum, config.Enum)
+	}
+	return &cpConfig
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (param *ResourceTemplateParameter) DeepCopy() *ResourceTemplateParameter {
+	if param == nil {
+		return nil
+	}
+
+	cpParam := *param
+
+	if param.Enum != nil {
+		cpParam.Enum = make([]string, len(param.Enum))
+		copy(cpParam.Enum, param.Enum)
+	}
+
+	return &cpParam
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (ann *ResourceTemplateAnnotations) DeepCopy() *ResourceTemplateAnnotations {
+	if ann == nil {
+		return nil
+	}
+
+	cpAnn := *ann
+
+	// Deep copy Audience slice
+	if ann.Audience != nil {
+		cpAnn.Audience = make([]string, len(ann.Audience))
+		copy(cpAnn.Audience, ann.Audience)
+	}
+
+	// Deep copy Priority pointer
+	if ann.Priority != nil {
+		p := *ann.Priority
+		cpAnn.Priority = &p
+	}
+
+	return &cpAnn
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *ResourceTemplateConfig) DeepCopy() *ResourceTemplateConfig {
+	if config == nil {
+		return nil
+	}
+
+	cpConfig := *config
+
+	// Deep copy Parameters slice
+	if config.Parameters != nil {
+		cpConfig.Parameters = make([]ResourceTemplateParameter, len(config.Parameters))
+		for i := range config.Parameters {
+			paramPtr := &config.Parameters[i]
+			cpConfig.Parameters[i] = *paramPtr.DeepCopy()
+		}
+	}
+
+	// Deep copy Annotations pointer
+	if config.Annotations != nil {
+		cpConfig.Annotations = config.Annotations.DeepCopy()
+	}
+
+	return &cpConfig
+}
+
+// DeepCopy returns a new independent copy of Config
+// Deep copy slices/maps to avoid sharing pointers with the factory
+func (config *PromptConfig) DeepCopy() *PromptConfig {
+	if config == nil {
+		return nil
+	}
+
+	cp := *config
+
+	// Deep copy Arguments slice
+	if config.Arguments != nil {
+		cp.Arguments = make([]PromptArgumentConfig, len(config.Arguments))
+		copy(cp.Arguments, config.Arguments)
+	}
+
+	// Deep copy Messages slice
+	if config.Messages != nil {
+		cp.Messages = make([]PromptMessageConfig, len(config.Messages))
+		copy(cp.Messages, config.Messages)
+	}
+
+	return &cp
+}
diff --git a/tools/benchmark/README.md b/tools/benchmark/README.md
index 987aca56..64e77cb0 100644
--- a/tools/benchmark/README.md
+++ b/tools/benchmark/README.md
@@ -1,5 +1,35 @@
 # Benchmark Results
 
+* dubbo:
+
+```
+dubbo protocol performance test
+      Name                 | N   | Min   | Median | Mean  | StdDev | Max   
+      =====================================================================
+      GetUser [duration]   | 500 | 100µs | 200µs  | 600µs | 2.8ms  | 20.7ms
+      ---------------------------------------------------------------------
+      GetGender [duration] | 500 | 0s    | 100µs  | 200µs | 100µs  | 600µs 
+      ---------------------------------------------------------------------
+      GetUser0 [duration]  | 500 | 100µs | 100µs  | 200µs | 0s     | 400µs 
+      ---------------------------------------------------------------------
+      GetUsers [duration]  | 500 | 100µs | 200µs  | 200µs | 100µs  | 600µs 
+      ---------------------------------------------------------------------
+      GetUser2 [duration]  | 500 | 0s    | 200µs  | 200µs | 100µs  | 500µs 
+      ---------------------------------------------------------------------
+      GetErr [duration]    | 500 | 300µs | 500µs  | 500µs | 200µs  | 1.4ms 
+
+ pixiu to dubbo protocol performance test
+      Name                 | N   | Min   | Median | Mean  | StdDev | Max  
+      ====================================================================
+      GetUser [duration]   | 500 | 200µs | 600µs  | 1.8ms | 8.4ms  | 61ms 
+      --------------------------------------------------------------------
+      GetGender [duration] | 499 | 200µs | 500µs  | 600µs | 300µs  | 4.1ms
+      --------------------------------------------------------------------
+      GetUser0 [duration]  | 497 | 200µs | 500µs  | 600µs | 300µs  | 2.5ms
+      --------------------------------------------------------------------
+      GetUsers [duration]  | 495 | 200µs | 600µs  | 700µs | 300µs  | 2.9ms
+```
+
 * grpc:
 
 ```
@@ -22,6 +52,20 @@ pixiu to grpc protocol performance test
       GetUserByName [duration] | 500 | 800µs | 1.9ms  | 2.7ms | 3.8ms  | 33.4ms
 ```
 
+* triple:
+
+```
+triple protocol performance test
+      Name                | N   | Min   | Median | Mean  | StdDev | Max  
+      ===================================================================
+      SayHello [duration] | 500 | 100µs | 200µs  | 300µs | 300µs  | 2.6ms
+
+pixiu to triple protocol performance test
+      Name                | N   | Min    | Median | Mean | StdDev | Max   
+      ====================================================================
+      SayHello [duration] | 490 | 10.1ms | 12.7ms | 14ms | 4.1ms  | 43.3ms
+```
+
 # How to Run
 
 1. Build the Pixiu executable
diff --git a/tools/benchmark/README_CN.md b/tools/benchmark/README_CN.md
index 94b24c8b..53cfc147 100644
--- a/tools/benchmark/README_CN.md
+++ b/tools/benchmark/README_CN.md
@@ -1,5 +1,35 @@
 # Benchmark 结果
 
+* dubbo:
+
+```
+dubbo protocol performance test
+      Name                 | N   | Min   | Median | Mean  | StdDev | Max   
+      =====================================================================
+      GetUser [duration]   | 500 | 100µs | 200µs  | 600µs | 2.8ms  | 20.7ms
+      ---------------------------------------------------------------------
+      GetGender [duration] | 500 | 0s    | 100µs  | 200µs | 100µs  | 600µs 
+      ---------------------------------------------------------------------
+      GetUser0 [duration]  | 500 | 100µs | 100µs  | 200µs | 0s     | 400µs 
+      ---------------------------------------------------------------------
+      GetUsers [duration]  | 500 | 100µs | 200µs  | 200µs | 100µs  | 600µs 
+      ---------------------------------------------------------------------
+      GetUser2 [duration]  | 500 | 0s    | 200µs  | 200µs | 100µs  | 500µs 
+      ---------------------------------------------------------------------
+      GetErr [duration]    | 500 | 300µs | 500µs  | 500µs | 200µs  | 1.4ms 
+
+ pixiu to dubbo protocol performance test
+      Name                 | N   | Min   | Median | Mean  | StdDev | Max  
+      ====================================================================
+      GetUser [duration]   | 500 | 200µs | 600µs  | 1.8ms | 8.4ms  | 61ms 
+      --------------------------------------------------------------------
+      GetGender [duration] | 499 | 200µs | 500µs  | 600µs | 300µs  | 4.1ms
+      --------------------------------------------------------------------
+      GetUser0 [duration]  | 497 | 200µs | 500µs  | 600µs | 300µs  | 2.5ms
+      --------------------------------------------------------------------
+      GetUsers [duration]  | 495 | 200µs | 600µs  | 700µs | 300µs  | 2.9ms
+```
+
 * grpc:
 
 ```
@@ -22,6 +52,20 @@ pixiu to grpc protocol performance test
       GetUserByName [duration] | 500 | 800µs | 1.9ms  | 2.7ms | 3.8ms  | 33.4ms
 ```
 
+* triple:
+
+```
+triple protocol performance test
+      Name                | N   | Min   | Median | Mean  | StdDev | Max  
+      ===================================================================
+      SayHello [duration] | 500 | 100µs | 200µs  | 300µs | 300µs  | 2.6ms
+
+pixiu to triple protocol performance test
+      Name                | N   | Min    | Median | Mean | StdDev | Max   
+      ====================================================================
+      SayHello [duration] | 490 | 10.1ms | 12.7ms | 14ms | 4.1ms  | 43.3ms
+```
+
 # 运行方法
 
 1. 构建 pixiu 可执行文件
diff --git a/tools/benchmark/protocol/dubbo/go-server/conf/dubbogo.yml b/tools/benchmark/protocol/dubbo/go-server/conf/dubbogo.yml
index 5f770fa0..eed0d783 100644
--- a/tools/benchmark/protocol/dubbo/go-server/conf/dubbogo.yml
+++ b/tools/benchmark/protocol/dubbo/go-server/conf/dubbogo.yml
@@ -25,11 +25,13 @@ dubbo:
       protocol: zookeeper
       timeout: 3s
       address: 127.0.0.1:2181
+      registry-type: all
   protocols:
     dubbo:
       name: dubbo
       port: 20000
   provider:
+    registry-ids: demoZK
     services:
       UserProvider:
         interface: org.apache.dubbo.sample.UserProvider
diff --git a/tools/benchmark/protocol/triple/pb/go-server/conf/dubbogo.yml b/tools/benchmark/protocol/triple/pb/go-server/conf/dubbogo.yml
index e26721f0..988845f1 100644
--- a/tools/benchmark/protocol/triple/pb/go-server/conf/dubbogo.yml
+++ b/tools/benchmark/protocol/triple/pb/go-server/conf/dubbogo.yml
@@ -21,11 +21,13 @@ dubbo:
   registries:
     zk:
       address: zookeeper://127.0.0.1:2181
+      registry-type: all
   protocols:
     triple:
       name: tri
       port: 20001
   provider:
+    registry-ids: zk
     services:
       GreeterProvider:
         protocol-ids: triple # 使用上面定义的协议id



Test patch:
diff --git a/pkg/filter/opa/opa_test.go b/pkg/filter/opa/opa_test.go
index c26e802b..98a50824 100644
--- a/pkg/filter/opa/opa_test.go
+++ b/pkg/filter/opa/opa_test.go
@@ -19,8 +19,11 @@ package opa
 
 import (
 	"context"
+	"encoding/json"
+	"net/http"
 	"net/http/httptest"
 	"testing"
+	"time"
 )
 
 import (
@@ -31,7 +34,7 @@ import (
 
 import (
 	"github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter"
-	"github.com/apache/dubbo-go-pixiu/pkg/context/http"
+	contextHttp "github.com/apache/dubbo-go-pixiu/pkg/context/http"
 )
 
 const testPolicy = `
@@ -65,13 +68,14 @@ func setupFilterWithoutFile(t *testing.T, policy string) *Filter {
 	}
 }
 
-func TestAllowedRule(t *testing.T) {
+// TestEmbeddedAllowedRule tests embedded mode with allowed request
+func TestEmbeddedAllowedRule(t *testing.T) {
 	f := setupFilterWithoutFile(t, testPolicy)
 	req := httptest.NewRequest("GET", "/test", nil)
 	req.Header.Set("Test_Header", "1")
 
 	rec := httptest.NewRecorder()
-	ctx := &http.HttpContext{
+	ctx := &contextHttp.HttpContext{
 		Writer:  rec,
 		Request: req,
 		Ctx:     context.Background(),
@@ -81,13 +85,14 @@ func TestAllowedRule(t *testing.T) {
 	assert.Equal(t, filter.Continue, result)
 }
 
-func TestDeniedRule(t *testing.T) {
+// TestEmbeddedDeniedRule tests embedded mode with denied request
+func TestEmbeddedDeniedRule(t *testing.T) {
 	f := setupFilterWithoutFile(t, testPolicy)
 	req := httptest.NewRequest("GET", "/test", nil)
 	req.Header.Set("Test_Header", "0")
 
 	rec := httptest.NewRecorder()
-	ctx := &http.HttpContext{
+	ctx := &contextHttp.HttpContext{
 		Writer:  rec,
 		Request: req,
 		Ctx:     context.Background(),
@@ -96,3 +101,448 @@ func TestDeniedRule(t *testing.T) {
 	result := f.Decode(ctx)
 	assert.Equal(t, filter.Stop, result)
 }
+
+// TestServerModeAllowed tests server mode with allowed request
+func TestServerModeAllowed(t *testing.T) {
+	// Create mock OPA server
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, "POST", r.Method)
+		assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
+		assert.Equal(t, "/v1/data/test/allow", r.URL.Path)
+
+		// Read and verify request body
+		var reqBody map[string]any
+		err := json.NewDecoder(r.Body).Decode(&reqBody)
+		assert.Nil(t, err)
+		assert.NotNil(t, reqBody["input"])
+
+		input := reqBody["input"].(map[string]any)
+
+		// Simulate policy: Test_Header == "1" allows
+		// After JSON unmarshaling, headers is map[string][]string
+		// Note: HTTP header keys are canonicalized (e.g., "Test_Header" -> "Test_header")
+		allow := false
+		if headersMap, ok := input["headers"].(map[string]any); ok {
+			// Check for "Test_header" (canonicalized form)
+			if testHeader, ok := headersMap["Test_header"]; ok {
+				if headerArray, ok := testHeader.([]any); ok && len(headerArray) > 0 {
+					if strVal, ok := headerArray[0].(string); ok {
+						allow = strVal == "1"
+					}
+				}
+			}
+		}
+
+		// Return OPA response format
+		response := map[string]any{
+			"result": allow,
+		}
+		w.Header().Set("Content-Type", "application/json")
+		json.NewEncoder(w).Encode(response)
+	}))
+	defer server.Close()
+
+	// Create FilterFactory with server mode configuration
+	factory := &FilterFactory{
+		cfg: &Config{
+			ServerURL:    server.URL,
+			DecisionPath: "/v1/data/test/allow",
+			TimeoutMs:    100,
+		},
+	}
+
+	err := factory.Apply()
+	assert.Nil(t, err)
+	assert.NotNil(t, factory.httpClient)
+
+	// Prepare filter chain
+	req := httptest.NewRequest("GET", "/test", nil)
+	req.Header.Set("Test_Header", "1")
+	rec := httptest.NewRecorder()
+	ctx := &contextHttp.HttpContext{
+		Writer:  rec,
+		Request: req,
+		Ctx:     context.Background(),
+	}
+
+	chain := &mockFilterChain{}
+	err = factory.PrepareFilterChain(ctx, chain)
+	assert.Nil(t, err)
+	assert.Len(t, chain.filters, 1)
+
+	// Execute filter
+	result := chain.filters[0].Decode(ctx)
+	assert.Equal(t, filter.Continue, result)
+}
+
+// TestServerModeDenied tests server mode with denied request
+func TestServerModeDenied(t *testing.T) {
+	// Create mock OPA server
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		var reqBody map[string]any
+		json.NewDecoder(r.Body).Decode(&reqBody)
+		input := reqBody["input"].(map[string]any)
+
+		// After JSON unmarshaling, headers is map[string][]string
+		// Note: HTTP header keys are canonicalized
+		allow := false
+		if headersMap, ok := input["headers"].(map[string]any); ok {
+			if testHeader, ok := headersMap["Test_header"]; ok {
+				if headerArray, ok := testHeader.([]any); ok && len(headerArray) > 0 {
+					if strVal, ok := headerArray[0].(string); ok {
+						allow = strVal == "1"
+					}
+				}
+			}
+		}
+
+		response := map[string]any{
+			"result": allow,
+		}
+		w.Header().Set("Content-Type", "application/json")
+		json.NewEncoder(w).Encode(response)
+	}))
+	defer server.Close()
+
+	factory := &FilterFactory{
+		cfg: &Config{
+			ServerURL:    server.URL,
+			DecisionPath: "/v1/data/test/allow",
+			TimeoutMs:    100,
+		},
+	}
+
+	err := factory.Apply()
+	assert.Nil(t, err)
+
+	req := httptest.NewRequest("GET", "/test", nil)
+	req.Header.Set("Test_Header", "0") // This will be denied
+	rec := httptest.NewRecorder()
+	ctx := &contextHttp.HttpContext{
+		Writer:  rec,
+		Request: req,
+		Ctx:     context.Background(),
+	}
+
+	chain := &mockFilterChain{}
+	err = factory.PrepareFilterChain(ctx, chain)
+	assert.Nil(t, err)
+
+	result := chain.filters[0].Decode(ctx)
+	assert.Equal(t, filter.Stop, result)
+}
+
+// TestServerModeWithBearerToken tests server mode with authentication token
+func TestServerModeWithBearerToken(t *testing.T) {
+	expectedToken := "test-token-123"
+
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Verify authentication header
+		authHeader := r.Header.Get("Authorization")
+		assert.Equal(t, "Bearer "+expectedToken, authHeader)
+
+		response := map[string]any{
+			"result": true,
+		}
+		w.Header().Set("Content-Type", "application/json")
+		json.NewEncoder(w).Encode(response)
+	}))
+	defer server.Close()
+
+	factory := &FilterFactory{
+		cfg: &Config{
+			ServerURL:    server.URL,
+			DecisionPath: "/v1/data/test/allow",
+			TimeoutMs:    100,
+			BearerToken:  expectedToken,
+		},
+	}
+
+	err := factory.Apply()
+	assert.Nil(t, err)
+
+	req := httptest.NewRequest("GET", "/test", nil)
+	rec := httptest.NewRecorder()
+	ctx := &contextHttp.HttpContext{
+		Writer:  rec,
+		Request: req,
+		Ctx:     context.Background(),
+	}
+
+	chain := &mockFilterChain{}
+	factory.PrepareFilterChain(ctx, chain)
+	result := chain.filters[0].Decode(ctx)
+	assert.Equal(t, filter.Continue, result)
+}
+
+// TestServerModeError tests error handling in server mode
+func TestServerModeError(t *testing.T) {
+	// Create a server that returns error
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusInternalServerError)
+		w.Write([]byte("Internal server error"))
+	}))
+	defer server.Close()
+
+	factory := &FilterFactory{
+		cfg: &Config{
+			ServerURL:    server.URL,
+			DecisionPath: "/v1/data/test/allow",
+			TimeoutMs:    100,
+		},
+	}
+
+	err := factory.Apply()
+	assert.Nil(t, err)
+
+	req := httptest.NewRequest("GET", "/test", nil)
+	rec := httptest.NewRecorder()
+	ctx := &contextHttp.HttpContext{
+		Writer:  rec,
+		Request: req,
+		Ctx:     context.Background(),
+	}
+
+	chain := &mockFilterChain{}
+	factory.PrepareFilterChain(ctx, chain)
+	result := chain.filters[0].Decode(ctx)
+	assert.Equal(t, filter.Stop, result) // Should deny on error
+}
+
+// TestServerModeTimeout tests timeout handling in server mode
+func TestServerModeTimeout(t *testing.T) {
+	// Create a server that delays response
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Sleep longer than the timeout
+		time.Sleep(200 * time.Millisecond)
+		response := map[string]any{
+			"result": true,
+		}
+		json.NewEncoder(w).Encode(response)
+	}))
+	defer server.Close()
+
+	factory := &FilterFactory{
+		cfg: &Config{
+			ServerURL:    server.URL,
+			DecisionPath: "/v1/data/test/allow",
+			TimeoutMs:    50, // Very short timeout to trigger timeout
+		},
+	}
+
+	err := factory.Apply()
+	assert.Nil(t, err)
+
+	req := httptest.NewRequest("GET", "/test", nil)
+	rec := httptest.NewRecorder()
+	ctx := &contextHttp.HttpContext{
+		Writer:  rec,
+		Request: req,
+		Ctx:     context.Background(),
+	}
+
+	chain := &mockFilterChain{}
+	factory.PrepareFilterChain(ctx, chain)
+	result := chain.filters[0].Decode(ctx)
+
+	// Should return Stop on timeout
+	assert.Equal(t, filter.Stop, result)
+
+	// Check that the response contains timeout error (504)
+	assert.Equal(t, 504, ctx.GetStatusCode())
+}
+
+// TestServerModeObjectResponse tests server mode with object response format
+func TestServerModeObjectResponse(t *testing.T) {
+	// Test object response with "allow" field
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		var reqBody map[string]any
+		json.NewDecoder(r.Body).Decode(&reqBody)
+		input := reqBody["input"].(map[string]any)
+
+		headersMap := input["headers"].(map[string]any)
+		testHeaderValue := ""
+		if testHeader, ok := headersMap["Test_header"]; ok {
+			if headerArray, ok := testHeader.([]any); ok && len(headerArray) > 0 {
+				if strVal, ok := headerArray[0].(string); ok {
+					testHeaderValue = strVal
+				}
+			}
+		}
+
+		// Return object format: {allow: true}
+		response := map[string]any{
+			"result": map[string]any{
+				"allow":  testHeaderValue == "1",
+				"reason": "test policy",
+			},
+		}
+		w.Header().Set("Content-Type", "application/json")
+		json.NewEncoder(w).Encode(response)
+	}))
+	defer server.Close()
+
+	factory := &FilterFactory{
+		cfg: &Config{
+			ServerURL:    server.URL,
+			DecisionPath: "/v1/data/test/allow",
+			TimeoutMs:    100,
+		},
+	}
+
+	err := factory.Apply()
+	assert.Nil(t, err)
+
+	// Test allow case
+	req := httptest.NewRequest("GET", "/test", nil)
+	req.Header.Set("Test_Header", "1")
+	rec := httptest.NewRecorder()
+	ctx := &contextHttp.HttpContext{
+		Writer:  rec,
+		Request: req,
+		Ctx:     context.Background(),
+	}
+
+	chain := &mockFilterChain{}
+	factory.PrepareFilterChain(ctx, chain)
+	result := chain.filters[0].Decode(ctx)
+	assert.Equal(t, filter.Continue, result)
+
+	// Test deny case
+	req2 := httptest.NewRequest("GET", "/test", nil)
+	req2.Header.Set("Test_Header", "0")
+	rec2 := httptest.NewRecorder()
+	ctx2 := &contextHttp.HttpContext{
+		Writer:  rec2,
+		Request: req2,
+		Ctx:     context.Background(),
+	}
+
+	chain2 := &mockFilterChain{}
+	factory.PrepareFilterChain(ctx2, chain2)
+	result2 := chain2.filters[0].Decode(ctx2)
+	assert.Equal(t, filter.Stop, result2)
+}
+
+// TestEmbeddedObjectResponse tests embedded mode with object response format
+func TestEmbeddedObjectResponse(t *testing.T) {
+	// Policy that returns object with allow field
+	objectPolicy := `
+package test
+import future.keywords.if
+
+allow if {
+    input.headers[Test_Header][0] == "1"
+}
+
+decision := {
+    "allow": allow,
+    "reason": "test policy"
+}
+`
+
+	r := rego.New(
+		rego.Query("data.test.decision"),
+		rego.Module("policy.rego", objectPolicy),
+	)
+
+	preparedQuery, err := r.PrepareForEval(context.Background())
+	assert.Nil(t, err)
+
+	f := &Filter{
+		cfg: &Config{
+			Policy:     objectPolicy,
+			Entrypoint: "data.test.decision",
+		},
+		preparedQuery: &preparedQuery,
+	}
+
+	// Test allow case
+	req := httptest.NewRequest("GET", "/test", nil)
+	req.Header.Set("Test_Header", "1")
+	rec := httptest.NewRecorder()
+	ctx := &contextHttp.HttpContext{
+		Writer:  rec,
+		Request: req,
+		Ctx:     context.Background(),
+	}
+
+	result := f.Decode(ctx)
+	assert.Equal(t, filter.Continue, result)
+
+	// Test deny case
+	req2 := httptest.NewRequest("GET", "/test", nil)
+	req2.Header.Set("Test_Header", "0")
+	rec2 := httptest.NewRecorder()
+	ctx2 := &contextHttp.HttpContext{
+		Writer:  rec2,
+		Request: req2,
+		Ctx:     context.Background(),
+	}
+
+	result2 := f.Decode(ctx2)
+	assert.Equal(t, filter.Stop, result2)
+}
+
+// TestConfigValidation tests configuration validation
+func TestConfigValidation(t *testing.T) {
+	// Test missing decision_path in server mode
+	factory := &FilterFactory{
+		cfg: &Config{
+			ServerURL: "http://localhost:8181",
+		},
+	}
+	err := factory.Apply()
+	assert.NotNil(t, err)
+	assert.Contains(t, err.Error(), "decision_path is required")
+
+	// Test missing policy in embedded mode
+	factory2 := &FilterFactory{
+		cfg: &Config{
+			Entrypoint: "data.test.allow",
+		},
+	}
+	err2 := factory2.Apply()
+	assert.NotNil(t, err2)
+	assert.Contains(t, err2.Error(), "server_url")
+
+	// Test missing entrypoint in embedded mode
+	factory3 := &FilterFactory{
+		cfg: &Config{
+			Policy: "package test",
+		},
+	}
+	err3 := factory3.Apply()
+	assert.NotNil(t, err3)
+	assert.Contains(t, err3.Error(), "entrypoint is required")
+}
+
+// mockFilterChain is a mock implementation of filter.FilterChain for testing
+type mockFilterChain struct {
+	filters []filter.HttpDecodeFilter
+}
+
+func (m *mockFilterChain) AppendDecodeFilters(f ...filter.HttpDecodeFilter) {
+	m.filters = append(m.filters, f...)
+}
+
+func (m *mockFilterChain) AppendEncodeFilters(f ...filter.HttpEncodeFilter) {
+	// Not needed for testing
+}
+
+func (m *mockFilterChain) OnDecode(ctx *contextHttp.HttpContext) {
+	// Not needed for testing
+}
+
+func (m *mockFilterChain) OnEncode(ctx *contextHttp.HttpContext) {
+	// Not needed for testing
+}
+
+// Backward compatibility test names
+func TestAllowedRule(t *testing.T) {
+	TestEmbeddedAllowedRule(t)
+}
+
+func TestDeniedRule(t *testing.T) {
+	TestEmbeddedDeniedRule(t)
+}
diff --git a/tools/benchmark/test/dubbo_suite/dubbo_test.go b/tools/benchmark/test/dubbo_suite/dubbo_test.go
index 54d1c193..6bf32541 100644
--- a/tools/benchmark/test/dubbo_suite/dubbo_test.go
+++ b/tools/benchmark/test/dubbo_suite/dubbo_test.go
@@ -21,6 +21,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"os"
 	"os/exec"
@@ -62,76 +63,21 @@ var _ = Describe("test", Ordered, func() {
 		test.CurPath, err = os.Getwd()
 		gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
-		dubboServerSession = prepareDubboServer()
-		pixiuSession = test.PreparePixiu("../../dist/pixiu", test.CurPath+"/../../protocol/dubbo/pixiu/conf/config.yaml")
+		// Wait for ports to be available before starting servers
+		waitForPortAvailable("20000", 10*time.Second)
+		waitForPortAvailable("8881", 10*time.Second)
 
-		time.Sleep(3 * time.Second)
+		dubboServerSession = prepareDubboServer()
+		// Wait for dubbo server to register to Zookeeper
+		time.Sleep(5 * time.Second)
 
-		prepareDubboClient()
+		pixiuSession = test.PreparePixiu("../../dist/pixiu", test.CurPath+"/../../protocol/dubbo/pixiu/conf/config.yaml")
+		// Wait for pixiu to discover services from Zookeeper
+		time.Sleep(5 * time.Second)
 	})
 
 	//TODO(kenwaycai): output to external file
 
-	It("dubbo protocol performance test", func() {
-		defer GinkgoRecover()
-
-		experiment := gmeasure.NewExperiment("dubbo protocol performance test")
-		AddReportEntry(experiment.Name, experiment)
-
-		experiment.Sample(func(idx int) {
-			reqUser := &pkg.User{}
-			reqUser.ID = "003"
-			experiment.MeasureDuration("GetUser", func() {
-				_, err := userProvider.GetUser(context.TODO(), reqUser)
-				gomega.Expect(err).To(gomega.Succeed())
-				//fmt.Printf("consumer:%+v", user)
-			})
-		}, test.SampleConfig)
-
-		experiment.Sample(func(idx int) {
-			experiment.MeasureDuration("GetGender", func() {
-				_, err := userProvider.GetGender(context.TODO(), 1)
-				gomega.Expect(err).To(gomega.Succeed())
-				//fmt.Printf("consumer:%+v", gender)
-			})
-		}, test.SampleConfig)
-
-		experiment.Sample(func(idx int) {
-			experiment.MeasureDuration("GetUser0", func() {
-				_, err := userProvider.GetUser0("003", "Moorse")
-				gomega.Expect(err).To(gomega.Succeed())
-				//fmt.Printf("consumer:%+v", ret)
-			})
-		}, test.SampleConfig)
-
-		experiment.Sample(func(idx int) {
-			experiment.MeasureDuration("GetUsers", func() {
-				_, err := userProvider.GetUsers([]string{"002", "003"})
-				gomega.Expect(err).To(gomega.Succeed())
-				//fmt.Printf("consumer:%+v", ret1)
-			})
-		}, test.SampleConfig)
-
-		experiment.Sample(func(idx int) {
-			experiment.MeasureDuration("GetUser2", func() {
-				var i int32 = 1
-				_, err := userProvider.GetUser2(context.TODO(), i)
-				gomega.Expect(err).To(gomega.Succeed())
-				//fmt.Printf("consumer:%+v", user)
-			})
-		}, test.SampleConfig)
-
-		experiment.Sample(func(idx int) {
-			experiment.MeasureDuration("GetErr", func() {
-				reqUser := &pkg.User{}
-				reqUser.ID = "003"
-				_, err := userProvider.GetErr(context.TODO(), reqUser)
-				gomega.Expect(err).To(gomega.HaveOccurred())
-				//fmt.Printf("consumer:%+v", err.Error())
-			})
-		}, test.SampleConfig)
-	})
-
 	It("pixiu to dubbo protocol performance test", func() {
 
 		urlPrefix := "http://localhost:8881/dubbo.io/org.apache.dubbo.sample.UserProvider/%s"
@@ -155,6 +101,7 @@ var _ = Describe("test", Ordered, func() {
 
 				resp, err := http.Post(url, "application/json", strings.NewReader(data))
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
+				defer resp.Body.Close()
 				gomega.Expect(resp.Status).To(gomega.Equal("200 OK"))
 				_, err = io.ReadAll(resp.Body)
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
@@ -172,6 +119,7 @@ var _ = Describe("test", Ordered, func() {
 `
 				resp, err := http.Post(url, "application/json", strings.NewReader(data))
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
+				defer resp.Body.Close()
 				gomega.Expect(resp.Status).To(gomega.Equal("200 OK"))
 
 				_, err = io.ReadAll(resp.Body)
@@ -193,6 +141,7 @@ var _ = Describe("test", Ordered, func() {
 `
 				resp, err := http.Post(url, "application/json", strings.NewReader(data))
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
+				defer resp.Body.Close()
 				gomega.Expect(resp.Status).To(gomega.Equal("200 OK"))
 
 				_, err = io.ReadAll(resp.Body)
@@ -216,19 +165,84 @@ var _ = Describe("test", Ordered, func() {
 `
 
 				resp, err := http.Post(url, "application/json", strings.NewReader(data))
-				_, err = io.ReadAll(resp.Body)
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
+				defer resp.Body.Close()
 				gomega.Expect(resp.Status).To(gomega.Equal("200 OK"))
+				_, err = io.ReadAll(resp.Body)
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
 			})
 
 		}, test.SampleConfig)
 	})
 
+	It("dubbo protocol performance test", func() {
+		defer GinkgoRecover()
+
+		// Initialize dubbo client only when needed for this test
+		prepareDubboClient()
+
+		experiment := gmeasure.NewExperiment("dubbo protocol performance test")
+		AddReportEntry(experiment.Name, experiment)
+
+		experiment.Sample(func(idx int) {
+			reqUser := &pkg.User{}
+			reqUser.ID = "003"
+			experiment.MeasureDuration("GetUser", func() {
+				_, err := userProvider.GetUser(context.TODO(), reqUser)
+				gomega.Expect(err).To(gomega.Succeed())
+			})
+		}, test.SampleConfig)
+
+		experiment.Sample(func(idx int) {
+			experiment.MeasureDuration("GetGender", func() {
+				_, err := userProvider.GetGender(context.TODO(), 1)
+				gomega.Expect(err).To(gomega.Succeed())
+			})
+		}, test.SampleConfig)
+
+		experiment.Sample(func(idx int) {
+			experiment.MeasureDuration("GetUser0", func() {
+				_, err := userProvider.GetUser0("003", "Moorse")
+				gomega.Expect(err).To(gomega.Succeed())
+			})
+		}, test.SampleConfig)
+
+		experiment.Sample(func(idx int) {
+			experiment.MeasureDuration("GetUsers", func() {
+				_, err := userProvider.GetUsers([]string{"002", "003"})
+				gomega.Expect(err).To(gomega.Succeed())
+			})
+		}, test.SampleConfig)
+
+		experiment.Sample(func(idx int) {
+			experiment.MeasureDuration("GetUser2", func() {
+				var i int32 = 1
+				_, err := userProvider.GetUser2(context.TODO(), i)
+				gomega.Expect(err).To(gomega.Succeed())
+			})
+		}, test.SampleConfig)
+
+		experiment.Sample(func(idx int) {
+			experiment.MeasureDuration("GetErr", func() {
+				reqUser := &pkg.User{}
+				reqUser.ID = "003"
+				_, err := userProvider.GetErr(context.TODO(), reqUser)
+				gomega.Expect(err).To(gomega.HaveOccurred())
+			})
+		}, test.SampleConfig)
+	})
+
 	AfterAll(func() {
-		time.Sleep(5 * time.Second)
-		dubboServerSession.Terminate().Wait()
-		pixiuSession.Terminate().Wait(5 * time.Second)
+		// Use Kill instead of Terminate to ensure processes are fully stopped
+		if pixiuSession != nil {
+			pixiuSession.Kill().Wait(10 * time.Second)
+		}
+		if dubboServerSession != nil {
+			dubboServerSession.Kill().Wait(10 * time.Second)
+		}
+		gexec.CleanupBuildArtifacts()
+		// Wait for ports to be released
+		time.Sleep(2 * time.Second)
 	})
 
 })
@@ -237,7 +251,9 @@ func prepareDubboServer() *gexec.Session {
 	serverProcess, err := gexec.Build("dubbo-go-pixiu-benchmark/protocol/dubbo/go-server/cmd")
 	gomega.Expect(err).NotTo(gomega.HaveOccurred())
 	command := exec.Command(serverProcess)
-	session, err := gexec.Start(command, io.Discard, io.Discard)
+	// Set working directory to test/dubbo_suite so that relative paths in server.go work correctly
+	command.Dir = test.CurPath
+	session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
 	//session, err := gexec.Start(command, os.Stdout, os.Stderr)
 	gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
@@ -254,3 +270,21 @@ func prepareDubboClient() {
 	err := config.Load(config.WithPath(test.CurPath + "/../../protocol/dubbo/go-client/conf/dubbogo.yml"))
 	gomega.Expect(err).NotTo(gomega.HaveOccurred())
 }
+
+// waitForPortAvailable waits until the port is available (not in use)
+func waitForPortAvailable(port string, timeout time.Duration) {
+	deadline := time.Now().Add(timeout)
+	for time.Now().Before(deadline) {
+		conn, err := net.DialTimeout("tcp", "127.0.0.1:"+port, 100*time.Millisecond)
+		if err != nil {
+			// Port is available (connection refused means no one is listening)
+			if conn != nil {
+				conn.Close()
+			}
+			return
+		}
+		conn.Close()
+		time.Sleep(500 * time.Millisecond)
+	}
+	gomega.Expect(fmt.Errorf("port %s is still in use after %v", port, timeout)).NotTo(gomega.HaveOccurred())
+}
diff --git a/tools/benchmark/test/triple_suite/proto_suite/proto_test.go b/tools/benchmark/test/triple_suite/proto_suite/proto_test.go
index 5b65c231..24503d6a 100644
--- a/tools/benchmark/test/triple_suite/proto_suite/proto_test.go
+++ b/tools/benchmark/test/triple_suite/proto_suite/proto_test.go
@@ -6,7 +6,7 @@
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -21,6 +21,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"os"
 	"os/exec"
@@ -62,33 +63,17 @@ var _ = Describe("triple protocol performance test", Ordered, func() {
 		test.CurPath, err = os.Getwd()
 		gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
-		tripleServerSession = prepareTripleServer()
-		pixiuSession = test.PreparePixiu("../../../dist/pixiu", test.CurPath+"/../../../protocol/triple/pb/pixiu/conf/config.yaml")
-
-		time.Sleep(3 * time.Second)
-
-		prepareTripleClient()
-
-	})
+		// Wait for ports to be available before starting servers
+		waitForPortAvailable("20000", 10*time.Second)
+		waitForPortAvailable("8881", 10*time.Second)
 
-	It("triple protocol performance test", func() {
-
-		experiment := gmeasure.NewExperiment("triple protocol performance test")
-		AddReportEntry(experiment.Name, experiment)
-
-		experiment.Sample(func(idx int) {
-			defer GinkgoRecover()
+		tripleServerSession = prepareTripleServer()
+		// Wait for dubbo server to register to Zookeeper
+		time.Sleep(5 * time.Second)
 
-			experiment.MeasureDuration("SayHello", func() {
-				req := &triplepb.HelloRequest{
-					Name: "laurence",
-				}
-				ctx := context.WithValue(context.Background(), tripleConstant.TripleCtxKey("tri-req-id"), "test_value_XXXXXXXX")
-				_, err := grpcGreeterImpl.SayHello(ctx, req)
-				gomega.Expect(err).NotTo(gomega.HaveOccurred())
-				//fmt.Printf("consumer:%+v\n", reply)
-			})
-		}, test.SampleConfig)
+		pixiuSession = test.PreparePixiu("../../../dist/pixiu", test.CurPath+"/../../../protocol/triple/pb/pixiu/conf/config.yaml")
+		// Wait for pixiu to discover services from Zookeeper
+		time.Sleep(5 * time.Second)
 	})
 
 	It("pixiu to triple protocol performance test", func() {
@@ -106,11 +91,12 @@ var _ = Describe("triple protocol performance test", Ordered, func() {
 				url := fmt.Sprintf(urlPrefix, "SayHello")
 				data := `
 {
-	"name":"test"
+    "name":"test"
 }
 `
 				resp, err := http.Post(url, "application/json", strings.NewReader(data))
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
+				defer resp.Body.Close()
 				gomega.Expect(resp.Status).To(gomega.Equal("200 OK"))
 				reply, err := io.ReadAll(resp.Body)
 				gomega.Expect(err).NotTo(gomega.HaveOccurred())
@@ -120,9 +106,39 @@ var _ = Describe("triple protocol performance test", Ordered, func() {
 		}, test.SampleConfig)
 	})
 
+	It("triple protocol performance test", func() {
+		// Initialize dubbo client only when needed for this test
+		prepareTripleClient()
+
+		experiment := gmeasure.NewExperiment("triple protocol performance test")
+		AddReportEntry(experiment.Name, experiment)
+
+		experiment.Sample(func(idx int) {
+			defer GinkgoRecover()
+
+			experiment.MeasureDuration("SayHello", func() {
+				req := &triplepb.HelloRequest{
+					Name: "laurence",
+				}
+				ctx := context.WithValue(context.Background(), tripleConstant.TripleCtxKey("tri-req-id"), "test_value_XXXXXXXX")
+				_, err := grpcGreeterImpl.SayHello(ctx, req)
+				gomega.Expect(err).NotTo(gomega.HaveOccurred())
+				//fmt.Printf("consumer:%+v\n", reply)
+			})
+		}, test.SampleConfig)
+	})
+
 	AfterAll(func() {
-		tripleServerSession.Terminate().Wait(5 * time.Second)
-		pixiuSession.Terminate().Wait(5 * time.Second)
+		// Use Kill instead of Terminate to ensure processes are fully stopped
+		if pixiuSession != nil {
+			pixiuSession.Kill().Wait(10 * time.Second)
+		}
+		if tripleServerSession != nil {
+			tripleServerSession.Kill().Wait(10 * time.Second)
+		}
+		gexec.CleanupBuildArtifacts()
+		// Wait for ports to be released
+		time.Sleep(2 * time.Second)
 	})
 })
 
@@ -131,6 +147,8 @@ func prepareTripleServer() *gexec.Session {
 	gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
 	command := exec.Command(serverProcess)
+	// Set working directory
+	command.Dir = test.CurPath
 	session, err := gexec.Start(command, io.Discard, io.Discard)
 	//session, err := gexec.Start(command, os.Stdout, os.Stderr)
 
@@ -144,3 +162,17 @@ func prepareTripleClient() {
 	err := config.Load(config.WithPath(test.CurPath + "/../../../protocol/triple/pb/go-client/conf/dubbogo.yml"))
 	gomega.Expect(err).NotTo(gomega.HaveOccurred())
 }
+
+// waitForPortAvailable waits until the port is available (not in use)
+func waitForPortAvailable(port string, timeout time.Duration) {
+	deadline := time.Now().Add(timeout)
+	for time.Now().Before(deadline) {
+		conn, err := net.DialTimeout("tcp", "127.0.0.1:"+port, 100*time.Millisecond)
+		if err != nil {
+			// Port is available (connection refused means no one is listening)
+			return
+		}
+		conn.Close()
+		time.Sleep(500 * time.Millisecond)
+	}
+}