How to avoid package dependency - go

I have a project consisting of 4 parts:
A gateway (gateway/gateway.go) it's a package that knows how to talk with an application server and open this connection channel.
A runner (runner/runner.go) it's the main (go build -o runner/runner runner/runner.go) It loads and execute Modules (using reflect I run functions from the module)!
A framework (framework/framework.go) Implements many functionalities calling the gateway.
Modules (aka Plugins in Go) (modules/sample.go) (go build -buildmode plugin -o modules/sample.so ./modules/sample.go) Using the framework, does customer logic! When init I export the reflect.Value of struct, then runner can run methods of this struct.
I want the runner instantiate the gateway and the framework obtain this instance without create a dependency between runner/framework.
Why? To avoid Go error 'plugin was built with a different version of package' when runner loads the module!
If I update the runner (with the framework changed), I will invalidate old modules.
I already do that using 2 ways I don't like:
Using context, but all functions from module and framework need receive a parameter context, then framework extract the gateway.
Just let the framework instantiate gateway, but then the runner cannot use gateway.

There have been a lot of headaches with Go plugins, especially the plugin compiler version must exactly match the program's compiler version. But the example works.
runner/runner.go
package main
import (
"context"
"fmt"
"os"
"plugin"
"reflect"
"../gateway"
"../dep"
)
var a *gateway.Gateway
func main() {
myModule := os.Args[1]
if _, err := plugin.Open(myModule); err != nil {
os.Exit(1)
}
mod, err := dep.NewModule()
if err != nil {
os.Exit(1)
}
a = gateway.NewGW()
ctx := context.WithValue(context.Background(), "gateway", a)
modreflect, err := mod.Init(ctx, dep.Config{})
if err != nil {
os.Exit(1)
}
if !modreflect.IsValid() {
os.Exit(1)
}
modnode, err := mod.Start(ctx)
if err != nil {
os.Exit(1002)
}
for {
if len(modnode) <= 0 {
break
}
modnoderefl := modreflect.MethodByName(modnode)
if !modnoderefl.IsValid() {
break
}
result := modnoderefl.Call([]reflect.Value{reflect.ValueOf(ctx)})
if len(result) != 2 {
break
}
modnode = result[0].String()
}
mod.End(ctx)
}
gateway/gateway.go
package gateway
type Gateway struct {}
fun NewGW() *Gateway {
a := Gateway{}
return &a
}
dep/dep.go
package dep
import (
"errors"
"context"
"reflect"
)
// Config is a configuration provider.
type Config map[string]interface{}
// Module is the interface implementated by types that
// register themselves as modular plug-ins.
type Module interface {
Init(ctx context.Context, config Config) (reflect.Value,error)
Start(ctx context.Context) (string,error)
End(ctx context.Context) error
}
var themod = []func() Module{}
func RegisterModule(ctor func() Module) {
themod = append(themod, ctor)
}
func NewModule() (Module, error) {
if len(themod) == 0 {
return nil, errors.New("Module not registered")
}
return themod[0](), nil
}
framework/framework.go
package framework
import (
"fmt"
"context"
"../gateway"
)
type PlayFileInput struct {
Path string
}
func Play(ctx context.Context, p PlayFileInput) error {
if a := ctx.Value("gateway"); a != nil {
if a.(*gateway.Gateway) != nil {
_, err := a.(*gateway.Gateway).Exec("Playback", p.Path)
return err
}
}
return nil
}
modules/sample.go
package main
import "C"
import (
"context"
"fmt"
"os"
"reflect"
"../dep"
"../framework"
)
type MyModuleImpl struct {}
func init() {
dep.RegisterModule(func() dep.Module {
return &MyModuleImpl{}
})
}
func (m *MyModuleImpl) Init(ctx context.Context, config dep.Config) (reflect.Value, error) {
return reflect.ValueOf(m), nil
}
func (m *MyModuleImpl) Start(ctx context.Context) (string,error) {
return "Menu_1",nil
}
func (n *MyModuleImpl)Menu_1(ctx context.Context) (string, error) {
framework.Play(ctx, framework.PlayFileInput{Path: "welcome.wav"})
return "Menu_2",nil
}
func (n *MyModuleImpl)Menu_2(ctx context.Context) (string, error) {
return "Menu_3", nil
}
// ....
// ....
func (m *MyModuleImpl) End(ctx context.Context) error {
return nil
}

Related

Go - How to combine multiple error objects

Say I have the following code:
package lib
import (
"errors"
"strconv"
)
var ErrSomething = errors.New("foobar")
func SomeFunc(str string) (int, error) {
i, err := strconv.Atoi(str)
if err != nil {
// how to combine ErrSomething with err?
return 0, fmt.Errorf("%w: %w", ErrSomething, err)
}
// do other things, potentially return other errors
return i
}
How do I combine the error returned from strconv.Atoi with my named error ErrSomething. The reason for combining is so that users of my SomeFunc() can check what exactly went wrong using my error "constants" while not losing information about the underlying error.
I have read similar questions but the usual answer is to just do: return 0, fmt.Errorf("foobar: %w", err) but this way my users can't check the error using errors.Is(err, ???)
You can achieve the desired behavior by creating an error type that implements the Is and Unwrap methods as follows:
package lib
import (
"fmt"
"strconv"
)
type FoobarError struct {
msg string
original error
}
func (err *FoobarError) Error() string {
return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}
func (err *FoobarError) Unwrap() error {
return err.original
}
func (err *FoobarError) Is(target error) bool {
_, ok := target.(*FoobarError)
return ok
}
func SomeFunc() error {
// strconv.ErrSyntax is used as a dummy error here for the error
// that might be returned by strconv.Atoi or any other operation.
err := strconv.ErrSyntax
return &FoobarError{"foobar", err}
}
Usage:
package main
import (
"errors"
"fmt"
"strconv"
"lib"
)
func main() {
if err := lib.SomeFunc(); err != nil {
fmt.Println(err) // foobar: invalid syntax
fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
fmt.Println(errors.Is(err, strconv.ErrSyntax)) // true
}
}
You can read more about this approach here.
Bonus
Similar to Go's os.IsExist, you may be interested in adding a helper function to your library that makes it easier for the user to check the error:
package lib
import (
"errors"
// ...
)
// ...
func IsFoobar(err error) bool {
return errors.Is(err, &FoobarError{})
}
Usage:
package main
// ...
func main() {
err := lib.SomeFunc();
if lib.IsFoobar(err) {
// ...
}
}

go revel undefined: sql or Txn

mac highsierra
use mysql
https://paiza.hatenablog.com/entry/2018/03/23/paizacloud_golang_revel)
I'm trying to use the Revel framework for mysql.
It's revel Tutorial booking.
what is error?
ERROR 18:33:28 watcher.go:270: Build detected an error error="Go Compilation Error (in app/controllers/gorm.go:31): undefined: sql"
before
ERROR 18:18:26 watcher.go:270: Build detected an error error="Go Compilation Error (in app/controllers/app.go:26): c.Txn undefined (type Application has no field or method Txn)"
I did this because I got an error before
controllers/gorm.go
type Transactional struct {
*revel.Controller
Txn *sql.Tx
}
After running it results in error
Please tell me how you can solve it
add
controllers/app.go
package controllers
import (
"github.com/revel/revel"
"booking/app/models"
"booking/app/routes"
"database/sql"
)
type Application struct {
*revel.Controller
}
func (c Application) Index() revel.Result {
if c.connected() != nil {
return c.Redirect(routes.Hotels.Index())
}
c.Flash.Error("Please log in first")
return c.Render()
}
func (c Application) connected() *models.User {
if c.ViewArgs["user"] != nil {
return c.ViewArgs["user"].(*models.User)
}
if username, ok := c.Session["user"]; ok {
return c.getUser(username.(string))
}
return nil
}
controllers/gorm.go
package controllers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"github.com/revel/revel"
"booking/app/models"
"log"
)
type GormController struct {
*revel.Controller
Txn *gorm.DB
}
func InitDB() {
dbInfo, _ := revel.Config.String("db.info")
db, err := gorm.Open("mysql", dbInfo)
if err != nil {
log.Panicf("Failed gorm.Open: %v\n", err)
}
db.DB()
db.AutoMigrate(&models.Booking{})
db.AutoMigrate(&models.Hotel{})
db.AutoMigrate(&models.User{})
DB = db
}
func (c *GormController) SetDB() revel.Result {
c.Txn = DB
return nil
}
type Transactional struct {
*revel.Controller
Txn *sql.Tx
}
tree
enter image description here
maybe import miss?

Can golang plugins be used for factory functions?

I have the following code in a golang plugin module:
plug.go
package main
import "fmt"
var (
Thing = New("first thing")
ThingFactory = thingFactory{}
)
type thing struct {
i int
s string
}
func New(s string) thing {
return thing{s: s}
}
func (t *thing) Say() string {
t.i++
return fmt.Sprintf("%s - %d", t.s, t.i)
}
type thingFactory struct{}
func (t thingFactory) Make(s string) thing {
return New(s)
}
it is compiled as a .so object and used in another program:
main.go
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("../plug/plug.so")
if err != nil {
panic(err)
}
symbol, err := p.Lookup("Thing")
if err != nil {
panic(err)
}
thing := symbol.(Sayer)
fmt.Println(thing.Say())
symbol, err = p.Lookup("ThingFactory") // <-problems start here
if err != nil {
panic(err)
}
factory := symbol.(GetSayer)
madeThing := factory.Make("how about me?")
fmt.Println(madeThing.Say())
fmt.Println(madeThing.Say())
}
type Sayer interface {
Say() string
}
type GetSayer interface {
Make(string) Sayer
}
I'm able to lookup the Thing, and call Say() on it, but the second interface conversion panics:
first thing - 1
panic: interface conversion: *main.thingFactory is not main.GetSayer: missing method Make
even though the runtime recognizes the first symbol as a Sayer it doesn't recognize that thingFactory obviously has a Make() method, which should return something that is also a Sayer.
Am I missing something obvious here?
The first problem is that in your plugin thingFactory (more precicely *thingfactory) does not have a method described in your main app's GetSayer interface:
Make(string) Sayer
You have:
Make(string) thing
So (first) you have to change thingFactory.Make() to this:
type Sayer interface {
Say() string
}
func (t thingFactory) Make(s string) Sayer {
th := New(s)
return &th
}
After this it still won't work. And the reason for this is because the plugin's Sayer type is not identical to your main app's Sayer type. But they must be the same in order to implement your main app's GetSayer interface.
One solution is to "outsource" the Sayer interface to its own package, and use this common, shared package both in the plugin and in the main app.
Let's create a new package, call it subplay:
package subplay
type Sayer interface {
Say() string
}
Import this package and use it in the plugin:
package main
import (
"fmt"
"path/to/subplay"
)
var (
Thing = New("first thing")
ThingFactory = thingFactory{}
)
type thing struct {
i int
s string
}
func New(s string) thing {
return thing{s: s}
}
func (t *thing) Say() string {
t.i++
return fmt.Sprintf("%s - %d", t.s, t.i)
}
type thingFactory struct{}
func (t thingFactory) Make(s string) subplay.Sayer {
th := New(s)
return &th
}
And also import and use it in the main app:
package main
import (
"fmt"
"path/to/subplay"
"plugin"
)
func main() {
p, err := plugin.Open("../plug/plug.so")
if err != nil {
panic(err)
}
symbol, err := p.Lookup("Thing")
if err != nil {
panic(err)
}
thing := symbol.(subplay.Sayer)
fmt.Println(thing.Say())
symbol, err = p.Lookup("ThingFactory")
if err != nil {
panic(err)
}
factory := symbol.(GetSayer)
madeThing := factory.Make("how about me?")
fmt.Println(madeThing.Say())
fmt.Println(madeThing.Say())
}
type GetSayer interface {
Make(string) subplay.Sayer
}
Now it will work, and output will be:
first thing - 1
how about me? - 1
how about me? - 2
See related questions:
go 1.8 plugin use custom interface
How do Go plugin dependencies work?
Your plugin Make method should return a Sayer object not thing
type Sayer interface {
Say() string
}
func (t *thingFactory) Make(s string) Sayer {
return New(s)
}

go 1.8 plugin use custom interface

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.
filter.Filter is in pkg/filter package.
load plugin file in proxy/factory.go
plugin go file is in another project.
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 (
"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
}
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 (
"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())
}
Output:
[plugin GetFilter] Returning filter: main.plgFilter {}
GetFilter result: main.plgFilter {} <nil>
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 (
"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
}
And the main app:
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
}
Output:
[plugin GetFilterIface] Returning filter: main.plgFilter {}
GetFilterIface result: main.plgFilter {} <nil>
Name: Bob
Age: 23
Also see related question: How do Go plugin dependencies work?

Interfaces and overloading a struct?

I am completely new to go and am planning on building a bittorent client as my first project to learn the language. One aspect is econding and decoding of bittorrent files. An example that I've found creates a struct containing a bufio.Reader, and then uses some interfaces to add additional methods. Can someone explain the mechanism for constructing these interfaces in such a way that overloads the struct? I'm not clear on how the functions in this file are being added to the decoder struct (https://github.com/marksamman/bencode/blob/master/decoder.go):
package bencode
import (
"bufio"
"errors"
"io"
"strconv"
)
type decoder struct {
bufio.Reader
}
func (decoder *decoder) readDictionary() (map[string]interface{}, error) {
dict := make(map[string]interface{})
}
func (decoder *decoder) readString() (string, error) {
...
}
func (decoder *decoder) readList() ([]interface{}, error) {
...
}
func Decode(reader io.Reader) (map[string]interface{}, error) {
decoder := decoder{*bufio.NewReader(reader)}
if firstByte, err := decoder.ReadByte(); err != nil {
return make(map[string]interface{}), nil
} else if firstByte != 'd' {
return nil, errors.New("bencode data must begin with a dictionary")
}
return decoder.readDictionary()
}

Resources