commit 25ad77318d92842b7e91c94e2d10da6ab95d2286 Author: Javier Guerra Date: Fri Nov 15 11:22:25 2019 -0500 feat(*) initial commit msgpackrpc and plugin loader (copied from existing runtime) diff --git a/pluginserver.go b/pluginserver.go new file mode 100644 index 0000000..46df8f3 --- /dev/null +++ b/pluginserver.go @@ -0,0 +1,236 @@ +package main + +import ( + "flag" + "fmt" + "github.com/kong/go-pdk" + "github.com/ugorji/go/codec" + "log" + "net" + "net/rpc" + "path" + "plugin" + "reflect" + "strings" +) + +var socket = flag.String("socket", "", "Socket to listen into") + +func runServer(listener net.Listener) { + var handle codec.MsgpackHandle + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + log.Printf("accept(): %s", err) + return + } + // rpcCodec := codec.GoRpc.ServerCodec(conn, &handle) + rpcCodec := codec.MsgpackSpecRpc.ServerCodec(conn, &handle) + rpc.ServeCodec(rpcCodec) + } + }() +} + +func main() { + flag.Parse() + + if *socket != "" { + listener, err := net.Listen("unix", *socket) + if err != nil { + log.Printf(`listen("%s"): %s`, socket, err) + return + } + + rpc.RegisterName("plugin", &PluginServer{}) + + runServer(listener) + } +} + +// --- pluginData --- // +type pluginData struct { + code *plugin.Plugin + initialized bool + config interface{} + handlers map[string]func(kong *pdk.PDK) +} + +type ( + certificater interface{ Certificate(*pdk.PDK) } + rewriter interface{ Rewrite(*pdk.PDK) } + accesser interface{ Access(*pdk.PDK) } + headerFilter interface{ HeaderFilter(*pdk.PDK) } + bodyFilter interface{ BodyFilter(*pdk.PDK) } + prereader interface{ Preread(*pdk.PDK) } + logger interface{ Log(*pdk.PDK) } +) + +func (plug *pluginData) setHandlers() { + handlers := map[string]func(kong *pdk.PDK){} + config := plug.config + + if h, ok := config.(certificater); ok { handlers["certificate"] = h.Certificate } + if h, ok := config.(rewriter) ; ok { handlers["rewrite"] = h.Rewrite } + if h, ok := config.(accesser) ; ok { handlers["access"] = h.Access } + if h, ok := config.(headerFilter); ok { handlers["header_filter"] = h.HeaderFilter } + if h, ok := config.(bodyFilter) ; ok { handlers["body_filter"] = h.BodyFilter } + if h, ok := config.(prereader) ; ok { handlers["preread"] = h.Preread } + if h, ok := config.(logger) ; ok { handlers["log"] = h.Log } + + plug.handlers = handlers +} + +// --- PluginServer --- // +type PluginServer struct { + pluginsDir string + plugins map[string]pluginData +} + +/// exported method +func (s *PluginServer) SetPluginDir(dir string, reply *string) error { + s.pluginsDir = dir + *reply = "ok" + return nil +} + +func (s PluginServer) loadPlugin(name string) (plug pluginData, err error) { + plug, ok := s.plugins[name] + if ok { + return + } + + code, err := plugin.Open(path.Join(s.pluginsDir, name+".so")) + if err != nil { + err = fmt.Errorf("failed to open plugin %s: %w", name, err) + return + } + + constructorSymbol, err := code.Lookup("New") + if err != nil { + err = fmt.Errorf("No constructor function on plugin %s: %w", name, err) + return + } + constructor, ok := constructorSymbol.(func() interface{}) + if !ok { + err = fmt.Errorf("Wrong constructor signature on plugin %s: %w", name, err) + return + } + + plug = pluginData{ + code: code, + config: constructor(), + } + plug.setHandlers() + + s.plugins[name] = plug + return +} + +func getSchemaType(t reflect.Type) (string, bool) { + //log.Printf("SCHEMA TYPE FOR T IS %s\n", t.String()) + switch t.Kind() { + case reflect.String: + return `"string"`, true + case reflect.Bool: + return `"boolean"`, true + case reflect.Int, reflect.Int32: + return `"integer"`, true + case reflect.Uint, reflect.Uint32: + return `"integer","between":[0,2147483648]`, true + case reflect.Float32, reflect.Float64: + return `"number"`, true + case reflect.Array: + elemType, ok := getSchemaType(t.Elem()) + if !ok { + break + } + return `"array","elements":{"type":` + elemType + `}`, true + case reflect.Map: + kType, ok := getSchemaType(t.Key()) + vType, ok := getSchemaType(t.Elem()) + if !ok { + break + } + return `"map","keys":{"type":` + kType + `},"values":{"type":` + vType + `}`, true + case reflect.Struct: + var out strings.Builder + out.WriteString(`"record","fields":[`) + n := t.NumField() + for i := 0; i < n; i++ { + field := t.Field(i) + typeDecl, ok := getSchemaType(field.Type) + if !ok { + // ignore unrepresentable types + continue + } + if i > 0 { + out.WriteString(`,`) + } + name := field.Tag.Get("json") + if name == "" { + name = strings.ToLower(field.Name) + } + out.WriteString(`{"`) + out.WriteString(name) + out.WriteString(`":{"type":`) + out.WriteString(typeDecl) + out.WriteString(`}}`) + } + out.WriteString(`]`) + return out.String(), true + } + return "", false +} + +type PluginInfo struct { + name string + phases []string + version string + priority int + schema string +} + +/// exported method +func (s *PluginServer) GetPluginInfo(name string, info *PluginInfo) error { + info.name = name + + plug, err := s.loadPlugin(name) + if err != nil { + return err + } + + *info = PluginInfo{} + + info.phases = make([]string, len(plug.handlers)) + var i = 0 + for name := range plug.handlers { + info.phases[i] = name + i++ + } + + v, _ := plug.code.Lookup("Version") + if v != nil { + info.version = v.(string) + } + + prio, _ := plug.code.Lookup("Priority") + if prio != nil { + info.priority = prio.(int) + } + + var out strings.Builder + out.WriteString(`{"name":"`) + out.WriteString(name) + out.WriteString(`","fields":[{"config":{"type":`) + + st, _ := getSchemaType(reflect.TypeOf(plug.config).Elem()) + out.WriteString(st) + + out.WriteString(`}}]}`) + + info.schema = out.String() + + return nil +}