使用自定义接口的Go 1.8插件

huangapple go评论96阅读模式
英文:

go 1.8 plugin use custom interface

问题

我想使用基于Go插件的自定义接口,但我发现它不支持。

filter.Filter 的定义

package filter

import (
    "net/http"

    "github.com/valyala/fasthttp"
)

// Context 过滤器上下文
type Context interface {
    SetStartAt(startAt int64)
    SetEndAt(endAt int64)
    GetStartAt() int64
    GetEndAt() int64

    GetProxyServerAddr() string
    GetProxyOuterRequest() *fasthttp.Request
    GetProxyResponse() *fasthttp.Response
    NeedMerge() bool

    GetOriginRequestCtx() *fasthttp.RequestCtx

    GetMaxQPS() int

    ValidateProxyOuterRequest() bool

    InBlacklist(ip string) bool
    InWhitelist(ip string) bool

    IsCircuitOpen() bool
    IsCircuitHalf() bool

    GetOpenToCloseFailureRate() int
    GetHalfTrafficRate() int
    GetHalfToOpenSucceedRate() int
    GetOpenToCloseCollectSeconds() int

    ChangeCircuitStatusToClose()
    ChangeCircuitStatusToOpen()

    RecordMetricsForRequest()
    RecordMetricsForResponse()
    RecordMetricsForFailure()
    RecordMetricsForReject()

    GetRecentlyRequestSuccessedCount(sec int) int
    GetRecentlyRequestCount(sec int) int
    GetRecentlyRequestFailureCount(sec int) int
}

// Filter 过滤器接口
type Filter interface {
    Name() string

    Pre(c Context) (statusCode int, err error)
    Post(c Context) (statusCode int, err error)
    PostErr(c Context)
}

// BaseFilter 基础过滤器,提供默认实现
type BaseFilter struct{}

// Pre 在代理之前执行
func (f BaseFilter) Pre(c Context) (statusCode int, err error) {
    return http.StatusOK, nil
}

// Post 在代理之后执行
func (f BaseFilter) Post(c Context) (statusCode int, err error) {
    return http.StatusOK, nil
}

// PostErr 在代理发生错误时执行
func (f BaseFilter) PostErr(c Context) {

}

这个包是我Go应用程序项目中的一部分。

加载插件文件

package proxy

import (
    "errors"
    "plugin"
    "strings"

    "github.com/fagongzi/gateway/pkg/conf"
    "github.com/fagongzi/gateway/pkg/filter"
)

var (
    // ErrKnownFilter 已知过滤器错误
    ErrKnownFilter = errors.New("未知过滤器")
)

const (
    // FilterHTTPAccess 访问日志过滤器
    FilterHTTPAccess = "HTTP-ACCESS"
    // FilterHeader 头部过滤器
    FilterHeader = "HEAD" // 处理头部过滤器
    // FilterXForward X-Forwarded 过滤器
    FilterXForward = "XFORWARD"
    // FilterBlackList 黑名单过滤器
    FilterBlackList = "BLACKLIST"
    // FilterWhiteList 白名单过滤器
    FilterWhiteList = "WHITELIST"
    // FilterAnalysis 分析过滤器
    FilterAnalysis = "ANALYSIS"
    // FilterRateLimiting 限流过滤器
    FilterRateLimiting = "RATE-LIMITING"
    // FilterCircuitBreake 熔断过滤器
    FilterCircuitBreake = "CIRCUIT-BREAKE"
    // FilterValidation 请求验证过滤器
    FilterValidation = "VALIDATION"
)

func newFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
    if filterSpec.External {
        return newExternalFilter(filterSpec)
    }

    input := strings.ToUpper(filterSpec.Name)

    switch input {
    case FilterHTTPAccess:
        return newAccessFilter(), nil
    case FilterHeader:
        return newHeadersFilter(), nil
    case FilterXForward:
        return newXForwardForFilter(), nil
    case FilterAnalysis:
        return newAnalysisFilter(), nil
    case FilterBlackList:
        return newBlackListFilter(), nil
    case FilterWhiteList:
        return newWhiteListFilter(), nil
    case FilterRateLimiting:
        return newRateLimitingFilter(), nil
    case FilterCircuitBreake:
        return newCircuitBreakeFilter(), nil
    case FilterValidation:
        return newValidationFilter(), nil
    default:
        return nil, ErrKnownFilter
    }
}

func newExternalFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
    p, err := plugin.Open(filterSpec.ExternalPluginFile)
    if err != nil {
        return nil, err
    }

    s, err := p.Lookup("NewExternalFilter")
    if err != nil {
        return nil, err
    }

    sf := s.(func() (filter.Filter, error))
    return sf()
}

这是我Go应用程序项目中加载插件的代码。

package main

import (
    "C"
    "strings"
    "time"

    "github.com/CodisLabs/codis/pkg/utils/log"
    "github.com/fagongzi/gateway/pkg/filter"
    "github.com/valyala/fasthttp"
)

// AccessFilter 记录HTTP访问日志
// 日志格式: $remoteip "$method $path" $code "$agent" $svr $cost
type AccessFilter struct {
}

// NewExternalFilter 创建一个外部过滤器
func NewExternalFilter() (filter.Filter, error) {
    return &AccessFilter{}, nil
}

// Name 返回过滤器的名称
func (f *AccessFilter) Name() string {
    return "HTTP-ACCESS"
}

// Pre 预处理
func (f *AccessFilter) Pre(c filter.Context) (statusCode int, err error) {
    return 200, nil
}

// Post 在代理之后执行
func (f *AccessFilter) Post(c filter.Context) (statusCode int, err error) {
    cost := (c.GetStartAt() - c.GetEndAt())

    log.Infof("%s %s \"%s\" %d \"%s\" %s %s",
        GetRealClientIP(c.GetOriginRequestCtx()),
        c.GetOriginRequestCtx().Method(),
        c.GetProxyOuterRequest().RequestURI(),
        c.GetProxyResponse().StatusCode(),
        c.GetOriginRequestCtx().UserAgent(),
        c.GetProxyServerAddr(),
        time.Duration(cost))

    return 200, nil
}

// PostErr 在代理发生错误时执行
func (f *AccessFilter) PostErr(c filter.Context) {

}

// GetRealClientIP 获取真实客户端IP
func GetRealClientIP(ctx *fasthttp.RequestCtx) string {
    xforward := ctx.Request.Header.Peek("X-Forwarded-For")
    if nil == xforward {
        return strings.SplitN(ctx.RemoteAddr().String(), ":", 2)[0]
    }

    return strings.SplitN(string(xforward), ",", 2)[0]
}

这是插件的定义,它位于我的插件项目中。插件项目和Go应用程序项目是不同的项目。

我发现了错误:

panic: interface conversion: plugin.Symbol is func() (filter.Filter, error), not func() (filter.Filter, error)

你可以在这个项目中找到代码 https://github.com/fagongzi/gateway/tree/go18-plugin-support

  1. filter.Filterpkg/filter 包中。
  2. load plugin fileproxy/factory.go 中。
  3. plugin go file 在另一个项目中。
英文:

I want to use custom interface based on go plugin, but I found it's not support.

Definition of filter.Filter

package filter
import (
"net/http"
"github.com/valyala/fasthttp"
)
// Context filter context
type Context interface {
SetStartAt(startAt int64)
SetEndAt(endAt int64)
GetStartAt() int64
GetEndAt() int64
GetProxyServerAddr() string
GetProxyOuterRequest() *fasthttp.Request
GetProxyResponse() *fasthttp.Response
NeedMerge() bool
GetOriginRequestCtx() *fasthttp.RequestCtx
GetMaxQPS() int
ValidateProxyOuterRequest() bool
InBlacklist(ip string) bool
InWhitelist(ip string) bool
IsCircuitOpen() bool
IsCircuitHalf() bool
GetOpenToCloseFailureRate() int
GetHalfTrafficRate() int
GetHalfToOpenSucceedRate() int
GetOpenToCloseCollectSeconds() int
ChangeCircuitStatusToClose()
ChangeCircuitStatusToOpen()
RecordMetricsForRequest()
RecordMetricsForResponse()
RecordMetricsForFailure()
RecordMetricsForReject()
GetRecentlyRequestSuccessedCount(sec int) int
GetRecentlyRequestCount(sec int) int
GetRecentlyRequestFailureCount(sec int) int
}
// Filter filter interface
type Filter interface {
Name() string
Pre(c Context) (statusCode int, err error)
Post(c Context) (statusCode int, err error)
PostErr(c Context)
}
// BaseFilter base filter support default implemention
type BaseFilter struct{}
// Pre execute before proxy
func (f BaseFilter) Pre(c Context) (statusCode int, err error) {
return http.StatusOK, nil
}
// Post execute after proxy
func (f BaseFilter) Post(c Context) (statusCode int, err error) {
return http.StatusOK, nil
}
// PostErr execute proxy has errors
func (f BaseFilter) PostErr(c Context) {
}

This pkg is in my go app project.

load plugin file

package proxy
import (
"errors"
"plugin"
"strings"
"github.com/fagongzi/gateway/pkg/conf"
"github.com/fagongzi/gateway/pkg/filter"
)
var (
// ErrKnownFilter known filter error
ErrKnownFilter = errors.New("unknow filter")
)
const (
// FilterHTTPAccess access log filter
FilterHTTPAccess = "HTTP-ACCESS"
// FilterHeader header filter
FilterHeader = "HEAD" // process header fiter
// FilterXForward xforward fiter
FilterXForward = "XFORWARD"
// FilterBlackList blacklist filter
FilterBlackList = "BLACKLIST"
// FilterWhiteList whitelist filter
FilterWhiteList = "WHITELIST"
// FilterAnalysis analysis filter
FilterAnalysis = "ANALYSIS"
// FilterRateLimiting limit filter
FilterRateLimiting = "RATE-LIMITING"
// FilterCircuitBreake circuit breake filter
FilterCircuitBreake = "CIRCUIT-BREAKE"
// FilterValidation validation request filter
FilterValidation = "VALIDATION"
)
func newFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
if filterSpec.External {
return newExternalFilter(filterSpec)
}
input := strings.ToUpper(filterSpec.Name)
switch input {
case FilterHTTPAccess:
return newAccessFilter(), nil
case FilterHeader:
return newHeadersFilter(), nil
case FilterXForward:
return newXForwardForFilter(), nil
case FilterAnalysis:
return newAnalysisFilter(), nil
case FilterBlackList:
return newBlackListFilter(), nil
case FilterWhiteList:
return newWhiteListFilter(), nil
case FilterRateLimiting:
return newRateLimitingFilter(), nil
case FilterCircuitBreake:
return newCircuitBreakeFilter(), nil
case FilterValidation:
return newValidationFilter(), nil
default:
return nil, ErrKnownFilter
}
}
func newExternalFilter(filterSpec *conf.FilterSpec) (filter.Filter, error) {
p, err := plugin.Open(filterSpec.ExternalPluginFile)
if err != nil {
return nil, err
}
s, err := p.Lookup("NewExternalFilter")
if err != nil {
return nil, err
}
sf := s.(func() (filter.Filter, error))
return sf()
}

This is the code of load plugin in my go app project

package main
import (
"C"
"strings"
"time"
"github.com/CodisLabs/codis/pkg/utils/log"
"github.com/fagongzi/gateway/pkg/filter"
"github.com/valyala/fasthttp"
)
// AccessFilter record the http access log
// log format: $remoteip "$method $path" $code "$agent" $svr $cost
type AccessFilter struct {
}
// NewExternalFilter create a External filter
func NewExternalFilter() (filter.Filter, error) {
return &AccessFilter{}, nil
}
// Name return name of this filter
func (f *AccessFilter) Name() string {
return "HTTP-ACCESS"
}
// Pre pre process
func (f *AccessFilter) Pre(c filter.Context) (statusCode int, err error) {
return 200, nil
}
// Post execute after proxy
func (f *AccessFilter) Post(c filter.Context) (statusCode int, err error) {
cost := (c.GetStartAt() - c.GetEndAt())
log.Infof("%s %s \"%s\" %d \"%s\" %s %s",
GetRealClientIP(c.GetOriginRequestCtx()),
c.GetOriginRequestCtx().Method(),
c.GetProxyOuterRequest().RequestURI(),
c.GetProxyResponse().StatusCode(),
c.GetOriginRequestCtx().UserAgent(),
c.GetProxyServerAddr(),
time.Duration(cost))
return 200, nil
}
// PostErr post error process
func (f *AccessFilter) PostErr(c filter.Context) {
}
// GetRealClientIP get read client ip
func GetRealClientIP(ctx *fasthttp.RequestCtx) string {
xforward := ctx.Request.Header.Peek("X-Forwarded-For")
if nil == xforward {
return strings.SplitN(ctx.RemoteAddr().String(), ":", 2)[0]
}
return strings.SplitN(string(xforward), ",", 2)[0]
}

This is the definition of plugin, it's in my plugin project. The plugin project and go app project are different projects.

I found errors:

panic: interface conversion: plugin.Symbol is func() (filter.Filter, error), not func() (filter.Filter, error)

You can find code in this project https://github.com/fagongzi/gateway/tree/go18-plugin-support.

  1. filter.Filter is in pkg/filter package.
  2. load plugin file in proxy/factory.go
  3. plugin go file is in another project.

答案1

得分: 10

自定义接口可以正常工作。

但有一件重要的事情:你只能从在插件之外定义的插件查找的值中type assert类型(你不能引用在插件中定义的类型)。这也适用于“复合类型”的每个组件,例如,你只能对函数类型进行类型断言,其参数和结果类型也是在插件之外定义的。

1. 使用插件之外的公共包

一种解决方案是在插件之外的包中定义接口,插件和你的应用程序都可以导入并引用它。

filter包中定义:

package filter
type Filter interface {
Name() string
Age() int
}

插件位于pq包中,并导入filter包:

package main
import (
"fmt"
"filter"
)
type plgFilter struct{}
func (plgFilter) Name() string { return "Bob" }
func (plgFilter) Age() int     { return 23 }
func GetFilter() (f filter.Filter, err error) {
f = plgFilter{}
fmt.Printf("[plugin GetFilter] Returning filter: %T %v\n", f, f)
return
}

主应用程序也导入(相同的)filter包,加载插件,查找GetFilter(),调用它并使用返回的Filter

package main
import (
"fmt"
"filter"
"plugin"
)
func main() {
p, err := plugin.Open("pg/pg.so")
if err != nil {
panic(err)
}
GetFilter, err := p.Lookup("GetFilter")
if err != nil {
panic(err)
}
filter, err := GetFilter.(func() (filter.Filter, error))()
fmt.Printf("GetFilter result: %T %v %v\n", filter, filter, err)
fmt.Println("\tName:", filter.Name())
fmt.Println("\tAge:", filter.Age())
}

输出:

[plugin GetFilter] Returning filter: main.plgFilter {}
GetFilter result: main.plgFilter {} <nil>
Name: Bob
Age: 23

2. 插件返回interface{},并在主应用程序中定义接口

另一种解决方案是让插件函数返回一个interface{}类型的值。主应用程序可以定义它所期望的接口,并且可以对插件返回的interface{}值进行类型断言。

这次没有filter包。

插件位于pq包中:

package main
import (
"fmt"
)
type plgFilter struct{}
func (plgFilter) Name() string { return "Bob" }
func (plgFilter) Age() int     { return 23 }
func GetFilterIface() (f interface{}, err error) {
f = plgFilter{}
fmt.Printf("[plugin GetFilterIface] Returning filter: %T %v\n", f, f)
return
}

主应用程序:

package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("pg/pg.so")
if err != nil {
panic(err)
}
GetFilterIface, err := p.Lookup("GetFilterIface")
if err != nil {
panic(err)
}
filterIface, err := GetFilterIface.(func() (interface{}, error))()
fmt.Printf("GetFilterIface result: %T %v %v\n", filterIface, filterIface, err)
myfilter := filterIface.(MyFilter)
fmt.Println("\tName:", myfilter.Name())
fmt.Println("\tAge:", myfilter.Age())
}
type MyFilter interface {
Name() string
Age() int
}

输出:

[plugin GetFilterIface] Returning filter: main.plgFilter {}
GetFilterIface result: main.plgFilter {} <nil>
Name: Bob
Age: 23

还请参阅相关问题:https://stackoverflow.com/questions/42218472/how-do-go-plugin-dependencies-work/42220856#42220856

英文:

Custom interfaces work just fine.

But one important thing: you can only type assert types from values looked up from plugins that are defined outside of the plugin (you can't refer types defined in plugins). This also applies to each component of "composite types", for example you can only type assert a function type whose parameter and result types are also defined outside of the plugin.

1. With a common package outside of the plugin

One solution is to define the interface in a package outside of the plugin, and both the plugin and your app can import it and refer to it.

Define it in package filter:

package filter
type Filter interface {
Name() string
Age() int
}

The plugin is in package pq and imports package filter:

package main
import (
&quot;fmt&quot;
&quot;filter&quot;
)
type plgFilter struct{}
func (plgFilter) Name() string { return &quot;Bob&quot; }
func (plgFilter) Age() int     { return 23 }
func GetFilter() (f filter.Filter, err error) {
f = plgFilter{}
fmt.Printf(&quot;[plugin GetFilter] Returning filter: %T %v\n&quot;, f, f)
return
}

And the main app that also imports (the same) package filter, loads the plugin, looks up GetFilter(), calls it and also uses the returned Filter:

package main
import (
&quot;fmt&quot;
&quot;filter&quot;
&quot;plugin&quot;
)
func main() {
p, err := plugin.Open(&quot;pg/pg.so&quot;)
if err != nil {
panic(err)
}
GetFilter, err := p.Lookup(&quot;GetFilter&quot;)
if err != nil {
panic(err)
}
filter, err := GetFilter.(func() (filter.Filter, error))()
fmt.Printf(&quot;GetFilter result: %T %v %v\n&quot;, filter, filter, err)
fmt.Println(&quot;\tName:&quot;, filter.Name())
fmt.Println(&quot;\tAge:&quot;, filter.Age())
}

Output:

[plugin GetFilter] Returning filter: main.plgFilter {}
GetFilter result: main.plgFilter {} &lt;nil&gt;
Name: Bob
Age: 23

2. With plugin returning interface{}, and interface defined in main app

Another solution is to have the plugin function return a value of type interface{}. Your main app can define the interface it expects, and it can use type assertion on the interface{} value returned by the plugin.

No filter package this time.

The plugin is in package pq:

package main
import (
&quot;fmt&quot;
)
type plgFilter struct{}
func (plgFilter) Name() string { return &quot;Bob&quot; }
func (plgFilter) Age() int     { return 23 }
func GetFilterIface() (f interface{}, err error) {
f = plgFilter{}
fmt.Printf(&quot;[plugin GetFilterIface] Returning filter: %T %v\n&quot;, f, f)
return
}

And the main app:

package main
import (
&quot;fmt&quot;
&quot;plugin&quot;
)
func main() {
p, err := plugin.Open(&quot;pg/pg.so&quot;)
if err != nil {
panic(err)
}
GetFilterIface, err := p.Lookup(&quot;GetFilterIface&quot;)
if err != nil {
panic(err)
}
filterIface, err := GetFilterIface.(func() (interface{}, error))()
fmt.Printf(&quot;GetFilterIface result: %T %v %v\n&quot;, filterIface, filterIface, err)
myfilter := filterIface.(MyFilter)
fmt.Println(&quot;\tName:&quot;, myfilter.Name())
fmt.Println(&quot;\tAge:&quot;, myfilter.Age())
}
type MyFilter interface {
Name() string
Age() int
}

Output:

[plugin GetFilterIface] Returning filter: main.plgFilter {}
GetFilterIface result: main.plgFilter {} &lt;nil&gt;
Name: Bob
Age: 23

Also see related question: https://stackoverflow.com/questions/42218472/how-do-go-plugin-dependencies-work/42220856#42220856

huangapple
  • 本文由 发表于 2017年2月22日 17:51:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/42388090.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定