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]