github.com/keys-pub/mattermost-server@v4.10.10+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  }