起源

最近在看apisix的时候, 发现它的upstream写法很是固定, 比如只能指定一组ip或者域名的节点, 或者从Nacos等注册中心拉取节点, 对于动态上游的支持很有限, 比如这位老哥提出的Issue:

Question: How to select dynamic upstream by url path or url parameter ? · Issue #3503 · apache/apisix

人家就想简简单单的根据paramater或者url去决定转发到哪个upstream都不支持, 开发者给出的建议是你可以根据traffic-split这种插件自己写个去~ 然后潇洒 close issue

然后我在看它的Plugin插件开发说明的时候, 发现了 External Plugin 这个东西, 简单来说就它支持其他语言写的插件, 跑在别的语言的runner上面, 然后apisix通过RPC远程调用去调用插件, 工作原理就是这张图

https://s1.ax1x.com/2022/08/11/v8o0c8.png

这就激起了我的好奇心, 然后拉下他的代码来看了一下

代码仓库地址: https://github.com/apache/apisix-java-plugin-runner

他的大概原理就是通过监听同一个Unix套接字来进行RPC调用, java runner是一个springboot项目, 内部使用netty来实现RPC通信

例子

有两种方式, 最新的方式是用jar包的方式引入他的runner, 他给出的例子是这个仓库:https://github.com/tzssangglass/java-plugin-runner-demo-1, 但是测试之后发现maven中央仓库中好像没有他的jar包, 所以先用另一种方式, 就是直接在runner项目中进行插件开发

https://github.com/apache/apisix-java-plugin-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

https://s1.ax1x.com/2022/08/11/v87yYn.png