拉取apisix-java-plugin-runner源码,编写java插件
拉取apisix-java-plugin-runner源码
可以直接在github上面下载源码,解压zip包,可以用idea打开(本身就是个springboot工程)。
github网址:https://github.com/apache/apisix-java-plugin-runner/
添加依赖
在runner-plungin模块下的pom文件增加依赖,也就是postgresql的jdbc
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
版本可以不用写,springboot管理了版本
编写插件
插件要在apisix-java-plugin-runner-main\runner-plugin\src\main\java\org\apache\apisix\plugin\runner\filter\下,我们写个PostgresqlLog类,必须实现PluginFilter接口。源码如下,就写了个简单的批处理逻辑,日志缓存满5条则发送到数据库。
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.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class PostgresqlLog implements PluginFilter {
public int retryNum = 3;
public static final Gson gson = new Gson();
public static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static final Map logMap = new ConcurrentHashMap<Connection, String>();
@Override
public String name() {
return "PostgresqlLog";
}
@Override
public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
// 缓存日志
saveLog(request);
System.out.println("saveLog");
return chain.filter(request, response);
}
/**
* 缓存日志
*
* @param request
*/
private void saveLog(HttpRequest request) {
// 获取插件的参数配置
String configStr = request.getConfig(this);
Map<String, Object> conf = new HashMap<>();
conf = gson.fromJson(configStr, conf.getClass());
Connection connection = getConnection(conf);
System.out.println("getConnection");
String log = getLog(request);
logMap.put(connection, log);
// 缓存超5条直接发送日志
if (logMap.size() > 5) {
System.out.println("logMap.size>5");
sendLogBatcher();
}
}
/**
* 发送日志批处理器
*
* @return
*/
public void sendLogBatcher() {
Set connections = logMap.keySet();
for (Object connection : connections) {
System.out.println("sendLog");
sendLog((Connection) connection, retryNum);
}
}
private void sendLog(Connection connection, int retryNum) {
if (retryNum == 0) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
logMap.remove(connection);
return;
}
String log = (String) logMap.get(connection);
int modifideLine = 0;
try {
System.out.println("log: "+log);
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO LOG (log) VALUES (?)");
preparedStatement.setString(1, log);
modifideLine = preparedStatement.executeUpdate();
System.out.println("modifideLine: "+modifideLine);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (modifideLine == 1) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
logMap.remove(connection);
} else {
sendLog(connection, --retryNum);
}
}
}
private String getLog(HttpRequest request) {
long requestId = request.getRequestId();
String path = request.getPath();
String method = request.getMethod().toString();
Map<String, String> args = request.getArgs();
Map<String, String> header = request.getHeader();
String sourceIP = request.getSourceIP();
String format = dateFormat.format(new Date());
Map<String, Object> logMap = new HashMap<>();
logMap.put("requestId", requestId);
logMap.put("path", path);
logMap.put("method", method);
logMap.put("args", args);
logMap.put("header", header);
logMap.put("sourceIP", sourceIP);
logMap.put("date", format);
String log = gson.toJson(logMap);
return log;
}
private Connection getConnection(Map<String, Object> conf) {
Connection connection = null;
String dbname = (String) conf.get("dbname");
String host = (String) conf.get("host");
String username = (String) conf.get("username");
String password = (String) conf.get("password");
String port = (String) conf.get("port");
String url = "jdbc:postgresql://" + host + ":" + port + "/" + dbname;
try {
Class.forName("org.postgresql.Driver");
connection = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
修改checkstyle.xml
因为源码拉下来,里面用代码检查,小弟不才,代码写得垃圾,服务器上打包时候checkstyle通不过打不成tar.gz包。所以索性把apisix-java-plugin-runner-main\src\main\checkstyle\checkstyle.xml的module标签全注释了,如下所示
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
</module>
打包
直接在项目根路径下压缩成zip文件,扔apisix容器里
配置apisix,启动apisix-java-plugin-runner
解压zip,构建jar包
解压上述操作传过来的zip文件,在文件根目录执行
./mvnw package
这个项目用了maven wrapper,可以不用额外安装maven即可下载依赖,帮助我们打包,具体可以看我另一篇博客。执行结束后会出现dist文件夹,解压该文件夹下的tar.gz文件,解压出的文件夹下有个jar包,就是我们要执行的java插件运行器。
配置apisix,执行jar包,修改runner.socket权限,重启apisix
测试apisix-java-plugin-runner,需要配置apisix,执行jar包,修改runner.socket权限,重启apisix。如果生产环境的话,可以省去执行jar包这一步骤。
在/usr/local/apisix/conf下配置config.yaml,增加
#测试下增加这个
ext-plugin:
path_for_test: /tmp/runner.sock
#生产环境下增加这个,xxxx/apisix-java-plugin-runner.jar是上述jar包的位置
ext-plugin:
cmd: ['java', '-jar', '-Xmx4g', '-Xms4g', '/xxxx/apisix-java-plugin-runner.jar']
执行jar包(仅测试时)
#/xxxx/apisix-java-plugin-runner.jar是上述jar包的位置
java -jar -DAPISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock -DAPISIX_CONF_EXPIRE_TIME=3600 /xxxx/apisix-java-plugin-runner.jar
修改runner.socket权限,避免插件执行失败。这个文件是jar启动后生成的,插件运行器用它来和apisix通信,所以jar要先启动起来。
chmod 777 /tmp/runner.socket
重启apisix
apisix restart
配置路由,测试
配置路由,这里用的是ext-plugin-post-req插件,具体见下节“运行原理”的内容。
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri":"/myinfo",
"plugins":{
"ext-plugin-post-req":{
"conf":[
{
"name":"PostgresqlLog",
# 必须有value属性,值可以为空
"value":"{\"dbname\":\"test\",\"host\":\"159.75.26.246\",\"port\":\"5432\",\"username\":\"postgres\",\"password\":\"xxxxxx\"}"
}
]
}
},
"upstream":{
"nodes":{
"159.75.26.246:80":1
},
"type":"roundrobin"
}
}'
测试成功,数据库有数据插入,如图所示。
apisix-java-plugin-runner运行原理
运行原理
下图左边是 Apache APISIX 的工作流程,右边的 plugin runner 是指插件运行器,泛指多语言支持的项目。本文提到的 apisix-java-plugin-runner 项目就是支持 Java 语言的 plugin runner。
当你在 Apache APISIX 中配置一个 plugin runner 时,Apache APISIX 会启动一个子进程运行 plugin runner,该子进程与 Apache APISIX 进程属于同一个用户。当我们重启或重新加载 Apache APISIX 时,plugin runner 也将被重启。
如果你为一个给定的路由配置了 ext-plugin-* 插件,命中该路由的请求将触发 Apache APISIX,通过 unix socket 向 plugin runner 执行 RPC 调用。调用细分为两个阶段:
ext-plugin-pre-req: 在执行 Apache APISIX 内置插件(Lua 语言插件)之前;ext-plugin-post-req: 在执行 Apache APISIX 内置插件(Lua 语言插件)之后。根据需要配置 plugin runner 的执行时机。
plugin runner 会处理 RPC 调用,在其内部创建一个模拟请求,然后运行多语言编写的插件,并将结果返回给 Apache APISIX。
多语言插件的执行顺序是在 ext-plugin-* 插件配置项中定义的。像其他插件一样,它们可以被启用并在运行中重新定义。