github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+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/model" 15 "github.com/mattermost/mattermost-server/plugin" 16 ) 17 18 type LocalHooks struct { 19 hooks interface{} 20 muxer *Muxer 21 remoteAPI *RemoteAPI 22 } 23 24 // Implemented replies with the names of the hooks that are implemented. 25 func (h *LocalHooks) Implemented(args struct{}, reply *[]string) error { 26 ifaceType := reflect.TypeOf((*plugin.Hooks)(nil)).Elem() 27 implType := reflect.TypeOf(h.hooks) 28 selfType := reflect.TypeOf(h) 29 var methods []string 30 for i := 0; i < ifaceType.NumMethod(); i++ { 31 method := ifaceType.Method(i) 32 if m, ok := implType.MethodByName(method.Name); !ok { 33 continue 34 } else if m.Type.NumIn() != method.Type.NumIn()+1 { 35 continue 36 } else if m.Type.NumOut() != method.Type.NumOut() { 37 continue 38 } else { 39 match := true 40 for j := 0; j < method.Type.NumIn(); j++ { 41 if m.Type.In(j+1) != method.Type.In(j) { 42 match = false 43 break 44 } 45 } 46 for j := 0; j < method.Type.NumOut(); j++ { 47 if m.Type.Out(j) != method.Type.Out(j) { 48 match = false 49 break 50 } 51 } 52 if !match { 53 continue 54 } 55 } 56 if _, ok := selfType.MethodByName(method.Name); !ok { 57 continue 58 } 59 methods = append(methods, method.Name) 60 } 61 *reply = methods 62 return nil 63 } 64 65 func (h *LocalHooks) OnActivate(args int64, reply *struct{}) error { 66 if h.remoteAPI != nil { 67 h.remoteAPI.Close() 68 h.remoteAPI = nil 69 } 70 if hook, ok := h.hooks.(interface { 71 OnActivate(plugin.API) error 72 }); ok { 73 stream := h.muxer.Connect(args) 74 h.remoteAPI = ConnectAPI(stream, h.muxer) 75 return hook.OnActivate(h.remoteAPI) 76 } 77 return nil 78 } 79 80 func (h *LocalHooks) OnDeactivate(args, reply *struct{}) (err error) { 81 if hook, ok := h.hooks.(interface { 82 OnDeactivate() error 83 }); ok { 84 err = hook.OnDeactivate() 85 } 86 if h.remoteAPI != nil { 87 h.remoteAPI.Close() 88 h.remoteAPI = nil 89 } 90 return 91 } 92 93 func (h *LocalHooks) OnConfigurationChange(args, reply *struct{}) error { 94 if hook, ok := h.hooks.(interface { 95 OnConfigurationChange() error 96 }); ok { 97 return hook.OnConfigurationChange() 98 } 99 return nil 100 } 101 102 type ServeHTTPArgs struct { 103 ResponseWriterStream int64 104 Request *http.Request 105 RequestBodyStream int64 106 } 107 108 func (h *LocalHooks) ServeHTTP(args ServeHTTPArgs, reply *struct{}) error { 109 w := ConnectHTTPResponseWriter(h.muxer.Connect(args.ResponseWriterStream)) 110 defer w.Close() 111 112 r := args.Request 113 if args.RequestBodyStream != 0 { 114 r.Body = ConnectIOReader(h.muxer.Connect(args.RequestBodyStream)) 115 } else { 116 r.Body = ioutil.NopCloser(&bytes.Buffer{}) 117 } 118 defer r.Body.Close() 119 120 if hook, ok := h.hooks.(http.Handler); ok { 121 hook.ServeHTTP(w, r) 122 } else { 123 http.NotFound(w, r) 124 } 125 126 return nil 127 } 128 129 type HooksExecuteCommandReply struct { 130 Response *model.CommandResponse 131 Error *model.AppError 132 } 133 134 func (h *LocalHooks) ExecuteCommand(args *model.CommandArgs, reply *HooksExecuteCommandReply) error { 135 if hook, ok := h.hooks.(interface { 136 ExecuteCommand(*model.CommandArgs) (*model.CommandResponse, *model.AppError) 137 }); ok { 138 reply.Response, reply.Error = hook.ExecuteCommand(args) 139 } 140 return nil 141 } 142 143 func ServeHooks(hooks interface{}, conn io.ReadWriteCloser, muxer *Muxer) { 144 server := rpc.NewServer() 145 server.Register(&LocalHooks{ 146 hooks: hooks, 147 muxer: muxer, 148 }) 149 server.ServeConn(conn) 150 } 151 152 // These assignments are part of the wire protocol. You can add more, but should not change existing 153 // assignments. 154 const ( 155 remoteOnActivate = 0 156 remoteOnDeactivate = 1 157 remoteServeHTTP = 2 158 remoteOnConfigurationChange = 3 159 remoteExecuteCommand = 4 160 maxRemoteHookCount = iota 161 ) 162 163 type RemoteHooks struct { 164 client *rpc.Client 165 muxer *Muxer 166 apiCloser io.Closer 167 implemented [maxRemoteHookCount]bool 168 } 169 170 var _ plugin.Hooks = (*RemoteHooks)(nil) 171 172 func (h *RemoteHooks) Implemented() (impl []string, err error) { 173 err = h.client.Call("LocalHooks.Implemented", struct{}{}, &impl) 174 return 175 } 176 177 func (h *RemoteHooks) OnActivate(api plugin.API) error { 178 if h.apiCloser != nil { 179 h.apiCloser.Close() 180 h.apiCloser = nil 181 } 182 if !h.implemented[remoteOnActivate] { 183 return nil 184 } 185 id, stream := h.muxer.Serve() 186 h.apiCloser = stream 187 go ServeAPI(api, stream, h.muxer) 188 return h.client.Call("LocalHooks.OnActivate", id, nil) 189 } 190 191 func (h *RemoteHooks) OnDeactivate() error { 192 if !h.implemented[remoteOnDeactivate] { 193 return nil 194 } 195 return h.client.Call("LocalHooks.OnDeactivate", struct{}{}, nil) 196 } 197 198 func (h *RemoteHooks) OnConfigurationChange() error { 199 if !h.implemented[remoteOnConfigurationChange] { 200 return nil 201 } 202 return h.client.Call("LocalHooks.OnConfigurationChange", struct{}{}, nil) 203 } 204 205 func (h *RemoteHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) { 206 if !h.implemented[remoteServeHTTP] { 207 http.NotFound(w, r) 208 return 209 } 210 211 responseWriterStream, stream := h.muxer.Serve() 212 defer stream.Close() 213 go ServeHTTPResponseWriter(w, stream) 214 215 requestBodyStream := int64(0) 216 if r.Body != nil { 217 rid, rstream := h.muxer.Serve() 218 defer rstream.Close() 219 go ServeIOReader(r.Body, rstream) 220 requestBodyStream = rid 221 } 222 223 forwardedRequest := &http.Request{ 224 Method: r.Method, 225 URL: r.URL, 226 Proto: r.Proto, 227 ProtoMajor: r.ProtoMajor, 228 ProtoMinor: r.ProtoMinor, 229 Header: r.Header, 230 Host: r.Host, 231 RemoteAddr: r.RemoteAddr, 232 RequestURI: r.RequestURI, 233 } 234 235 if err := h.client.Call("LocalHooks.ServeHTTP", ServeHTTPArgs{ 236 ResponseWriterStream: responseWriterStream, 237 Request: forwardedRequest, 238 RequestBodyStream: requestBodyStream, 239 }, nil); err != nil { 240 http.Error(w, "500 internal server error", http.StatusInternalServerError) 241 } 242 } 243 244 func (h *RemoteHooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { 245 if !h.implemented[remoteExecuteCommand] { 246 return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err=ExecuteCommand hook not implemented", http.StatusInternalServerError) 247 } 248 var reply HooksExecuteCommandReply 249 if err := h.client.Call("LocalHooks.ExecuteCommand", args, &reply); err != nil { 250 return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) 251 } 252 return reply.Response, reply.Error 253 } 254 255 func (h *RemoteHooks) Close() error { 256 if h.apiCloser != nil { 257 h.apiCloser.Close() 258 h.apiCloser = nil 259 } 260 return h.client.Close() 261 } 262 263 func ConnectHooks(conn io.ReadWriteCloser, muxer *Muxer) (*RemoteHooks, error) { 264 remote := &RemoteHooks{ 265 client: rpc.NewClient(conn), 266 muxer: muxer, 267 } 268 implemented, err := remote.Implemented() 269 if err != nil { 270 remote.Close() 271 return nil, err 272 } 273 for _, method := range implemented { 274 switch method { 275 case "OnActivate": 276 remote.implemented[remoteOnActivate] = true 277 case "OnDeactivate": 278 remote.implemented[remoteOnDeactivate] = true 279 case "OnConfigurationChange": 280 remote.implemented[remoteOnConfigurationChange] = true 281 case "ServeHTTP": 282 remote.implemented[remoteServeHTTP] = true 283 case "ExecuteCommand": 284 remote.implemented[remoteExecuteCommand] = true 285 } 286 } 287 return remote, nil 288 }