You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2021/05/31 11:34:12 UTC

[apisix-java-plugin-runner] branch main updated: docs: add the-internal-of-apisix-java-plugin-runner.md, development.md and how-it-works.md (#19)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/apisix-java-plugin-runner.git


The following commit(s) were added to refs/heads/main by this push:
     new ecf80b2  docs: add the-internal-of-apisix-java-plugin-runner.md, development.md and how-it-works.md (#19)
ecf80b2 is described below

commit ecf80b2f1838b7f367a2909d86461636d861f5c8
Author: tzssangglass <tz...@gmail.com>
AuthorDate: Mon May 31 19:34:02 2021 +0800

    docs: add the-internal-of-apisix-java-plugin-runner.md, development.md and how-it-works.md (#19)
---
 docs/development.md                                | 119 +++++++++++++++++
 docs/how-it-works.md                               |  87 ++++++++++++
 .../the-internal-of-apisix-java-plugin-runner.png  | Bin 0 -> 58191 bytes
 docs/the-internal-of-apisix-java-plugin-runner.md  |  58 ++++++++
 runner-core/pom.xml                                |   5 +
 .../runner/codec/impl/FlatBuffersEncoderTest.java  |   4 +-
 .../plugin/runner/handler/A6ConfigHandlerTest.java |  14 +-
 .../runner/handler/A6HttpCallHandlerTest.java      |  47 +++----
 .../apache/apisix/plugin/runner/HttpRequest.java   | 148 +++++++++++++++++++--
 .../apache/apisix/plugin/runner/HttpResponse.java  |  53 +++++++-
 .../apisix/plugin/runner/filter/PluginFilter.java  |   3 +-
 .../plugin/runner/filter/PluginFilterChain.java    |   7 +-
 runner-starter/src/main/resources/application.yaml |   4 +-
 .../runner/filter/RewriteRequestDemoFilter.java    |  33 +++--
 .../runner/filter/StopRequestDemoFilter.java       |  31 +++--
 15 files changed, 531 insertions(+), 82 deletions(-)

diff --git a/docs/development.md b/docs/development.md
index e69de29..aaa5ae9 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -0,0 +1,119 @@
+Development
+
+This document explains how to get started to develop the apisix-java-plugin-runner.
+
+Prerequisites
+-------------
+
+* JDK 8
+* APISIX 2.6.0
+* Clone the [apisix-java-plugin-runner](https://github.com/apache/apisix-java-plugin-runner) project.
+* Refer to [Debug](how-it-works.md#debug)  to build the debug environment.
+
+Install
+-------
+
+```shell
+cd /path/to/apisix-java-plugin-runner
+./mvnw install
+```
+
+Write Filter
+------------
+
+Refer to the code in the [sample](https://github.com/apache/apisix-java-plugin-runner/tree/main/sample)
+to learn how to extend `PluginFilter`, define the order, rewrite requests and stop requests.
+
+####  Code Location
+
+You need to put the code in [runner-plugin-sdk](https://github.com/apache/apisix-java-plugin-runner/tree/main/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner)
+so that the `apisix-java-plugin-runner.jar` will contain the filter implementation class you wrote when you package it.
+
+####  The order of filter execution
+
+The order of execution of the filter in the runner is determined by the index of the `conf` array in the `ext-plugin-pre-req` or `ext-plugin-post-req` configuration.
+
+####  The name of filter execution
+
+The requests go through filters that are dynamically configured on APISIX.
+For example, if the following configuration is done on APISIX
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri":"/hello",
+    "plugins":{
+        "ext-plugin-pre-req":{
+            "conf":[
+                {
+                    "name":"FooFilter",
+                    "value":"bar"
+                }
+            ]
+        }
+    },
+    "upstream":{
+        "nodes":{
+            "127.0.0.1:1980":1
+        },
+        "type":"roundrobin"
+    }
+}'
+```
+
+apisix-java-plugin-runner will look for implementation classes named `FooFilter`,
+and the name of each filter's implementation class is the return value of its overridden function `public String name()`.
+
+
+####  Rewrite Request
+
+If you perform the following function call in the filter chain of the implementation class
+
+*  request.setPath()
+*  request.setHeader()
+*  request.setArg()
+
+this means to rewrit the current request, the upstream server will receive
+the relevant parameters rewritten here.
+
+####  Stop Request
+
+If you perform the following function call in the filter chain of the implementation class
+
+*  response.setStatusCode()
+*  response.setHeader()
+*  response.setBody()
+
+this means to stop the current request, the client will receive
+the relevant parameters generated here.
+
+Test
+----
+
+### Run Unit Test Suites
+
+```shell
+cd /path/to/apisix-java-plugin-runner
+ ./mvnw test
+```
+
+
+### Mimic practical environment
+
+If you want to mimic the practical environment, you need to configure the route on APISIX
+by having the request go through the filter you want to test, for example
+
+```json
+"plugins":{
+    "ext-plugin-pre-req":{
+        "conf":[
+            {
+                "name":"FooFilter",
+                "value":"bar"
+            }
+        ]
+    }
+}
+```
+
+and then make a request to APISIX to trigger the route.
diff --git a/docs/how-it-works.md b/docs/how-it-works.md
index e69de29..2ddf57c 100644
--- a/docs/how-it-works.md
+++ b/docs/how-it-works.md
@@ -0,0 +1,87 @@
+# How It Works
+
+This article explains how apisix-java-plugin-runner collaborate with [Apache APISIX](https://apisix.apache.org) to run plugins written in java.
+
+## Run Mode
+
+apisix-java-plugin-runner can be run alone or bundled with Apache APISIX.
+It depends on whether you need to debug it or run it.
+
+### Debug
+
+If you are developing a new plugin and need to debug the code, then you can run the main class
+[PluginRunnerApplication](https://github.com/apache/apisix-java-plugin-runner/blob/main/runner-starter/src/main/java/org/apache/apisix/plugin/runner/PluginRunnerApplication.java),
+and before start, you need to set the following two environment variables:
+
+- APISIX_LISTEN_ADDRESS: apisix-java-plugin-runner and APISIX for inter-process communication (Unix Domain Socket) socket type file address.
+  And do not need to actively create this file, apisix-java-plugin-runner will automatically create this file when it starts.
+- APISIX_CONF_EXPIRE_TIME: the time that APISIX's configuration is cached in the apisix-java-plugin-runner process.
+
+For example, if you start apisix-java-plugin-runner as a jar package, pass the environment variables as below
+
+```shell
+java -jar -DAPISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock -DAPISIX_CONF_EXPIRE_TIME=3600 /path/to/apisix-java-plugin-runner.jar
+```
+
+Note: Refer to [apisix-java-plugin-runner.jar](#run) to get it.
+
+and add the following configure in the `config.yaml` file of APISIX
+
+```yaml
+ext-plugin:
+  path_for_test: /tmp/runner.sock
+```
+
+The `/tmp/runner.sock` is the address of the file where apisix-java-plugin-runner
+and APISIX communicate between processes and must be consistent.
+
+Note: If you see some error logs like
+
+```
+phase_func(): failed to connect to the unix socket unix:/tmp/runner.sock: permission denied
+```
+
+in the `error.log` of APISIX, you can change the permissions of this file for debug, execute commands like
+
+```shell
+chmod 777 /tmp/runner.sock
+```
+
+### Run
+
+No environment variables need to be set in Run mode, execute
+
+```shell
+cd /path/to/apisix-java-plugin-runner
+ ./mvnw package
+```
+
+to built apisix-java-plugin-runner as a jar package, then you will see the `dist` directory, execute
+
+```
+cd dist
+tar -zxvf apache-apisix-runner-bin.tar.gz
+```
+
+the layout of files in the `dist` directory is as below
+
+```
+dist
+├── apache-apisix-runner-bin.tar.gz
+└── apisix-runner-bin
+    ├── apisix-java-plugin-runner.jar
+    ├── bin
+    │   ├── shutdown.sh
+    │   └── startup.sh
+    ├── LICENSE
+    ├── NOTICE
+    └── README.md
+
+```
+
+then add the following configure in the `config.yaml` file of APISIX
+
+```yaml
+ext-plugin:
+  cmd: ['java', '-jar', '-Xmx4g', '-Xms4g', '/path/to/apisix-runner-bin/apisix-java-plugin-runner.jar']
+```
diff --git a/docs/images/the-internal-of-apisix-java-plugin-runner.png b/docs/images/the-internal-of-apisix-java-plugin-runner.png
new file mode 100644
index 0000000..458e8a3
Binary files /dev/null and b/docs/images/the-internal-of-apisix-java-plugin-runner.png differ
diff --git a/docs/the-internal-of-apisix-java-plugin-runner.md b/docs/the-internal-of-apisix-java-plugin-runner.md
index e69de29..c8cf410 100644
--- a/docs/the-internal-of-apisix-java-plugin-runner.md
+++ b/docs/the-internal-of-apisix-java-plugin-runner.md
@@ -0,0 +1,58 @@
+The Internal of apisix java plugin runner
+
+This article explains the internal design of apisix-java-plugin-runner.
+
+## Table of Contents
+
+- [Overview](#overview)
+- [Communication](#communication)
+- [Serialization](#serialization)
+- [Codec](#codec)
+
+## Overview
+
+The apisix-java-plugin-runner designed as a TCP server built using [reactor-netty](https://github.com/reactor/reactor-netty),
+it provides a `PluginFilter` interface for users to implement.
+
+Users only need to focus on their business logic, not on the details of how the apisix java plugin runner communicates with APISIX.
+
+The inter-process communication between them is depicted by the following diagram.
+
+![the-internal-of-apisix-java-plugin-runner](./images/the-internal-of-apisix-java-plugin-runner.png)
+
+## Communication
+
+apisix-java-plugin-runner and APISIX use the Unix Domain Socket for inter-process communication,
+so they need to be deployed in the same instance.
+
+apisix-java-plugin-runner is managed by APISIX. APISIX starts the apisix-java-plugin-runner when it starts and ends it when it
+ends. if the apisix-java-plugin-runner quits in the middle, APISIX will restart it automatically.
+
+## Serialization
+
+Refer to [flatbuffers](https://github.com/google/flatbuffers)
+
+FlatBuffers is a cross platform serialization library architected for maximum memory efficiency.
+It allows you to directly access serialized data without parsing/unpacking it first, while still having great forward/backward compatibility.
+
+You can refer to the [ext-plugin.fbs](https://github.com/api7/ext-plugin-proto/blob/main/ext-plugin.fbs)
+ schema file to see how Lua and Java layout the serialized objects.
+
+## Codec
+
+apisix-java-plugin-runner and APISIX use a private binary protocol for coding and decoding.
+The protocol format is
+
+```
+1 byte of type + 3 bytes of length + data
+```
+
+The type can be 0 ~ 7, and the length can be [0, 8M). The length of data is determined by length.
+
+The current type takes the following values
+
+* 0 means error
+* 1 means prepare_conf
+* 2 means http_req_call
+
+The binary data generated by the flatbuffer serialization is placed in the data segment.
diff --git a/runner-core/pom.xml b/runner-core/pom.xml
index 5997c7a..6594fd8 100644
--- a/runner-core/pom.xml
+++ b/runner-core/pom.xml
@@ -91,5 +91,10 @@
             <artifactId>reactor-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoderTest.java b/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoderTest.java
index 8a9e38b..dbba24f 100644
--- a/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoderTest.java
+++ b/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoderTest.java
@@ -111,7 +111,7 @@ class FlatBuffersEncoderTest {
         HttpResponse httpResponse = new HttpResponse(0L);
         // set path, args, req header means rewrite request
         httpResponse.setPath("/hello");
-        httpResponse.setArgs("foo", "bar");
+        httpResponse.setArg("foo", "bar");
         httpResponse.setReqHeader("Server", "APISIX");
         ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
         result.position(4);
@@ -156,7 +156,7 @@ class FlatBuffersEncoderTest {
         HttpResponse httpResponse = new HttpResponse(0L);
         // set path, args, req header means rewrite request
         httpResponse.setPath("/hello");
-        httpResponse.setArgs("foo", "bar");
+        httpResponse.setArg("foo", "bar");
         httpResponse.setReqHeader("Server", "APISIX");
 
         // set status, body, resp header means stop request
diff --git a/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6ConfigHandlerTest.java b/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6ConfigHandlerTest.java
index 1f0d982..6ec9c12 100644
--- a/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6ConfigHandlerTest.java
+++ b/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6ConfigHandlerTest.java
@@ -65,11 +65,6 @@ class A6ConfigHandlerTest {
             public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
                 return chain.filter(request, response);
             }
-
-            @Override
-            public int getOrder() {
-                return 0;
-            }
         });
 
         filters.put("CatFilter", new PluginFilter() {
@@ -82,11 +77,6 @@ class A6ConfigHandlerTest {
             public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
                 return chain.filter(request, response);
             }
-
-            @Override
-            public int getOrder() {
-                return 1;
-            }
         });
         cache = CacheBuilder.newBuilder().expireAfterWrite(3600, TimeUnit.SECONDS).maximumSize(1000).build();
         a6ConfigHandler = new A6ConfigHandler(cache, filters);
@@ -112,7 +102,7 @@ class A6ConfigHandlerTest {
         A6Conf config = cache.getIfPresent(0L);
         Assertions.assertNotNull(config.getChain());
         Assertions.assertEquals(config.getChain().getFilters().size(), 1);
-        Assertions.assertEquals(config.getChain().getFilters().get(0).getOrder(), 0);
+        Assertions.assertEquals(config.getChain().getIndex(), 0);
         Assertions.assertEquals(config.get("FooFilter"), "Bar");
 
     }
@@ -141,8 +131,6 @@ class A6ConfigHandlerTest {
 
         A6Conf config = cache.getIfPresent(0L);
         Assertions.assertEquals(config.getChain().getFilters().size(), 2);
-        Assertions.assertEquals(config.getChain().getFilters().get(0).getOrder(), 0);
-        Assertions.assertEquals(config.getChain().getFilters().get(1).getOrder(), 1);
     }
 
     @Test
diff --git a/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6HttpCallHandlerTest.java b/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6HttpCallHandlerTest.java
index f55b8d1..c323fb3 100644
--- a/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6HttpCallHandlerTest.java
+++ b/runner-core/src/test/java/org/apache/apisix/plugin/runner/handler/A6HttpCallHandlerTest.java
@@ -20,6 +20,7 @@ package org.apache.apisix.plugin.runner.handler;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.flatbuffers.FlatBufferBuilder;
+import com.google.gson.Gson;
 import io.github.api7.A6.Err.Code;
 import io.github.api7.A6.HTTPReqCall.Action;
 import io.github.api7.A6.TextEntry;
@@ -67,10 +68,15 @@ class A6HttpCallHandlerTest {
             }
 
             @Override
+            @SuppressWarnings("unchecked")
             public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
-                logger.info("do filter: FooFilter, order: {}", getOrder());
+                logger.info("do filter: FooFilter, order: {}", chain.getIndex());
                 logger.info("do filter: FooFilter, config: {}", request.getConfig(this));
-
+                Gson gson = new Gson();
+                Map<String, Object> conf = new HashMap<>();
+                conf = gson.fromJson(request.getConfig(this), conf.getClass());
+                logger.info("do filter: FooFilter, conf_key1 value: {}", conf.get("conf_key1"));
+                logger.info("do filter: FooFilter, conf_key2 value: {}", conf.get("conf_key2"));
                 if (!Objects.isNull(request.getPath())) {
                     logger.info("do filter: path: {}", request.getPath());
                 }
@@ -82,8 +88,8 @@ class A6HttpCallHandlerTest {
                     }
                 }
 
-                if (!Objects.isNull(request.getHeaders())) {
-                    for (Map.Entry<String, String> header : request.getHeaders().entrySet()) {
+                if (!Objects.isNull(request.getHeader())) {
+                    for (Map.Entry<String, String> header : request.getHeader().entrySet()) {
                         logger.info("do filter: header key: {}", header.getKey());
                         logger.info("do filter: header value: {}", header.getValue());
                     }
@@ -96,10 +102,6 @@ class A6HttpCallHandlerTest {
                 return chain.filter(request, response);
             }
 
-            @Override
-            public int getOrder() {
-                return 0;
-            }
         });
 
         filters.put("CatFilter", new PluginFilter() {
@@ -110,28 +112,25 @@ class A6HttpCallHandlerTest {
 
             @Override
             public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
-                logger.info("do filter: CatFilter, order: {}", getOrder());
+                logger.info("do filter: CatFilter, order: {}", chain.getIndex());
                 logger.info("do filter: CatFilter, config: {}", request.getConfig(this));
 
                 response.setStatusCode(401);
                 return chain.filter(request, response);
             }
 
-            @Override
-            public int getOrder() {
-                return 1;
-            }
         });
         cache = CacheBuilder.newBuilder().expireAfterWrite(3600, TimeUnit.SECONDS).maximumSize(1000).build();
         FlatBufferBuilder builder = new FlatBufferBuilder();
-        int cat = builder.createString("CatFilter");
-        int dog = builder.createString("Dog");
-        int filter2 = TextEntry.createTextEntry(builder, cat, dog);
 
         int foo = builder.createString("FooFilter");
-        int bar = builder.createString("Bar");
+        int bar = builder.createString("{\"conf_key1\":\"conf_value1\",\"conf_key2\":2}");
         int filter1 = TextEntry.createTextEntry(builder, foo, bar);
 
+        int cat = builder.createString("CatFilter");
+        int dog = builder.createString("Dog");
+        int filter2 = TextEntry.createTextEntry(builder, cat, dog);
+
         int confVector = io.github.api7.A6.PrepareConf.Req.createConfVector(builder, new int[]{filter1, filter2});
         io.github.api7.A6.PrepareConf.Req.startReq(builder);
         io.github.api7.A6.PrepareConf.Req.addConf(builder, confVector);
@@ -178,9 +177,11 @@ class A6HttpCallHandlerTest {
         HttpRequest request = new HttpRequest(req);
         HttpResponse response = new HttpResponse(1L);
         a6HttpCallHandler.handle(request, response);
-        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, order: 0"));
-        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, config: Bar"));
-        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: CatFilter, order: 1"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, order: 1"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, config: {\"conf_key1\":\"conf_value1\",\"conf_key2\":2}"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, conf_key1 value: conf_value1"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, conf_key2 value: 2.0"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: CatFilter, order: 2"));
         Assertions.assertTrue(capturedOutput.getOut().contains("do filter: CatFilter, config: Dog"));
     }
 
@@ -215,8 +216,8 @@ class A6HttpCallHandlerTest {
         HttpRequest request = new HttpRequest(req);
         HttpResponse response = new HttpResponse(1L);
         a6HttpCallHandler.handle(request, response);
-        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, order: 0"));
-        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, config: Bar"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, order: 1"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: FooFilter, config: {\"conf_key1\":\"conf_value1\",\"conf_key2\":2}"));
 
         Assertions.assertTrue(capturedOutput.getOut().contains("do filter: path: /path"));
         Assertions.assertTrue(capturedOutput.getOut().contains("do filter: arg key: argKey"));
@@ -225,7 +226,7 @@ class A6HttpCallHandlerTest {
         Assertions.assertTrue(capturedOutput.getOut().contains("do filter: header value: headerValue"));
         Assertions.assertTrue(capturedOutput.getOut().contains("do filter: method: GET"));
 
-        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: CatFilter, order: 1"));
+        Assertions.assertTrue(capturedOutput.getOut().contains("do filter: CatFilter, order: 2"));
         Assertions.assertTrue(capturedOutput.getOut().contains("do filter: CatFilter, config: Dog"));
     }
 
diff --git a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpRequest.java b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpRequest.java
index 1255507..c0d60b8 100644
--- a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpRequest.java
+++ b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpRequest.java
@@ -20,9 +20,9 @@ package org.apache.apisix.plugin.runner;
 import io.github.api7.A6.HTTPReqCall.Req;
 import io.github.api7.A6.TextEntry;
 import org.apache.apisix.plugin.runner.filter.PluginFilter;
+import org.springframework.util.CollectionUtils;
 
 import java.nio.ByteBuffer;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -51,6 +51,12 @@ public class HttpRequest implements A6Request {
         this.req = req;
     }
 
+    /**
+     * Gets current filter config.
+     *
+     * @param filter the filter
+     * @return the config
+     */
     public String getConfig(PluginFilter filter) {
         for (int i = 0; i < config.confLength(); i++) {
             TextEntry conf = config.conf(i);
@@ -69,6 +75,11 @@ public class HttpRequest implements A6Request {
         return requestId;
     }
 
+    /**
+     * Gets source ip.
+     *
+     * @return the source ip
+     */
     public String getSourceIP() {
         if (Objects.isNull(sourceIP)) {
             StringBuilder builder = new StringBuilder();
@@ -81,6 +92,11 @@ public class HttpRequest implements A6Request {
         return sourceIP;
     }
 
+    /**
+     * Gets method.
+     *
+     * @return the method
+     */
     public Method getMethod() {
         if (Objects.isNull(method)) {
             method = Method.values()[req.method()];
@@ -88,6 +104,11 @@ public class HttpRequest implements A6Request {
         return method;
     }
 
+    /**
+     * Gets path.
+     *
+     * @return the path
+     */
     public String getPath() {
         if (Objects.isNull(path)) {
             path = req.path();
@@ -95,11 +116,27 @@ public class HttpRequest implements A6Request {
         return path;
     }
 
+    /**
+     * Rewrite path.
+     *
+     * @param path the path
+     */
     public void setPath(String path) {
         response.setPath(path);
     }
 
-    public Map<String, String> getHeaders() {
+    /**
+     * Gets all headers.
+     * <p>Examples:</p>
+     *
+     * <pre>
+     * {@code
+     * request.getHeaders()
+     * }
+     * </pre>
+     * @return the all headers
+     */
+    public Map<String, String> getHeader() {
         if (Objects.isNull(headers)) {
             headers = new HashMap<>();
             for (int i = 0; i < req.headersLength(); i++) {
@@ -110,10 +147,61 @@ public class HttpRequest implements A6Request {
         return headers;
     }
 
+    /**
+     * Gets the specified header
+     *
+     * <p>Examples:</p>
+     *
+     * <pre>
+     * {@code
+     * request.getHeader("Content-Type");
+     * }
+     * </pre>
+     *
+     * @param headerName the header name
+     * @return the header value or null
+     */
+    public String getHeader(String headerName) {
+        Map<String, String> headers = getHeader();
+        if (!CollectionUtils.isEmpty(headers)) {
+            for (Map.Entry<String, String> header : headers.entrySet()) {
+                if (header.getKey().equals(headerName)) {
+                    return header.getValue();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add, rewrite or delete the specified header
+     * <p>Examples:</p>
+     *
+     * <pre>
+     * {@code
+     *
+     * add new header
+     * request.setHeader("New-Header", "new header value");
+     *
+     * overwrite existing header
+     * request.setHeader("Accept", "application/json");
+     *
+     * delete existing header
+     * request.setHeader("Accept", null);
+     * }
+     * </pre>
+     * @param headerKey   the header key
+     * @param headerValue the header value
+     */
     public void setHeader(String headerKey, String headerValue) {
         response.setReqHeader(headerKey, headerValue);
     }
 
+    /**
+     * Gets all args.
+     *
+     * @return the args
+     */
     public Map<String, String> getArgs() {
         if (Objects.isNull(args)) {
             args = new HashMap<>();
@@ -125,20 +213,56 @@ public class HttpRequest implements A6Request {
         return args;
     }
 
-    public void setArg(String argKey, String argValue) {
-        response.setArgs(argKey, argValue);
-    }
 
-    public Map getParameterMap() {
-        return null;
-    }
-
-    public Enumeration getParameters() {
+    /**
+     * Gets the specified  arg.
+     *
+     * <p>Examples:</p>
+     *
+     * <pre>
+     * {@code
+     * request.getArg("foo");
+     * }
+     * </pre>
+     *
+     * @param argName the arg name
+     * @return the arg
+     */
+    public String getArg(String argName) {
+        Map<String, String> args = getArgs();
+        if (!CollectionUtils.isEmpty(args)) {
+            for (Map.Entry<String, String> arg : args.entrySet()) {
+                if (arg.getKey().equals(argName)) {
+                    return arg.getValue();
+                }
+            }
+        }
         return null;
     }
 
-    public String[] getParameterValues(String name) {
-        return null; // todo
+    /**
+     * Add, rewrite or delete the specified header
+     * <p>Examples:</p>
+     *
+     *
+     * <pre>
+     * {@code
+     *
+     * add new arg
+     * request.setArg("foo", "bar");
+     *
+     * overwrite existing arg
+     * request.setArg("foo", "bar");
+     *
+     * delete existing header
+     * request.setArg("foo", null);
+     * }
+     * </pre>
+     * @param argKey the arg key
+     * @param argValue the arg value
+     */
+    public void setArg(String argKey, String argValue) {
+        response.setArg(argKey, argValue);
     }
 
     public long getConfToken() {
diff --git a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java
index 2c861be..52eaa64 100644
--- a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java
+++ b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java
@@ -73,7 +73,13 @@ public class HttpResponse implements A6Response {
         reqHeaders.put(headerKey, headerValue);
     }
 
-    public void setArgs(String argKey, String argValue) {
+    /**
+     * Sets arg.
+     *
+     * @param argKey   the arg key
+     * @param argValue the arg value
+     */
+    public void setArg(String argKey, String argValue) {
         actionType = ActionType.Rewrite;
         if (Objects.isNull(args)) {
             args = new HashMap<>();
@@ -81,11 +87,22 @@ public class HttpResponse implements A6Response {
         args.put(argKey, argValue);
     }
 
+    /**
+     * Sets path.
+     *
+     * @param path the path
+     */
     public void setPath(String path) {
         actionType = ActionType.Rewrite;
         this.path = path;
     }
 
+    /**
+     * Sets header.
+     *
+     * @param headerKey   the header key
+     * @param headerValue the header value
+     */
     public void setHeader(String headerKey, String headerValue) {
         actionType = ActionType.Stop;
         if (Objects.isNull(respHeaders)) {
@@ -94,11 +111,21 @@ public class HttpResponse implements A6Response {
         respHeaders.put(headerKey, headerValue);
     }
 
+    /**
+     * Sets body.
+     *
+     * @param body the body(string)
+     */
     public void setBody(String body) {
         actionType = ActionType.Stop;
         this.body = body;
     }
 
+    /**
+     * Sets status code.
+     *
+     * @param statusCode the status code
+     */
     public void setStatusCode(int statusCode) {
         actionType = ActionType.Stop;
         this.statusCode = statusCode;
@@ -186,8 +213,16 @@ public class HttpResponse implements A6Response {
             for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
                 int i = -1;
                 int key = builder.createString(header.getKey());
-                int value = builder.createString(header.getValue());
-                int text = TextEntry.createTextEntry(builder, key, value);
+                int value = 0;
+                if (!Objects.isNull(header.getValue())) {
+                    value = builder.createString(header.getValue());
+                }
+                TextEntry.startTextEntry(builder);
+                TextEntry.addName(builder, key);
+                if (!Objects.isNull(header.getValue())) {
+                    TextEntry.addValue(builder, value);
+                }
+                int text = TextEntry.endTextEntry(builder);
                 headerTexts[++i] = text;
             }
             headerIndex = Rewrite.createHeadersVector(builder, headerTexts);
@@ -199,8 +234,16 @@ public class HttpResponse implements A6Response {
             for (Map.Entry<String, String> arg : args.entrySet()) {
                 int i = -1;
                 int key = builder.createString(arg.getKey());
-                int value = builder.createString(arg.getValue());
-                int text = TextEntry.createTextEntry(builder, key, value);
+                int value = 0;
+                if (!Objects.isNull(arg.getValue())) {
+                    value = builder.createString(arg.getValue());
+                }
+                TextEntry.startTextEntry(builder);
+                TextEntry.addName(builder, key);
+                if (!Objects.isNull(arg.getValue())) {
+                    TextEntry.addValue(builder, value);
+                }
+                int text = TextEntry.endTextEntry(builder);
                 argTexts[++i] = text;
             }
             argsIndex = Rewrite.createArgsVector(builder, argTexts);
diff --git a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilter.java b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilter.java
index a7109a6..22f1622 100644
--- a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilter.java
+++ b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilter.java
@@ -19,10 +19,9 @@ package org.apache.apisix.plugin.runner.filter;
 
 import org.apache.apisix.plugin.runner.HttpRequest;
 import org.apache.apisix.plugin.runner.HttpResponse;
-import org.springframework.core.Ordered;
 import reactor.core.publisher.Mono;
 
-public interface PluginFilter extends Ordered {
+public interface PluginFilter {
 
     String name();
 
diff --git a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilterChain.java b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilterChain.java
index e1a8960..0b15967 100644
--- a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilterChain.java
+++ b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/filter/PluginFilterChain.java
@@ -19,7 +19,6 @@ package org.apache.apisix.plugin.runner.filter;
 
 import org.apache.apisix.plugin.runner.HttpRequest;
 import org.apache.apisix.plugin.runner.HttpResponse;
-import org.springframework.core.OrderComparator;
 import reactor.core.publisher.Mono;
 
 import java.util.List;
@@ -29,9 +28,11 @@ public class PluginFilterChain {
 
     private final List<PluginFilter> filters;
 
+    public int getIndex() {
+        return index;
+    }
+
     public PluginFilterChain(List<PluginFilter> filters) {
-        // sort filters in order
-        OrderComparator.sort(filters);
         this.filters = filters;
         this.index = 0;
     }
diff --git a/runner-starter/src/main/resources/application.yaml b/runner-starter/src/main/resources/application.yaml
index 5f4d487..4e681b1 100644
--- a/runner-starter/src/main/resources/application.yaml
+++ b/runner-starter/src/main/resources/application.yaml
@@ -12,7 +12,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+logging:
+  level:
+    root: debug
 cache.config:
   expired: ${APISIX_CONF_EXPIRE_TIME}
   capacity: 1000
diff --git a/sample/src/main/java/org/apache/apisix/plugin/runner/filter/RewriteRequestDemoFilter.java b/sample/src/main/java/org/apache/apisix/plugin/runner/filter/RewriteRequestDemoFilter.java
index 7ad93da..406e605 100644
--- a/sample/src/main/java/org/apache/apisix/plugin/runner/filter/RewriteRequestDemoFilter.java
+++ b/sample/src/main/java/org/apache/apisix/plugin/runner/filter/RewriteRequestDemoFilter.java
@@ -17,11 +17,15 @@
 
 package org.apache.apisix.plugin.runner.filter;
 
+import com.google.gson.Gson;
 import org.apache.apisix.plugin.runner.HttpRequest;
 import org.apache.apisix.plugin.runner.HttpResponse;
 import org.springframework.stereotype.Component;
 import reactor.core.publisher.Mono;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @Component
 public class RewriteRequestDemoFilter implements PluginFilter {
 
@@ -56,25 +60,32 @@ public class RewriteRequestDemoFilter implements PluginFilter {
 
     @Override
     public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
+        /*
+         * If the conf you configured is of type json, you can convert it to Map or json.
+         */
+
+        String configStr = request.getConfig(this);
+        Gson gson = new Gson();
+        Map<String, Object> conf = new HashMap<>();
+        conf = gson.fromJson(configStr, conf.getClass());
+
+        /*
+         * You can use the parameters in the configuration.
+         */
+
         // note: the path to the rewrite must start with '/'
-        request.setPath("/get");
-        request.setHeader("new-header", "header_by_runner");
+        request.setPath((String) conf.get("rewrite_path"));
+        request.setHeader((String) conf.get("conf_header_name"), (String) conf.get("conf_header_value"));
         /* note: The value of the parameter is currently a string type.
                  If you need the json type, you need the upstream service to parse the string value to json.
-                 For example, if the arg is set as follows
+                 For example, if the arg is set as below
                  request.setArg("new arg", "{\"key1\":\"value1\",\"key2\":2}");
 
-                 The arg received by the upstream service will be as follows
+                 The arg received by the upstream service will be as below
                  "new arg": "{\"key1\":\"value1\",\"key2\":2}"
          */
-        request.setArg("new arg", "{\"key1\":\"value1\",\"key2\":2}");
+        request.setArg((String) conf.get("conf_arg_name"), (String) conf.get("conf_arg_value"));
 
         return chain.filter(request, response);
     }
-
-    @Override
-    public int getOrder() {
-        //The order of filter execution in runner is determined by the order here, the smaller the order number, the higher the execution order.
-        return 0;
-    }
 }
diff --git a/sample/src/main/java/org/apache/apisix/plugin/runner/filter/StopRequestDemoFilter.java b/sample/src/main/java/org/apache/apisix/plugin/runner/filter/StopRequestDemoFilter.java
index d190d02..97ae800 100644
--- a/sample/src/main/java/org/apache/apisix/plugin/runner/filter/StopRequestDemoFilter.java
+++ b/sample/src/main/java/org/apache/apisix/plugin/runner/filter/StopRequestDemoFilter.java
@@ -17,11 +17,15 @@
 
 package org.apache.apisix.plugin.runner.filter;
 
+import com.google.gson.Gson;
 import org.apache.apisix.plugin.runner.HttpRequest;
 import org.apache.apisix.plugin.runner.HttpResponse;
 import org.springframework.stereotype.Component;
 import reactor.core.publisher.Mono;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @Component
 public class StopRequestDemoFilter implements PluginFilter {
     @Override
@@ -31,17 +35,29 @@ public class StopRequestDemoFilter implements PluginFilter {
 
     @Override
     public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
-        response.setStatusCode(401);
-        response.setHeader("new-header", "header_by_runner");
+        /*
+         * If the conf you configured is of type json, you can convert it to Map or json.
+         */
+
+        String configStr = request.getConfig(this);
+        Gson gson = new Gson();
+        Map<String, Object> conf = new HashMap<>();
+        conf = gson.fromJson(configStr, conf.getClass());
+
+        /*
+         * You can use the parameters in the configuration.
+         */
+        response.setStatusCode((Integer) conf.get("stop_response_code"));
+        response.setHeader((String) conf.get("stop_response_header_name"), (String) conf.get("stop_response_header_value"));
         /* note: The body is currently a string type.
                  If you need the json type, you need to escape the json content here.
-                 For example, if the body is set as follows
+                 For example, if the body is set as below
                  "{\"key1\":\"value1\",\"key2\":2}"
 
-                 The body received by the client will be as follows
+                 The body received by the client will be as below
                  {"key1":"value1","key2":2}
          */
-        response.setBody("{\"key1\":\"value1\",\"key2\":2}");
+        response.setBody((String) conf.get("stop_response_body"));
 
         /*  Using the above code, the client side receives the following
 
@@ -57,9 +73,4 @@ public class StopRequestDemoFilter implements PluginFilter {
          */
         return chain.filter(request, response);
     }
-
-    @Override
-    public int getOrder() {
-        return 1;
-    }
 }