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  }