refactor(req+resp): optimize parameter analysis logic (#38)

diff --git a/Makefile b/Makefile
index d2e02c0..f60fadf 100644
--- a/Makefile
+++ b/Makefile
@@ -51,7 +51,7 @@
 
 .PHONY: dev
 dev:
-	export PYTHONPATH=$(PWD) && python3 apisix/main.py start
+	export PYTHONPATH=$(PWD) && python3 bin/py-runner start
 
 
 .PHONY: release
diff --git a/apisix/runner/http/request.py b/apisix/runner/http/request.py
index 98bfb00..1764839 100644
--- a/apisix/runner/http/request.py
+++ b/apisix/runner/http/request.py
@@ -47,7 +47,8 @@
         self._req_configs = {}
         self._req_args = {}
         self._req_src_ip = ""
-        self._init()
+        self._err_code = 0
+        self.__init()
 
     @property
     def rpc_type(self) -> int:
@@ -210,6 +211,22 @@
         """
         self._req_src_ip = req_src_ip
 
+    @property
+    def err_code(self) -> int:
+        """
+        get error code for request handler
+        :return:
+        """
+        return self._err_code
+
+    @err_code.setter
+    def err_code(self, err_code: int) -> None:
+        """
+        set error code for request handler
+        :return:
+        """
+        self._err_code = err_code
+
     def reset(self) -> None:
         """
         reset request handler
@@ -230,93 +247,62 @@
         self._req_args = {}
         self._req_src_ip = ""
 
-    def _parse_src_ip(self, req: HCReq) -> None:
-        """
-        parse request source ip address
-        :param req:
-        :return:
-        """
-        if req.SrcIpIsNone():
-            return
-        ip_len = req.SrcIpLength()
-        if ip_len == 0:
-            return
-        ip_arr = bytearray()
-        for i in range(ip_len):
-            ip_arr.append(req.SrcIp(i))
-        ip_byte = bytes(ip_arr)
-
-        if ip_len == 4:
-            self.src_ip = IPv4Address(ip_byte).exploded
-        if ip_len == 16:
-            self.src_ip = IPv6Address(ip_byte).exploded
-
-    def _parse_headers(self, req: HCReq) -> None:
-        """
-        parse request headers
-        :param req:
-        :return:
-        """
-        if not req.HeadersIsNone():
-            headers = {}
-            for i in range(req.HeadersLength()):
-                key = str(req.Headers(i).Name(), encoding="UTF-8")
-                val = str(req.Headers(i).Value(), encoding="UTF-8")
-                headers[key] = val
-            self.headers = headers
-
-    def _parse_args(self, req: HCReq) -> None:
-        """
-        parse request args
-        :param req:
-        :return:
-        """
-        if not req.ArgsIsNone():
-            args = {}
-            for i in range(req.ArgsLength()):
-                key = str(req.Args(i).Name(), encoding="UTF-8")
-                val = str(req.Args(i).Value(), encoding="UTF-8")
-                args[key] = val
-            self.args = args
-
-    def _parse_configs(self, req: PCReq) -> None:
-        """
-        parse request plugin configs
-        :param req:
-        :return:
-        """
-        if not req.ConfIsNone():
-            plugins = runner_plugin.loading()
-            configs = {}
-            for i in range(req.ConfLength()):
-                name = str(req.Conf(i).Name(), encoding="UTF-8").lower()
-                plugin = plugins.get(name)
-                if not plugin:
-                    continue
-                value = str(req.Conf(i).Value(), encoding="UTF-8")
-                plugin = plugin()
-                plugin.config = json.loads(value)
-                configs[name] = plugin
-            self.configs = configs
-
-    def _init(self) -> None:
+    def __init(self) -> None:
         """
         init request handler
         :return:
         """
         if self.rpc_type == runner_utils.RPC_HTTP_REQ_CALL:
             req = HCReq.Req.GetRootAsReq(self.rpc_buf)
+
+            # fetch request id
             self.id = req.Id()
-            self.method = runner_utils.get_method_name_by_code(req.Method())
-            self.path = str(req.Path(), encoding="UTF-8")
+
+            # fetch request conf token
             self.conf_token = req.ConfToken()
-            self._parse_src_ip(req)
-            self._parse_headers(req)
-            self._parse_args(req)
+
+            # fetch request uri
+            self.path = req.Path().decode()
+
+            # fetch request method
+            self.method = runner_utils.get_method_name_by_code(req.Method())
+
+            # fetch request remote_addr
+            ip_list = runner_utils.parse_list_vector(req, runner_utils.VECTOR_TYPE_SOURCE_IP, True)
+            if ip_list:
+                if len(ip_list) == 16:
+                    self.src_ip = IPv6Address(bytes(ip_list)).exploded
+                else:
+                    self.src_ip = IPv4Address(bytes(ip_list)).exploded
+
+            # fetch request headers
+            hdr_dict = runner_utils.parse_dict_vector(req, runner_utils.VECTOR_TYPE_HEADER)
+            if hdr_dict:
+                self.headers = hdr_dict
+
+            # fetch request args
+            arg_dict = runner_utils.parse_dict_vector(req, runner_utils.VECTOR_TYPE_QUERY)
+            if arg_dict:
+                self.args = arg_dict
 
         if self.rpc_type == runner_utils.RPC_PREPARE_CONF:
             req = PCReq.Req.GetRootAsReq(self.rpc_buf)
-            self._parse_configs(req)
+            if req.ConfIsNone():
+                return
+
+            # loading plugin
+            plugins = runner_plugin.loading()
+            configs = {}
+            for i in range(req.ConfLength()):
+                name = str(req.Conf(i).Name().decode()).lower()
+                plugin = plugins.get(name)
+                if not plugin:
+                    continue
+                value = req.Conf(i).Value().decode()
+                plugin = plugin()
+                plugin.config = json.loads(value)
+                configs[name] = plugin
+            self.configs = configs
 
     def checked(self):
         """
@@ -356,4 +342,4 @@
 
     @runner_utils.response_unknown
     def unknown_handler(self, builder: flatbuffers.Builder):
-        return self.code
+        return self._err_code
diff --git a/apisix/runner/plugin/core.py b/apisix/runner/plugin/core.py
index abade21..f130b97 100644
--- a/apisix/runner/plugin/core.py
+++ b/apisix/runner/plugin/core.py
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
 import os
 import importlib
 from pkgutil import iter_modules
@@ -31,10 +32,10 @@
         try:
             plugin.filter(req, reps)
         except AttributeError as e:
-            r.log.error("execute plugin `%s`, %s" % (name, e.args.__str__()))
+            r.log.error("execute plugin `%s` AttributeError, %s" % (name, e.args.__str__()))
             return False
         except TypeError as e:
-            r.log.error("execute plugin `%s`, %s" % (name, e.args.__str__()))
+            r.log.error("execute plugin `%s` TypeError, %s" % (name, e.args.__str__()))
             return False
     return True
 
diff --git a/apisix/runner/utils/common.py b/apisix/runner/utils/common.py
index cdba9a5..32b932e 100644
--- a/apisix/runner/utils/common.py
+++ b/apisix/runner/utils/common.py
@@ -32,6 +32,18 @@
 
 VECTOR_TYPE_HEADER = 1
 VECTOR_TYPE_QUERY = 2
+VECTOR_TYPE_CONFIG = 3
+VECTOR_TYPE_SOURCE_IP = 4
+
+dictVectorParseFuncNames = {
+    VECTOR_TYPE_HEADER: "Headers",
+    VECTOR_TYPE_QUERY: "Args",
+    VECTOR_TYPE_CONFIG: "Conf",
+}
+
+listVectorParseFuncNames = {
+    VECTOR_TYPE_SOURCE_IP: "SrcIp",
+}
 
 A6MethodGET = "GET"
 A6MethodHEAD = "HEAD"
@@ -196,3 +208,40 @@
         return True
 
     return wrapper
+
+
+def parse_dict_vector(cls: object, ty: int) -> dict:
+    res = {}
+    fn = dictVectorParseFuncNames.get(ty)
+    if not fn:
+        return res
+
+    length = getattr(cls, "%sLength" % fn)()
+    if not length or length == 0:
+        return res
+
+    for i in range(length):
+        key = getattr(cls, fn)(i).Name().decode()
+        val = getattr(cls, fn)(i).Value().decode()
+        res[key] = val
+
+    return res
+
+
+def parse_list_vector(cls: object, ty: int, out_bytes: bool = False) -> list:
+    res = []
+    if out_bytes:
+        res = bytearray()
+    fn = listVectorParseFuncNames.get(ty)
+    if not fn:
+        return res
+
+    length = getattr(cls, "%sLength" % fn)()
+    if not length or length == 0:
+        return res
+
+    for i in range(length):
+        val = getattr(cls, fn)(i)
+        res.append(val)
+
+    return res
diff --git a/docs/en/latest/getting-started.md b/docs/en/latest/getting-started.md
index 9e26b01..67dfe76 100644
--- a/docs/en/latest/getting-started.md
+++ b/docs/en/latest/getting-started.md
@@ -113,8 +113,7 @@
             "127.0.0.1:1980": 1
         }
     }
-}
-'
+}'
 ```
 
 
diff --git a/tests/runner/http/test_request.py b/tests/runner/http/test_request.py
index f8a4a30..b63736d 100644
--- a/tests/runner/http/test_request.py
+++ b/tests/runner/http/test_request.py
@@ -39,6 +39,7 @@
 
 def test_request_config_handler():
     builder = runner_utils.new_builder()
+
     r = default_request()
     req = RunnerHttpRequest(r)
     req.conf_token = 0
@@ -90,6 +91,8 @@
     assert req.args == {"hello": "Python"}
     req.src_ip = "127.0.0.1"
     assert req.src_ip == "127.0.0.1"
+    req.err_code = 400
+    assert req.err_code == 400
     req.reset()
     assert req.rpc_type == 0
     assert req.rpc_buf == b''
diff --git a/tests/runner/server/test_handle.py b/tests/runner/server/test_handle.py
index 9d07f61..9ddaa34 100644
--- a/tests/runner/server/test_handle.py
+++ b/tests/runner/server/test_handle.py
@@ -62,12 +62,15 @@
     return builder.Output()
 
 
-def default_call_buffer(token: int = 0, id: int = 1):
+def default_call_buffer(token: int = 0, id: int = 1, ipv6: bool = False):
     builder = runner_utils.new_builder()
     # request path
     path = builder.CreateString("/hello/python/runner")
     # request ip
-    src_ip = bytes(bytearray([127, 0, 0, 1]))
+    if ipv6:
+        src_ip = bytes(bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]))
+    else:
+        src_ip = bytes(bytearray([127, 0, 0, 1]))
     src_ip = builder.CreateByteVector(src_ip)
     # request args
     arg_k = builder.CreateString("hello")
@@ -120,7 +123,7 @@
     handle = RunnerServerHandle(r)
     response = handle.dispatch()
     err = ErrResp.GetRootAsResp(response.Output())
-    assert err.Code() == ErrCode.CONF_TOKEN_NOT_FOUND
+    assert err.Code() == ErrCode.BAD_REQUEST
 
     buf = default_plugin_buffer("stop")
     r.request.ty = runner_utils.RPC_PREPARE_CONF
@@ -171,6 +174,13 @@
     rewrite.Init(resp.Action().Bytes, resp.Action().Pos)
     assert rewrite.Path() == b'/a6/python/runner'
 
+    r.request.data = default_call_buffer(conf_token, ipv6=True)
+    handle = RunnerServerHandle(r)
+    response = handle.dispatch()
+    resp = HCResp.GetRootAsResp(response.Output())
+    assert resp.Id() > 0
+    assert resp.ActionType() == HCAction.Rewrite
+
     r.request.ty = runner_utils.RPC_HTTP_REQ_CALL
     r.request.data = default_call_buffer(conf_token, 0)
     handle = RunnerServerHandle(r)
@@ -182,4 +192,4 @@
     handle = RunnerServerHandle(r)
     response = handle.dispatch()
     reps = ErrResp.GetRootAs(response.Output())
-    assert reps.Code() == ErrCode.CONF_TOKEN_NOT_FOUND
+    assert reps.Code() == ErrCode.BAD_REQUEST
diff --git a/tests/runner/utils/test_common.py b/tests/runner/utils/test_common.py
index accbf65..5cb7d2f 100644
--- a/tests/runner/utils/test_common.py
+++ b/tests/runner/utils/test_common.py
@@ -20,6 +20,36 @@
 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
+from A6.HTTPReqCall import Req as A6HTTPReqCallReq
+from A6 import TextEntry as A6TextEntry
+
+
+def get_request_buffer():
+    builder = runner_utils.new_builder()
+    # request path
+    path = builder.CreateString("/hello/python/runner")
+    # request ip
+    src_ip = builder.CreateByteVector(bytes([127, 0, 0, 1]))
+
+    # request headers
+    head_k = builder.CreateString("hello")
+    head_v = builder.CreateString("world")
+    A6TextEntry.Start(builder)
+    A6TextEntry.AddName(builder, head_k)
+    A6TextEntry.AddValue(builder, head_v)
+    headers = A6TextEntry.End(builder)
+    A6HTTPReqCallReq.StartHeadersVector(builder, 1)
+    builder.PrependUOffsetTRelative(headers)
+    headers_vec = builder.EndVector()
+
+    A6HTTPReqCallReq.Start(builder)
+    A6HTTPReqCallReq.AddId(builder, 1)
+    A6HTTPReqCallReq.AddPath(builder, path)
+    A6HTTPReqCallReq.AddSrcIp(builder, src_ip)
+    A6HTTPReqCallReq.AddHeaders(builder, headers_vec)
+    req = A6HTTPReqCallReq.End(builder)
+    builder.Finish(req)
+    return builder.Output()
 
 
 def test_get_method_code_by_name():
@@ -82,3 +112,21 @@
     assert obj
     obj = runner_utils.get_vector_object(HCAction.Action.Stop, VECTOR_TYPE_QUERY)
     assert not obj
+
+
+def test_parse_dict_vector():
+    buf = get_request_buffer()
+    req = A6HTTPReqCallReq.Req.GetRootAsReq(buf)
+    parse_fail = runner_utils.parse_dict_vector(req, 0)
+    assert parse_fail == {}
+    parse_ok = runner_utils.parse_dict_vector(req, runner_utils.VECTOR_TYPE_HEADER)
+    assert parse_ok.get("hello") == "world"
+
+
+def test_parse_list_vector():
+    buf = get_request_buffer()
+    req = A6HTTPReqCallReq.Req.GetRootAsReq(buf)
+    parse_fail = runner_utils.parse_list_vector(req, 0)
+    assert parse_fail == []
+    parse_ok = runner_utils.parse_list_vector(req, runner_utils.VECTOR_TYPE_SOURCE_IP)
+    assert parse_ok == [127, 0, 0, 1]