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  }