refactor: request processing flow (#36)
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 @@
- 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 @@
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 @@
# 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 @@
# 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
else:
return False
- def _gen_config_flat(self, builder: flatbuffers.Builder) -> int:
- A6PrepareConfResp.Start(builder)
- A6PrepareConfResp.AddConfToken(builder, self.token)
- return A6PrepareConfResp.End(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)
- def _gen_unknown_flat(self, builder: flatbuffers.Builder) -> int:
- A6ErrResp.Start(builder)
- A6ErrResp.AddCode(builder, self.error_code)
- return A6ErrResp.End(builder)
+ body_vector = runner_utils.create_str_vector(builder, self.body)
- 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
+ status_code = 200
+ if self.status_code > 0:
+ status_code = self.status_code
- 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
+ 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 @@
"""
self._name = name
self._config = {}
- self._action = PLUGIN_ACTION_REWRITE
@property
def name(self) -> str:
@@ -66,34 +62,3 @@
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 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 @@
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
+ self.r = r
- @property
- def type(self) -> int:
- return self._type
+ def dispatch(self) -> flatbuffers.Builder:
+ # init builder
+ builder = runner_utils.new_builder()
+ # parse request
+ req = NewHttpRequest(self.r)
- @type.setter
- def type(self, ty: int = 0) -> None:
- self._type = ty
+ 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
- @property
- def buffer(self) -> bytes:
- return self._buffer
+ 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
- @buffer.setter
- def buffer(self, buf: bytes = b'') -> None:
- self._buffer = buf
+ return builder
- @property
- def debug(self) -> bool:
- return self._debug
+ 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)
- @debug.setter
- def debug(self, debug: bool = False) -> None:
- self._debug = debug
+ 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
- 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()
+ # init response
+ resp = NewHttpResponse(self.r.request.ty)
+ resp.id = req.id
- return NewServerResponse(code=RESP_STATUS_CODE_OK, message=RESP_STATUS_MESSAGE_OK, data=response.Output(),
- ty=self.type)
+ # 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
- 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 changed
+ ok = resp.call_handler(builder)
+ if ok:
+ return builder
- response = resp.flatbuffers()
- return NewServerResponse(code=code, message=message, data=response.Output(),
- ty=self.type)
+ # 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
- @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)
+ return builder
- 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
+ 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.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 @@
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 @@
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 84%
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 91%
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 @@
@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 @@
## 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 @@
# 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 @@
## Prerequisites
-* Python 3.6+
+* Python 3.7+
* APISIX 2.7.0
@@ -35,6 +35,7 @@
```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 @@
#### 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 @@
### 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 @@
author="Jinchao Shuai",
author_email="dev@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 @@
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 @@
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 @@
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_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_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.
#
+import logging
+import socket
-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
+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 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 default_request():
+ sock = socket.socket()
+ logger = RunnerServerLogger(logging.INFO)
+ return RunnerRPCRequest(sock, logger)
-def test_buffer():
- handle = NewServerHandle(buf="Hello Python Runner".encode())
- assert handle.buffer == b"Hello Python Runner"
+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)
-
-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()
+ 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 @@
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()
-
- handle = NewServerHandle(ty=RPC_HTTP_REQ_CALL, buf=buf)
- response = handle.dispatch()
- assert response.code == RESP_STATUS_CODE_CONF_TOKEN_NOT_FOUND
- assert response.type == RPC_UNKNOWN
+ return builder.Output()
def test_dispatch_unknown():
- handle = NewServerHandle(ty=RPC_UNKNOWN)
+ r = default_request()
+ r.request.ty = runner_utils.RPC_UNKNOWN
+ 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
+ err = ErrResp.GetRootAsResp(response.Output())
+ assert err.Code() == ErrCode.BAD_REQUEST
+
+
+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()
+ 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 @@
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_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