-
Notifications
You must be signed in to change notification settings - Fork 2
plugin interface
黄孟柱 edited this page Jul 18, 2019
·
3 revisions
#插件接口说明
项目内plugin.go文件定义了四个interface一个struct
type PluginObj struct {
BeforeMatch PluginBeforeMatch
Access PluginAccess
Proxy PluginProxy
}
type PluginFactory interface {
Create(config string, clusterName string, updateTag string, strategyId string, apiId int) (*PluginObj, error)
}
type PluginBeforeMatch interface {
BeforeMatch(ctx ContextBeforeMatch) (isContinue bool, e error)
}
type PluginAccess interface {
Access(ctx ContextAccess) (isContinue bool, e error)
}
type PluginProxy interface {
Proxy(ctx ContextProxy) (isContinue bool, e error)
}PluginFactory 为插件必须实现的工厂接口,用于给对应作用域创建插件实例,create方法返回一个PluginObj对象,PluginObj的成员表示插件需要执行的周期,nil值表示忽略该周期的执行
在插件的package中,必须实现一个 Builder的方法,方法签名如下
func Builder() goku_plugin.PluginFactory改方法返回一个 PluginFactory的对象
实现demo如下
customer-example.go
package main
// 该插件为自定义示例插件
// 该示例插件作用:替换返回头部
import (
"encoding/json"
"errors"
"github.com/eolinker/goku-plugin"
"reflect"
"strconv"
"strings"
)
const pluginName = "customer-example"
var builder =new(CustomerExamplePluginFactory)
// 该定义必须存在
// Builder 定义插件工厂类入口方法,必须为 func Builder() goku_plugin.PluginFactory
func Builder() goku_plugin.PluginFactory {
return builder
}
// HeaderInfo 头部信息结构
type HeaderInfo struct {
// 请求头键名
HeaderKey string `json:"headerKey"`
// 待替换的请求头键名
ReplaceHeaderKey string `json:"replaceHeaderKey"`
}
type customerExample struct {
// 匹配响应的状态码,若matchStatusCode字段包含了响应的状态码,则启用自定义插件
MatchStatusCode string `json:"matchStatusCode"`
// 需要替换的请求头列表
Headers []HeaderInfo `json:"headers"`
}
// 工厂类实现,必须实现 goku_plugin.PluginFactory
type CustomerExamplePluginFactory struct {
}
func (f *CustomerExamplePluginFactory) Create(config string, clusterName string, updateTag string, strategyId string, apiId int) (*goku_plugin.PluginObj, error) {
if config != "" {
con:= new(customerExample)
err := json.Unmarshal([]byte(config), &con)
if err != nil {
// json转换失败,将错误信息记录到插件的错误日志中
logger:=goku_plugin.GenAccessLogger(pluginName,pluginName,goku_plugin.PeriodHour)
if !reflect.ValueOf(logger).IsNil(){
logger.Log(err.Error())
}
}
return &goku_plugin.PluginObj{
BeforMatch:nil,
Access:nil,
Proxy: &CustomerExamplePlugin{
config:con,
configErr:err,
},
},nil
}
//无配置,无法启动
//返回(nil,error) 时,网关会忽略该插件
return nil,errors.New("need config")
}
// 插件Handler
type CustomerExamplePlugin struct {
config *customerExample
configErr error
}
// 实现转发后操作
func (p *CustomerExamplePlugin) Proxy(ctx goku_plugin.ContextProxy) (isContinue bool, e error) {
if p.configErr != nil {
// json转换失败
// 网关通过ctx.StatusCode字段进行次数统计
ctx.SetStatus(500,"500 time_out")
return false, p.configErr
}
MatchCode(p.config.MatchStatusCode, ctx, p.config.Headers)
return true, nil
}
func MatchCode(matchCode string, ctx goku_plugin.ContextProxy, headers []HeaderInfo) bool {
pr:= ctx.ProxyResponse()
// 这里需要注意,对于动态interface,不能直接用== nil 来判断
if !reflect.ValueOf(pr).IsNil() {
return true
}
statusCode := "504"
statusCode = strconv.Itoa(ctx.ProxyResponse().StatusCode())
if strings.Contains(matchCode, statusCode) || matchCode == "*" {
for _, value := range headers {
headerList := ctx.ProxyResponse().Headers()
if v, ok := headerList[value.HeaderKey]; ok {
headerValue := strings.Join(v, ",")
if headerValue != "" {
ctx.AddHeader(value.ReplaceHeaderKey, headerValue)
ctx.DelHeader( value.HeaderKey)
}
}
}
}
return true
}