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

[apisix-java-plugin-runner] branch main updated: test: add test case about codec (#14)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new aeda437  test: add test case about codec (#14)
aeda437 is described below

commit aeda437a7597302049f2b5c4f73bcf2dde80d68d
Author: tzssangglass <tz...@gmail.com>
AuthorDate: Thu May 27 15:57:10 2021 +0800

    test: add test case about codec (#14)
    
    Co-authored-by: 罗泽轩 <sp...@gmail.com>
---
 .../plugin/runner/codec/frame/FrameCodec.java      |  68 --------
 .../runner/codec/impl/FlatBuffersDecoder.java      |  55 ++++++-
 .../runner/codec/impl/FlatBuffersEncoder.java      |  27 +++-
 .../runner/codec/impl/FlatBuffersDecoderTest.java  | 161 +++++++++++++++++++
 .../runner/codec/impl/FlatBuffersEncoderTest.java  | 172 +++++++++++++++++++++
 .../runner/server/ApplicationRunnerTest.java       |  77 ---------
 .../apache/apisix/plugin/runner/HttpResponse.java  |  27 ++--
 .../apisix/plugin/runner/HttpRewriteResponse.java  |  43 ------
 .../apisix/plugin/runner/HttpStopResponse.java     |  42 -----
 runner-starter/pom.xml                             |  11 ++
 .../plugin/runner/PluginRunnerApplicationTest.java |  47 ++++++
 11 files changed, 474 insertions(+), 256 deletions(-)

diff --git a/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/frame/FrameCodec.java b/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/frame/FrameCodec.java
deleted file mode 100644
index 8cc6f7b..0000000
--- a/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/frame/FrameCodec.java
+++ /dev/null
@@ -1,68 +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.
- */
-
-package org.apache.apisix.plugin.runner.codec.frame;
-
-import java.nio.ByteBuffer;
-
-public class FrameCodec {
-
-    public static int getDataLength(ByteBuffer payload) {
-        byte[] bytes = new byte[3];
-        for (int i = 0; i < 3; i++) {
-            bytes[i] = payload.get();
-        }
-        return byte3ToInt(bytes);
-    }
-
-    public static ByteBuffer getBody(ByteBuffer payload) {
-        int length = getDataLength(payload);
-        ByteBuffer buffer = payload.slice();
-        byte[] dst = new byte[length];
-        buffer.get(dst, 0, length);
-        buffer.flip();
-        return buffer;
-    }
-
-    public static ByteBuffer setBody(ByteBuffer payload, byte type) {
-        byte[] data = new byte[payload.remaining()];
-        payload.get(data);
-        ByteBuffer buffer = ByteBuffer.allocate(data.length + 4);
-        buffer.put(type);
-        // data length
-        byte[] length = intToByte3(data.length);
-        buffer.put(length);
-        // data
-        buffer.put(data);
-        buffer.flip();
-        return buffer;
-    }
-
-    private static byte[] intToByte3(int i) {
-        byte[] targets = new byte[3];
-        targets[2] = (byte) (i & 0xFF);
-        targets[1] = (byte) (i >> 8 & 0xFF);
-        targets[0] = (byte) ((i >> 16 & 0xFF));
-        return targets;
-    }
-
-    private static int byte3ToInt(byte[] bytes) {
-        return bytes[2] & 0xFF |
-                (bytes[1] & 0xFF << 8) |
-                (bytes[0] & 0xFF << 16);
-    }
-}
diff --git a/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersDecoder.java b/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersDecoder.java
index 560897a..8b199e8 100644
--- a/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersDecoder.java
+++ b/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersDecoder.java
@@ -23,10 +23,10 @@ import org.apache.apisix.plugin.runner.A6ErrRequest;
 import org.apache.apisix.plugin.runner.A6Request;
 import org.apache.apisix.plugin.runner.HttpRequest;
 import org.apache.apisix.plugin.runner.codec.PluginRunnerDecoder;
-import org.apache.apisix.plugin.runner.codec.frame.FrameCodec;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 
 public class FlatBuffersDecoder implements PluginRunnerDecoder {
@@ -35,13 +35,35 @@ public class FlatBuffersDecoder implements PluginRunnerDecoder {
 
     @Override
     public A6Request decode(ByteBuffer buffer) {
-        byte type = buffer.get();
-        ByteBuffer body = FrameCodec.getBody(buffer);
+        byte type;
+        try {
+            type = buffer.get();
+        } catch (BufferUnderflowException e) {
+            logger.error("receive empty data");
+            return new A6ErrRequest(Code.BAD_REQUEST);
+        }
+
+        ByteBuffer body;
         switch (type) {
             case 1:
-                return A6ConfigRequest.from(body);
+                A6ConfigRequest a6ConfigRequest;
+                try {
+                    body = getBody(buffer);
+                    a6ConfigRequest = A6ConfigRequest.from(body);
+                } catch (BufferUnderflowException | IndexOutOfBoundsException e) {
+                    logger.error("receive error data length");
+                    return new A6ErrRequest(Code.BAD_REQUEST);
+                }
+                return a6ConfigRequest;
             case 2:
-                return HttpRequest.from(body);
+                HttpRequest httpRequest;
+                try {
+                    body = getBody(buffer);
+                    httpRequest = HttpRequest.from(body);
+                } catch (BufferUnderflowException | IndexOutOfBoundsException e) {
+                    return new A6ErrRequest(Code.BAD_REQUEST);
+                }
+                return httpRequest;
             default:
                 break;
         }
@@ -49,4 +71,27 @@ public class FlatBuffersDecoder implements PluginRunnerDecoder {
         logger.error("receive unsupported type: {}", type);
         return new A6ErrRequest(Code.BAD_REQUEST);
     }
+
+    private int getDataLength(ByteBuffer payload) {
+        byte[] bytes = new byte[3];
+        for (int i = 0; i < 3; i++) {
+            bytes[i] = payload.get();
+        }
+        return byte3ToInt(bytes);
+    }
+
+    private ByteBuffer getBody(ByteBuffer payload) throws BufferUnderflowException, IndexOutOfBoundsException {
+        int length = getDataLength(payload);
+        ByteBuffer buffer = payload.slice();
+        byte[] dst = new byte[length];
+        buffer.get(dst, 0, length);
+        buffer.flip();
+        return buffer;
+    }
+
+    private int byte3ToInt(byte[] bytes) {
+        return bytes[2] & 0xFF |
+                (bytes[1] & 0xFF << 8) |
+                (bytes[0] & 0xFF << 16);
+    }
 }
diff --git a/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoder.java b/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoder.java
index 973074b..5abde30 100644
--- a/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoder.java
+++ b/runner-core/src/main/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoder.java
@@ -19,7 +19,6 @@ package org.apache.apisix.plugin.runner.codec.impl;
 
 import org.apache.apisix.plugin.runner.A6Response;
 import org.apache.apisix.plugin.runner.codec.PluginRunnerEncoder;
-import org.apache.apisix.plugin.runner.codec.frame.FrameCodec;
 
 import java.nio.ByteBuffer;
 
@@ -29,8 +28,30 @@ public class FlatBuffersEncoder implements PluginRunnerEncoder {
     public ByteBuffer encode(A6Response response) {
         ByteBuffer buffer = response.encode();
         if (null != response.getErrResponse()) {
-            return FrameCodec.setBody(buffer, (byte) 0);
+            return setBody(buffer, (byte) 0);
         }
-        return FrameCodec.setBody(buffer, response.getType());
+        return setBody(buffer, response.getType());
+    }
+
+    private ByteBuffer setBody(ByteBuffer payload, byte type) {
+        byte[] data = new byte[payload.remaining()];
+        payload.get(data);
+        ByteBuffer buffer = ByteBuffer.allocate(data.length + 4);
+        buffer.put(type);
+        // data length
+        byte[] length = intToByte3(data.length);
+        buffer.put(length);
+        // data
+        buffer.put(data);
+        buffer.flip();
+        return buffer;
+    }
+
+    private byte[] intToByte3(int i) {
+        byte[] targets = new byte[3];
+        targets[2] = (byte) (i & 0xFF);
+        targets[1] = (byte) (i >> 8 & 0xFF);
+        targets[0] = (byte) ((i >> 16 & 0xFF));
+        return targets;
     }
 }
diff --git a/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersDecoderTest.java b/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersDecoderTest.java
new file mode 100644
index 0000000..ba6708d
--- /dev/null
+++ b/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersDecoderTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+package org.apache.apisix.plugin.runner.codec.impl;
+
+import com.google.flatbuffers.FlatBufferBuilder;
+import io.github.api7.A6.Err.Code;
+import io.github.api7.A6.TextEntry;
+import org.apache.apisix.plugin.runner.A6ConfigRequest;
+import org.apache.apisix.plugin.runner.A6ErrRequest;
+import org.apache.apisix.plugin.runner.A6Request;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@ExtendWith(OutputCaptureExtension.class)
+@DisplayName("test decode data")
+class FlatBuffersDecoderTest {
+
+    @InjectMocks
+    FlatBuffersDecoder flatBuffersDecoder;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    @DisplayName("test empty data")
+    void testEmptyData(CapturedOutput capturedOutput) {
+        byte[] bytes = new byte[]{};
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        A6Request result = flatBuffersDecoder.decode(buffer);
+        Assertions.assertEquals(Code.BAD_REQUEST, ((A6ErrRequest) result).getCode());
+        Assertions.assertTrue(capturedOutput.getOut().contains("receive empty data"));
+    }
+
+    @Test
+    @DisplayName("test unsupported type")
+    void testUnsupportedType(CapturedOutput capturedOutput) {
+        byte[] bytes = new byte[]{4};
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        A6Request result = flatBuffersDecoder.decode(buffer);
+        Assertions.assertEquals(Code.BAD_REQUEST, ((A6ErrRequest) result).getCode());
+        Assertions.assertTrue(capturedOutput.getOut().contains("receive unsupported type: 4"));
+    }
+
+    @Test
+    @DisplayName("test error data length(1)")
+    void testErrorDataLength1(CapturedOutput capturedOutput) {
+        // data length is greater than actual length
+        byte[] bytes = new byte[]{1, 0, 0, 3, 0};
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        A6Request result = flatBuffersDecoder.decode(buffer);
+        Assertions.assertEquals(Code.BAD_REQUEST, ((A6ErrRequest) result).getCode());
+        Assertions.assertTrue(capturedOutput.getOut().contains("receive error data length"));
+    }
+
+    @Test
+    @DisplayName("test error data length(2)")
+    void testErrorDataLength2(CapturedOutput capturedOutput) {
+        // data length equal to 0
+        byte[] bytes = new byte[]{1, 0, 0, 0, 0};
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        A6Request result = flatBuffersDecoder.decode(buffer);
+        Assertions.assertEquals(Code.BAD_REQUEST, ((A6ErrRequest) result).getCode());
+        Assertions.assertTrue(capturedOutput.getOut().contains("receive error data length"));
+    }
+
+    @Test
+    @DisplayName("test error data length(3)")
+    void testErrorDataLength3(CapturedOutput capturedOutput) {
+        // wrong data content
+        byte[] bytes = new byte[]{1, 0, 0, 1, 0, 1};
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        A6Request result = flatBuffersDecoder.decode(buffer);
+        Assertions.assertEquals(Code.BAD_REQUEST, ((A6ErrRequest) result).getCode());
+        Assertions.assertTrue(capturedOutput.getOut().contains("receive error data length"));
+    }
+
+    @Test
+    @DisplayName("test get body")
+    void testGetBody() {
+        // mock client assembly data
+        FlatBufferBuilder builder = new FlatBufferBuilder();
+        int foo = builder.createString("foo");
+        int bar = builder.createString("bar");
+        int confIndex = TextEntry.createTextEntry(builder, foo, bar);
+        int vector = io.github.api7.A6.PrepareConf.Req.createConfVector(builder, new int[]{confIndex});
+        io.github.api7.A6.PrepareConf.Req.startReq(builder);
+        io.github.api7.A6.PrepareConf.Req.addConf(builder, vector);
+        builder.finish(io.github.api7.A6.PrepareConf.Req.endReq(builder));
+        byte[] data = new byte[builder.dataBuffer().remaining()];
+        builder.dataBuffer().get(data, 0, data.length);
+        // use the correct data length
+        byte[] header = new byte[]{1, 0, 0, (byte) data.length};
+        byte[] bytes = new byte[header.length + data.length];
+        // assembly data format
+        System.arraycopy(header, 0, bytes, 0, header.length);
+        System.arraycopy(data, 0, bytes, header.length, data.length);
+
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        A6ConfigRequest configReq = (A6ConfigRequest) flatBuffersDecoder.decode(buffer);
+        for (int i = 0; i < configReq.getReq().confLength(); i++) {
+            TextEntry conf = configReq.getReq().conf(i);
+            Assertions.assertEquals("foo", conf.name());
+            Assertions.assertEquals("bar", conf.value());
+        }
+    }
+
+    @Test
+    @DisplayName("test get body with error data length")
+    void testGetBody2() {
+        // {"name":"foo", "value":"bar"}
+        FlatBufferBuilder builder = new FlatBufferBuilder();
+        int foo = builder.createString("foo");
+        int bar = builder.createString("bar");
+        int confIndex = TextEntry.createTextEntry(builder, foo, bar);
+        int vector = io.github.api7.A6.PrepareConf.Req.createConfVector(builder, new int[]{confIndex});
+        io.github.api7.A6.PrepareConf.Req.startReq(builder);
+        io.github.api7.A6.PrepareConf.Req.addConf(builder, vector);
+        builder.finish(io.github.api7.A6.PrepareConf.Req.endReq(builder));
+        byte[] data = new byte[builder.dataBuffer().remaining()];
+        builder.dataBuffer().get(data, 0, data.length);
+        // se the error data length
+        byte errDateLength = (byte) (data.length / 2);
+        byte[] header = new byte[]{1, 0, 0, errDateLength};
+        byte[] bytes = new byte[header.length + data.length];
+        // assembly data format
+        System.arraycopy(header, 0, bytes, 0, header.length);
+        System.arraycopy(data, 0, bytes, header.length, data.length);
+
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        A6ConfigRequest configReq = (A6ConfigRequest) flatBuffersDecoder.decode(buffer);
+        assertThrows(IndexOutOfBoundsException.class, () -> configReq.getReq().conf(0));
+    }
+}
diff --git a/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoderTest.java b/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoderTest.java
new file mode 100644
index 0000000..76e5175
--- /dev/null
+++ b/runner-core/src/test/java/org/apache/apisix/plugin/runner/codec/impl/FlatBuffersEncoderTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+package org.apache.apisix.plugin.runner.codec.impl;
+
+import io.github.api7.A6.Err.Code;
+import io.github.api7.A6.HTTPReqCall.Action;
+import io.github.api7.A6.HTTPReqCall.Rewrite;
+import io.github.api7.A6.HTTPReqCall.Stop;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import org.apache.apisix.plugin.runner.A6ConfigResponse;
+import org.apache.apisix.plugin.runner.A6ErrResponse;
+import org.apache.apisix.plugin.runner.HttpResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.nio.ByteBuffer;
+
+@DisplayName("test encode data")
+class FlatBuffersEncoderTest {
+    FlatBuffersEncoder flatBuffersEncoder = new FlatBuffersEncoder();
+
+    @Test
+    @DisplayName("test encode error response(1)")
+    void testErrResponseEncode1() {
+        A6ErrResponse errResponse = new A6ErrResponse(Code.BAD_REQUEST);
+        ByteBuffer result = flatBuffersEncoder.encode(errResponse);
+        byte[] res = new byte[result.remaining()];
+        result.get(res);
+
+        //example
+        byte[] example = new byte[]{0, 0, 0, 12, 8, 0, 0, 0, 4, 0, 4, 0, 4, 0, 0, 0};
+        Assertions.assertArrayEquals(res, example);
+    }
+
+    @Test
+    @DisplayName("test encode error response(2)")
+    void testErrResponseEncode2() {
+        A6ErrResponse errResponse = new A6ErrResponse(Code.BAD_REQUEST);
+        ByteBuffer result = flatBuffersEncoder.encode(errResponse);
+        result.position(4);
+        io.github.api7.A6.Err.Resp resp = io.github.api7.A6.Err.Resp.getRootAsResp(result);
+        Assertions.assertEquals(Code.BAD_REQUEST, resp.code());
+    }
+
+    @Test
+    @DisplayName("test prepare conf response(1)")
+    void testPrepareConfResponseEncode1() {
+        A6ConfigResponse configResponse = new A6ConfigResponse(0L);
+        ByteBuffer result = flatBuffersEncoder.encode(configResponse);
+        byte[] res = new byte[result.remaining()];
+        result.get(res);
+
+        //example
+        byte[] example = new byte[]{1, 0, 0, 12, 8, 0, 0, 0, 4, 0, 4, 0, 4, 0, 0, 0};
+        Assertions.assertArrayEquals(res, example);
+    }
+
+    @Test
+    @DisplayName("test prepare conf response(2)")
+    void testPrepareConfResponseEncode2() {
+        A6ConfigResponse configResponse = new A6ConfigResponse(0L);
+        ByteBuffer result = flatBuffersEncoder.encode(configResponse);
+        result.position(4);
+        io.github.api7.A6.PrepareConf.Resp resp = io.github.api7.A6.PrepareConf.Resp.getRootAsResp(result);
+        Assertions.assertEquals(resp.confToken(), 0L);
+    }
+
+    @Test
+    @DisplayName("test http call response")
+    void testHTTPCallResponseEncode1() {
+        HttpResponse errResponse = new HttpResponse(0L);
+        ByteBuffer result = flatBuffersEncoder.encode(errResponse);
+        byte[] res = new byte[result.remaining()];
+        result.get(res);
+
+        //example
+        byte[] example = new byte[]{2, 0, 0, 12, 8, 0, 0, 0, 4, 0, 4, 0, 4, 0, 0, 0};
+        Assertions.assertArrayEquals(res, example);
+    }
+
+    @Test
+    @DisplayName("test http call response, action: none")
+    void testHTTPCallResponseEncode2() {
+        HttpResponse httpResponse = new HttpResponse(0L);
+        ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
+        result.position(4);
+        io.github.api7.A6.HTTPReqCall.Resp resp = io.github.api7.A6.HTTPReqCall.Resp.getRootAsResp(result);
+        Assertions.assertEquals(resp.id(), 0L);
+        // default action is none
+        Assertions.assertEquals(resp.actionType(), Action.NONE);
+    }
+
+    @Test
+    @DisplayName("test http call response, action: rewrite")
+    void testRewriteResponseEncode() {
+        HttpResponse httpResponse = new HttpResponse(0L);
+        // set path, args, req header means rewrite request
+        httpResponse.setPath("/hello");
+        httpResponse.setArgs("foo", "bar");
+        httpResponse.setReqHeader("Server", "APISIX");
+        ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
+        result.position(4);
+        io.github.api7.A6.HTTPReqCall.Resp resp = io.github.api7.A6.HTTPReqCall.Resp.getRootAsResp(result);
+        Assertions.assertEquals(resp.actionType(), Action.Rewrite);
+        Rewrite rewrite = (Rewrite) resp.action(new Rewrite());
+
+        Assertions.assertEquals(rewrite.path(), "/hello");
+        Assertions.assertEquals(rewrite.args(0).name(), "foo");
+        Assertions.assertEquals(rewrite.args(0).value(), "bar");
+        Assertions.assertEquals(rewrite.headers(0).name(), "Server");
+        Assertions.assertEquals(rewrite.headers(0).value(), "APISIX");
+    }
+
+    @Test
+    @DisplayName("test http call response, action: stop")
+    void testStopResponseEncode() {
+        HttpResponse httpResponse = new HttpResponse(0L);
+        // set status, body, resp header means stop request
+        httpResponse.setStatus(HttpResponseStatus.UNAUTHORIZED);
+        httpResponse.setRespHeaders("code", "401");
+        httpResponse.setBody("Unauthorized");
+        ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
+        result.position(4);
+        io.github.api7.A6.HTTPReqCall.Resp resp = io.github.api7.A6.HTTPReqCall.Resp.getRootAsResp(result);
+        Assertions.assertEquals(resp.actionType(), Action.Stop);
+        Stop stop = (Stop) resp.action(new Stop());
+
+        Assertions.assertEquals(stop.status(), 401);
+        StringBuilder body = new StringBuilder();
+        for (int i = 0; i < stop.bodyLength(); i++) {
+            body.append((char) stop.body(i));
+        }
+        Assertions.assertEquals(body.toString(), "Unauthorized");
+        Assertions.assertEquals(stop.headers(0).name(), "code");
+        Assertions.assertEquals(stop.headers(0).value(), "401");
+    }
+
+    @Test
+    @DisplayName("test mix stop and rewrite response, will use stop response")
+    void testMixStopAndRewriteResponseEncode() {
+        HttpResponse httpResponse = new HttpResponse(0L);
+        // set path, args, req header means rewrite request
+        httpResponse.setPath("/hello");
+        httpResponse.setArgs("foo", "bar");
+        httpResponse.setReqHeader("Server", "APISIX");
+
+        // set status, body, resp header means stop request
+        httpResponse.setStatus(HttpResponseStatus.UNAUTHORIZED);
+        httpResponse.setRespHeaders("code", "401");
+        httpResponse.setBody("Unauthorized");
+        ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
+        result.position(4);
+        io.github.api7.A6.HTTPReqCall.Resp resp = io.github.api7.A6.HTTPReqCall.Resp.getRootAsResp(result);
+        Assertions.assertEquals(resp.actionType(), Action.Stop);
+    }
+}
diff --git a/runner-core/src/test/java/org/apache/apisix/plugin/runner/server/ApplicationRunnerTest.java b/runner-core/src/test/java/org/apache/apisix/plugin/runner/server/ApplicationRunnerTest.java
deleted file mode 100644
index a7018c1..0000000
--- a/runner-core/src/test/java/org/apache/apisix/plugin/runner/server/ApplicationRunnerTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.apache.apisix.plugin.runner.server;
-
-import io.netty.channel.unix.DomainSocketAddress;
-import io.netty.channel.unix.DomainSocketChannel;
-import org.apache.apisix.plugin.runner.codec.PluginRunnerConfiguration;
-import org.apache.apisix.plugin.runner.handler.A6HandlerConfiguration;
-import org.apache.apisix.plugin.runner.handler.IOHandlerConfiguration;
-import org.apache.apisix.plugin.runner.server.config.TcpServerConfiguration;
-import org.apache.apisix.plugin.runner.service.CacheConfiguration;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.boot.WebApplicationType;
-import org.springframework.boot.builder.SpringApplicationBuilder;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-import reactor.netty.Connection;
-import reactor.netty.tcp.TcpClient;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-@DisplayName("Unix Domain Socket Listen Test")
-@ExtendWith(SpringExtension.class)
-class ApplicationRunnerTest {
-
-    protected ConfigurableApplicationContext context;
-
-    @BeforeEach
-    @DisplayName("start java plugin runner")
-    void setup() {
-        context = new SpringApplicationBuilder(ApplicationRunner.class,
-                IOHandlerConfiguration.class,
-                TcpServerConfiguration.class,
-                CacheConfiguration.class,
-                A6HandlerConfiguration.class,
-                PluginRunnerConfiguration.class)
-                .web(WebApplicationType.NONE)
-                .run();
-    }
-
-    @Test
-    @DisplayName("test listen on socket file success")
-    @Disabled
-    void testUDSConnectSuccess() {
-        Connection client =
-                TcpClient.create()
-                        .remoteAddress(() -> new DomainSocketAddress("/tmp/runner.sock"))
-                        .connectNow();
-        assertThat(client.channel().isOpen()).isTrue();
-        assertThat(client.channel().isActive()).isTrue();
-        assertThat(client.channel().isRegistered()).isTrue();
-        assertThat(client.channel() instanceof DomainSocketChannel).isTrue();
-        assertThat(client.channel().remoteAddress().toString()).isEqualTo("/tmp/runner.sock");
-        client.disposeNow();
-    }
-
-    @Test
-    @DisplayName("test listen on error socket file")
-    void testUDSConnectFail() {
-        Throwable exception = assertThrows(RuntimeException.class, () -> {
-            TcpClient.create()
-                    .remoteAddress(() -> new DomainSocketAddress("/tmp/error.sock"))
-                    .connectNow();
-        });
-        assertThat(exception.getMessage()).isEqualTo("java.io.FileNotFoundException");
-    }
-
-    @AfterEach
-    @DisplayName("shutdown java plugin runner")
-    void down() {
-        context.close();
-    }
-}
diff --git a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java
index a1ee383..bee6636 100644
--- a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java
+++ b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpResponse.java
@@ -18,15 +18,16 @@
 package org.apache.apisix.plugin.runner;
 
 import com.google.flatbuffers.FlatBufferBuilder;
-import io.github.api7.A6.DataEntry;
 import io.github.api7.A6.HTTPReqCall.Resp;
 import io.github.api7.A6.HTTPReqCall.Rewrite;
 import io.github.api7.A6.HTTPReqCall.Stop;
 import io.github.api7.A6.TextEntry;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -51,7 +52,7 @@ public class HttpResponse implements A6Response {
 
     private String path;
 
-    private Map<String, String> body;
+    private String body;
 
     private HttpResponseStatus status;
 
@@ -94,12 +95,9 @@ public class HttpResponse implements A6Response {
         respHeaders.put(headerKey, headerValue);
     }
 
-    public void setBody(String bodyKey, String bodyValue) {
+    public void setBody(String body) {
         actionType = ActionType.Stop;
-        if (Objects.isNull(body)) {
-            body = new HashMap<>();
-        }
-        body.put(bodyKey, bodyValue);
+        this.body = body;
     }
 
     public void setStatus(HttpResponseStatus status) {
@@ -154,16 +152,9 @@ public class HttpResponse implements A6Response {
         }
 
         int bodyIndex = -1;
-        if (!CollectionUtils.isEmpty(body)) {
-            byte[] bodyTexts = new byte[body.size()];
-            for (Map.Entry<String, String> arg : body.entrySet()) {
-                int i = -1;
-                int key = builder.createString(arg.getKey());
-                int value = builder.createString(arg.getValue());
-                int text = DataEntry.createDataEntry(builder, key, value);
-                bodyTexts[++i] = (byte) text;
-            }
-            bodyIndex = Stop.createBodyVector(builder, bodyTexts);
+        if (StringUtils.hasText(body)) {
+            byte[] bodyBytes = body.getBytes(StandardCharsets.US_ASCII);
+            bodyIndex = Stop.createBodyVector(builder, bodyBytes);
         }
 
         Stop.startStop(builder);
@@ -181,7 +172,7 @@ public class HttpResponse implements A6Response {
 
     private int buildRewriteResp(FlatBufferBuilder builder) {
         int pathIndex = -1;
-        if (Objects.isNull(path)) {
+        if (!Objects.isNull(path)) {
             pathIndex = builder.createString(path);
         }
 
diff --git a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpRewriteResponse.java b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpRewriteResponse.java
deleted file mode 100644
index 820aed8..0000000
--- a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpRewriteResponse.java
+++ /dev/null
@@ -1,43 +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.
- */
-
-package org.apache.apisix.plugin.runner;
-
-import java.util.Map;
-
-/**
- * table Rewrite {
- * path:string;
- * // To delete a request header, use a TextEntry without Value
- * // To change the upstream host, use a TextEntry with Name equals to `host`
- * headers:[TextEntry];
- * // To delete an argument, use a TextEntry without Value
- * args:[TextEntry];
- * }
- */
-@Deprecated
-public class HttpRewriteResponse extends HttpResponse {
-    
-    private String path;
-    
-    private Map<String, String> args;
-    
-    public HttpRewriteResponse(int requestId) {
-        super(requestId);
-    }
-    
-}
diff --git a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpStopResponse.java b/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpStopResponse.java
deleted file mode 100644
index bc5dbcb..0000000
--- a/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner/HttpStopResponse.java
+++ /dev/null
@@ -1,42 +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.
- */
-
-package org.apache.apisix.plugin.runner;
-
-/**
- * table Stop {
- * status:uint16;
- * headers:[TextEntry];
- * body:[ubyte];
- * }
- */
-@Deprecated
-public class HttpStopResponse extends HttpResponse {
-    
-    private int status;
-    
-    private byte[] body;
-    
-    public HttpStopResponse(int requestId) {
-        super(requestId);
-    }
-    
-    public void setBody(String body) {
-        this.body = body.getBytes();
-    }
-    
-}
diff --git a/runner-starter/pom.xml b/runner-starter/pom.xml
index be46e1f..72ce898 100644
--- a/runner-starter/pom.xml
+++ b/runner-starter/pom.xml
@@ -47,6 +47,17 @@
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/runner-starter/src/test/java/org/apache/apisix/plugin/runner/PluginRunnerApplicationTest.java b/runner-starter/src/test/java/org/apache/apisix/plugin/runner/PluginRunnerApplicationTest.java
new file mode 100644
index 0000000..0262ba1
--- /dev/null
+++ b/runner-starter/src/test/java/org/apache/apisix/plugin/runner/PluginRunnerApplicationTest.java
@@ -0,0 +1,47 @@
+package org.apache.apisix.plugin.runner;
+
+import io.netty.channel.unix.DomainSocketAddress;
+import io.netty.channel.unix.DomainSocketChannel;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import reactor.netty.Connection;
+import reactor.netty.tcp.TcpClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DisplayName("Unix Domain Socket Listen Test")
+class PluginRunnerApplicationTest {
+
+    @Test
+    void testMain() {
+        PluginRunnerApplication.main(new String[]{"args"});
+    }
+
+    @Test
+    @DisplayName("test listen on socket file success")
+    void testUDSConnectSuccess() {
+        Connection client =
+                TcpClient.create()
+                        .remoteAddress(() -> new DomainSocketAddress("/tmp/runner.sock"))
+                        .connectNow();
+        assertThat(client.channel().isOpen()).isTrue();
+        assertThat(client.channel().isActive()).isTrue();
+        assertThat(client.channel().isRegistered()).isTrue();
+        assertThat(client.channel() instanceof DomainSocketChannel).isTrue();
+        assertThat(client.channel().remoteAddress().toString()).isEqualTo("/tmp/runner.sock");
+        client.disposeNow();
+    }
+
+    @Test
+    @DisplayName("test listen on error socket file")
+    void testUDSConnectFail() {
+        Throwable exception = assertThrows(RuntimeException.class, () -> {
+            Connection client = TcpClient.create()
+                    .remoteAddress(() -> new DomainSocketAddress("/tmp/error.sock"))
+                    .connectNow();
+            client.disposeNow();
+        });
+        assertThat(exception.getMessage()).isEqualTo("java.io.FileNotFoundException");
+    }
+}