github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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 func ServeHooks(hooks interface{}, conn io.ReadWriteCloser, muxer *Muxer) { 145 server := rpc.NewServer() 146 server.Register(&LocalHooks{ 147 hooks: hooks, 148 muxer: muxer, 149 }) 150 server.ServeConn(conn) 151 } 152 153 // These assignments are part of the wire protocol. You can add more, but should not change existing 154 // assignments. 155 const ( 156 remoteOnActivate = 0 157 remoteOnDeactivate = 1 158 remoteServeHTTP = 2 159 remoteOnConfigurationChange = 3 160 remoteExecuteCommand = 4 161 maxRemoteHookCount = iota 162 ) 163 164 type RemoteHooks struct { 165 client *rpc.Client 166 muxer *Muxer 167 apiCloser io.Closer 168 implemented [maxRemoteHookCount]bool 169 pluginId string 170 } 171 172 var _ plugin.Hooks = (*RemoteHooks)(nil) 173 174 func (h *RemoteHooks) Implemented() (impl []string, err error) { 175 err = h.client.Call("LocalHooks.Implemented", struct{}{}, &impl) 176 return 177 } 178 179 func (h *RemoteHooks) OnActivate(api plugin.API) error { 180 if h.apiCloser != nil { 181 h.apiCloser.Close() 182 h.apiCloser = nil 183 } 184 if !h.implemented[remoteOnActivate] { 185 return nil 186 } 187 id, stream := h.muxer.Serve() 188 h.apiCloser = stream 189 go ServeAPI(api, stream, h.muxer) 190 return h.client.Call("LocalHooks.OnActivate", id, nil) 191 } 192 193 func (h *RemoteHooks) OnDeactivate() error { 194 if !h.implemented[remoteOnDeactivate] { 195 return nil 196 } 197 return h.client.Call("LocalHooks.OnDeactivate", struct{}{}, nil) 198 } 199 200 func (h *RemoteHooks) OnConfigurationChange() error { 201 if !h.implemented[remoteOnConfigurationChange] { 202 return nil 203 } 204 return h.client.Call("LocalHooks.OnConfigurationChange", struct{}{}, nil) 205 } 206 207 func (h *RemoteHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) { 208 if !h.implemented[remoteServeHTTP] { 209 http.NotFound(w, r) 210 return 211 } 212 213 responseWriterStream, stream := h.muxer.Serve() 214 defer stream.Close() 215 go ServeHTTPResponseWriter(w, stream) 216 217 requestBodyStream := int64(0) 218 if r.Body != nil { 219 rid, rstream := h.muxer.Serve() 220 defer rstream.Close() 221 go ServeIOReader(r.Body, rstream) 222 requestBodyStream = rid 223 } 224 225 forwardedRequest := &http.Request{ 226 Method: r.Method, 227 URL: r.URL, 228 Proto: r.Proto, 229 ProtoMajor: r.ProtoMajor, 230 ProtoMinor: r.ProtoMinor, 231 Header: r.Header, 232 Host: r.Host, 233 RemoteAddr: r.RemoteAddr, 234 RequestURI: r.RequestURI, 235 } 236 237 if err := h.client.Call("LocalHooks.ServeHTTP", ServeHTTPArgs{ 238 ResponseWriterStream: responseWriterStream, 239 Request: forwardedRequest, 240 RequestBodyStream: requestBodyStream, 241 }, nil); err != nil { 242 mlog.Error("Plugin failed to ServeHTTP", mlog.String("plugin_id", h.pluginId), mlog.Err(err)) 243 http.Error(w, "500 internal server error", http.StatusInternalServerError) 244 } 245 } 246 247 func (h *RemoteHooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { 248 if !h.implemented[remoteExecuteCommand] { 249 return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err=ExecuteCommand hook not implemented", http.StatusInternalServerError) 250 } 251 var reply HooksExecuteCommandReply 252 if err := h.client.Call("LocalHooks.ExecuteCommand", args, &reply); err != nil { 253 return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) 254 } 255 return reply.Response, reply.Error 256 } 257 258 func (h *RemoteHooks) Close() error { 259 if h.apiCloser != nil { 260 h.apiCloser.Close() 261 h.apiCloser = nil 262 } 263 return h.client.Close() 264 } 265 266 func ConnectHooks(conn io.ReadWriteCloser, muxer *Muxer, pluginId string) (*RemoteHooks, error) { 267 remote := &RemoteHooks{ 268 client: rpc.NewClient(conn), 269 muxer: muxer, 270 pluginId: pluginId, 271 } 272 implemented, err := remote.Implemented() 273 if err != nil { 274 remote.Close() 275 return nil, err 276 } 277 for _, method := range implemented { 278 switch method { 279 case "OnActivate": 280 remote.implemented[remoteOnActivate] = true 281 case "OnDeactivate": 282 remote.implemented[remoteOnDeactivate] = true 283 case "OnConfigurationChange": 284 remote.implemented[remoteOnConfigurationChange] = true 285 case "ServeHTTP": 286 remote.implemented[remoteServeHTTP] = true 287 case "ExecuteCommand": 288 remote.implemented[remoteExecuteCommand] = true 289 } 290 } 291 return remote, nil 292 }