github.com/turgay/mattermost-server@v5.3.2-0.20181002173352-2945e8a2b0ce+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 // ErrorString is a fallback for sending unregistered implementations of the error interface across 66 // rpc. For example, the errorString type from the github.com/pkg/errors package cannot be 67 // registered since it is not exported, but this precludes common error handling paradigms. 68 // ErrorString merely preserves the string description of the error, while satisfying the error 69 // interface itself to allow other registered types (such as model.AppError) to be sent unmodified. 70 type ErrorString struct { 71 Err string 72 } 73 74 func (e ErrorString) Error() string { 75 return e.Err 76 } 77 78 func encodableError(err error) error { 79 if err == nil { 80 return nil 81 } 82 if _, ok := err.(*model.AppError); ok { 83 return err 84 } 85 86 return &ErrorString{ 87 Err: err.Error(), 88 } 89 } 90 91 // Registering some types used by MM for encoding/gob used by rpc 92 func init() { 93 gob.Register([]*model.SlackAttachment{}) 94 gob.Register([]interface{}{}) 95 gob.Register(map[string]interface{}{}) 96 gob.Register(&model.AppError{}) 97 gob.Register(&ErrorString{}) 98 } 99 100 // These enforce compile time checks to make sure types implement the interface 101 // If you are getting an error here, you probably need to run `make pluginapi` to 102 // autogenerate RPC glue code 103 var _ plugin.Plugin = &hooksPlugin{} 104 var _ Hooks = &hooksRPCClient{} 105 106 // 107 // Below are specal cases for hooks or APIs that can not be auto generated 108 // 109 110 func (g *hooksRPCClient) Implemented() (impl []string, err error) { 111 err = g.client.Call("Plugin.Implemented", struct{}{}, &impl) 112 for _, hookName := range impl { 113 if hookId, ok := hookNameToId[hookName]; ok { 114 g.implemented[hookId] = true 115 } 116 } 117 return 118 } 119 120 // Implemented replies with the names of the hooks that are implemented. 121 func (s *hooksRPCServer) Implemented(args struct{}, reply *[]string) error { 122 ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem() 123 implType := reflect.TypeOf(s.impl) 124 selfType := reflect.TypeOf(s) 125 var methods []string 126 for i := 0; i < ifaceType.NumMethod(); i++ { 127 method := ifaceType.Method(i) 128 if m, ok := implType.MethodByName(method.Name); !ok { 129 continue 130 } else if m.Type.NumIn() != method.Type.NumIn()+1 { 131 continue 132 } else if m.Type.NumOut() != method.Type.NumOut() { 133 continue 134 } else { 135 match := true 136 for j := 0; j < method.Type.NumIn(); j++ { 137 if m.Type.In(j+1) != method.Type.In(j) { 138 match = false 139 break 140 } 141 } 142 for j := 0; j < method.Type.NumOut(); j++ { 143 if m.Type.Out(j) != method.Type.Out(j) { 144 match = false 145 break 146 } 147 } 148 if !match { 149 continue 150 } 151 } 152 if _, ok := selfType.MethodByName(method.Name); !ok { 153 continue 154 } 155 methods = append(methods, method.Name) 156 } 157 *reply = methods 158 return encodableError(nil) 159 } 160 161 type Z_OnActivateArgs struct { 162 APIMuxId uint32 163 } 164 165 type Z_OnActivateReturns struct { 166 A error 167 } 168 169 func (g *hooksRPCClient) OnActivate() error { 170 muxId := g.muxBroker.NextId() 171 go g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{ 172 impl: g.apiImpl, 173 }) 174 175 _args := &Z_OnActivateArgs{ 176 APIMuxId: muxId, 177 } 178 _returns := &Z_OnActivateReturns{} 179 180 if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil { 181 g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err)) 182 } 183 return _returns.A 184 } 185 186 func (s *hooksRPCServer) OnActivate(args *Z_OnActivateArgs, returns *Z_OnActivateReturns) error { 187 connection, err := s.muxBroker.Dial(args.APIMuxId) 188 if err != nil { 189 return err 190 } 191 192 s.apiRPCClient = &apiRPCClient{ 193 client: rpc.NewClient(connection), 194 } 195 196 if mmplugin, ok := s.impl.(interface { 197 SetAPI(api API) 198 OnConfigurationChange() error 199 }); !ok { 200 } else { 201 mmplugin.SetAPI(s.apiRPCClient) 202 mmplugin.OnConfigurationChange() 203 } 204 205 // Capture output of standard logger because go-plugin 206 // redirects it. 207 log.SetOutput(os.Stderr) 208 209 if hook, ok := s.impl.(interface { 210 OnActivate() error 211 }); ok { 212 returns.A = encodableError(hook.OnActivate()) 213 } 214 return nil 215 } 216 217 type Z_LoadPluginConfigurationArgsArgs struct { 218 } 219 220 type Z_LoadPluginConfigurationArgsReturns struct { 221 A []byte 222 } 223 224 func (g *apiRPCClient) LoadPluginConfiguration(dest interface{}) error { 225 _args := &Z_LoadPluginConfigurationArgsArgs{} 226 _returns := &Z_LoadPluginConfigurationArgsReturns{} 227 if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil { 228 log.Printf("RPC call to LoadPluginConfiguration API failed: %s", err.Error()) 229 } 230 if err := json.Unmarshal(_returns.A, dest); err != nil { 231 log.Printf("LoadPluginConfiguration API failed to unmarshal: %s", err.Error()) 232 } 233 return nil 234 } 235 236 func (s *apiRPCServer) LoadPluginConfiguration(args *Z_LoadPluginConfigurationArgsArgs, returns *Z_LoadPluginConfigurationArgsReturns) error { 237 var config interface{} 238 if hook, ok := s.impl.(interface { 239 LoadPluginConfiguration(dest interface{}) error 240 }); ok { 241 if err := hook.LoadPluginConfiguration(&config); err != nil { 242 return err 243 } 244 } 245 b, err := json.Marshal(config) 246 if err != nil { 247 return err 248 } 249 returns.A = b 250 return nil 251 } 252 253 func init() { 254 hookNameToId["ServeHTTP"] = ServeHTTPId 255 } 256 257 type Z_ServeHTTPArgs struct { 258 ResponseWriterStream uint32 259 Request *http.Request 260 Context *Context 261 RequestBodyStream uint32 262 } 263 264 func (g *hooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) { 265 if !g.implemented[ServeHTTPId] { 266 http.NotFound(w, r) 267 return 268 } 269 270 serveHTTPStreamId := g.muxBroker.NextId() 271 go func() { 272 connection, err := g.muxBroker.Accept(serveHTTPStreamId) 273 if err != nil { 274 g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't accept connection", mlog.Uint32("serve_http_stream_id", serveHTTPStreamId), mlog.Err(err)) 275 http.Error(w, "500 internal server error", http.StatusInternalServerError) 276 return 277 } 278 defer connection.Close() 279 280 rpcServer := rpc.NewServer() 281 if err := rpcServer.RegisterName("Plugin", &httpResponseWriterRPCServer{w: w}); err != nil { 282 g.log.Error("Plugin failed to ServeHTTP, coulden't register RPC name", mlog.Err(err)) 283 http.Error(w, "500 internal server error", http.StatusInternalServerError) 284 return 285 } 286 rpcServer.ServeConn(connection) 287 }() 288 289 requestBodyStreamId := uint32(0) 290 if r.Body != nil { 291 requestBodyStreamId = g.muxBroker.NextId() 292 go func() { 293 bodyConnection, err := g.muxBroker.Accept(requestBodyStreamId) 294 if err != nil { 295 g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't Accept request body connection", mlog.Err(err)) 296 http.Error(w, "500 internal server error", http.StatusInternalServerError) 297 return 298 } 299 defer bodyConnection.Close() 300 serveIOReader(r.Body, bodyConnection) 301 }() 302 } 303 304 forwardedRequest := &http.Request{ 305 Method: r.Method, 306 URL: r.URL, 307 Proto: r.Proto, 308 ProtoMajor: r.ProtoMajor, 309 ProtoMinor: r.ProtoMinor, 310 Header: r.Header, 311 Host: r.Host, 312 RemoteAddr: r.RemoteAddr, 313 RequestURI: r.RequestURI, 314 } 315 316 if err := g.client.Call("Plugin.ServeHTTP", Z_ServeHTTPArgs{ 317 Context: c, 318 ResponseWriterStream: serveHTTPStreamId, 319 Request: forwardedRequest, 320 RequestBodyStream: requestBodyStreamId, 321 }, nil); err != nil { 322 g.log.Error("Plugin failed to ServeHTTP, RPC call failed", mlog.Err(err)) 323 http.Error(w, "500 internal server error", http.StatusInternalServerError) 324 } 325 } 326 327 func (s *hooksRPCServer) ServeHTTP(args *Z_ServeHTTPArgs, returns *struct{}) error { 328 connection, err := s.muxBroker.Dial(args.ResponseWriterStream) 329 if err != nil { 330 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error()) 331 return err 332 } 333 w := connectHTTPResponseWriter(connection) 334 defer w.Close() 335 336 r := args.Request 337 if args.RequestBodyStream != 0 { 338 connection, err := s.muxBroker.Dial(args.RequestBodyStream) 339 if err != nil { 340 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error()) 341 return err 342 } 343 r.Body = connectIOReader(connection) 344 } else { 345 r.Body = ioutil.NopCloser(&bytes.Buffer{}) 346 } 347 defer r.Body.Close() 348 349 if hook, ok := s.impl.(interface { 350 ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) 351 }); ok { 352 hook.ServeHTTP(args.Context, w, r) 353 } else { 354 http.NotFound(w, r) 355 } 356 357 return nil 358 } 359 360 func init() { 361 hookNameToId["FileWillBeUploaded"] = FileWillBeUploadedId 362 } 363 364 type Z_FileWillBeUploadedArgs struct { 365 A *Context 366 B *model.FileInfo 367 UploadedFileStream uint32 368 ReplacementFileStream uint32 369 } 370 371 type Z_FileWillBeUploadedReturns struct { 372 A *model.FileInfo 373 B string 374 } 375 376 func (g *hooksRPCClient) FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) { 377 if !g.implemented[FileWillBeUploadedId] { 378 return info, "" 379 } 380 381 uploadedFileStreamId := g.muxBroker.NextId() 382 go func() { 383 uploadedFileConnection, err := g.muxBroker.Accept(uploadedFileStreamId) 384 if err != nil { 385 g.log.Error("Plugin failed to serve upload file stream. MuxBroker could not Accept connection", mlog.Err(err)) 386 return 387 } 388 defer uploadedFileConnection.Close() 389 serveIOReader(file, uploadedFileConnection) 390 }() 391 392 replacementFileStreamId := g.muxBroker.NextId() 393 go func() { 394 replacementFileConnection, err := g.muxBroker.Accept(replacementFileStreamId) 395 if err != nil { 396 g.log.Error("Plugin failed to serve replacement file stream. MuxBroker could not Accept connection", mlog.Err(err)) 397 return 398 } 399 defer replacementFileConnection.Close() 400 if _, err := io.Copy(output, replacementFileConnection); err != nil && err != io.EOF { 401 g.log.Error("Error reading replacement file.", mlog.Err(err)) 402 } 403 }() 404 405 _args := &Z_FileWillBeUploadedArgs{c, info, uploadedFileStreamId, replacementFileStreamId} 406 _returns := &Z_FileWillBeUploadedReturns{} 407 if g.implemented[FileWillBeUploadedId] { 408 if err := g.client.Call("Plugin.FileWillBeUploaded", _args, _returns); err != nil { 409 g.log.Error("RPC call FileWillBeUploaded to plugin failed.", mlog.Err(err)) 410 } 411 } 412 return _returns.A, _returns.B 413 } 414 415 func (s *hooksRPCServer) FileWillBeUploaded(args *Z_FileWillBeUploadedArgs, returns *Z_FileWillBeUploadedReturns) error { 416 uploadFileConnection, err := s.muxBroker.Dial(args.UploadedFileStream) 417 if err != nil { 418 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote upload file stream, error: %v", err.Error()) 419 return err 420 } 421 defer uploadFileConnection.Close() 422 fileReader := connectIOReader(uploadFileConnection) 423 defer fileReader.Close() 424 425 replacementFileConnection, err := s.muxBroker.Dial(args.ReplacementFileStream) 426 if err != nil { 427 fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote replacement file stream, error: %v", err.Error()) 428 return err 429 } 430 defer replacementFileConnection.Close() 431 returnFileWriter := replacementFileConnection 432 433 if hook, ok := s.impl.(interface { 434 FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) 435 }); ok { 436 returns.A, returns.B = hook.FileWillBeUploaded(args.A, args.B, fileReader, returnFileWriter) 437 } else { 438 return fmt.Errorf("Hook FileWillBeUploaded called but not implemented.") 439 } 440 return nil 441 }