github.com/levb/mattermost-server@v5.3.1+incompatible/plugin/client_rpc.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 //go:generate go run interface_generator/main.go 5 6 package plugin 7 8 import ( 9 "bytes" 10 "encoding/gob" 11 "encoding/json" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "log" 16 "net/http" 17 "net/rpc" 18 "os" 19 "reflect" 20 21 "github.com/hashicorp/go-plugin" 22 "github.com/mattermost/mattermost-server/mlog" 23 "github.com/mattermost/mattermost-server/model" 24 ) 25 26 var hookNameToId map[string]int = make(map[string]int) 27 28 type hooksRPCClient struct { 29 client *rpc.Client 30 log *mlog.Logger 31 muxBroker *plugin.MuxBroker 32 apiImpl API 33 implemented [TotalHooksId]bool 34 } 35 36 type hooksRPCServer struct { 37 impl interface{} 38 muxBroker *plugin.MuxBroker 39 apiRPCClient *apiRPCClient 40 } 41 42 // Implements hashicorp/go-plugin/plugin.Plugin interface to connect the hooks of a plugin 43 type hooksPlugin struct { 44 hooks interface{} 45 apiImpl API 46 log *mlog.Logger 47 } 48 49 func (p *hooksPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { 50 return &hooksRPCServer{impl: p.hooks, muxBroker: b}, nil 51 } 52 53 func (p *hooksPlugin) Client(b *plugin.MuxBroker, client *rpc.Client) (interface{}, error) { 54 return &hooksRPCClient{client: client, log: p.log, muxBroker: b, apiImpl: p.apiImpl}, nil 55 } 56 57 type apiRPCClient struct { 58 client *rpc.Client 59 } 60 61 type apiRPCServer struct { 62 impl API 63 } 64 65 // Registering some types used by MM for encoding/gob used by rpc 66 func init() { 67 gob.Register([]*model.SlackAttachment{}) 68 gob.Register([]interface{}{}) 69 gob.Register(map[string]interface{}{}) 70 gob.Register(&model.AppError{}) 71 } 72 73 // These enforce compile time checks to make sure types implement the interface 74 // If you are getting an error here, you probably need to run `make pluginapi` to 75 // autogenerate RPC glue code 76 var _ plugin.Plugin = &hooksPlugin{} 77 var _ Hooks = &hooksRPCClient{} 78 79 // 80 // Below are specal cases for hooks or APIs that can not be auto generated 81 // 82 83 func (g *hooksRPCClient) Implemented() (impl []string, err error) { 84 err = g.client.Call("Plugin.Implemented", struct{}{}, &impl) 85 for _, hookName := range impl { 86 if hookId, ok := hookNameToId[hookName]; ok { 87 g.implemented[hookId] = true 88 } 89 } 90 return 91 } 92 93 // Implemented replies with the names of the hooks that are implemented. 94 func (s *hooksRPCServer) Implemented(args struct{}, reply *[]string) error { 95 ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem() 96 implType := reflect.TypeOf(s.impl) 97 selfType := reflect.TypeOf(s) 98 var methods []string 99 for i := 0; i < ifaceType.NumMethod(); i++ { 100 method := ifaceType.Method(i) 101 if m, ok := implType.MethodByName(method.Name); !ok { 102 continue 103 } else if m.Type.NumIn() != method.Type.NumIn()+1 { 104 continue 105 } else if m.Type.NumOut() != method.Type.NumOut() { 106 continue 107 } else { 108 match := true 109 for j := 0; j < method.Type.NumIn(); j++ { 110 if m.Type.In(j+1) != method.Type.In(j) { 111 match = false 112 break 113 } 114 } 115 for j := 0; j < method.Type.NumOut(); j++ { 116 if m.Type.Out(j) != method.Type.Out(j) { 117 match = false 118 break 119 } 120 } 121 if !match { 122 continue 123 } 124 } 125 if _, ok := selfType.MethodByName(method.Name); !ok { 126 continue 127 } 128 methods = append(methods, method.Name) 129 } 130 *reply = methods 131 return nil 132 } 133 134 type Z_OnActivateArgs struct { 135 APIMuxId uint32 136 } 137 138 type Z_OnActivateReturns struct { 139 A error 140 } 141 142 func (g *hooksRPCClient) OnActivate() error { 143 muxId := g.muxBroker.NextId() 144 go g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{ 145 impl: g.apiImpl, 146 }) 147 148 _args := &Z_OnActivateArgs{ 149 APIMuxId: muxId, 150 } 151 _returns := &Z_OnActivateReturns{} 152 153 if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil { 154 g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err)) 155 } 156 return _returns.A 157 } 158 159 func (s *hooksRPCServer) OnActivate(args *Z_OnActivateArgs, returns *Z_OnActivateReturns) error { 160 connection, err := s.muxBroker.Dial(args.APIMuxId) 161 if err != nil { 162 return err 163 } 164 165 s.apiRPCClient = &apiRPCClient{ 166 client: rpc.NewClient(connection), 167 } 168 169 if mmplugin, ok := s.impl.(interface { 170 SetAPI(api API) 171 OnConfigurationChange() error 172 }); !ok { 173 } else { 174 mmplugin.SetAPI(s.apiRPCClient) 175 mmplugin.OnConfigurationChange() 176 } 177 178 // Capture output of standard logger because go-plugin 179 // redirects it. 180 log.SetOutput(os.Stderr) 181 182 if hook, ok := s.impl.(interface { 183 OnActivate() error 184 }); ok { 185 returns.A = hook.OnActivate() 186 } 187 return nil 188 } 189 190 type Z_LoadPluginConfigurationArgsArgs struct { 191 } 192 193 type Z_LoadPluginConfigurationArgsReturns struct { 194 A []byte 195 } 196 197 func (g *apiRPCClient) LoadPluginConfiguration(dest interface{}) error { 198 _args := &Z_LoadPluginConfigurationArgsArgs{} 199 _returns := &Z_LoadPluginConfigurationArgsReturns{} 200 if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil { 201 log.Printf("RPC call to LoadPluginConfiguration API failed: %s", err.Error()) 202 } 203 if err := json.Unmarshal(_returns.A, dest); err != nil { 204 log.Printf("LoadPluginConfiguration API failed to unmarshal: %s", err.Error()) 205 } 206 return nil 207 } 208 209 func (s *apiRPCServer) LoadPluginConfiguration(args *Z_LoadPluginConfigurationArgsArgs, returns *Z_LoadPluginConfigurationArgsReturns) error { 210 var config interface{} 211 if hook, ok := s.impl.(interface { 212 LoadPluginConfiguration(dest interface{}) error 213 }); ok { 214 if err := hook.LoadPluginConfiguration(&config); err != nil { 215 return err 216 } 217 } 218 b, err := json.Marshal(config) 219 if err != nil { 220 return err 221 } 222 returns.A = b 223 return nil 224 } 225 226 func init() { 227 hookNameToId["ServeHTTP"] = ServeHTTPId 228 } 229 230 type Z_ServeHTTPArgs struct { 231 ResponseWriterStream uint32 232 Request *http.Request 233 Context *Context 234 RequestBodyStream uint32 235 } 236 237 func (g *hooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) { 238 if !g.implemented[ServeHTTPId] { 239 http.NotFound(w, r) 240 return 241 } 242 243 serveHTTPStreamId := g.muxBroker.NextId() 244 go func() { 245 connection, err := g.muxBroker.Accept(serveHTTPStreamId) 246 if err != nil { 247 g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't accept connection", mlog.Uint32("serve_http_stream_id", serveHTTPStreamId), mlog.Err(err)) 248 http.Error(w, "500 internal server error", http.StatusInternalServerError) 249 return 250 } 251 defer connection.Close() 252 253 rpcServer := rpc.NewServer() 254 if err := rpcServer.RegisterName("Plugin", &httpResponseWriterRPCServer{w: w}); err != nil { 255 g.log.Error("Plugin failed to ServeHTTP, coulden't register RPC name", mlog.Err(err)) 256 http.Error(w, "500 internal server error", http.StatusInternalServerError) 257 return 258 } 259 rpcServer.ServeConn(connection) 260 }() 261 262 requestBodyStreamId := uint32(0) 263 if r.Body != nil { 264 requestBodyStreamId = g.muxBroker.NextId() 265 go func() { 266 bodyConnection, err := g.muxBroker.Accept(requestBodyStreamId) 267 if err != nil { 268 g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't Accept request body connecion", mlog.Err(err)) 269 http.Error(w, "500 internal server error", http.StatusInternalServerError) 270 return 271 } 272 defer bodyConnection.Close() 273 serveIOReader(r.Body, bodyConnection) 274 }() 275 } 276 277 forwardedRequest := &http.Request{ 278 Method: r.Method, 279 URL: r.URL, 280 Proto: r.Proto, 281 ProtoMajor: r.ProtoMajor, 282 ProtoMinor: r.ProtoMinor, 283 Header: r.Header, 284 Host: r.Host, 285 RemoteAddr: r.RemoteAddr, 286 RequestURI: r.RequestURI, 287 } 288 289 if err := g.client.Call("Plugin.ServeHTTP", Z_ServeHTTPArgs{ 290 Context: c, 291 ResponseWriterStream: serveHTTPStreamId, 292 Request: forwardedRequest, 293 RequestBodyStream: requestBodyStreamId, 294 }, nil); err != nil { 295 g.log.Error("Plugin failed to ServeHTTP, RPC call failed", mlog.Err(err)) 296 http.Error(w, "500 internal server error", http.StatusInternalServerError) 297 } 298 return 299 } 300 301 func (s *hooksRPCServer) ServeHTTP(args *Z_ServeHTTPArgs, returns *struct{}) error { 302 connection, err := s.muxBroker.Dial(args.ResponseWriterStream) 303 if err != nil { 304 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error()) 305 return err 306 } 307 w := connectHTTPResponseWriter(connection) 308 defer w.Close() 309 310 r := args.Request 311 if args.RequestBodyStream != 0 { 312 connection, err := s.muxBroker.Dial(args.RequestBodyStream) 313 if err != nil { 314 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error()) 315 return err 316 } 317 r.Body = connectIOReader(connection) 318 } else { 319 r.Body = ioutil.NopCloser(&bytes.Buffer{}) 320 } 321 defer r.Body.Close() 322 323 if hook, ok := s.impl.(interface { 324 ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) 325 }); ok { 326 hook.ServeHTTP(args.Context, w, r) 327 } else { 328 http.NotFound(w, r) 329 } 330 331 return nil 332 } 333 334 func init() { 335 hookNameToId["FileWillBeUploaded"] = FileWillBeUploadedId 336 } 337 338 type Z_FileWillBeUploadedArgs struct { 339 A *Context 340 B *model.FileInfo 341 UploadedFileStream uint32 342 ReplacementFileStream uint32 343 } 344 345 type Z_FileWillBeUploadedReturns struct { 346 A *model.FileInfo 347 B string 348 } 349 350 func (g *hooksRPCClient) FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { 351 if !g.implemented[FileWillBeUploadedId] { 352 return info, "" 353 } 354 355 uploadedFileStreamId := g.muxBroker.NextId() 356 go func() { 357 uploadedFileConnection, err := g.muxBroker.Accept(uploadedFileStreamId) 358 if err != nil { 359 g.log.Error("Plugin failed to serve upload file stream. MuxBroker could not Accept connection", mlog.Err(err)) 360 return 361 } 362 defer uploadedFileConnection.Close() 363 serveIOReader(file, uploadedFileConnection) 364 }() 365 366 replacementFileStreamId := g.muxBroker.NextId() 367 go func() { 368 replacementFileConnection, err := g.muxBroker.Accept(replacementFileStreamId) 369 if err != nil { 370 g.log.Error("Plugin failed to serve replacement file stream. MuxBroker could not Accept connection", mlog.Err(err)) 371 return 372 } 373 defer replacementFileConnection.Close() 374 if _, err := io.Copy(output, replacementFileConnection); err != nil && err != io.EOF { 375 g.log.Error("Error reading replacement file.", mlog.Err(err)) 376 } 377 }() 378 379 _args := &Z_FileWillBeUploadedArgs{c, info, uploadedFileStreamId, replacementFileStreamId} 380 _returns := &Z_FileWillBeUploadedReturns{} 381 if g.implemented[FileWillBeUploadedId] { 382 if err := g.client.Call("Plugin.FileWillBeUploaded", _args, _returns); err != nil { 383 g.log.Error("RPC call FileWillBeUploaded to plugin failed.", mlog.Err(err)) 384 } 385 } 386 return _returns.A, _returns.B 387 } 388 389 func (s *hooksRPCServer) FileWillBeUploaded(args *Z_FileWillBeUploadedArgs, returns *Z_FileWillBeUploadedReturns) error { 390 uploadFileConnection, err := s.muxBroker.Dial(args.UploadedFileStream) 391 if err != nil { 392 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote upload file stream, error: %v", err.Error()) 393 return err 394 } 395 defer uploadFileConnection.Close() 396 fileReader := connectIOReader(uploadFileConnection) 397 defer fileReader.Close() 398 399 replacementFileConnection, err := s.muxBroker.Dial(args.ReplacementFileStream) 400 if err != nil { 401 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote replacement file stream, error: %v", err.Error()) 402 return err 403 } 404 defer replacementFileConnection.Close() 405 returnFileWriter := replacementFileConnection 406 407 if hook, ok := s.impl.(interface { 408 FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) 409 }); ok { 410 returns.A, returns.B = hook.FileWillBeUploaded(args.A, args.B, fileReader, returnFileWriter) 411 } else { 412 return fmt.Errorf("Hook FileWillBeUploaded called but not implemented.") 413 } 414 return nil 415 }