【问题标题】:go 1.8 plugin use custom interfacego 1.8 插件使用自定义接口
【发布时间】:2017-02-22 09:51:09
【问题描述】:

我想使用基于 go 插件的自定义界面,但我发现它不支持。

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) {

}

这个 pkg 在我的 go app 项目中。

加载插件文件

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()
}

这是我的go app项目中加载插件的代码

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]
}

这是插件的定义,在我的插件项目中。插件项目和 go app 项目是不同的项目。

我发现错误:

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.Filter 在 pkg/filter 包中。
  2. load plugin file 在 proxy/factory.go 中
  3. plugin go file 在另一个项目中。

【问题讨论】:

标签: go plugins


【解决方案1】:

自定义界面可以正常工作。

但是一件重要的事情:您只能从在插件外部定义的插件中查找的值中查找 type assert 类型(您不能引用插件中定义的类型)。这也适用于“复合类型”的每个组件,例如,您只能键入 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

另见相关问题:How do Go plugin dependencies work?

【讨论】:

  • 非常感谢您的回答,但这并不能解决问题,因为我描述的原因不清楚。现在我正在编辑这个问题,实际上我的解决方案就像你的解决方案 1。
  • @ZhangXu 我没有看到您声称有任何filter 包:github.com/fagongzi/gateway/tree/master/pkg。您以某种方式混合了导入/包路径。
  • 嗨,对不起,它在github.com/fagongzi/gateway/tree/go18-plugin-support 分支中。我也给golang(github.com/golang/go/issues/19233)开个issue,可能是vendor package的问题,有空我会去验证的
  • 插件代码在main包中定义,插件在main中加载。如果插件是从另一个包而不是 main 加载的,这仍然有效吗?换句话说,插件包名称是否需要与加载它的包名称匹配(在您给出的示例中为main)?
  • @chmike 插件必须有包main,并且可以从任何地方加载。但是一个可运行的应用程序还必须有一个包main,包mainmain()函数是应用程序的入口点。插件的 main 和应用程序的 main 是 2 个不同的不同包。
猜你喜欢
  • 2012-10-28
  • 1970-01-01
  • 2021-05-03
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-19
相关资源
最近更新 更多