github.com/qichengzx/mattermost-server@v4.5.1-0.20180604164826-2c75247c97d0+incompatible/plugin/rpcplugin/hooks.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package rpcplugin 5 6 import ( 7 "bytes" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "net/rpc" 12 "reflect" 13 14 "github.com/mattermost/mattermost-server/mlog" 15 "github.com/mattermost/mattermost-server/model" 16 "github.com/mattermost/mattermost-server/plugin" 17 ) 18 19 type LocalHooks struct { 20 hooks interface{} 21 muxer *Muxer 22 remoteAPI *RemoteAPI 23 } 24 25 // Implemented replies with the names of the hooks that are implemented. 26 func (h *LocalHooks) Implemented(args struct{}, reply *[]string) error { 27 ifaceType := reflect.TypeOf((*plugin.Hooks)(nil)).Elem() 28 implType := reflect.TypeOf(h.hooks) 29 selfType := reflect.TypeOf(h) 30 var methods []string 31 for i := 0; i < ifaceType.NumMethod(); i++ { 32 method := ifaceType.Method(i) 33 if m, ok := implType.MethodByName(method.Name); !ok { 34 continue 35 } else if m.Type.NumIn() != method.Type.NumIn()+1 { 36 continue 37 } else if m.Type.NumOut() != method.Type.NumOut() { 38 continue 39 } else { 40 match := true 41 for j := 0; j < method.Type.NumIn(); j++ { 42 if m.Type.In(j+1) != method.Type.In(j) { 43 match = false 44 break 45 } 46 } 47 for j := 0; j < method.Type.NumOut(); j++ { 48 if m.Type.Out(j) != method.Type.Out(j) { 49 match = false 50 break 51 } 52 } 53 if !match { 54 continue 55 } 56 } 57 if _, ok := selfType.MethodByName(method.Name); !ok { 58 continue 59 } 60 methods = append(methods, method.Name) 61 } 62 *reply = methods 63 return nil 64 } 65 66 func (h *LocalHooks) OnActivate(args int64, reply *struct{}) error { 67 if h.remoteAPI != nil { 68 h.remoteAPI.Close() 69 h.remoteAPI = nil 70 } 71 if hook, ok := h.hooks.(interface { 72 OnActivate(plugin.API) error 73 }); ok { 74 stream := h.muxer.Connect(args) 75 h.remoteAPI = ConnectAPI(stream, h.muxer) 76 return hook.OnActivate(h.remoteAPI) 77 } 78 return nil 79 } 80 81 func (h *LocalHooks) OnDeactivate(args, reply *struct{}) (err error) { 82 if hook, ok := h.hooks.(interface { 83 OnDeactivate() error 84 }); ok { 85 err = hook.OnDeactivate() 86 } 87 if h.remoteAPI != nil { 88 h.remoteAPI.Close() 89 h.remoteAPI = nil 90 } 91 return 92 } 93 94 func (h *LocalHooks) OnConfigurationChange(args, reply *struct{}) error { 95 if hook, ok := h.hooks.(interface { 96 OnConfigurationChange() error 97 }); ok { 98 return hook.OnConfigurationChange() 99 } 100 return nil 101 } 102 103 type ServeHTTPArgs struct { 104 ResponseWriterStream int64 105 Request *http.Request 106 RequestBodyStream int64 107 } 108 109 func (h *LocalHooks) ServeHTTP(args ServeHTTPArgs, reply *struct{}) error { 110 w := ConnectHTTPResponseWriter(h.muxer.Connect(args.ResponseWriterStream)) 111 defer w.Close() 112 113 r := args.Request 114 if args.RequestBodyStream != 0 { 115 r.Body = ConnectIOReader(h.muxer.Connect(args.RequestBodyStream)) 116 } else { 117 r.Body = ioutil.NopCloser(&bytes.Buffer{}) 118 } 119 defer r.Body.Close() 120 121 if hook, ok := h.hooks.(http.Handler); ok { 122 hook.ServeHTTP(w, r) 123 } else { 124 http.NotFound(w, r) 125 } 126 127 return nil 128 } 129 130 type HooksExecuteCommandReply struct { 131 Response *model.CommandResponse 132 Error *model.AppError 133 } 134 135 func (h *LocalHooks) ExecuteCommand(args *model.CommandArgs, reply *HooksExecuteCommandReply) error { 136 if hook, ok := h.hooks.(interface { 137 ExecuteCommand(*model.CommandArgs) (*model.CommandResponse, *model.AppError) 138 }); ok { 139 reply.Response, reply.Error = hook.ExecuteCommand(args) 140 } 141 return nil 142 } 143 144 type MessageWillBeReply struct { 145 Post *model.Post 146 RejectionReason string 147 } 148 149 type MessageUpdatedArgs struct { 150 NewPost *model.Post 151 OldPost *model.Post 152 } 153 154 func (h *LocalHooks) MessageWillBePosted(args *model.Post, reply *MessageWillBeReply) error { 155 if hook, ok := h.hooks.(interface { 156 MessageWillBePosted(*model.Post) (*model.Post, string) 157 }); ok { 158 reply.Post, reply.RejectionReason = hook.MessageWillBePosted(args) 159 } 160 return nil 161 } 162 163 func (h *LocalHooks) MessageWillBeUpdated(args *MessageUpdatedArgs, reply *MessageWillBeReply) error { 164 if hook, ok := h.hooks.(interface { 165 MessageWillBeUpdated(*model.Post, *model.Post) (*model.Post, string) 166 }); ok { 167 reply.Post, reply.RejectionReason = hook.MessageWillBeUpdated(args.NewPost, args.OldPost) 168 } 169 return nil 170 } 171 172 func (h *LocalHooks) MessageHasBeenPosted(args *model.Post, reply *struct{}) error { 173 if hook, ok := h.hooks.(interface { 174 MessageHasBeenPosted(*model.Post) 175 }); ok { 176 hook.MessageHasBeenPosted(args) 177 } 178 return nil 179 } 180 181 func (h *LocalHooks) MessageHasBeenUpdated(args *MessageUpdatedArgs, reply *struct{}) error { 182 if hook, ok := h.hooks.(interface { 183 MessageHasBeenUpdated(*model.Post, *model.Post) 184 }); ok { 185 hook.MessageHasBeenUpdated(args.NewPost, args.OldPost) 186 } 187 return nil 188 } 189 190 func ServeHooks(hooks interface{}, conn io.ReadWriteCloser, muxer *Muxer) { 191 server := rpc.NewServer() 192 server.Register(&LocalHooks{ 193 hooks: hooks, 194 muxer: muxer, 195 }) 196 server.ServeConn(conn) 197 } 198 199 // These assignments are part of the wire protocol. You can add more, but should not change existing 200 // assignments. 201 const ( 202 remoteOnActivate = 0 203 remoteOnDeactivate = 1 204 remoteServeHTTP = 2 205 remoteOnConfigurationChange = 3 206 remoteExecuteCommand = 4 207 remoteMessageWillBePosted = 5 208 remoteMessageWillBeUpdated = 6 209 remoteMessageHasBeenPosted = 7 210 remoteMessageHasBeenUpdated = 8 211 maxRemoteHookCount = iota 212 ) 213 214 type RemoteHooks struct { 215 client *rpc.Client 216 muxer *Muxer 217 apiCloser io.Closer 218 implemented [maxRemoteHookCount]bool 219 pluginId string 220 } 221 222 var _ plugin.Hooks = (*RemoteHooks)(nil) 223 224 func (h *RemoteHooks) Implemented() (impl []string, err error) { 225 err = h.client.Call("LocalHooks.Implemented", struct{}{}, &impl) 226 return 227 } 228 229 func (h *RemoteHooks) OnActivate(api plugin.API) error { 230 if h.apiCloser != nil { 231 h.apiCloser.Close() 232 h.apiCloser = nil 233 } 234 if !h.implemented[remoteOnActivate] { 235 return nil 236 } 237 id, stream := h.muxer.Serve() 238 h.apiCloser = stream 239 go ServeAPI(api, stream, h.muxer) 240 return h.client.Call("LocalHooks.OnActivate", id, nil) 241 } 242 243 func (h *RemoteHooks) OnDeactivate() error { 244 if !h.implemented[remoteOnDeactivate] { 245 return nil 246 } 247 return h.client.Call("LocalHooks.OnDeactivate", struct{}{}, nil) 248 } 249 250 func (h *RemoteHooks) OnConfigurationChange() error { 251 if !h.implemented[remoteOnConfigurationChange] { 252 return nil 253 } 254 return h.client.Call("LocalHooks.OnConfigurationChange", struct{}{}, nil) 255 } 256 257 func (h *RemoteHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) { 258 if !h.implemented[remoteServeHTTP] { 259 http.NotFound(w, r) 260 return 261 } 262 263 responseWriterStream, stream := h.muxer.Serve() 264 defer stream.Close() 265 go ServeHTTPResponseWriter(w, stream) 266 267 requestBodyStream := int64(0) 268 if r.Body != nil { 269 rid, rstream := h.muxer.Serve() 270 defer rstream.Close() 271 go ServeIOReader(r.Body, rstream) 272 requestBodyStream = rid 273 } 274 275 forwardedRequest := &http.Request{ 276 Method: r.Method, 277 URL: r.URL, 278 Proto: r.Proto, 279 ProtoMajor: r.ProtoMajor, 280 ProtoMinor: r.ProtoMinor, 281 Header: r.Header, 282 Host: r.Host, 283 RemoteAddr: r.RemoteAddr, 284 RequestURI: r.RequestURI, 285 } 286 287 if err := h.client.Call("LocalHooks.ServeHTTP", ServeHTTPArgs{ 288 ResponseWriterStream: responseWriterStream, 289 Request: forwardedRequest, 290 RequestBodyStream: requestBodyStream, 291 }, nil); err != nil { 292 mlog.Error("Plugin failed to ServeHTTP", mlog.String("plugin_id", h.pluginId), mlog.Err(err)) 293 http.Error(w, "500 internal server error", http.StatusInternalServerError) 294 } 295 } 296 297 func (h *RemoteHooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { 298 if !h.implemented[remoteExecuteCommand] { 299 return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err=ExecuteCommand hook not implemented", http.StatusInternalServerError) 300 } 301 var reply HooksExecuteCommandReply 302 if err := h.client.Call("LocalHooks.ExecuteCommand", args, &reply); err != nil { 303 return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) 304 } 305 return reply.Response, reply.Error 306 } 307 308 func (h *RemoteHooks) MessageWillBePosted(args *model.Post) (*model.Post, string) { 309 if !h.implemented[remoteMessageWillBePosted] { 310 return args, "" 311 } 312 var reply MessageWillBeReply 313 if err := h.client.Call("LocalHooks.MessageWillBePosted", args, &reply); err != nil { 314 return nil, "" 315 } 316 return reply.Post, reply.RejectionReason 317 } 318 319 func (h *RemoteHooks) MessageWillBeUpdated(newPost, oldPost *model.Post) (*model.Post, string) { 320 if !h.implemented[remoteMessageWillBeUpdated] { 321 return newPost, "" 322 } 323 var reply MessageWillBeReply 324 args := &MessageUpdatedArgs{ 325 NewPost: newPost, 326 OldPost: oldPost, 327 } 328 if err := h.client.Call("LocalHooks.MessageWillBeUpdated", args, &reply); err != nil { 329 return nil, "" 330 } 331 return reply.Post, reply.RejectionReason 332 } 333 334 func (h *RemoteHooks) MessageHasBeenPosted(args *model.Post) { 335 if !h.implemented[remoteMessageHasBeenPosted] { 336 return 337 } 338 if err := h.client.Call("LocalHooks.MessageHasBeenPosted", args, nil); err != nil { 339 return 340 } 341 } 342 343 func (h *RemoteHooks) MessageHasBeenUpdated(newPost, oldPost *model.Post) { 344 if !h.implemented[remoteMessageHasBeenUpdated] { 345 return 346 } 347 args := &MessageUpdatedArgs{ 348 NewPost: newPost, 349 OldPost: oldPost, 350 } 351 if err := h.client.Call("LocalHooks.MessageHasBeenUpdated", args, nil); err != nil { 352 return 353 } 354 } 355 356 func (h *RemoteHooks) Close() error { 357 if h.apiCloser != nil { 358 h.apiCloser.Close() 359 h.apiCloser = nil 360 } 361 return h.client.Close() 362 } 363 364 func ConnectHooks(conn io.ReadWriteCloser, muxer *Muxer, pluginId string) (*RemoteHooks, error) { 365 remote := &RemoteHooks{ 366 client: rpc.NewClient(conn), 367 muxer: muxer, 368 pluginId: pluginId, 369 } 370 implemented, err := remote.Implemented() 371 if err != nil { 372 remote.Close() 373 return nil, err 374 } 375 for _, method := range implemented { 376 switch method { 377 case "OnActivate": 378 remote.implemented[remoteOnActivate] = true 379 case "OnDeactivate": 380 remote.implemented[remoteOnDeactivate] = true 381 case "OnConfigurationChange": 382 remote.implemented[remoteOnConfigurationChange] = true 383 case "ServeHTTP": 384 remote.implemented[remoteServeHTTP] = true 385 case "ExecuteCommand": 386 remote.implemented[remoteExecuteCommand] = true 387 case "MessageWillBePosted": 388 remote.implemented[remoteMessageWillBePosted] = true 389 case "MessageWillBeUpdated": 390 remote.implemented[remoteMessageWillBeUpdated] = true 391 case "MessageHasBeenPosted": 392 remote.implemented[remoteMessageHasBeenPosted] = true 393 case "MessageHasBeenUpdated": 394 remote.implemented[remoteMessageHasBeenUpdated] = true 395 } 396 } 397 return remote, nil 398 }