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

shuaijinchao pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/apisix-python-plugin-runner.git


The following commit(s) were added to refs/heads/master by this push:
     new 232450b  refactor: request processing flow (#36)
232450b is described below

commit 232450b9a11baaad6453700b3b8d78def330475d
Author: 帅进超 <shuaijinc...@gmail.com>
AuthorDate: Mon Dec 6 09:58:48 2021 +0800

    refactor: request processing flow (#36)
---
 .github/workflows/runner-lint.yml                  |   2 +-
 .github/workflows/runner-test.yml                  |   8 +-
 apisix/plugins/rewrite.py                          |  14 +-
 apisix/plugins/stop.py                             |   7 +-
 apisix/runner/http/method.py                       |  77 --------
 apisix/runner/http/request.py                      |  88 ++++++---
 apisix/runner/http/response.py                     | 131 +++-----------
 apisix/runner/plugin/base.py                       |  35 ----
 apisix/runner/plugin/cache.py                      |   2 +
 apisix/runner/plugin/core.py                       |  48 ++---
 apisix/runner/server/config.py                     |   4 +-
 apisix/runner/server/handle.py                     | 198 +++++++++------------
 apisix/runner/server/response.py                   |   2 +-
 apisix/runner/server/server.py                     |  51 ++++--
 .../runner/{http/protocol.py => utils/__init__.py} |   9 -
 apisix/runner/utils/common.py                      | 198 +++++++++++++++++++++
 apisix/main.py => bin/py-runner                    |   4 +-
 {apisix => conf}/config.yaml                       |   0
 docs/en/latest/developer-guide.md                  |  29 +--
 docs/en/latest/getting-started.md                  |   7 +-
 requirements.txt                                   |   2 +-
 setup.py                                           |   2 +-
 tests/runner/http/test_method.py                   |  55 ------
 tests/runner/http/test_protocol.py                 |  26 ---
 tests/runner/http/test_request.py                  | 122 +++++--------
 tests/runner/http/test_response.py                 | 115 ++----------
 tests/runner/plugin/test_base.py                   |   4 +
 tests/runner/plugin/test_cache.py                  |  21 +++
 tests/runner/plugin/test_core.py                   |  72 ++++----
 tests/runner/server/test_config.py                 |   2 +-
 tests/runner/server/test_handle.py                 | 194 ++++++++++++--------
 tests/runner/server/test_protocol.py               |  12 +-
 tests/runner/server/test_response.py               |  22 ++-
 tests/runner/server/test_server.py                 |  22 ++-
 tests/runner/utils/test_common.py                  |  84 +++++++++
 35 files changed, 817 insertions(+), 852 deletions(-)

diff --git a/.github/workflows/runner-lint.yml 
b/.github/workflows/runner-lint.yml
index 6222861..1372061 100644
--- a/.github/workflows/runner-lint.yml
+++ b/.github/workflows/runner-lint.yml
@@ -37,6 +37,6 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.6
+          python-version: 3.7
       - name: Lint codes
         run: make lint
diff --git a/.github/workflows/runner-test.yml 
b/.github/workflows/runner-test.yml
index af36bef..9cd9859 100644
--- a/.github/workflows/runner-test.yml
+++ b/.github/workflows/runner-test.yml
@@ -31,21 +31,25 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [ 3.6, 3.7, 3.8, 3.9 ]
+        python-version: [ '3.7', '3.8', '3.9', '3.10' ]
       fail-fast: false
     steps:
       - name: Checkout source codes
         uses: actions/checkout@v2
         with:
           submodules: true
+
       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python-version }}
+
       - name: Set up dependencies
         run: make setup install
+
       - name: Run unit tests
         run: make test
+
       - name: Upload coverage profile
-        if: ${{ matrix.python-version == '3.6' }}
+        if: ${{ matrix.python-version == '3.7' }}
         run: bash <(curl -s https://codecov.io/bash)
diff --git a/apisix/plugins/rewrite.py b/apisix/plugins/rewrite.py
index af034ea..12543dc 100644
--- a/apisix/plugins/rewrite.py
+++ b/apisix/plugins/rewrite.py
@@ -42,18 +42,10 @@ class Rewrite(Base):
         # print(self.config)
 
         # Rewrite request headers
-        headers = request.headers
-        headers["X-Resp-A6-Runner"] = "Python"
-        response.headers = headers
+        request.headers["X-Resp-A6-Runner"] = "Python"
 
         # Rewrite request args
-        args = request.args
-        args["a6_runner"] = "Python"
-        response.args = args
+        request.args["a6_runner"] = "Python"
 
         # Rewrite request path
-        path = request.path
-        response.path = path
-
-        # Set plugin to `rewrite` type, default `rewrite`
-        self.rewrite()
+        request.path = "/a6/python/runner"
diff --git a/apisix/plugins/stop.py b/apisix/plugins/stop.py
index 6434afc..f0bdd8f 100644
--- a/apisix/plugins/stop.py
+++ b/apisix/plugins/stop.py
@@ -42,15 +42,10 @@ class Stop(Base):
         # print(self.config)
 
         # Set response headers
-        headers = request.headers
-        headers["X-Resp-A6-Runner"] = "Python"
-        response.headers = headers
+        response.headers["X-Resp-A6-Runner"] = "Python"
 
         # Set response body
         response.body = "Hello, Python Runner of APISIX"
 
         # Set response status code
         response.status_code = 201
-
-        # Set plugin to `stop` type, default `rewrite`
-        self.stop()
diff --git a/apisix/runner/http/method.py b/apisix/runner/http/method.py
deleted file mode 100644
index df90f8d..0000000
--- a/apisix/runner/http/method.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (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
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# 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.
-#
-from a6pluginproto import Method as A6Method
-
-A6MethodGET = "GET"
-A6MethodHEAD = "HEAD"
-A6MethodPOST = "POST"
-A6MethodPUT = "PUT"
-A6MethodDELETE = "DELETE"
-A6MethodMKCOL = "MKCOL"
-A6MethodCOPY = "COPY"
-A6MethodMOVE = "MOVE"
-A6MethodOPTIONS = "OPTIONS"
-A6MethodPROPFIND = "PROPFIND"
-A6MethodPROPPATCH = "PROPPATCH"
-A6MethodLOCK = "LOCK"
-A6MethodUNLOCK = "UNLOCK"
-A6MethodPATCH = "PATCH"
-A6MethodTRACE = "TRACE"
-
-methodName = {
-    A6Method.Method.GET: A6MethodGET,
-    A6Method.Method.HEAD: A6MethodHEAD,
-    A6Method.Method.POST: A6MethodPOST,
-    A6Method.Method.PUT: A6MethodPUT,
-    A6Method.Method.DELETE: A6MethodDELETE,
-    A6Method.Method.MKCOL: A6MethodMKCOL,
-    A6Method.Method.COPY: A6MethodCOPY,
-    A6Method.Method.MOVE: A6MethodMOVE,
-    A6Method.Method.OPTIONS: A6MethodOPTIONS,
-    A6Method.Method.PROPFIND: A6MethodPROPFIND,
-    A6Method.Method.PROPPATCH: A6MethodPROPPATCH,
-    A6Method.Method.LOCK: A6MethodLOCK,
-    A6Method.Method.UNLOCK: A6MethodUNLOCK,
-    A6Method.Method.PATCH: A6MethodPATCH,
-    A6Method.Method.TRACE: A6MethodTRACE,
-}
-
-methodCode = {
-    A6MethodGET: A6Method.Method.GET,
-    A6MethodHEAD: A6Method.Method.HEAD,
-    A6MethodPOST: A6Method.Method.POST,
-    A6MethodPUT: A6Method.Method.PUT,
-    A6MethodDELETE: A6Method.Method.DELETE,
-    A6MethodMKCOL: A6Method.Method.MKCOL,
-    A6MethodCOPY: A6Method.Method.COPY,
-    A6MethodMOVE: A6Method.Method.MOVE,
-    A6MethodOPTIONS: A6Method.Method.OPTIONS,
-    A6MethodPROPFIND: A6Method.Method.PROPFIND,
-    A6MethodPROPPATCH: A6Method.Method.PROPPATCH,
-    A6MethodLOCK: A6Method.Method.LOCK,
-    A6MethodUNLOCK: A6Method.Method.UNLOCK,
-    A6MethodPATCH: A6Method.Method.PATCH,
-    A6MethodTRACE: A6Method.Method.TRACE,
-}
-
-
-def get_name_by_code(code: int) -> str:
-    return methodName.get(code)
-
-
-def get_code_by_name(name: str) -> int:
-    return methodCode.get(name)
diff --git a/apisix/runner/http/request.py b/apisix/runner/http/request.py
index 844b647..98bfb00 100644
--- a/apisix/runner/http/request.py
+++ b/apisix/runner/http/request.py
@@ -14,30 +14,32 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
 import json
+import flatbuffers
+import apisix.runner.plugin.core as runner_plugin
+import apisix.runner.utils.common as runner_utils
+
 from ipaddress import IPv4Address
 from ipaddress import IPv6Address
-import apisix.runner.plugin.core as RunnerPlugin
-import apisix.runner.http.method as RunnerMethod
-from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from a6pluginproto.HTTPReqCall import Req as A6HTTPReqCallReq
-from a6pluginproto.PrepareConf import Req as A6PrepareConfReq
+from A6.HTTPReqCall import Rewrite as HCRw
+from A6.HTTPReqCall import Action as HCAction
+from A6.HTTPReqCall import Req as HCReq
+from A6.PrepareConf import Req as PCReq
 
 
 class Request:
 
-    def __init__(self, ty: int = 0, buf: bytes = b''):
+    def __init__(self, r):
         """
         Init and parse request
-        :param ty:
-            rpc request protocol type
-        :param buf:
-            rpc request buffer data
+        :param r:
+            rpc request object
         """
-        self._rpc_type = ty
-        self._rpc_buf = buf
+        self._rpc_type = r.request.ty
+        self._rpc_buf = r.request.data
         self._req_id = 0
+        self.code = 0
         self._req_conf_token = 0
         self._req_method = ""
         self._req_path = ""
@@ -228,7 +230,7 @@ class Request:
         self._req_args = {}
         self._req_src_ip = ""
 
-    def _parse_src_ip(self, req: A6HTTPReqCallReq) -> None:
+    def _parse_src_ip(self, req: HCReq) -> None:
         """
         parse request source ip address
         :param req:
@@ -249,7 +251,7 @@ class Request:
         if ip_len == 16:
             self.src_ip = IPv6Address(ip_byte).exploded
 
-    def _parse_headers(self, req: A6HTTPReqCallReq) -> None:
+    def _parse_headers(self, req: HCReq) -> None:
         """
         parse request headers
         :param req:
@@ -263,7 +265,7 @@ class Request:
                 headers[key] = val
             self.headers = headers
 
-    def _parse_args(self, req: A6HTTPReqCallReq) -> None:
+    def _parse_args(self, req: HCReq) -> None:
         """
         parse request args
         :param req:
@@ -277,14 +279,14 @@ class Request:
                 args[key] = val
             self.args = args
 
-    def _parse_configs(self, req: A6PrepareConfReq) -> None:
+    def _parse_configs(self, req: PCReq) -> None:
         """
         parse request plugin configs
         :param req:
         :return:
         """
         if not req.ConfIsNone():
-            plugins = RunnerPlugin.loading()
+            plugins = runner_plugin.loading()
             configs = {}
             for i in range(req.ConfLength()):
                 name = str(req.Conf(i).Name(), encoding="UTF-8").lower()
@@ -302,16 +304,56 @@ class Request:
         init request handler
         :return:
         """
-        if self.rpc_type == RPC_HTTP_REQ_CALL:
-            req = A6HTTPReqCallReq.Req.GetRootAsReq(self.rpc_buf)
+        if self.rpc_type == runner_utils.RPC_HTTP_REQ_CALL:
+            req = HCReq.Req.GetRootAsReq(self.rpc_buf)
             self.id = req.Id()
-            self.method = RunnerMethod.get_name_by_code(req.Method())
+            self.method = runner_utils.get_method_name_by_code(req.Method())
             self.path = str(req.Path(), encoding="UTF-8")
             self.conf_token = req.ConfToken()
             self._parse_src_ip(req)
             self._parse_headers(req)
             self._parse_args(req)
 
-        if self.rpc_type == RPC_PREPARE_CONF:
-            req = A6PrepareConfReq.Req.GetRootAsReq(self.rpc_buf)
+        if self.rpc_type == runner_utils.RPC_PREPARE_CONF:
+            req = PCReq.Req.GetRootAsReq(self.rpc_buf)
             self._parse_configs(req)
+
+    def checked(self):
+        """
+        check request params is valid
+        :return:
+        """
+        if len(self._req_path) == 0 and len(self._req_headers) == 0 and 
len(self._req_args) == 0:
+            return False
+        else:
+            return True
+
+    @runner_utils.response_config
+    def config_handler(self, builder: flatbuffers.Builder):
+        return self.conf_token
+
+    @runner_utils.response_call(HCAction.Action.Rewrite)
+    def call_handler(self, builder: flatbuffers.Builder):
+        if not self.checked():
+            return None, 0
+
+        if len(self._req_path) <= 0:
+            self._req_path = "/"
+        path_vector = runner_utils.create_str_vector(builder, self._req_path)
+
+        headers_vector = runner_utils.create_dict_vector(builder, 
self._req_headers, HCAction.Action.Rewrite,
+                                                         
runner_utils.VECTOR_TYPE_HEADER)
+
+        args_vector = runner_utils.create_dict_vector(builder, self._req_args, 
HCAction.Action.Rewrite,
+                                                      
runner_utils.VECTOR_TYPE_QUERY)
+
+        HCRw.RewriteStart(builder)
+        HCRw.RewriteAddPath(builder, path_vector)
+        HCRw.RewriteAddHeaders(builder, headers_vector)
+        HCRw.RewriteAddArgs(builder, args_vector)
+        rewrite = HCRw.RewriteEnd(builder)
+        return rewrite, self._req_id
+
+    @runner_utils.response_unknown
+    def unknown_handler(self, builder: flatbuffers.Builder):
+        return self.code
diff --git a/apisix/runner/http/response.py b/apisix/runner/http/response.py
index f39bcfb..4f06034 100644
--- a/apisix/runner/http/response.py
+++ b/apisix/runner/http/response.py
@@ -16,21 +16,14 @@
 #
 
 import flatbuffers
-from a6pluginproto import TextEntry as A6TextEntry
-from a6pluginproto.Err import Resp as A6ErrResp
-from a6pluginproto.HTTPReqCall import Stop as A6HTTPReqCallStop
-from a6pluginproto.HTTPReqCall import Rewrite as A6HTTPReqCallRewrite
-from a6pluginproto.HTTPReqCall import Resp as A6HTTPReqCallResp
-from a6pluginproto.HTTPReqCall import Action as A6HTTPReqCallAction
-from a6pluginproto.PrepareConf import Resp as A6PrepareConfResp
-from apisix.runner.http.protocol import new_builder
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
+import apisix.runner.utils.common as runner_utils
+from A6.HTTPReqCall import Stop as HCStop
+from A6.HTTPReqCall import Action as HCAction
 
 RESP_MAX_DATA_SIZE = 2 << 24 - 1
 
-PLUGIN_ACTION_STOP = A6HTTPReqCallAction.Action.Stop
-PLUGIN_ACTION_REWRITE = A6HTTPReqCallAction.Action.Rewrite
+PLUGIN_ACTION_STOP = HCAction.Action.Stop
+PLUGIN_ACTION_REWRITE = HCAction.Action.Rewrite
 
 
 class Response:
@@ -239,98 +232,22 @@ class Response:
         else:
             return False
 
-    def _gen_config_flat(self, builder: flatbuffers.Builder) -> int:
-        A6PrepareConfResp.Start(builder)
-        A6PrepareConfResp.AddConfToken(builder, self.token)
-        return A6PrepareConfResp.End(builder)
-
-    def _gen_unknown_flat(self, builder: flatbuffers.Builder) -> int:
-        A6ErrResp.Start(builder)
-        A6ErrResp.AddCode(builder, self.error_code)
-        return A6ErrResp.End(builder)
-
-    def _gen_request_flat(self, builder: flatbuffers.Builder) -> int:
-        def _to_a6_entry(data: dict) -> list:
-            entries = []
-            if not isinstance(data, dict) and len(data) <= 0:
-                return entries
-            for key in data:
-                val = data[key]
-                key_b = builder.CreateString(key)
-                val_b = builder.CreateString(val)
-                A6TextEntry.Start(builder)
-                A6TextEntry.AddName(builder, key_b)
-                A6TextEntry.AddValue(builder, val_b)
-                entry = A6TextEntry.End(builder)
-                entries.append(entry)
-            return entries
-
-        if self.action_type == A6HTTPReqCallAction.Action.Stop:
-            headers_entry = _to_a6_entry(self.headers)
-            headers_len = len(headers_entry)
-            A6HTTPReqCallStop.StopStartHeadersVector(builder, headers_len)
-            for i in range(headers_len - 1, -1, -1):
-                builder.PrependUOffsetTRelative(headers_entry[i])
-            headers_vector = builder.EndVector()
-
-            body = b''
-            if self.body and len(self.body) > 0:
-                body = self.body.encode(encoding="UTF-8")
-            body_vector = builder.CreateByteVector(body)
-
-            status_code = 200
-            if self.status_code > 0:
-                status_code = self.status_code
-
-            A6HTTPReqCallStop.StopStart(builder)
-            A6HTTPReqCallStop.StopAddStatus(builder, status_code)
-            A6HTTPReqCallStop.StopAddBody(builder, body_vector)
-            A6HTTPReqCallStop.StopAddHeaders(builder, headers_vector)
-            action = A6HTTPReqCallStop.StopEnd(builder)
-        else:
-            args_entry = _to_a6_entry(self.args)
-            args_len = len(args_entry)
-            A6HTTPReqCallRewrite.RewriteStartArgsVector(builder, args_len)
-            for i in range(args_len - 1, -1, -1):
-                builder.PrependUOffsetTRelative(args_entry[i])
-            args_vector = builder.EndVector()
-
-            headers_entry = _to_a6_entry(self.headers)
-            headers_len = len(headers_entry)
-            A6HTTPReqCallRewrite.RewriteStartHeadersVector(builder, 
headers_len)
-            for i in range(headers_len - 1, -1, -1):
-                builder.PrependUOffsetTRelative(headers_entry[i])
-            headers_vector = builder.EndVector()
-
-            path = b'/'
-            if self.path and len(self.path) > 0:
-                path = self.path.encode(encoding="UTF-8")
-            path_vector = builder.CreateByteVector(path)
-
-            A6HTTPReqCallRewrite.RewriteStart(builder)
-            A6HTTPReqCallRewrite.RewriteAddPath(builder, path_vector)
-            A6HTTPReqCallRewrite.RewriteAddArgs(builder, args_vector)
-            A6HTTPReqCallRewrite.RewriteAddHeaders(builder, headers_vector)
-            action = A6HTTPReqCallRewrite.RewriteEnd(builder)
-
-        A6HTTPReqCallResp.Start(builder)
-        A6HTTPReqCallResp.AddId(builder, self.id)
-        A6HTTPReqCallResp.AddActionType(builder, self.action_type)
-        A6HTTPReqCallResp.AddAction(builder, action)
-        return A6HTTPReqCallResp.End(builder)
-
-    def flatbuffers(self) -> flatbuffers.Builder:
-        """
-        response to flat buffer object
-        :return:
-        """
-        builder = new_builder()
-
-        rpc_handlers = {
-            RPC_PREPARE_CONF: self._gen_config_flat,
-            RPC_HTTP_REQ_CALL: self._gen_request_flat
-        }
-
-        res = rpc_handlers.get(self.rpc_type, self._gen_unknown_flat)(builder)
-        builder.Finish(res)
-        return builder
+    @runner_utils.response_call(HCAction.Action.Stop)
+    def call_handler(self, builder: flatbuffers.Builder):
+        if not self.changed():
+            return None, 0
+        headers_vector = runner_utils.create_dict_vector(builder, 
self.headers, HCAction.Action.Stop,
+                                                         
runner_utils.VECTOR_TYPE_HEADER)
+
+        body_vector = runner_utils.create_str_vector(builder, self.body)
+
+        status_code = 200
+        if self.status_code > 0:
+            status_code = self.status_code
+
+        HCStop.StopStart(builder)
+        HCStop.StopAddStatus(builder, status_code)
+        HCStop.StopAddBody(builder, body_vector)
+        HCStop.StopAddHeaders(builder, headers_vector)
+        stop = HCStop.StopEnd(builder)
+        return stop, self.id
diff --git a/apisix/runner/plugin/base.py b/apisix/runner/plugin/base.py
index 00d7ccf..6a2202d 100644
--- a/apisix/runner/plugin/base.py
+++ b/apisix/runner/plugin/base.py
@@ -15,9 +15,6 @@
 # limitations under the License.
 #
 
-from apisix.runner.http.response import PLUGIN_ACTION_REWRITE
-from apisix.runner.http.response import PLUGIN_ACTION_STOP
-
 
 class Base:
     def __init__(self, name: str):
@@ -28,7 +25,6 @@ class Base:
         """
         self._name = name
         self._config = {}
-        self._action = PLUGIN_ACTION_REWRITE
 
     @property
     def name(self) -> str:
@@ -66,34 +62,3 @@ class Base:
             self._config = config
         else:
             self._config = {}
-
-    @property
-    def action(self) -> int:
-        """
-        get plugin type
-        :return:
-        """
-        return self._action
-
-    @action.setter
-    def action(self, action: int) -> None:
-        """
-        set plugin type
-        :param action:
-        :return:
-        """
-        self._action = action
-
-    def stop(self) -> None:
-        """
-        Set plugin to `Stop` type
-        :return:
-        """
-        self.action = PLUGIN_ACTION_STOP
-
-    def rewrite(self) -> None:
-        """
-        Set plugin to `Rewrite` type
-        :return:
-        """
-        self.action = PLUGIN_ACTION_REWRITE
diff --git a/apisix/runner/plugin/cache.py b/apisix/runner/plugin/cache.py
index 30d5c60..ce22b13 100644
--- a/apisix/runner/plugin/cache.py
+++ b/apisix/runner/plugin/cache.py
@@ -28,6 +28,8 @@ def generate_token() -> int:
 
 
 def set_config_by_token(token: int, configs: dict) -> bool:
+    if len(configs) <= 0:
+        return False
     cache_key = "%s:%s" % (RUNNER_CACHE_ENTRY, token)
     cache.update(cache_key, configs)
     return cache.has(cache_key)
diff --git a/apisix/runner/plugin/core.py b/apisix/runner/plugin/core.py
index e5667fb..abade21 100644
--- a/apisix/runner/plugin/core.py
+++ b/apisix/runner/plugin/core.py
@@ -17,52 +17,26 @@
 import os
 import importlib
 from pkgutil import iter_modules
-from typing import Tuple
-from apisix.runner.server.response import RESP_STATUS_CODE_OK
-from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
-from apisix.runner.server.response import RESP_STATUS_CODE_SERVICE_UNAVAILABLE
-from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
+from apisix.runner.http.response import Response as HttpResponse
+from apisix.runner.http.request import Request as HttpRequest
 
 
-def execute(configs: dict, request, response) -> Tuple[int, str]:
+def execute(configs: dict, r, req: HttpRequest, reps: HttpResponse) -> bool:
     for name in configs:
         plugin = configs.get(name)
         if type(plugin).__name__.lower() != name.lower():
-            return RESP_STATUS_CODE_BAD_REQUEST, "execute plugin `%s`, plugin 
handler is not object" % name
+            r.log.error("execute plugin `%s`, plugin handler is not object" % 
name)
+            return False
 
         try:
-            plugin.filter(request, response)
+            plugin.filter(req, reps)
         except AttributeError as e:
-            return RESP_STATUS_CODE_SERVICE_UNAVAILABLE, "execute plugin `%s`, 
%s" % (name, e.args.__str__())
+            r.log.error("execute plugin `%s`, %s" % (name, e.args.__str__()))
+            return False
         except TypeError as e:
-            return RESP_STATUS_CODE_BAD_REQUEST, "execute plugin `%s`, %s" % 
(name, e.args.__str__())
-        else:
-            response.action_type = plugin.action
-            refresh_response(request, response)
-
-    return RESP_STATUS_CODE_OK, RESP_STATUS_MESSAGE_OK
-
-
-def refresh_response(request, response) -> None:
-    # setting default header
-    if len(request.headers) >= 1:
-        for req_hk in request.headers.keys():
-            req_hv = request.headers.get(req_hk)
-            resp_hv = response.headers.get(req_hk)
-            if not resp_hv:
-                response.headers[req_hk] = req_hv
-
-    # setting default path
-    if not response.path or len(response.path) == 0:
-        response.path = request.path
-
-    # setting default args
-    if len(request.args) >= 1:
-        for req_ak in request.args.keys():
-            req_av = request.args.get(req_ak)
-            resp_av = response.args.get(req_ak)
-            if not resp_av:
-                response.args[req_ak] = req_av
+            r.log.error("execute plugin `%s`, %s" % (name, e.args.__str__()))
+            return False
+    return True
 
 
 def loading() -> dict:
diff --git a/apisix/runner/server/config.py b/apisix/runner/server/config.py
index 9e9f4a9..0234168 100644
--- a/apisix/runner/server/config.py
+++ b/apisix/runner/server/config.py
@@ -113,8 +113,8 @@ class Config:
         if len(config_path) and os.path.exists(config_path):
             abs_path = config_path
         else:
-            abs_path = 
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
-        cf_path = "%s/%s" % (abs_path, config_name)
+            abs_path = 
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
+        cf_path = "%s/conf/%s" % (abs_path, config_name)
         if not os.path.exists(cf_path):
             print("ERR: config file `%s` not exists" % cf_path)
             exit(1)
diff --git a/apisix/runner/server/handle.py b/apisix/runner/server/handle.py
index 761c23d..c79f1e3 100644
--- a/apisix/runner/server/handle.py
+++ b/apisix/runner/server/handle.py
@@ -15,128 +15,94 @@
 # limitations under the License.
 #
 
-import apisix.runner.plugin.core as RunnerPlugin
-import apisix.runner.plugin.cache as RunnerCache
+import flatbuffers
+import apisix.runner.plugin.core as runner_plugin
+import apisix.runner.plugin.cache as runner_cache
+import apisix.runner.utils.common as runner_utils
 from apisix.runner.http.response import Response as NewHttpResponse
-from apisix.runner.http.response import RESP_MAX_DATA_SIZE
 from apisix.runner.http.request import Request as NewHttpRequest
-from apisix.runner.server.response import Response as NewServerResponse
-from apisix.runner.server.response import RESP_STATUS_CODE_OK
-from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
-from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
-from apisix.runner.server.response import RESP_STATUS_MESSAGE_BAD_REQUEST
-from apisix.runner.server.response import RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
-from apisix.runner.server.response import RESP_STATUS_CODE_SERVICE_UNAVAILABLE
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
-from apisix.runner.http.protocol import RPC_UNKNOWN
+from A6.Err.Code import Code as ErrCode
 
 
 class Handle:
 
-    def __init__(self, ty: int = 0, buf: bytes = b'', debug: bool = False):
+    def __init__(self, r):
         """
-        Init Python runner server
-        :param ty:
+        Init RPC Handle
+        :param r:
             rpc request protocol type
-        :param buf:
-            rpc request buffer data
-        :param debug:
-            enable debug mode
         """
-        self.type = ty
-        self.buffer = buf
-        self.debug = debug
-
-    @property
-    def type(self) -> int:
-        return self._type
-
-    @type.setter
-    def type(self, ty: int = 0) -> None:
-        self._type = ty
-
-    @property
-    def buffer(self) -> bytes:
-        return self._buffer
-
-    @buffer.setter
-    def buffer(self, buf: bytes = b'') -> None:
-        self._buffer = buf
-
-    @property
-    def debug(self) -> bool:
-        return self._debug
-
-    @debug.setter
-    def debug(self, debug: bool = False) -> None:
-        self._debug = debug
-
-    def _rpc_config(self) -> NewServerResponse:
-        # init request
-        req = NewHttpRequest(RPC_PREPARE_CONF, self.buffer)
-        # generate token
-        token = RunnerCache.generate_token()
-        # get plugins config
-        configs = req.configs
-        # cache plugins config
-        ok = RunnerCache.set_config_by_token(token, configs)
-        if not ok:
-            return NewServerResponse(code=RESP_STATUS_CODE_SERVICE_UNAVAILABLE,
-                                     message="token `%d` cache setting failed" 
% token)
-        # init response
-        resp = NewHttpResponse(RPC_PREPARE_CONF)
-        resp.token = token
-        response = resp.flatbuffers()
-
-        return NewServerResponse(code=RESP_STATUS_CODE_OK, 
message=RESP_STATUS_MESSAGE_OK, data=response.Output(),
-                                 ty=self.type)
-
-    def _rpc_call(self) -> NewServerResponse:
-        # init request
-        req = NewHttpRequest(RPC_HTTP_REQ_CALL, self.buffer)
-        # get request token
-        token = req.conf_token
-        # get plugins
-        configs = RunnerCache.get_config_by_token(token)
-        if len(configs) == 0:
-            return 
NewServerResponse(code=RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND,
-                                     message="token `%d` cache acquisition 
failed" % token)
-        # init response
-        resp = NewHttpResponse(RPC_HTTP_REQ_CALL)
-        # execute plugins
-        (code, message) = RunnerPlugin.execute(configs, req, resp)
-
-        response = resp.flatbuffers()
-        return NewServerResponse(code=code, message=message, 
data=response.Output(),
-                                 ty=self.type)
-
-    @staticmethod
-    def _rpc_unknown(err_code: int = RESP_STATUS_CODE_BAD_REQUEST,
-                     err_message: str = RESP_STATUS_MESSAGE_BAD_REQUEST) -> 
NewServerResponse:
-        resp = NewHttpResponse(RPC_UNKNOWN)
-        resp.error_code = err_code
-        response = resp.flatbuffers()
-        return NewServerResponse(code=err_code, message=err_message, 
data=response.Output(),
-                                 ty=RPC_UNKNOWN)
-
-    def dispatch(self) -> NewServerResponse:
-        resp = None
-
-        if self.type == RPC_PREPARE_CONF:
-            resp = self._rpc_config()
-
-        if self.type == RPC_HTTP_REQ_CALL:
-            resp = self._rpc_call()
-
-        if not resp:
-            return self._rpc_unknown()
-
-        size = len(resp.data)
-        if (size > RESP_MAX_DATA_SIZE or size <= 0) and resp.code == 
RESP_STATUS_CODE_OK:
-            resp = NewServerResponse(RESP_STATUS_CODE_SERVICE_UNAVAILABLE,
-                                     "The maximum length of the data is %d, 
the minimum is 1, but got %d" % (
-                                         RESP_MAX_DATA_SIZE, size))
-        if resp.code != 200:
-            resp = self._rpc_unknown(resp.code, resp.message)
-        return resp
+        self.r = r
+
+    def dispatch(self) -> flatbuffers.Builder:
+        # init builder
+        builder = runner_utils.new_builder()
+        # parse request
+        req = NewHttpRequest(self.r)
+
+        if self.r.request.ty == runner_utils.RPC_PREPARE_CONF:
+            # generate token
+            token = runner_cache.generate_token()
+            # get plugins config
+            configs = req.configs
+            # cache plugins config
+            ok = runner_cache.set_config_by_token(token, configs)
+            if not ok:
+                self.r.log.error("token `%d` cache setting failed" % token)
+                req.code = ErrCode.CONF_TOKEN_NOT_FOUND
+                req.unknown_handler(builder)
+                return builder
+
+            req.conf_token = token
+            ok = req.config_handler(builder)
+            if not ok:
+                self.r.log.error("prepare conf request failure")
+                req.code = ErrCode.BAD_REQUEST
+                req.unknown_handler(builder)
+                return builder
+
+            return builder
+
+        elif self.r.request.ty == runner_utils.RPC_HTTP_REQ_CALL:
+            # get request token
+            token = req.conf_token
+            # get plugins
+            configs = runner_cache.get_config_by_token(token)
+
+            if len(configs) == 0:
+                self.r.log.error("token `%d` cache acquisition failed" % token)
+                req.code = ErrCode.CONF_TOKEN_NOT_FOUND
+                req.unknown_handler(builder)
+                return builder
+
+            # init response
+            resp = NewHttpResponse(self.r.request.ty)
+            resp.id = req.id
+
+            # execute plugins
+            ok = runner_plugin.execute(configs, self.r, req, resp)
+            if not ok:
+                req.code = ErrCode.SERVICE_UNAVAILABLE
+                req.unknown_handler(builder)
+                return builder
+
+            # response changed
+            ok = resp.call_handler(builder)
+            if ok:
+                return builder
+
+            # request changed
+            ok = req.call_handler(builder)
+            if not ok:
+                self.r.log.error("http request call failure")
+                req.code = ErrCode.BAD_REQUEST
+                req.unknown_handler(builder)
+                return builder
+
+            return builder
+
+        else:
+            self.r.log.error("unknown request")
+            req.code = ErrCode.BAD_REQUEST
+            req.unknown_handler(builder)
+            return builder
diff --git a/apisix/runner/server/response.py b/apisix/runner/server/response.py
index 8dc7a42..dcfe9c8 100644
--- a/apisix/runner/server/response.py
+++ b/apisix/runner/server/response.py
@@ -15,7 +15,7 @@
 # limitations under the License.
 #
 
-from a6pluginproto.Err import Code as A6ErrCode
+from A6.Err import Code as A6ErrCode
 
 RESP_STATUS_CODE_OK = 200
 RESP_STATUS_MESSAGE_OK = "OK"
diff --git a/apisix/runner/server/server.py b/apisix/runner/server/server.py
index ca311a7..eb7cd3a 100644
--- a/apisix/runner/server/server.py
+++ b/apisix/runner/server/server.py
@@ -25,41 +25,54 @@ from apisix.runner.server.config import Config as 
NewServerConfig
 from apisix.runner.server.logger import Logger as NewServerLogger
 from apisix.runner.server.response import RESP_STATUS_CODE_OK
 
-logger = NewServerLogger()
+PROTOCOL_HEADER_LEN = 4
 
 
-def _threaded(conn: socket.socket):
+class RPCData:
+    def __init__(self, ty: int = 0, data: bytes = b''):
+        self.ty = ty
+        self.data = data
+
+
+class RPCRequest:
+    def __init__(self, conn: socket.socket, log: NewServerLogger):
+        self.conn = conn
+        self.log = log
+        self.request = RPCData()
+        self.response = RPCData()
+
+
+def _threaded(r: RPCRequest):
     while True:
         try:
-            buffer = conn.recv(4)
+            buffer = r.conn.recv(PROTOCOL_HEADER_LEN)
             protocol = NewServerProtocol(buffer, 0)
             err = protocol.decode()
             if err.code != RESP_STATUS_CODE_OK:
-                logger.error(err.message)
+                r.log.error(err.message)
                 break
 
-            logger.info("request type:{}, len:{}", protocol.type, 
protocol.length)
+            r.request.ty = protocol.type
+            r.log.info("request type:{}, len:{}", protocol.type, 
protocol.length)
 
-            buffer = conn.recv(protocol.length)
-            handler = NewServerHandle(protocol.type, buffer)
+            r.request.data = r.conn.recv(protocol.length)
+            handler = NewServerHandle(r)
             response = handler.dispatch()
-            if response.code != RESP_STATUS_CODE_OK:
-                logger.error(response.message)
-
-            protocol = NewServerProtocol(response.data, response.type)
+            protocol = NewServerProtocol(response.Output(), protocol.type)
             protocol.encode()
 
-            logger.info("response type:{}, len:{}", protocol.type, 
protocol.length)
+            r.log.info("response type:{}, len:{}", protocol.type, 
protocol.length)
 
-            conn.sendall(protocol.buffer)
+            r.conn.sendall(protocol.buffer)
         except socket.timeout as e:
-            logger.info("connection timout: {}", e.args.__str__())
+            r.log.info("connection timout: {}", e.args.__str__())
             break
         except socket.error as e:
-            logger.error("connection error: {}", e.args.__str__())
+            r.log.error("connection error: {}", e.args.__str__())
             break
 
-    conn.close()
+    r.conn.close()
+    del r
 
 
 class Server:
@@ -71,7 +84,8 @@ class Server:
         self.sock.bind(self.fd)
         self.sock.listen(1024)
 
-        logger.set_level(config.logging.level)
+        self.logger = NewServerLogger(config.logging.level)
+
         print("listening on unix:%s" % self.fd)
 
     def receive(self):
@@ -79,7 +93,8 @@ class Server:
             conn, address = self.sock.accept()
             conn.settimeout(60)
 
-            thread = NewThread(target=_threaded, args=(conn,))
+            r = RPCRequest(conn, self.logger)
+            thread = NewThread(target=_threaded, args=(r,))
             thread.setDaemon(True)
             thread.start()
 
diff --git a/apisix/runner/http/protocol.py b/apisix/runner/utils/__init__.py
similarity index 85%
rename from apisix/runner/http/protocol.py
rename to apisix/runner/utils/__init__.py
index 57ae400..b1312a0 100644
--- a/apisix/runner/http/protocol.py
+++ b/apisix/runner/utils/__init__.py
@@ -14,12 +14,3 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import flatbuffers
-
-RPC_PREPARE_CONF = 1
-RPC_HTTP_REQ_CALL = 2
-RPC_UNKNOWN = 0
-
-
-def new_builder():
-    return flatbuffers.Builder(256)
diff --git a/apisix/runner/utils/common.py b/apisix/runner/utils/common.py
new file mode 100644
index 0000000..cdba9a5
--- /dev/null
+++ b/apisix/runner/utils/common.py
@@ -0,0 +1,198 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+#
+
+import flatbuffers
+from A6 import Method as A6Method
+from A6 import TextEntry as A6Entry
+from A6.Err.Code import Code as A6ErrCode
+from A6.HTTPReqCall import Rewrite as HCRewrite
+from A6.HTTPReqCall import Stop as HCStop
+from A6.HTTPReqCall import Action as HCAction
+from A6.HTTPReqCall import Resp as HCResp
+from A6.PrepareConf import Resp as PCResp
+from A6.Err import Resp as ErrResp
+
+RPC_PREPARE_CONF = 1
+RPC_HTTP_REQ_CALL = 2
+RPC_UNKNOWN = 0
+
+VECTOR_TYPE_HEADER = 1
+VECTOR_TYPE_QUERY = 2
+
+A6MethodGET = "GET"
+A6MethodHEAD = "HEAD"
+A6MethodPOST = "POST"
+A6MethodPUT = "PUT"
+A6MethodDELETE = "DELETE"
+A6MethodMKCOL = "MKCOL"
+A6MethodCOPY = "COPY"
+A6MethodMOVE = "MOVE"
+A6MethodOPTIONS = "OPTIONS"
+A6MethodPROPFIND = "PROPFIND"
+A6MethodPROPPATCH = "PROPPATCH"
+A6MethodLOCK = "LOCK"
+A6MethodUNLOCK = "UNLOCK"
+A6MethodPATCH = "PATCH"
+A6MethodTRACE = "TRACE"
+
+methodNames = {
+    A6Method.Method.GET: A6MethodGET,
+    A6Method.Method.HEAD: A6MethodHEAD,
+    A6Method.Method.POST: A6MethodPOST,
+    A6Method.Method.PUT: A6MethodPUT,
+    A6Method.Method.DELETE: A6MethodDELETE,
+    A6Method.Method.MKCOL: A6MethodMKCOL,
+    A6Method.Method.COPY: A6MethodCOPY,
+    A6Method.Method.MOVE: A6MethodMOVE,
+    A6Method.Method.OPTIONS: A6MethodOPTIONS,
+    A6Method.Method.PROPFIND: A6MethodPROPFIND,
+    A6Method.Method.PROPPATCH: A6MethodPROPPATCH,
+    A6Method.Method.LOCK: A6MethodLOCK,
+    A6Method.Method.UNLOCK: A6MethodUNLOCK,
+    A6Method.Method.PATCH: A6MethodPATCH,
+    A6Method.Method.TRACE: A6MethodTRACE,
+}
+
+methodCodes = {
+    A6MethodGET: A6Method.Method.GET,
+    A6MethodHEAD: A6Method.Method.HEAD,
+    A6MethodPOST: A6Method.Method.POST,
+    A6MethodPUT: A6Method.Method.PUT,
+    A6MethodDELETE: A6Method.Method.DELETE,
+    A6MethodMKCOL: A6Method.Method.MKCOL,
+    A6MethodCOPY: A6Method.Method.COPY,
+    A6MethodMOVE: A6Method.Method.MOVE,
+    A6MethodOPTIONS: A6Method.Method.OPTIONS,
+    A6MethodPROPFIND: A6Method.Method.PROPFIND,
+    A6MethodPROPPATCH: A6Method.Method.PROPPATCH,
+    A6MethodLOCK: A6Method.Method.LOCK,
+    A6MethodUNLOCK: A6Method.Method.UNLOCK,
+    A6MethodPATCH: A6Method.Method.PATCH,
+    A6MethodTRACE: A6Method.Method.TRACE,
+}
+
+
+def create_dict_entry(builder: flatbuffers.Builder, data: dict) -> list:
+    entries = []
+    if not isinstance(data, dict) or len(data) <= 0:
+        return entries
+    for key in data:
+        val = data[key]
+        key_bytes = builder.CreateString(key)
+        val_bytes = builder.CreateString(val)
+        A6Entry.Start(builder)
+        A6Entry.AddName(builder, key_bytes)
+        A6Entry.AddValue(builder, val_bytes)
+        entry = A6Entry.End(builder)
+        entries.append(entry)
+    return entries
+
+
+def get_vector_object(action: int = 0, ty: int = 0):
+    objects = {
+        "%s:%s" % (HCAction.Action.Rewrite, VECTOR_TYPE_HEADER): 
HCRewrite.RewriteStartHeadersVector,
+        "%s:%s" % (HCAction.Action.Rewrite, VECTOR_TYPE_QUERY): 
HCRewrite.RewriteStartArgsVector,
+        "%s:%s" % (HCAction.Action.Stop, VECTOR_TYPE_HEADER): 
HCStop.StopStartHeadersVector,
+    }
+    return objects.get("%s:%s" % (action, ty), None)
+
+
+def create_dict_vector(builder: flatbuffers.Builder, data: dict, action: int = 
0, ty: int = 0):
+    res = 0
+    entries = create_dict_entry(builder, data)
+    entries_len = len(entries)
+    if entries_len == 0:
+        return res
+
+    vector_object = get_vector_object(action, ty)
+    if not vector_object:
+        return res
+
+    vector_object(builder, entries_len)
+    for i in range(entries_len - 1, -1, -1):
+        builder.PrependUOffsetTRelative(entries[i])
+    return builder.EndVector()
+
+
+def create_str_vector(builder: flatbuffers.Builder, data: str):
+    res = 0
+    if not data or len(data) <= 0:
+        return res
+
+    data = data.encode(encoding="UTF-8")
+    return builder.CreateByteVector(data)
+
+
+def new_builder():
+    return flatbuffers.Builder(256)
+
+
+def get_method_name_by_code(code: int) -> str:
+    return methodNames.get(code)
+
+
+def get_method_code_by_name(name: str) -> int:
+    return methodCodes.get(name)
+
+
+def response_call(action_type: int):
+    def decorator(func):
+        def wrapper(cls, builder: flatbuffers.Builder):
+            (action, id) = func(cls, builder)
+            if not action or id == 0:
+                return False
+
+            HCResp.Start(builder)
+            HCResp.AddId(builder, id)
+            HCResp.AddActionType(builder, action_type)
+            HCResp.AddAction(builder, action)
+            res = HCResp.End(builder)
+            builder.Finish(res)
+            return True
+
+        return wrapper
+
+    return decorator
+
+
+def response_config(func):
+    def wrapper(cls, builder: flatbuffers.Builder):
+        token = func(cls, builder)
+        if token <= 0:
+            return False
+
+        PCResp.Start(builder)
+        PCResp.AddConfToken(builder, token)
+        res = PCResp.End(builder)
+        builder.Finish(res)
+        return True
+
+    return wrapper
+
+
+def response_unknown(func):
+    def wrapper(cls, builder: flatbuffers.Builder):
+        err_code = func(cls, builder)
+        if not err_code:
+            err_code = A6ErrCode.BAD_REQUEST
+        ErrResp.Start(builder)
+        ErrResp.AddCode(builder, err_code)
+        res = ErrResp.End(builder)
+        builder.Finish(res)
+        return True
+
+    return wrapper
diff --git a/apisix/main.py b/bin/py-runner
old mode 100644
new mode 100755
similarity index 92%
rename from apisix/main.py
rename to bin/py-runner
index 00ae269..9ec88aa
--- a/apisix/main.py
+++ b/bin/py-runner
@@ -1,3 +1,5 @@
+#!/usr/bin/python3
+
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -32,7 +34,7 @@ def runner() -> None:
 
 @runner.command()
 def start() -> None:
-    config = NewConfig(os.path.dirname(os.path.abspath(__file__)))
+    config = 
NewConfig(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
     server = NewServer(config)
     server.receive()
 
diff --git a/apisix/config.yaml b/conf/config.yaml
similarity index 100%
rename from apisix/config.yaml
rename to conf/config.yaml
diff --git a/docs/en/latest/developer-guide.md 
b/docs/en/latest/developer-guide.md
index 79d9c4b..d034f0b 100644
--- a/docs/en/latest/developer-guide.md
+++ b/docs/en/latest/developer-guide.md
@@ -27,12 +27,13 @@ This documentation explains how to develop this project.
 
 ## Prerequisites
 
-* Python 3.6+
+* Python 3.7+
 * APISIX 2.7.0
 
 ## Debug
 
-- Run `make setup` Installation dependencies
+- Run `make setup` installation dependencies
+- Run `make install` installation runner to system
 - Run `make dev` to start it
 
 ## Plugin
@@ -76,29 +77,35 @@ class Test(Base):
         # Get plugin configuration information through `self.config`
         # print(self.config)
 
+        # Setting the request object will continue to forward the request
+
+        # Rewrite request headers
+        request.headers["X-Resp-A6-Runner"] = "Python"
+
+        # Rewrite request args
+        request.args["a6_runner"] = "Python"
+
+        # Rewrite request path
+        request.path = "/a6/python/runner"
+
+        # Setting the response object will terminate the request and respond 
to the data
+
         # Set response headers
-        headers = request.headers
-        headers["X-Resp-A6-Runner"] = "Python"
-        response.headers = headers
+        response.headers["X-Resp-A6-Runner"] = "Python"
 
         # Set response body
         response.body = "Hello, Python Runner of APISIX"
 
         # Set response status code
         response.status_code = 201
-
-        # Set the plug-in to `stop` type, default `rewrite`, use 
`self.rewrite()` to declare it as `rewrite` type.
-        self.stop()
 ```
 
 - The plugin must inherit the `Base` class
 - The plugin must implement the `filter` function
 - `filter` function parameters can only contain `Request` and `Response` 
classes as parameters
-- Request parameter can get request information
+- Request parameter can get and set request information
 - Response parameter can set response information
 - `self.config` can get plug-in configuration information
-- Use `self.stop()` to set the plugin as a `stop` type plugin, which will 
interrupt the request.
-- Use `self.rewrite()` to set the plugin as a `rewrite` type plugin, which 
will not interrupt the request.
 
 ## Test
 
diff --git a/docs/en/latest/getting-started.md 
b/docs/en/latest/getting-started.md
index 1212848..9e26b01 100644
--- a/docs/en/latest/getting-started.md
+++ b/docs/en/latest/getting-started.md
@@ -26,7 +26,7 @@ This document explains how to use Python Runner
 
 ## Prerequisites
 
-* Python 3.6+
+* Python 3.7+
 * APISIX 2.7.0
 
 
@@ -35,6 +35,7 @@ This document explains how to use Python Runner
 ```bash
 $ git clone https://github.com/apache/apisix-python-plugin-runner.git
 $ cd apisix-python-plugin-runner
+$ make setup
 $ make install
 ```
 
@@ -47,7 +48,7 @@ $ make install
 #### Run APISIX Python Runner
 ```bash
 $ cd /path/to/apisix-python-plugin-runner
-$ APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock python3 apisix/main.py start
+$ APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock python3 bin/py-runner start
 ```
 
 #### Modify APISIX configuration file
@@ -79,7 +80,7 @@ ext-plugin:
 ### Log level and socket configuration (Optional)
 
 ```bash
-$ vim /path/to/apisix-python-plugin-runner/apisix/config.yaml
+$ vim /path/to/apisix-python-plugin-runner/conf/config.yaml
 socket:
   file: $env.APISIX_LISTEN_ADDRESS # Environment variable or absolute path
 
diff --git a/requirements.txt b/requirements.txt
index 1b4de80..8bcdef9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-a6pluginprotos==0.1.0
+a6pluginprotos==0.2.1
 click==8.0.1
 minicache==0.0.1
 PyYAML==5.4.1
diff --git a/setup.py b/setup.py
index 79aa082..07d1438 100644
--- a/setup.py
+++ b/setup.py
@@ -29,7 +29,7 @@ setup(
     author="Jinchao Shuai",
     author_email="d...@apisix.apache.org",
     license="Apache 2.0",
-    python_requires=">=3.6.0",
+    python_requires=">=3.7.0",
     packages=find_packages(exclude=["tests"]),
     install_requires=requirements,
 )
diff --git a/tests/runner/http/test_method.py b/tests/runner/http/test_method.py
deleted file mode 100644
index 388efb0..0000000
--- a/tests/runner/http/test_method.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (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
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# 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.
-#
-
-import apisix.runner.http.method as RunnerMethod
-from a6pluginproto import Method as A6Method
-
-
-def test_get_name_by_code():
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodGET) == 
A6Method.Method.GET
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodHEAD) == 
A6Method.Method.HEAD
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodPOST) == 
A6Method.Method.POST
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodPUT) == 
A6Method.Method.PUT
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodDELETE) == 
A6Method.Method.DELETE
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodMKCOL) == 
A6Method.Method.MKCOL
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodCOPY) == 
A6Method.Method.COPY
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodMOVE) == 
A6Method.Method.MOVE
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodOPTIONS) == 
A6Method.Method.OPTIONS
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodPROPFIND) == 
A6Method.Method.PROPFIND
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodPROPPATCH) == 
A6Method.Method.PROPPATCH
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodLOCK) == 
A6Method.Method.LOCK
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodUNLOCK) == 
A6Method.Method.UNLOCK
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodPATCH) == 
A6Method.Method.PATCH
-    assert RunnerMethod.get_code_by_name(RunnerMethod.A6MethodTRACE) == 
A6Method.Method.TRACE
-
-
-def test_get_code_by_name():
-    assert RunnerMethod.get_name_by_code(A6Method.Method.GET) == 
RunnerMethod.A6MethodGET
-    assert RunnerMethod.get_name_by_code(A6Method.Method.HEAD) == 
RunnerMethod.A6MethodHEAD
-    assert RunnerMethod.get_name_by_code(A6Method.Method.POST) == 
RunnerMethod.A6MethodPOST
-    assert RunnerMethod.get_name_by_code(A6Method.Method.PUT) == 
RunnerMethod.A6MethodPUT
-    assert RunnerMethod.get_name_by_code(A6Method.Method.DELETE) == 
RunnerMethod.A6MethodDELETE
-    assert RunnerMethod.get_name_by_code(A6Method.Method.MKCOL) == 
RunnerMethod.A6MethodMKCOL
-    assert RunnerMethod.get_name_by_code(A6Method.Method.COPY) == 
RunnerMethod.A6MethodCOPY
-    assert RunnerMethod.get_name_by_code(A6Method.Method.MOVE) == 
RunnerMethod.A6MethodMOVE
-    assert RunnerMethod.get_name_by_code(A6Method.Method.OPTIONS) == 
RunnerMethod.A6MethodOPTIONS
-    assert RunnerMethod.get_name_by_code(A6Method.Method.PROPFIND) == 
RunnerMethod.A6MethodPROPFIND
-    assert RunnerMethod.get_name_by_code(A6Method.Method.PROPPATCH) == 
RunnerMethod.A6MethodPROPPATCH
-    assert RunnerMethod.get_name_by_code(A6Method.Method.LOCK) == 
RunnerMethod.A6MethodLOCK
-    assert RunnerMethod.get_name_by_code(A6Method.Method.UNLOCK) == 
RunnerMethod.A6MethodUNLOCK
-    assert RunnerMethod.get_name_by_code(A6Method.Method.PATCH) == 
RunnerMethod.A6MethodPATCH
-    assert RunnerMethod.get_name_by_code(A6Method.Method.TRACE) == 
RunnerMethod.A6MethodTRACE
diff --git a/tests/runner/http/test_protocol.py 
b/tests/runner/http/test_protocol.py
deleted file mode 100644
index be4764e..0000000
--- a/tests/runner/http/test_protocol.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (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
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# 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.
-#
-
-import flatbuffers
-from apisix.runner.http.protocol import new_builder
-
-
-def test_new_builder():
-    builder = new_builder()
-    assert isinstance(builder, flatbuffers.Builder)
-    assert builder.Bytes == flatbuffers.Builder(256).Bytes
-    assert builder.Bytes != flatbuffers.Builder(512).Bytes
diff --git a/tests/runner/http/test_request.py 
b/tests/runner/http/test_request.py
index c0ccdb6..f8a4a30 100644
--- a/tests/runner/http/test_request.py
+++ b/tests/runner/http/test_request.py
@@ -14,96 +14,66 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-import flatbuffers
 
-from apisix.runner.http.request import Request as NewHttpRequest
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
-from apisix.runner.http.protocol import RPC_UNKNOWN
-from apisix.runner.http.protocol import new_builder
-from apisix.runner.http.method import get_name_by_code
-from apisix.runner.plugin.core import loading
-from a6pluginproto.HTTPReqCall import Req as A6HTTPReqCallReq
-from a6pluginproto.PrepareConf import Req as A6PrepareConfReq
-from a6pluginproto import TextEntry as A6TextEntry
-from a6pluginproto import Method as A6Method
+import socket
+import logging
+import apisix.runner.utils.common as runner_utils
+from apisix.runner.server.logger import Logger as RunnerServerLogger
+from apisix.runner.server.server import RPCRequest as RunnerRPCRequest
+from apisix.runner.http.request import Request as RunnerHttpRequest
 
 
-def _create_entry(builder: flatbuffers.Builder, name: str, value: str) -> int:
-    name = builder.CreateString(name)
-    value = builder.CreateString(value)
-    A6TextEntry.Start(builder)
-    A6TextEntry.AddName(builder, name)
-    A6TextEntry.AddValue(builder, value)
-    return A6TextEntry.End(builder)
+def default_request():
+    sock = socket.socket()
+    logger = RunnerServerLogger(logging.INFO)
+    return RunnerRPCRequest(sock, logger)
 
 
-def test_request_config():
-    builder = new_builder()
-    plugins = loading()
-    conf_data = 0
-    for name in plugins:
-        conf_data = _create_entry(builder, name, '{"runner":"Python"}')
-        break
-    A6PrepareConfReq.ReqStartConfVector(builder, 1)
-    builder.PrependUOffsetTRelative(conf_data)
-    conf = builder.EndVector()
-    A6PrepareConfReq.Start(builder)
-    A6PrepareConfReq.AddConf(builder, conf)
-    req = A6PrepareConfReq.End(builder)
-    builder.Finish(req)
-    buf = builder.Output()
-    req = NewHttpRequest(ty=RPC_PREPARE_CONF, buf=buf)
-    assert req.configs
-    assert len(req.configs) >= 1
+def test_request_unknown_handler():
+    builder = runner_utils.new_builder()
+    r = default_request()
+    req = RunnerHttpRequest(r)
+    ok = req.unknown_handler(builder)
+    assert ok
 
 
-def test_request_call():
-    req_path = "/hello/python/runner"
-    req_src_ip = [127, 0, 0, 1]
-    req_args = {"a": "args"}
-    req_headers = {"h": "headers"}
+def test_request_config_handler():
+    builder = runner_utils.new_builder()
+    r = default_request()
+    req = RunnerHttpRequest(r)
+    req.conf_token = 0
+    ok = req.config_handler(builder)
+    assert not ok
+    req.conf_token = 1
+    ok = req.config_handler(builder)
+    assert ok
 
-    builder = new_builder()
-    path = builder.CreateString(req_path)
-    src_ip = bytes(bytearray(req_src_ip))
-    src_ip = builder.CreateByteVector(src_ip)
 
-    args = _create_entry(builder, "a", req_args.get("a"))
-    A6HTTPReqCallReq.StartArgsVector(builder, 1)
-    builder.PrependUOffsetTRelative(args)
-    args_vec = builder.EndVector()
-
-    headers = _create_entry(builder, "h", req_headers.get("h"))
-    A6HTTPReqCallReq.StartHeadersVector(builder, 1)
-    builder.PrependUOffsetTRelative(headers)
-    headers_vec = builder.EndVector()
-
-    A6HTTPReqCallReq.Start(builder)
-    A6HTTPReqCallReq.AddId(builder, 1)
-    A6HTTPReqCallReq.AddMethod(builder, A6Method.Method.GET)
-    A6HTTPReqCallReq.AddPath(builder, path)
-    A6HTTPReqCallReq.AddSrcIp(builder, src_ip)
-    A6HTTPReqCallReq.AddArgs(builder, args_vec)
-    A6HTTPReqCallReq.AddHeaders(builder, headers_vec)
-    req = A6HTTPReqCallReq.End(builder)
-    builder.Finish(req)
-    buf = builder.Output()
-    req = NewHttpRequest(ty=RPC_HTTP_REQ_CALL, buf=buf)
-
-    assert req.src_ip == ".".join('%s' % ip for ip in req_src_ip)
-    assert req.path == req_path
-    assert req.args.get("a") == req_args.get("a")
-    assert req.headers.get("h") == req_headers.get("h")
-    assert req.method == get_name_by_code(A6Method.Method.GET)
+def test_request_call_handler():
+    builder = runner_utils.new_builder()
+    r = default_request()
+    req = RunnerHttpRequest(r)
+    req.path = ""
+    req.headers = {}
+    req.args = {}
+    ok = req.call_handler(builder)
+    assert not ok
+    req.headers["X-Hello"] = "World"
+    req.id = 1
+    ok = req.call_handler(builder)
+    assert ok
+    req.path = "/hello"
+    ok = req.call_handler(builder)
+    assert ok
 
 
 def test_request_handler():
-    req = NewHttpRequest()
+    r = default_request()
+    req = RunnerHttpRequest(r)
     req.id = 1000
     assert req.id == 1000
-    req.rpc_type = RPC_UNKNOWN
-    assert req.rpc_type == RPC_UNKNOWN
+    req.rpc_type = runner_utils.RPC_UNKNOWN
+    assert req.rpc_type == runner_utils.RPC_UNKNOWN
     req.rpc_buf = b'hello'
     assert req.rpc_buf == b'hello'
     req.conf_token = 10
diff --git a/tests/runner/http/test_response.py 
b/tests/runner/http/test_response.py
index 3867495..50689fa 100644
--- a/tests/runner/http/test_response.py
+++ b/tests/runner/http/test_response.py
@@ -15,112 +15,25 @@
 # limitations under the License.
 #
 
-from apisix.runner.http.response import Response as NewHttpResponse
-from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from apisix.runner.http.protocol import RPC_UNKNOWN
-from a6pluginproto.PrepareConf.Resp import Resp as PrepareConfResp
-from a6pluginproto.HTTPReqCall.Resp import Resp as HTTPReqCallResp
-from a6pluginproto.HTTPReqCall.Action import Action as HTTPReqCallAction
-from a6pluginproto.HTTPReqCall.Stop import Stop as HTTPReqCallStop
-from a6pluginproto.HTTPReqCall.Rewrite import Rewrite as HTTPReqCallRewrite
-from a6pluginproto.Err.Code import Code as A6ErrCode
-from a6pluginproto.Err.Resp import Resp as A6ErrResp
+import apisix.runner.utils.common as runner_utils
+from apisix.runner.http.response import Response as RunnerHttpResponse
 
 
-def test_response_config():
-    token = 1
-    resp = NewHttpResponse(ty=RPC_PREPARE_CONF)
-    resp.token = token
-    response = resp.flatbuffers()
-    flat_resp = PrepareConfResp.GetRootAs(response.Output())
-    assert resp.changed()
-    assert flat_resp.ConfToken() == token
-
-
-def test_response_call():
-    headers = {
-        "X-TEST-HELLO": "hello",
-        "X-TEST-WORLD": "world"
-    }
-    args = {
-        "A-TEST-HELLO": "hello",
-        "A-TEST-WORLD": "world",
-    }
-    body = "hello world"
-    resp = NewHttpResponse(ty=RPC_HTTP_REQ_CALL)
-    resp.headers = headers
-    resp.body = body
-    resp.status_code = 200
-    resp.action_type = HTTPReqCallAction.Stop
-    response = resp.flatbuffers()
-    flat_resp = HTTPReqCallResp.GetRootAs(response.Output())
-    assert resp.changed()
-    assert flat_resp.ActionType() == HTTPReqCallAction.Stop
-    action = flat_resp.Action()
-    stop = HTTPReqCallStop()
-    stop.Init(action.Bytes, action.Pos)
-    body_list = []
-    body_len = stop.BodyLength()
-    for i in range(body_len):
-        body_list.append(chr(stop.Body(i)))
-    assert "".join(body_list) == body
-    header_dict = {}
-    header_len = stop.HeadersLength()
-    for j in range(header_len):
-        entry = stop.Headers(j)
-        hk = str(entry.Name(), encoding="utf-8")
-        hv = str(entry.Value(), encoding="utf-8")
-        header_dict[hk] = hv
-    assert header_dict.get("X-TEST-HELLO") == headers.get("X-TEST-HELLO")
-    assert header_dict.get("X-TEST-WORLD") == headers.get("X-TEST-WORLD")
-    assert stop.Status() == resp.status_code
-
-    resp = NewHttpResponse(ty=RPC_HTTP_REQ_CALL)
-    resp.headers = headers
-    resp.args = args
-    resp.path = "/hello/runner"
-    resp.action_type = HTTPReqCallAction.Rewrite
-    response = resp.flatbuffers()
-    flat_resp = HTTPReqCallResp.GetRootAs(response.Output())
-    assert resp.changed()
-    assert flat_resp.ActionType() == HTTPReqCallAction.Rewrite
-    action = flat_resp.Action()
-    rewrite = HTTPReqCallRewrite()
-    rewrite.Init(action.Bytes, action.Pos)
-    args_dict = {}
-    args_len = rewrite.ArgsLength()
-    for k in range(args_len):
-        entry = rewrite.Args(k)
-        ak = str(entry.Name(), encoding="utf-8")
-        av = str(entry.Value(), encoding="utf-8")
-        args_dict[ak] = av
-    assert args_dict.get("A-TEST-HELLO") == args.get("A-TEST-HELLO")
-    assert args_dict.get("A-TEST-WORLD") == args.get("A-TEST-WORLD")
-    header_dict = {}
-    header_len = rewrite.HeadersLength()
-    for j in range(header_len):
-        entry = rewrite.Headers(j)
-        hk = str(entry.Name(), encoding="utf-8")
-        hv = str(entry.Value(), encoding="utf-8")
-        header_dict[hk] = hv
-    assert header_dict.get("X-TEST-HELLO") == headers.get("X-TEST-HELLO")
-    assert header_dict.get("X-TEST-WORLD") == headers.get("X-TEST-WORLD")
-    assert rewrite.Path().decode(encoding="UTF-8") == resp.path
-
-
-def test_response_unknown():
-    resp = NewHttpResponse(ty=RPC_UNKNOWN)
-    resp.error_code = A6ErrCode.BAD_REQUEST
-    response = resp.flatbuffers()
-    flat_resp = A6ErrResp.GetRootAs(response.Output())
-    assert flat_resp.Code() == A6ErrCode.BAD_REQUEST
+def test_response_call_handler():
+    builder = runner_utils.new_builder()
+    resp = RunnerHttpResponse()
+    resp.id = 1
+    ok = resp.call_handler(builder)
+    assert not ok
+    resp.body = "Hello Python Runner"
+    ok = resp.call_handler(builder)
+    assert ok
 
 
 def test_response_handler():
-    resp = NewHttpResponse()
-    resp.rpc_type = RPC_UNKNOWN
-    assert resp.rpc_type == RPC_UNKNOWN
+    resp = RunnerHttpResponse()
+    resp.rpc_type = runner_utils.RPC_UNKNOWN
+    assert resp.rpc_type == runner_utils.RPC_UNKNOWN
     resp.token = 1000
     assert resp.token == 1000
     resp.headers = {"X-HELLO": "Python"}
diff --git a/tests/runner/plugin/test_base.py b/tests/runner/plugin/test_base.py
index a5f814d..df7192f 100644
--- a/tests/runner/plugin/test_base.py
+++ b/tests/runner/plugin/test_base.py
@@ -25,6 +25,8 @@ def test_base():
     hello.config = hello_config
     assert hello.name == hello_name
     assert hello.config == hello_config
+    hello.name = "hello1"
+    assert hello.name != hello_name
 
     world_name = "world"
     world_config = "apisxi"
@@ -32,3 +34,5 @@ def test_base():
     world.config = world_config
     assert world.name == world_name
     assert world.config != world_config
+    world.name = "world1"
+    assert world.name != world_name
diff --git a/tests/runner/plugin/test_cache.py 
b/tests/runner/plugin/test_cache.py
index 91d4435..982316c 100644
--- a/tests/runner/plugin/test_cache.py
+++ b/tests/runner/plugin/test_cache.py
@@ -29,3 +29,24 @@ def test_cache():
     assert ok
     config = get_config_by_token(token)
     assert config == cache_config
+
+
+def test_generate_token():
+    token = generate_token()
+    assert token
+
+
+def test_set_config_by_token():
+    ok = set_config_by_token(1, {})
+    assert not ok
+    ok = set_config_by_token(1, {"q": "hello"})
+    assert ok
+
+
+def test_get_config_by_token():
+    token = 1
+    data = {"q": "hello"}
+    ok = set_config_by_token(token, data)
+    assert ok
+    d = get_config_by_token(token)
+    assert d == data
diff --git a/tests/runner/plugin/test_core.py b/tests/runner/plugin/test_core.py
index 86ac90a..eff0b20 100644
--- a/tests/runner/plugin/test_core.py
+++ b/tests/runner/plugin/test_core.py
@@ -16,28 +16,16 @@
 #
 
 import os
+import socket
+import logging
 from pkgutil import iter_modules
 
 from apisix.runner.plugin.core import loading as plugin_loading
 from apisix.runner.plugin.core import execute as plugin_execute
-from apisix.runner.plugin.core import refresh_response as refresh_response
+from apisix.runner.server.logger import Logger as RunnerServerLogger
+from apisix.runner.server.server import RPCRequest as RunnerRPCRequest
 from apisix.runner.http.request import Request as NewHttpRequest
 from apisix.runner.http.response import Response as NewHttpResponse
-from apisix.runner.server.response import RESP_STATUS_CODE_OK
-from apisix.runner.server.response import RESP_STATUS_CODE_SERVICE_UNAVAILABLE
-from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
-
-
-class Test:
-    """
-    test plugin
-    """
-    def filter(self):
-        """
-        test plugin handler
-        :return:
-        """
-        pass
 
 
 def test_loading():
@@ -51,31 +39,39 @@ def test_loading():
 
 
 def test_execute():
-    request = NewHttpRequest()
+    sock = socket.socket()
+    logger = RunnerServerLogger(logging.INFO)
+    r = RunnerRPCRequest(sock, logger)
+    request = NewHttpRequest(r)
     response = NewHttpResponse()
     configs = plugin_loading()
     for p_name in configs:
         configs[p_name] = configs.get(p_name)()
-    (code, _) = plugin_execute(configs, request, response)
-    assert code == RESP_STATUS_CODE_OK
-    (code, _) = plugin_execute(configs, request, None)
-    assert code == RESP_STATUS_CODE_SERVICE_UNAVAILABLE
-    configs["test"] = Test()
-    (code, _) = plugin_execute(configs, request, response)
-    assert code == RESP_STATUS_CODE_BAD_REQUEST
+    ok = plugin_execute(configs, r, request, response)
+    assert ok
+    # stop plugin
+    assert response.headers.get("X-Resp-A6-Runner") == "Python"
+    assert response.body == "Hello, Python Runner of APISIX"
+    assert response.status_code == 201
+    # rewrite plugin
+    assert request.headers.get("X-Resp-A6-Runner") == "Python"
+    assert request.args.get("a6_runner") == "Python"
+    assert request.path == "/a6/python/runner"
+    configs = {"test": {}}
+    ok = plugin_execute(configs, r, request, response)
+    assert not ok
 
+    class AttributeErrorExample:
+        pass
 
-def test_refresh_response():
-    request = NewHttpRequest()
-    request.path = "/hello"
-    request.args = {
-        "q": "hello"
-    }
-    request.headers = {
-        "h": "world"
-    }
-    response = NewHttpResponse()
-    refresh_response(request, response)
-    assert request.path == response.path
-    assert request.args == response.args
-    assert request.headers == response.headers
+    configs = {AttributeErrorExample.__name__.lower(): AttributeErrorExample()}
+    ok = plugin_execute(configs, r, request, response)
+    assert not ok
+
+    class TypeErrorExample:
+        def __init__(self):
+            self.filter = 10
+
+    configs = {TypeErrorExample.__name__.lower(): TypeErrorExample()}
+    ok = plugin_execute(configs, r, request, response)
+    assert not ok
diff --git a/tests/runner/server/test_config.py 
b/tests/runner/server/test_config.py
index a5df767..5c95fcc 100644
--- a/tests/runner/server/test_config.py
+++ b/tests/runner/server/test_config.py
@@ -40,7 +40,7 @@ def test_config_default():
 
 
 def test_config_custom():
-    config = NewServerConfig("%s/apisix" % 
os.path.abspath(os.path.join(os.getcwd())), "config.yaml")
+    config = NewServerConfig("%s" % 
os.path.abspath(os.path.join(os.getcwd())), "config.yaml")
 
     config.logging.level = "NOTSET"
     assert config.logging.level == logging.NOTSET
diff --git a/tests/runner/server/test_handle.py 
b/tests/runner/server/test_handle.py
index 2904c21..9d07f61 100644
--- a/tests/runner/server/test_handle.py
+++ b/tests/runner/server/test_handle.py
@@ -14,74 +14,56 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-from apisix.runner.server.handle import Handle as NewServerHandle
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
-from apisix.runner.http.protocol import RPC_UNKNOWN
-from apisix.runner.http.protocol import new_builder
-from apisix.runner.server.response import RESP_STATUS_CODE_OK
-from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
-from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
-from apisix.runner.server.response import RESP_STATUS_MESSAGE_BAD_REQUEST
-from apisix.runner.server.response import RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
-from a6pluginproto.HTTPReqCall import Req as A6HTTPReqCallReq
-from a6pluginproto.PrepareConf import Req as A6PrepareConfReq
-from a6pluginproto.PrepareConf import Resp as A6PrepareConfResp
-from a6pluginproto import TextEntry as A6TextEntry
-from a6pluginproto import Method as A6Method
-
-
-def test_type():
-    handle = NewServerHandle(ty=RPC_UNKNOWN)
-    assert handle.type == RPC_UNKNOWN
-    handle = NewServerHandle(ty=RPC_PREPARE_CONF)
-    assert handle.type == RPC_PREPARE_CONF
-    handle = NewServerHandle(ty=RPC_HTTP_REQ_CALL)
-    assert handle.type == RPC_HTTP_REQ_CALL
-
-
-def test_buffer():
-    handle = NewServerHandle(buf="Hello Python Runner".encode())
-    assert handle.buffer == b"Hello Python Runner"
-
-
-def test_debug():
-    handle = NewServerHandle(debug=False)
-    assert not handle.debug
-    handle = NewServerHandle(debug=True)
-    assert handle.debug
-
-
-def test_dispatch_config():
-    builder = new_builder()
-    name = builder.CreateString("say")
-    value = builder.CreateString('{"body":"Hello Python Runner"}')
-    A6TextEntry.Start(builder)
-    A6TextEntry.AddName(builder, name)
-    A6TextEntry.AddValue(builder, value)
-    conf_data = A6TextEntry.End(builder)
-
-    A6PrepareConfReq.ReqStartConfVector(builder, 1)
-    builder.PrependUOffsetTRelative(conf_data)
-    conf = builder.EndVector()
+import logging
+import socket
+
+import apisix.runner.utils.common as runner_utils
+from apisix.runner.server.handle import Handle as RunnerServerHandle
+from apisix.runner.server.logger import Logger as RunnerServerLogger
+from apisix.runner.server.server import RPCRequest as RunnerRPCRequest
+from A6.HTTPReqCall import Req as A6HTTPReqCallReq
+from A6.PrepareConf import Req as A6PrepareConfReq
+from A6.PrepareConf import Resp as A6PrepareConfResp
+from A6 import TextEntry as A6TextEntry
+from A6 import Method as A6Method
+from A6.Err.Resp import Resp as ErrResp
+from A6.HTTPReqCall.Resp import Resp as HCResp
+from A6.HTTPReqCall.Action import Action as HCAction
+from A6.Err.Code import Code as ErrCode
+from A6.HTTPReqCall.Stop import Stop as HCStop
+from A6.HTTPReqCall.Rewrite import Rewrite as HCRewrite
+
+
+def default_request():
+    sock = socket.socket()
+    logger = RunnerServerLogger(logging.INFO)
+    return RunnerRPCRequest(sock, logger)
+
+
+def default_plugin_buffer(name: str = "stop", enable_conf: bool = True):
+    builder = runner_utils.new_builder()
+    conf = 0
+    if enable_conf:
+        name = builder.CreateString(name)
+        value = builder.CreateString('{"body":"Hello Python Runner"}')
+        A6TextEntry.Start(builder)
+        A6TextEntry.AddName(builder, name)
+        A6TextEntry.AddValue(builder, value)
+        conf_data = A6TextEntry.End(builder)
+
+        A6PrepareConfReq.ReqStartConfVector(builder, 1)
+        builder.PrependUOffsetTRelative(conf_data)
+        conf = builder.EndVector()
 
     A6PrepareConfReq.Start(builder)
     A6PrepareConfReq.AddConf(builder, conf)
     req = A6PrepareConfReq.End(builder)
     builder.Finish(req)
-    buf = builder.Output()
-    handle = NewServerHandle(ty=RPC_PREPARE_CONF, buf=buf)
-    response = handle.dispatch()
-    resp = A6PrepareConfResp.Resp.GetRootAs(response.data)
-    assert response.code == RESP_STATUS_CODE_OK
-    assert response.message == RESP_STATUS_MESSAGE_OK
-    assert response.type == RPC_PREPARE_CONF
-    assert resp.ConfToken() != 0
+    return builder.Output()
 
 
-def test_dispatch_call():
-    builder = new_builder()
+def default_call_buffer(token: int = 0, id: int = 1):
+    builder = runner_utils.new_builder()
     # request path
     path = builder.CreateString("/hello/python/runner")
     # request ip
@@ -109,25 +91,95 @@ def test_dispatch_call():
     headers_vec = builder.EndVector()
 
     A6HTTPReqCallReq.Start(builder)
-    A6HTTPReqCallReq.AddId(builder, 1)
+    A6HTTPReqCallReq.AddId(builder, id)
     A6HTTPReqCallReq.AddMethod(builder, A6Method.Method.GET)
     A6HTTPReqCallReq.AddPath(builder, path)
     A6HTTPReqCallReq.AddSrcIp(builder, src_ip)
     A6HTTPReqCallReq.AddArgs(builder, args_vec)
     A6HTTPReqCallReq.AddHeaders(builder, headers_vec)
+    A6HTTPReqCallReq.AddConfToken(builder, token)
     req = A6HTTPReqCallReq.End(builder)
     builder.Finish(req)
-    buf = builder.Output()
+    return builder.Output()
+
 
-    handle = NewServerHandle(ty=RPC_HTTP_REQ_CALL, buf=buf)
+def test_dispatch_unknown():
+    r = default_request()
+    r.request.ty = runner_utils.RPC_UNKNOWN
+    handle = RunnerServerHandle(r)
     response = handle.dispatch()
-    assert response.code == RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
-    assert response.type == RPC_UNKNOWN
+    err = ErrResp.GetRootAsResp(response.Output())
+    assert err.Code() == ErrCode.BAD_REQUEST
 
 
-def test_dispatch_unknown():
-    handle = NewServerHandle(ty=RPC_UNKNOWN)
+def test_dispatch_config():
+    buf = default_plugin_buffer("stop", False)
+    r = default_request()
+    r.request.ty = runner_utils.RPC_PREPARE_CONF
+    r.request.data = buf
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    err = ErrResp.GetRootAsResp(response.Output())
+    assert err.Code() == ErrCode.CONF_TOKEN_NOT_FOUND
+
+    buf = default_plugin_buffer("stop")
+    r.request.ty = runner_utils.RPC_PREPARE_CONF
+    r.request.data = buf
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    resp = A6PrepareConfResp.Resp.GetRootAs(response.Output())
+    assert resp.ConfToken() != 0
+
+
+def test_dispatch_call():
+    r = default_request()
+    r.request.ty = runner_utils.RPC_PREPARE_CONF
+    r.request.data = default_plugin_buffer("stop")
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    resp = A6PrepareConfResp.Resp.GetRootAs(response.Output())
+    assert resp.ConfToken() != 0
+
+    buf = default_call_buffer(resp.ConfToken())
+    r.request.ty = runner_utils.RPC_HTTP_REQ_CALL
+    r.request.data = buf
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    resp = HCResp.GetRootAsResp(response.Output())
+    assert resp.Id() > 0
+    assert resp.ActionType() == HCAction.Stop
+    stop = HCStop()
+    stop.Init(resp.Action().Bytes, resp.Action().Pos)
+    assert stop.BodyLength() == len("Hello, Python Runner of APISIX")
+    assert stop.Status() == 201
+
+    r.request.ty = runner_utils.RPC_PREPARE_CONF
+    r.request.data = default_plugin_buffer("rewrite")
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    resp = A6PrepareConfResp.Resp.GetRootAs(response.Output())
+    assert resp.ConfToken() != 0
+    conf_token = resp.ConfToken()
+    r.request.ty = runner_utils.RPC_HTTP_REQ_CALL
+    r.request.data = default_call_buffer(conf_token)
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    resp = HCResp.GetRootAsResp(response.Output())
+    assert resp.Id() > 0
+    assert resp.ActionType() == HCAction.Rewrite
+    rewrite = HCRewrite()
+    rewrite.Init(resp.Action().Bytes, resp.Action().Pos)
+    assert rewrite.Path() == b'/a6/python/runner'
+
+    r.request.ty = runner_utils.RPC_HTTP_REQ_CALL
+    r.request.data = default_call_buffer(conf_token, 0)
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    resp = ErrResp.GetRootAs(response.Output())
+    assert resp.Code() == ErrCode.BAD_REQUEST
+
+    r.request.data = default_call_buffer()
+    handle = RunnerServerHandle(r)
     response = handle.dispatch()
-    assert response.code == RESP_STATUS_CODE_BAD_REQUEST
-    assert response.message == RESP_STATUS_MESSAGE_BAD_REQUEST
-    assert response.type == RPC_UNKNOWN
+    reps = ErrResp.GetRootAs(response.Output())
+    assert reps.Code() == ErrCode.CONF_TOKEN_NOT_FOUND
diff --git a/tests/runner/server/test_protocol.py 
b/tests/runner/server/test_protocol.py
index a38c7b7..2e6ccc6 100644
--- a/tests/runner/server/test_protocol.py
+++ b/tests/runner/server/test_protocol.py
@@ -15,24 +15,24 @@
 # limitations under the License.
 #
 
+import apisix.runner.utils.common as runner_utils
 from apisix.runner.server.protocol import Protocol as NewServerProtocol
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
 from apisix.runner.server.response import RESP_STATUS_CODE_OK
 from apisix.runner.server.response import RESP_STATUS_MESSAGE_OK
 
 
 def test_protocol_encode():
     buf_str = "Hello Python Runner".encode()
-    protocol = NewServerProtocol(buffer=buf_str, ty=RPC_PREPARE_CONF)
+    protocol = NewServerProtocol(buffer=buf_str, 
ty=runner_utils.RPC_PREPARE_CONF)
     err = protocol.encode()
     buf_len = len(buf_str)
     buf_arr = bytearray(buf_len.to_bytes(4, byteorder="big"))
-    buf_arr[0] = RPC_PREPARE_CONF
+    buf_arr[0] = runner_utils.RPC_PREPARE_CONF
     buf_data = bytes(buf_arr) + buf_str
     buf_len = len(buf_data)
     assert err.code == RESP_STATUS_CODE_OK
     assert err.message == RESP_STATUS_MESSAGE_OK
-    assert protocol.type == RPC_PREPARE_CONF
+    assert protocol.type == runner_utils.RPC_PREPARE_CONF
     assert protocol.buffer == buf_data
     assert protocol.length == buf_len
 
@@ -41,11 +41,11 @@ def test_protocol_decode():
     buf_str = "Hello Python Runner".encode()
     buf_len = len(buf_str)
     buf_arr = bytearray(buf_len.to_bytes(4, byteorder="big"))
-    buf_arr[0] = RPC_PREPARE_CONF
+    buf_arr[0] = runner_utils.RPC_PREPARE_CONF
     buf_data = bytes(buf_arr)
     protocol = NewServerProtocol(buffer=buf_data)
     err = protocol.decode()
     assert err.code == RESP_STATUS_CODE_OK
     assert err.message == RESP_STATUS_MESSAGE_OK
-    assert protocol.type == RPC_PREPARE_CONF
+    assert protocol.type == runner_utils.RPC_PREPARE_CONF
     assert protocol.length == buf_len
diff --git a/tests/runner/server/test_response.py 
b/tests/runner/server/test_response.py
index 8fe6652..47a52aa 100644
--- a/tests/runner/server/test_response.py
+++ b/tests/runner/server/test_response.py
@@ -15,14 +15,12 @@
 # limitations under the License.
 #
 
+import apisix.runner.utils.common as runner_utils
 from apisix.runner.server.response import Response as NewServerResponse
 from apisix.runner.server.response import RESP_STATUS_CODE_BAD_REQUEST
 from apisix.runner.server.response import RESP_STATUS_CODE_SERVICE_UNAVAILABLE
 from apisix.runner.server.response import RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
 from apisix.runner.server.response import RESP_STATUS_CODE_OK
-from apisix.runner.http.protocol import RPC_PREPARE_CONF
-from apisix.runner.http.protocol import RPC_HTTP_REQ_CALL
-from apisix.runner.http.protocol import RPC_UNKNOWN
 
 
 def test_response_code():
@@ -47,20 +45,20 @@ def test_response_data():
 
 
 def test_response_type():
-    response = NewServerResponse(ty=RPC_UNKNOWN)
-    assert response.type == RPC_UNKNOWN
-    response = NewServerResponse(ty=RPC_PREPARE_CONF)
-    assert response.type == RPC_PREPARE_CONF
-    response = NewServerResponse(ty=RPC_HTTP_REQ_CALL)
-    assert response.type == RPC_HTTP_REQ_CALL
+    response = NewServerResponse(ty=runner_utils.RPC_UNKNOWN)
+    assert response.type == runner_utils.RPC_UNKNOWN
+    response = NewServerResponse(ty=runner_utils.RPC_PREPARE_CONF)
+    assert response.type == runner_utils.RPC_PREPARE_CONF
+    response = NewServerResponse(ty=runner_utils.RPC_HTTP_REQ_CALL)
+    assert response.type == runner_utils.RPC_HTTP_REQ_CALL
 
 
 def test_response_eq():
     resp1 = NewServerResponse(code=RESP_STATUS_CODE_OK, message="Hello Python 
Runner",
-                              data="Hello Python Runner".encode(), 
ty=RPC_PREPARE_CONF)
+                              data="Hello Python Runner".encode(), 
ty=runner_utils.RPC_PREPARE_CONF)
     resp2 = NewServerResponse(code=RESP_STATUS_CODE_BAD_REQUEST, 
message="Hello Python Runner",
-                              data="Hello Python Runner".encode(), 
ty=RPC_PREPARE_CONF)
+                              data="Hello Python Runner".encode(), 
ty=runner_utils.RPC_PREPARE_CONF)
     resp3 = NewServerResponse(code=RESP_STATUS_CODE_OK, message="Hello Python 
Runner",
-                              data="Hello Python Runner".encode(), 
ty=RPC_PREPARE_CONF)
+                              data="Hello Python Runner".encode(), 
ty=runner_utils.RPC_PREPARE_CONF)
     assert resp1 != resp2
     assert resp1 == resp3
diff --git a/tests/runner/server/test_server.py 
b/tests/runner/server/test_server.py
index cfe5d98..8d407be 100644
--- a/tests/runner/server/test_server.py
+++ b/tests/runner/server/test_server.py
@@ -15,14 +15,28 @@
 # limitations under the License.
 #
 
-from apisix.runner.server.server import Server as NewServer
-from apisix.runner.server.config import Config as NewConfig
+import socket
+import logging
+from apisix.runner.server.server import Server as RunnerServer
+from apisix.runner.server.server import RPCRequest as RunnerRPCRequest
+from apisix.runner.server.logger import Logger as RunnerServerLogger
+from apisix.runner.server.config import Config as RunnerConfig
 
 
 def test_server(capsys):
-    config = NewConfig()
-    server = NewServer(config)
+    config = RunnerConfig()
+    server = RunnerServer(config)
     del server
     captured = capsys.readouterr()
     assert captured.out.find("listening on unix") != -1
     assert captured.out.find("Bye") != -1
+
+
+def test_rpc_request():
+    sock = socket.socket()
+    logger = RunnerServerLogger(logging.INFO)
+    r = RunnerRPCRequest(sock, logger)
+    assert r.log == logger
+    assert r.conn == sock
+    assert r.request.ty == 0
+    assert len(r.request.data) == 0
diff --git a/tests/runner/utils/test_common.py 
b/tests/runner/utils/test_common.py
new file mode 100644
index 0000000..accbf65
--- /dev/null
+++ b/tests/runner/utils/test_common.py
@@ -0,0 +1,84 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+#
+
+import flatbuffers
+import apisix.runner.utils.common as runner_utils
+from apisix.runner.utils.common import VECTOR_TYPE_HEADER
+from apisix.runner.utils.common import VECTOR_TYPE_QUERY
+from A6.HTTPReqCall import Action as HCAction
+
+
+def test_get_method_code_by_name():
+    for name in runner_utils.methodCodes:
+        assert runner_utils.get_method_code_by_name(name) == 
runner_utils.methodCodes.get(name)
+
+
+def test_get_method_name_by_code():
+    for code in runner_utils.methodNames:
+        assert runner_utils.get_method_name_by_code(code) == 
runner_utils.methodNames.get(code)
+
+
+def test_new_builder():
+    builder = runner_utils.new_builder()
+    assert isinstance(builder, flatbuffers.Builder)
+    assert builder.Bytes == flatbuffers.Builder(256).Bytes
+    assert builder.Bytes != flatbuffers.Builder(512).Bytes
+
+
+def test_create_dict_entry():
+    builder = runner_utils.new_builder()
+    entries = runner_utils.create_dict_entry(builder, {})
+    assert not entries
+    examples = {"q": "hello", "a": "world"}
+    entries = runner_utils.create_dict_entry(builder, examples)
+    assert len(entries) == 2
+
+
+def test_create_dict_vector():
+    builder = runner_utils.new_builder()
+    b = runner_utils.create_dict_vector(builder, {})
+    assert not b
+    b = runner_utils.create_dict_vector(builder, {"q": "hello", "a": "world"}, 
HCAction.Action.Rewrite,
+                                        VECTOR_TYPE_HEADER)
+    assert b > 0
+    b = runner_utils.create_dict_vector(builder, {"q": "hello", "a": "world"}, 
HCAction.Action.Rewrite,
+                                        VECTOR_TYPE_QUERY)
+    assert b > 0
+    b = runner_utils.create_dict_vector(builder, {"q": "hello", "a": "world"}, 
HCAction.Action.Stop,
+                                        VECTOR_TYPE_HEADER)
+    assert b > 0
+    b = runner_utils.create_dict_vector(builder, {"q": "hello", "a": "world"}, 
0, 0)
+    assert not b
+
+
+def test_create_str_vector():
+    builder = runner_utils.new_builder()
+    b = runner_utils.create_str_vector(builder, "")
+    assert not b
+    b = runner_utils.create_str_vector(builder, "Hello")
+    assert b
+
+
+def test_get_vector_object():
+    obj = runner_utils.get_vector_object(HCAction.Action.Rewrite, 
VECTOR_TYPE_HEADER)
+    assert obj
+    obj = runner_utils.get_vector_object(HCAction.Action.Rewrite, 
VECTOR_TYPE_QUERY)
+    assert obj
+    obj = runner_utils.get_vector_object(HCAction.Action.Stop, 
VECTOR_TYPE_HEADER)
+    assert obj
+    obj = runner_utils.get_vector_object(HCAction.Action.Stop, 
VECTOR_TYPE_QUERY)
+    assert not obj

Reply via email to