基于APISIX开发Java插件
⛏️

基于APISIX开发Java插件

Created
Aug 11, 2022 05:57 AM
Tags

起源

最近在看apisix的时候, 发现它的upstream写法很是固定, 比如只能指定一组ip或者域名的节点, 或者从Nacos等注册中心拉取节点, 对于动态上游的支持很有限, 比如这位老哥提出的Issue:
人家就想简简单单的根据paramater或者url去决定转发到哪个upstream都不支持, 开发者给出的建议是你可以根据traffic-split这种插件自己写个去~ 然后潇洒 close issue
 
然后我在看它的Plugin插件开发说明的时候, 发现了 External Plugin 这个东西, 简单来说就它支持其他语言写的插件, 跑在别的语言的runner上面, 然后apisix通过RPC远程调用去调用插件, 工作原理就是这张图
notion image
这就激起了我的好奇心, 然后拉下他的代码来看了一下
他的大概原理就是通过监听同一个Unix套接字来进行RPC调用, java runner是一个springboot项目, 内部使用netty来实现RPC通信

例子

有两种方式, 最新的方式是用jar包的方式引入他的runner, 他给出的例子是这个仓库:https://github.com/tzssangglass/java-plugin-runner-demo-1, 但是测试之后发现maven中央仓库中好像没有他的jar包, 所以先用另一种方式, 就是直接在runner项目中进行插件开发
 
在runner-starter文件夹中的pom文件中增加依赖(如果有则忽略):
<dependency> <groupId>org.apache.apisix</groupId> <artifactId>apisix-runner-plugin</artifactId> <version>0.3.0-SNAPSHOT</version> </dependency>
然后在runner-plugin文件夹下的 src\main\java\org\apache\apisix\plugin\runner\filter\ 目录下新增插件文件(这边给了一个简单的例子, 就是根据传入的cityid去查询某个城市的天气)
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 java.net.URI; import java.net.http.HttpClient; import java.util.HashMap; import java.util.List; import java.util.Map; @Component public class ProxyPassFilter implements PluginFilter { @Override public String name() { return "ProxyPassFilter"; } @Override public void filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) { /* * If the conf you configured is of type json, you can convert it to Map or json. */ String configStr = request.getConfig(this); Gson gson = new Gson(); Map<String, Object> conf = new HashMap<>(); conf = gson.fromJson(configStr, conf.getClass()); /* * You can use the parameters in the configuration. */ String cityid = request.getArg("cityid"); java.net.http.HttpRequest httpRequest = java.net.http.HttpRequest.newBuilder() .GET() .uri(URI.create("https://v0.yiketianqi.com/api?unescape=1&version=v91&appid=43656176&appsecret=I42og6Lm&ext=&cityid=" + cityid)) .build(); java.net.http.HttpResponse<String> httpResponse; try { httpResponse = HttpClient.newHttpClient() .send(httpRequest, java.net.http.HttpResponse.BodyHandlers.ofString()); response.setStatusCode(httpResponse.statusCode()); response.setBody(httpResponse.body()); } catch (Exception e) { // write log response.setStatusCode(500); response.setBody("{\"code\":\"500\",\"message\":\"internal error\"}"); } /* note: The body is currently a string type. If you need the json type, you need to escape the json content here. For example, if the body is set as below "{\"key1\":\"value1\",\"key2\":2}" The body received by the client will be as below {"key1":"value1","key2":2} */ /* Using the above code, the client side receives the following header: HTTP/1.1 401 Unauthorized Content-Type: text/plain; charset=utf-8 Connection: keep-alive new-header: header_by_runner Server: APISIX/2.6 body: {"key1":"value1","key2":2} */ chain.filter(request, response); } @Override public List<String> requiredVars() { return null; } @Override public Boolean requiredBody() { return null; } }
在设置响应code和body的时候会把actionType设置为stop, 所以这个例子中, 如果配置了这个插件, 则永远不会调用到上游的upstream
notion image
 
插件写完之后用 mvn package 打包(因为我用的wsl2系统, windows不支持Unix套接字, 所以需要在wsl2中去测试)
在apisix的配置文件中加入
ext-plugin: path_for_test: /tmp/sock/runner.sock
在docker-compose文件中加入: (因为我的apisix是在docker中启动的, 所以需要把Unix socket映射进去)
apisix: image: apache/apisix restart: always volumes: - ./apisix_log:/usr/local/apisix/logs - ./config.yaml:/usr/local/apisix/conf/config.yaml:rw - ./static:/usr/local/apisix/stati - /opt/sock:/tmp/sock
然后启动apisix
然后启动java runner, 启动命令:
/opt/jdk11/bin/java -jar -DAPISIX_LISTEN_ADDRESS=unix:/opt/sock/runner.sock -DAPISIX_CONF_EXPIRE_TIME=3600 apisix-java-plugin-runner.jar
 
然后apisix中增加一条route:
ps: conf中的name要与插件中name()函数返回的一致
ps: 可以在conf中写一些配置性的东西, 在插件中用request.getConfig(this)读取
{ "uri": "/get", "name": "test", "plugins": { "ext-plugin-pre-req": { "conf": [ { "name": "ProxyPassFilter", "value": "{\"value1\":\"111\"}" } ] } }, "upstream": { "nodes": [ { "host": "127.0.0.1", "port": 80, "weight": 1 } ], "type": "roundrobin" }, "status": 1 }
然后打开浏览器访问:
http://127.0.0.1:9080/get?cityid=101010100
则会看到配置的插件生效了, 成功返回天气数据
notion image

部署

部署可以参考(其实就是在启动apisix的时候顺便启动这个springboot runner):
 

结语

虽然网上对于这部分开发的资源很少, 而且这个runner可能也并不是很完善, 但是这种设计思想还是很值得学习的, 这可以让不太了解nginx, 不太了解Lua脚本的Javaer们也可以很开心地用自己擅长的Java语言来开发一些网关层面的插件, 完结撒花~