Skip to main content

How to Write an Apache APISIX Plugin in Java

· 6 min read

Apache APISIX now supports writing plugins in Java! You can now write plugins in a programming language you are familiar with.

Introduction

Why Apache APISIX Supports Writing Plugins in Multiple Programming Languages?

Apache APISIX has been supporting customized plugins since the day it was born. But it only supported plugins written in Lua. As a result, developers need to have Lua and OpenResty-related development skills to actually write their own plugins. However, Lua and OpenResty are relatively less popular languages with fewer developers compared to Java and Go. Besides, learning Lua and OpenResty from scratch requires a lot of time and effort.

During technological selection, the most important consideration for the development team is whether the chosen product matches the team's technology stack. The niche technology stack limits Apache APISIX to become the first choice of API Gateway product in many scenarios.

Now, Apache APISIX supports multi-language development plugins. More importantly, the development ecosystem where the language is supported, so users can use their familiar technology stack to develop Apache APISIX. With Apache APISIX supporting plugins written in Java, for example, users can not only write plugins in Java but also integrate into the Spring Cloud ecosystem and use a wide range of technical components within the ecosystem.

Multiple Programming Languages Architecture Diagram

Multiple Programming Languages Architecture Diagram
Click to Preview

The left side of the diagram shows the workflow of Apache APISIX. The right side of the diagram is the plugin runner, which is a generic term for projects with multiple programming languages support. The apisix-java-plugin-runner is a plugin runner that supports the Java language.

When you configure a plugin runner in Apache APISIX, Apache APISIX starts a subprocess to run the plugin runner, which belongs to the same user as the Apache APISIX process. When we restart or reload Apache APISIX, the plugin runner will also be restarted.

If you configure the ext-plugin-* plugin for a given route, a request hitting that route will trigger Apache APISIX to perform an RPC call to the plugin runner via a Unix socket. The call is broken down into two phases:

  • ext-plugin-pre-req: Before executing the Apache APISIX built-in plugin (Lua plugin)
  • ext-plugin-post-req: After executing the Apache APISIX built-in plugin (Lua plugin)

Please configure the timing of the plugin runner execution as needed. The plugin runner processes the RPC call, creates a simulated request inside it, and then runs the multiple programming languages written a plugin and returns the result to Apache APISIX.

The order of execution of multiple programming languages plugins is defined in the ext-plugin-* plugin configuration entry. Like other plugins, they can be enabled and redefined on the fly.

Building Development Environment

First, you need to build the Apache APISIX runtime or development environment, please refer to Build Apache APISIX, and Build apisix-java-plugin-runner.

Note: Apache APISIX and apisix-java-plugin-runner need to be located on the same instance.

Enabling Debug Mode

Configuring Apache APISIX into Debug Mode

When Apache APISIX is configured into debug mode, it is allowed to run external plugins in a debugging manner. Add the following configuration to config.yaml to enable debug mode for Apache APISIX.

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

The configuration above means that Apache APISIX acts as the client and listens to the Unix Domain Socket link located at /tmp/runner.sock.

Setting apisix-java-plugin-runner to Debug Mode

Before starting the Mainclass(org.apache.apisix.plugin.runner.PluginRunnerApplication), you need to configure the user-level environment variables APISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock and APISIX_CONF_EXPIRE_TIME=3600.

If you are using IDEA for development, the configured environment variables are shown below.

configured environment
Click to Preview

apisix-java-plugin-runner is equivalent to the server side and will actively create the /tmp/runner.sock file at startup and communicate with Apache APISIX on this file for Unix Domain Socket.

How to Develop a Java Plugin for Apache APISIX?

Scenario

Let's say we have a scenario like this: we need to verify the validity of the token in the request header. The way to verify the token is to carry the token as a parameter in the request and access the SSO fixed interface.

Configuring Apache APISIX

First, name the plugin TokenValidator, and then design the properties. In order to achieve dynamic configuration as far as possible, the properties are designed as follows.

{
"validate_header": "token",
"validate_url": "https://www.sso.foo.com/token/validate",
"rejected_code": "403"
}

Start Apache APISIX, then add a new route configuration specifying that the route requires a call to apisix-java-plugin-runner's TokenValidator plugin, as shown in the following example.

curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri":"/get",
"plugins":{
"ext-plugin-pre-req":{
"conf":[
{
"name":"TokenValidator",
"value":"{\"validate_header\":\"token\",\"validate_url\":\"https://www.sso.foo.com/token/validate\",\"rejected_code\":\"403\"}"
}
]
}
},
"upstream":{
"nodes":{
"httpbin.org:80":1
},
"type":"roundrobin"
}
}

Note that the properties of TokenValidator need to be escaped by json and configured as string type (the upstream address here is configured as httpbin.org to simplify the debugging process).

Developing a Java Plugin

Add TokenValidatr.java with the following initial code skeleton In the runner-plugin/src/main/java/org/apache/apisix/plugin/runner/filter directory.

package org.apache.apisix.plugin.runner.filter;

import org.apache.apisix.plugin.runner.HttpRequest;
import org.apache.apisix.plugin.runner.HttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;


@Component
public class TokenValidator implements PluginFilter {

@Override
public String name() {
return "TokenValidator";
}

@Override
public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
return chain.filter(request, response);
}
}

This plugin inherits the PluginFilter interface and overrides the name and the filter function. Rewrite the return value of name to be consistent with "name" in the route attribute of APISIX configuration earlier. The complete code is as follows.

package org.apache.apisix.plugin.runner.filter;

import com.google.gson.Gson;
import org.apache.apisix.plugin.runner.HttpRequest;
import org.apache.apisix.plugin.runner.HttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

@Component
public class TokenValidator implements PluginFilter {

@Override
public String name() {
return "TokenValidator";
}

@Override
public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
// parse `conf` to json
String configStr = request.getConfig(this);
Gson gson = new Gson();
Map<String, Object> conf = new HashMap<>();
conf = gson.fromJson(configStr, conf.getClass());

// get configuration parameters
String token = request.getHeader((String) conf.get("validate_header"));
String validate_url = (String) conf.get("validate_url");
boolean flag = validate(token, validate_url);

// token verification results
if (!flag) {
String rejected_code = (String) conf.get("rejected_code");
response.setStatusCode(Integer.parseInt(rejected_code));
return chain.filter(request, response);
}

return chain.filter(request, response);
}

private Boolean validate(String token, String validate_url) {
//TODO: improve the validation process
return true;
}
}

Testing the Plugin

The upstream service configured on Apache APISIX is httpbin.org, which allows you to access Apache APISIX, trigger the route, and have Apache APISIX call apisix-java-plugin-runner to execute the TokenValidator plugin and test the functionality of the Java plugin.

curl -H 'token: 123456' 127.0.0.1:9080/get
{
"args": {},
"headers": {
"Accept": "/",
"Host": "127.0.0.1",
"Token": "123456",
"User-Agent": "curl/7.71.1",
"X-Amzn-Trace-Id": "Root=1-60cb0424-02b5bf804cfeab5252392f96",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "127.0.0.1",
"url": "http://127.0.0.1/get"
}

Deploying the Plugin

After the development of the plugin is completed, the deployment operation can be found in referred to Deploying apisix-java-plugin-runner.

Video Tutorial