Apinto 是基于Go语言,由 Eolink 自主研发的一款高性能、可扩展、易维护的云原生 API 网关。Apinto 能够帮助用户简单、快速、低成本、低风险地实现:系统微服务化、系统集成、向合作伙伴、开发者开放功能和数据。
通过 Apinto,企业能够专注于自身业务的发展,并且让企业间能互相借力,实现共赢。
借助 Apinto 强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展Apinto 的插件,丰富 Apinto 的能力。
本篇文章将给大家介绍如何开发 Apinto插件,自行拓展 Apinto 网关能力。
前置知识
插件执行流程
插件系统定义了 Access 、Proxy 两个插件执行周期,其执行阶段定义如下:
- Access:客户端请求 Apinto,将请求转发给上游服务前执行;
- Proxy:将响应返回给客户端前执行;
请求从客户端发出,到达路由进行转发前,按顺序从上到下进行每个插件的 Access 执行阶段,转发获取响应后,从下到上执行每个插件的 Proxy 执行阶段。
插件执行流程如下图,请求处理即为转发前执行的操作,响应处理即为转发获得响应后的操作。
插件实现 UML 图
插件实现采用工厂设计模式,系统初始化时,自动将插件工厂注册到插件管理器中,当有新增插件/修改插件需求时,插件工厂实例会创建插件执行器实例。
开发插件
为了更加直观介绍自定义开发插件,本文将以开发 响应重写 插件为例,完整代码可参考 [Apinto 项目响应插件]
定义插件配置
开发插件前,先确定插件的功能和界限,根据需求定义插件配置格式,此处以 响应重写 插件为例。
{
"status_code":0,
"body":"",
"body_base64":true,
"headers":{},
"match":{
"code":[]
}
}
根据上述配置,定义插件配置结构体。
type Config struct {
StatusCode int `json:"status_code" label:"响应状态码" minimum:"100" description:"最小值:100"`
Body string `json:"body" label:"响应内容"`
BodyBase64 bool `json:"body_base64" label:"是否base64加密"`
Headers map[string]string `json:"headers" label:"响应头部"`
Match *MatchConf `json:"match" label:"匹配状态码列表"`
}
type MatchConf struct {
Code []int `json:"code" label:"状态码" minimum:"100" description:"最小值:100"`
}
实现插件执行器
1. 定义执行器结构
type ResponseRewrite struct {
drivers.WorkerBase
statusCode int
body string
headers map[string]string
match *MatchConf
}
2. 实现 eosc.IWorker
接口
type IWorker interface {
Id() string
Start() error
Reset(conf interface{}, workers map[RequireId]IWorker) error
Stop() error
CheckSkill(skill string) bool
}
当插件配置被修改时,将会调用 Reset
方法,重置执行器内置数据,当插件被删除时,将会调用 Stop
方法。
3. 实现 eosc.IFilter
接口
type IFilter interface {
DoFilter(ctx EoContext, next IChain) (err error)
Destroy()
}
该接口在转发流程中被调用,其中 DoFilter
方法执行插件主流程,Destory
方法则在插件被删除时,销毁实例。
由于插件有顺序,在实际调用时,会将插件编排成调用链,并通过 next.DoChain(ctx)
操作调用下一个插件,您可以自定义调用插件前的操作,即 Access阶段 ,也可以自定义调用插件后的操作,即 Proxy 阶段 。代码示例如下:
func (r *ResponseRewrite) DoFilter(ctx eocontext.EoContext, next eocontext.IChain) (err error) {
return http_service.DoHttpFilter(r, ctx, next)
}
func (r *ResponseRewrite) DoHttpFilter(ctx http_service.IHttpContext, next eocontext.IChain) error {
if next != nil {
err := next.DoChain(ctx)
if err != nil {
log.Error(err)
}
}
return r.rewrite(ctx)
}
func (r *ResponseRewrite) Destroy() {
r.statusCode = 0
r.body = ""
r.headers = nil
r.match = nil
}
定义工厂创建方法
func NewFactory() eosc.IExtenderDriverFactory {
return drivers.NewFactory[Config](Create, Check)
}
func Create(id, name string, conf *Config, workers map[eosc.RequireId]eosc.IWorker) (eosc.IWorker, error) {
err := conf.doCheck()
if err != nil {
return nil, err
}
//若body非空且需要base64转码
if conf.Body != "" && conf.BodyBase64 {
conf.Body, err = utils.B64DecodeString(conf.Body)
if err != nil {
return nil, err
}
}
r := &ResponseRewrite{
WorkerBase: drivers.Worker(id, name),
statusCode: conf.StatusCode,
body: conf.Body,
headers: conf.Headers,
match: conf.Match,
}
return r, nil
}
结合上文代码,当插件新建时,插件工厂会调用 Create
方法和 Check
方法,Create
方法负责初始化插件执行器实例,Check
方法一般用来校验插件配置的合法性,Check
方法用户可以按需实现,但 Create
方法必须实现。
定义工厂注册方法
const (
Name = "response_rewrite"
)
func Register(register eosc.IExtenderDriverRegister) {
register.RegisterExtenderDriver(Name, NewFactory())
}
将插件注册到拓展驱动管理器中
文件名:app/apinto/worker.go
使用插件
开发完成后,新插件就会被同步到节点插件中。我们可以在控制台界面 基础设施
> 节点插件
新建插件。
新建插件时选择 eolinker.com:apinto:{插件名}
,本次演示的插件名为 response_rewrite
,因此选择插件 eolinker.com:apinto:response_rewrite
。
若想了解插件的详细使用教程,可点击链接。
写在最后
本文简单介绍了插件入门相关细节,使用者可以根据业务需要,自行开发、安装/卸载、开启/关闭、编排插件,丰富网关能力,强化网关功能。
未来我们计划支持多语言插件,引入插件市场,个人开发者/企业可以将开发的插件贡献到插件市场中,与 Apinto 社区的使用者共享,合作共赢。