前言
本文介绍了一种网关函数API的实现方式,核心为使用JDK自带的脚本引擎nashorn,驱动和执行用户自己开发的JS函数脚本,并把函数封装成一个API,发布到API网关上,像网关其他的API一样,对外提供服务。
一、函数API介绍
网关基本的功能是路由,就是将已知的外部接口再次包装后发布到网关,南向和北向应用使用网关发布的接口,可以通过一些规则路由到那些外部接口。
函数API实质是一个用户自定义的函数,这个函数是一个脚本,可以是JS、JAVA、C#等语言编写的,通过一致的函数入参和出参,以及传输协议包装之后,就成为一个可以被调用的接口,如果要被南向和北向系统调用,还要将它发布到网关。
函数是FaaS、PaaS等这些比较流行的ServerLess架构系统中比较重要的元素,当然这里将要介绍的函数和FaaS还不是一个概念,但是原理及用法有相似之处,比如这里是将函数作为一个API执行,这个和FaaS是一致的。
二、原理及实现
1、函数API实现方案:
1)第一种是利用freemark等模板引擎,将脚本直接生成接口代码,然后用javax.tools下面的工具动态编译成字节码,或者采用其他的动态编译工具都可以,这样就可以动态创建一个接口;
2)第二种无需动态生成接口,只需要使用一个脚本引擎,传入接口的上下文之后,利 用脚本引擎去调度脚本并返回执行结果就可以了。
方案优缺点分析:
1、方案一需要集成模板引擎生成代码,还要动态编译,例如js脚本,得利用脚本引擎JavaScript将js转成java,需要在工程里面生成代码,并编译成字节码,这就有个问题,将来的接口会非常的多,几千几万是很正常的,要生成如此之多的接口,工程慢慢会越来越臃肿,接口调用性能上也是很差的,而且多实例部署还要接口同步,脚本修改也要将上述过程再来一遍,所以这种方式显然不好。
2、方案二就简单多了,函数脚本统一存放在RDB,或者存放在redis、csv等数据库都可以,增加修改一个函数无需生成代码、编译、同步,也不会增加工程存储及接口io占用,利用java的脚本引擎做脚本的统一调度,拿到上下文执行脚本并返回处理结果就行了,这里介绍的就是这种方式。
2、函数API实现原理:
采用JDK最新的脚本引擎nashorn,脚本采用JavaScript编写,支持java、js混合编程
3、函数API实现过程
1)编写js脚本,持久化到数据库,这里是一个简单实例,脚本发起一个http求,调用一个接口
importClass(com.icss.cig.spms.integration.service.apiis.design.client.HttpClient);
function execute(request) {
// 设置函数返回值
var result = new Response();
// 获取请求上下文
var header = request.headers; // Head参数
var parameter = request.parameters; // Harameter参数
var body = request.body;
var host = 'http://spms-integration-service-system';
var url = '/rpc/pass/system/app/getToken';
var req = Request.getInstance(header, parameter, body);
var response = HttpClient.doPost(host, url, req);
if (response.header.status == 200) {
// 请求成功,设置返回值,自己封装返回值结构
result = response;
} else {
// 请求失败,设置异常信息,自己封装返回异常信息
result.header.code = response.header.status;
result.header.message = response.header.message;
result.header.error = response.header.error;
}
return result;
}
2)函数包装成一个接口wzf0616,这个接口为发布到网关的接口,对外定义调用方式
3)开发一个脚本调度接口,用来处理函数调用的不同请求,此接口暴露在注册中心
@ApiOperation(value = "脚本执行调度", tags = {"API设计"})
@PostMapping(value = "/rpc/pass/integration/apiis/design/redirectLocalInterface")
Mono<SPMSRsp<Object>> redirectLocalInterfacePost(ServerWebExchange exchange);
4)将接口wzf0616发布到API网关,通过注册中心路由到接口redirectLocalInterface
5)函数API调用,用户通过网关接口wzf0616就可以调用定义的函数excute了
6)脚本引擎调度函数
ScriptEngine scriptEngine = ScriptManager.createScriptEngine();
scriptEngine.eval(scriptEntity.getScript());
Invocable invocable = (Invocable) scriptEngine;
Object result = invocable.invokeFunction("execute", scriptRequest);
public static ScriptEngine createScriptEngine() throws IOException, ScriptException {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn");
jsScriptEngine = scriptEngine;
// 加载公共js
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
org.springframework.core.io.Resource[] resources =
resolver.getResources("templates/freemark" + File.separator + "common.js");
org.springframework.core.io.Resource resource = resources[0];
InputStream inputStream = resource.getInputStream();
Reader reader = new InputStreamReader(inputStream, "utf-8");
scriptEngine.eval(reader);
return scriptEngine;
}