Plugins: a very simple solution

290 views
Skip to first unread message

Tamás Gulácsi

unread,
Mar 4, 2015, 12:42:36 AM3/4/15
to golan...@googlegroups.com
Hi all!

I've needed a very simple plugin system, to separate the core, pure-go program from the specific, "cgo-polluted" parts.
And thanks to interfaces and net/rpc, I've found a very simple solution: just call the plugin from the core program
with os/exec, and use stdin/stdout for communication with RPC.

// NewClient creates a new RPC client.
func
NewClient(pluginName string) (*rpc.Client, error) {
    cmd
:= exec.Command("my-plugins-" + pluginName)
   
in, err := cmd.StdinPipe()
   
if err != nil {
       
return nil, err
   
}
   
out, err := cmd.StdoutPipe()
   
if err != nil {
       
return nil, err
   
}
    cmd
.Stderr = os.Stderr
   
if err := cmd.Start(); err != nil {
       
return nil, err
   
}
    cc
:= procCloser{cmd.Process}

    inout
:= struct {
        io
.Reader
        io
.Writer
        io
.Closer
   
}{out, in, multiCloser{[]io.Closer{in, out, cc}}}

   
return rpc.NewClient(inout), nil
}

// NewServer creates a new server using the process' stdin and stdout.
// Does not return, starts serving.
func
NewServerServe(rcvr HookRPC) {
    s
:= rpc.NewServer()
    s
.RegisterName("Hook", rcvr)
    s
.ServeConn(struct {
        io
.Reader
        io
.Writer
        io
.Closer
   
}{os.Stdin, os.Stdout,
        multiCloser
{[]io.Closer{os.Stdout, os.Stdin, procCloser{}}},
   
})
}

A more complete example is here: http://play.golang.org/p/TWq1vsB0Xj


Another nice trick I've found that is net/rpc finds exported function using reflect, and complains on non-rpcable functions.
This can be solved by simply wrapping our PluginRPC interface implementing thing in a

struct rpcWrap struct {
  PluginRPC
}

and using that. This way net/rpc sees only those specific function in PluginRPC which we wanted to export.
Reply all
Reply to author
Forward
0 new messages