forked from documize/glick
/
rpc.go
168 lines (156 loc) · 3.72 KB
/
rpc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package glick
import (
"crypto/tls"
"fmt"
"io"
"net/rpc"
"net/rpc/jsonrpc"
"net/url"
"os/exec"
"reflect"
"golang.org/x/net/context"
)
// InsecureSkipVerifyTLS should only be set to true when testing.
var InsecureSkipVerifyTLS = false
// PluginRPC returns a type which implements the Plugger interface for making an RPC.
// The return type of this class of plugin must be a pointer.
// The plugin creates a client per call to allow services to go up-and-down between calls.
func PluginRPC(useJSON bool, serviceMethod, endPoint string, ppo ProtoPlugOut) Plugin {
if endPoint == "" || serviceMethod == "" ||
reflect.TypeOf(ppo()).Kind() != reflect.Ptr {
return nil
}
url, err := url.Parse(endPoint)
if err != nil {
return nil
}
useTLS := false
switch url.Scheme {
case "http":
endPoint = url.Host
case "https":
endPoint = url.Host
useTLS = true
}
return func(ctx context.Context, in interface{}) (out interface{}, err error) {
var client *rpc.Client
var conn *tls.Conn
var connClose = func() {
if e := conn.Close(); err == nil {
err = e
}
}
var errDial error
var cfg = &tls.Config{
InsecureSkipVerify: InsecureSkipVerifyTLS,
}
if useJSON {
if useTLS {
conn, errDial = tls.Dial("tcp", endPoint, cfg)
if errDial == nil {
defer connClose()
client = jsonrpc.NewClient(conn)
}
} else {
client, errDial = jsonrpc.Dial("tcp", endPoint)
}
} else {
if useTLS {
conn, errDial = tls.Dial("tcp", endPoint, cfg)
if errDial == nil {
defer connClose()
client = rpc.NewClient(conn)
}
} else {
client, errDial = rpc.Dial("tcp", endPoint)
}
}
if errDial != nil {
return nil, errDial
}
out = ppo()
err = client.Call(serviceMethod, in, out)
err2 := client.Close()
if err == nil {
err = err2
}
return
}
}
// ConfigRPC provides the Configurator for the RPC class of plugin.
func ConfigRPC(lib *Library) error {
if lib == nil {
return ErrNilLib
}
return lib.AddConfigurator("RPC", func(l *Library, line int, cfg *Config) error {
ppo := l.apim[cfg.API].ppo
pi := PluginRPC(!cfg.Gob, cfg.Method, cfg.Path, ppo)
for _, action := range cfg.Actions {
if err := l.RegPlugin(cfg.API, action, pi, cfg); err != nil {
return fmt.Errorf("entry %d RPC register plugin error: %v",
line, err)
}
}
return nil
})
}
type rpcLog struct {
plugin []byte
target io.Writer
}
func (l rpcLog) Write(p []byte) (int, error) {
b := make([]byte, 0, len(l.plugin)+len(p))
b = append(b, l.plugin...)
b = append(b, p...)
_, err := l.target.Write(b)
return len(p), err
}
func validRPC(v plugval) bool {
if v.cfg != nil {
if !v.cfg.Disabled &&
v.cfg.Type == "RPC" &&
len(v.cfg.Cmd) > 0 &&
v.cfg.Cmd[0] != "" &&
v.cfg.Plugin != "" {
return true
}
}
return false
}
// StartLocalRPCservers starts up local RPC server plugins.
// TODO add tests.
func (l *Library) StartLocalRPCservers(stdOut, stdErr io.Writer) error {
if l == nil {
return ErrNilLib
}
l.mtx.RLock()
defer l.mtx.RUnlock()
servers := make(map[string]struct{})
for _, v := range l.pim {
if validRPC(v) {
_, found := servers[v.cfg.Plugin]
if !found {
servers[v.cfg.Plugin] = struct{}{}
cmdPath, e := exec.LookPath(v.cfg.Cmd[0])
if e != nil {
return errNoPlug(v.cfg.Cmd[0] + " (error: " + e.Error() + ")")
}
fmt.Fprintln(stdOut, "Start local RPC server:", v.cfg.Plugin)
var se, so rpcLog
se.plugin = []byte(v.cfg.Plugin + ": ")
so.plugin = se.plugin
se.target = stdErr
so.target = stdOut
ecmd := exec.Command(cmdPath, v.cfg.Cmd[1:]...)
ecmd.Stdout = so
ecmd.Stderr = se
err := ecmd.Start()
if err != nil {
return err
}
l.subprocs = append(l.subprocs, ecmd)
}
}
}
return nil
}