Skip to content

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

PluginFactory 为插件必须实现的工厂接口,用于给对应作用域创建插件实例,create方法返回一个PluginObj对象,PluginObj的成员表示插件需要执行的周期,nil值表示忽略该周期的执行

Builder

在插件的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
}

Clone this wiki locally