Skip to content
Permalink
Browse files
docs: add the-internal-of-apisix-java-plugin-runner.md, development.m…
…d and how-it-works.md (#19)
  • Loading branch information
tzssangglass committed May 31, 2021
1 parent 732cd2d commit ecf80b2f1838b7f367a2909d86461636d861f5c8
Showing 15 changed files with 531 additions and 82 deletions.
@@ -0,0 +1,119 @@
Development

This document explains how to get started to develop the apisix-java-plugin-runner.

Prerequisites
-------------

* JDK 8
* APISIX 2.6.0
* Clone the [apisix-java-plugin-runner](https://github.com/apache/apisix-java-plugin-runner) project.
* Refer to [Debug](how-it-works.md#debug) to build the debug environment.

Install
-------

```shell
cd /path/to/apisix-java-plugin-runner
./mvnw install
```

Write Filter
------------

Refer to the code in the [sample](https://github.com/apache/apisix-java-plugin-runner/tree/main/sample)
to learn how to extend `PluginFilter`, define the order, rewrite requests and stop requests.

#### Code Location

You need to put the code in [runner-plugin-sdk](https://github.com/apache/apisix-java-plugin-runner/tree/main/runner-plugin-sdk/src/main/java/org/apache/apisix/plugin/runner)
so that the `apisix-java-plugin-runner.jar` will contain the filter implementation class you wrote when you package it.

#### The order of filter execution

The order of execution of the filter in the runner is determined by the index of the `conf` array in the `ext-plugin-pre-req` or `ext-plugin-post-req` configuration.

#### The name of filter execution

The requests go through filters that are dynamically configured on APISIX.
For example, if the following configuration is done on APISIX

```shell
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri":"/hello",
"plugins":{
"ext-plugin-pre-req":{
"conf":[
{
"name":"FooFilter",
"value":"bar"
}
]
}
},
"upstream":{
"nodes":{
"127.0.0.1:1980":1
},
"type":"roundrobin"
}
}'
```

apisix-java-plugin-runner will look for implementation classes named `FooFilter`,
and the name of each filter's implementation class is the return value of its overridden function `public String name()`.


#### Rewrite Request

If you perform the following function call in the filter chain of the implementation class

* request.setPath()
* request.setHeader()
* request.setArg()

this means to rewrit the current request, the upstream server will receive
the relevant parameters rewritten here.

#### Stop Request

If you perform the following function call in the filter chain of the implementation class

* response.setStatusCode()
* response.setHeader()
* response.setBody()

this means to stop the current request, the client will receive
the relevant parameters generated here.

Test
----

### Run Unit Test Suites

```shell
cd /path/to/apisix-java-plugin-runner
./mvnw test
```


### Mimic practical environment

If you want to mimic the practical environment, you need to configure the route on APISIX
by having the request go through the filter you want to test, for example

```json
"plugins":{
"ext-plugin-pre-req":{
"conf":[
{
"name":"FooFilter",
"value":"bar"
}
]
}
}
```

and then make a request to APISIX to trigger the route.
@@ -0,0 +1,87 @@
# How It Works

This article explains how apisix-java-plugin-runner collaborate with [Apache APISIX](https://apisix.apache.org) to run plugins written in java.

## Run Mode

apisix-java-plugin-runner can be run alone or bundled with Apache APISIX.
It depends on whether you need to debug it or run it.

### Debug

If you are developing a new plugin and need to debug the code, then you can run the main class
[PluginRunnerApplication](https://github.com/apache/apisix-java-plugin-runner/blob/main/runner-starter/src/main/java/org/apache/apisix/plugin/runner/PluginRunnerApplication.java),
and before start, you need to set the following two environment variables:

- APISIX_LISTEN_ADDRESS: apisix-java-plugin-runner and APISIX for inter-process communication (Unix Domain Socket) socket type file address.
And do not need to actively create this file, apisix-java-plugin-runner will automatically create this file when it starts.
- APISIX_CONF_EXPIRE_TIME: the time that APISIX's configuration is cached in the apisix-java-plugin-runner process.

For example, if you start apisix-java-plugin-runner as a jar package, pass the environment variables as below

```shell
java -jar -DAPISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock -DAPISIX_CONF_EXPIRE_TIME=3600 /path/to/apisix-java-plugin-runner.jar
```

Note: Refer to [apisix-java-plugin-runner.jar](#run) to get it.

and add the following configure in the `config.yaml` file of APISIX

```yaml
ext-plugin:
path_for_test: /tmp/runner.sock
```

The `/tmp/runner.sock` is the address of the file where apisix-java-plugin-runner
and APISIX communicate between processes and must be consistent.

Note: If you see some error logs like

```
phase_func(): failed to connect to the unix socket unix:/tmp/runner.sock: permission denied
```

in the `error.log` of APISIX, you can change the permissions of this file for debug, execute commands like

```shell
chmod 777 /tmp/runner.sock
```

### Run

No environment variables need to be set in Run mode, execute

```shell
cd /path/to/apisix-java-plugin-runner
./mvnw package
```

to built apisix-java-plugin-runner as a jar package, then you will see the `dist` directory, execute

```
cd dist
tar -zxvf apache-apisix-runner-bin.tar.gz
```

the layout of files in the `dist` directory is as below

```
dist
├── apache-apisix-runner-bin.tar.gz
└── apisix-runner-bin
├── apisix-java-plugin-runner.jar
├── bin
│   ├── shutdown.sh
│   └── startup.sh
├── LICENSE
├── NOTICE
└── README.md
```

then add the following configure in the `config.yaml` file of APISIX

```yaml
ext-plugin:
cmd: ['java', '-jar', '-Xmx4g', '-Xms4g', '/path/to/apisix-runner-bin/apisix-java-plugin-runner.jar']
```
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,58 @@
The Internal of apisix java plugin runner

This article explains the internal design of apisix-java-plugin-runner.

## Table of Contents

- [Overview](#overview)
- [Communication](#communication)
- [Serialization](#serialization)
- [Codec](#codec)

## Overview

The apisix-java-plugin-runner designed as a TCP server built using [reactor-netty](https://github.com/reactor/reactor-netty),
it provides a `PluginFilter` interface for users to implement.

Users only need to focus on their business logic, not on the details of how the apisix java plugin runner communicates with APISIX.

The inter-process communication between them is depicted by the following diagram.

![the-internal-of-apisix-java-plugin-runner](./images/the-internal-of-apisix-java-plugin-runner.png)

## Communication

apisix-java-plugin-runner and APISIX use the Unix Domain Socket for inter-process communication,
so they need to be deployed in the same instance.

apisix-java-plugin-runner is managed by APISIX. APISIX starts the apisix-java-plugin-runner when it starts and ends it when it
ends. if the apisix-java-plugin-runner quits in the middle, APISIX will restart it automatically.

## Serialization

Refer to [flatbuffers](https://github.com/google/flatbuffers)

FlatBuffers is a cross platform serialization library architected for maximum memory efficiency.
It allows you to directly access serialized data without parsing/unpacking it first, while still having great forward/backward compatibility.

You can refer to the [ext-plugin.fbs](https://github.com/api7/ext-plugin-proto/blob/main/ext-plugin.fbs)
schema file to see how Lua and Java layout the serialized objects.

## Codec

apisix-java-plugin-runner and APISIX use a private binary protocol for coding and decoding.
The protocol format is

```
1 byte of type + 3 bytes of length + data
```

The type can be 0 ~ 7, and the length can be [0, 8M). The length of data is determined by length.

The current type takes the following values

* 0 means error
* 1 means prepare_conf
* 2 means http_req_call

The binary data generated by the flatbuffer serialization is placed in the data segment.
@@ -91,5 +91,10 @@
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -111,7 +111,7 @@ void testRewriteResponseEncode() {
HttpResponse httpResponse = new HttpResponse(0L);
// set path, args, req header means rewrite request
httpResponse.setPath("/hello");
httpResponse.setArgs("foo", "bar");
httpResponse.setArg("foo", "bar");
httpResponse.setReqHeader("Server", "APISIX");
ByteBuffer result = flatBuffersEncoder.encode(httpResponse);
result.position(4);
@@ -156,7 +156,7 @@ void testMixStopAndRewriteResponseEncode() {
HttpResponse httpResponse = new HttpResponse(0L);
// set path, args, req header means rewrite request
httpResponse.setPath("/hello");
httpResponse.setArgs("foo", "bar");
httpResponse.setArg("foo", "bar");
httpResponse.setReqHeader("Server", "APISIX");

// set status, body, resp header means stop request
@@ -65,11 +65,6 @@ public String name() {
public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
return chain.filter(request, response);
}

@Override
public int getOrder() {
return 0;
}
});

filters.put("CatFilter", new PluginFilter() {
@@ -82,11 +77,6 @@ public String name() {
public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
return chain.filter(request, response);
}

@Override
public int getOrder() {
return 1;
}
});
cache = CacheBuilder.newBuilder().expireAfterWrite(3600, TimeUnit.SECONDS).maximumSize(1000).build();
a6ConfigHandler = new A6ConfigHandler(cache, filters);
@@ -112,7 +102,7 @@ void testAddFilter1() {
A6Conf config = cache.getIfPresent(0L);
Assertions.assertNotNull(config.getChain());
Assertions.assertEquals(config.getChain().getFilters().size(), 1);
Assertions.assertEquals(config.getChain().getFilters().get(0).getOrder(), 0);
Assertions.assertEquals(config.getChain().getIndex(), 0);
Assertions.assertEquals(config.get("FooFilter"), "Bar");

}
@@ -141,8 +131,6 @@ void testAddFilter2() {

A6Conf config = cache.getIfPresent(0L);
Assertions.assertEquals(config.getChain().getFilters().size(), 2);
Assertions.assertEquals(config.getChain().getFilters().get(0).getOrder(), 0);
Assertions.assertEquals(config.getChain().getFilters().get(1).getOrder(), 1);
}

@Test

0 comments on commit ecf80b2

Please sign in to comment.