github.com/turgay/mattermost-server@v5.3.2-0.20181002173352-2945e8a2b0ce+incompatible/plugin/client_rpc.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  //go:generate go run interface_generator/main.go
     5  
     6  package plugin
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/gob"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"io/ioutil"
    15  	"log"
    16  	"net/http"
    17  	"net/rpc"
    18  	"os"
    19  	"reflect"
    20  
    21  	"github.com/hashicorp/go-plugin"
    22  	"github.com/mattermost/mattermost-server/mlog"
    23  	"github.com/mattermost/mattermost-server/model"
    24  )
    25  
    26  var hookNameToId map[string]int = make(map[string]int)
    27  
    28  type hooksRPCClient struct {
    29  	client      *rpc.Client
    30  	log         *mlog.Logger
    31  	muxBroker   *plugin.MuxBroker
    32  	apiImpl     API
    33  	implemented [TotalHooksId]bool
    34  }
    35  
    36  type hooksRPCServer struct {
    37  	impl         interface{}
    38  	muxBroker    *plugin.MuxBroker
    39  	apiRPCClient *apiRPCClient
    40  }
    41  
    42  // Implements hashicorp/go-plugin/plugin.Plugin interface to connect the hooks of a plugin
    43  type hooksPlugin struct {
    44  	hooks   interface{}
    45  	apiImpl API
    46  	log     *mlog.Logger
    47  }
    48  
    49  func (p *hooksPlugin) Server(b *plugin.MuxBroker) (interface{}, error) {
    50  	return &hooksRPCServer{impl: p.hooks, muxBroker: b}, nil
    51  }
    52  
    53  func (p *hooksPlugin) Client(b *plugin.MuxBroker, client *rpc.Client) (interface{}, error) {
    54  	return &hooksRPCClient{client: client, log: p.log, muxBroker: b, apiImpl: p.apiImpl}, nil
    55  }
    56  
    57  type apiRPCClient struct {
    58  	client *rpc.Client
    59  }
    60  
    61  type apiRPCServer struct {
    62  	impl API
    63  }
    64  
    65  // ErrorString is a fallback for sending unregistered implementations of the error interface across
    66  // rpc. For example, the errorString type from the github.com/pkg/errors package cannot be
    67  // registered since it is not exported, but this precludes common error handling paradigms.
    68  // ErrorString merely preserves the string description of the error, while satisfying the error
    69  // interface itself to allow other registered types (such as model.AppError) to be sent unmodified.
    70  type ErrorString struct {
    71  	Err string
    72  }
    73  
    74  func (e ErrorString) Error() string {
    75  	return e.Err
    76  }
    77  
    78  func encodableError(err error) error {
    79  	if err == nil {
    80  		return nil
    81  	}
    82  	if _, ok := err.(*model.AppError); ok {
    83  		return err
    84  	}
    85  
    86  	return &ErrorString{
    87  		Err: err.Error(),
    88  	}
    89  }
    90  
    91  // Registering some types used by MM for encoding/gob used by rpc
    92  func init() {
    93  	gob.Register([]*model.SlackAttachment{})
    94  	gob.Register([]interface{}{})
    95  	gob.Register(map[string]interface{}{})
    96  	gob.Register(&model.AppError{})
    97  	gob.Register(&ErrorString{})
    98  }
    99  
   100  // These enforce compile time checks to make sure types implement the interface
   101  // If you are getting an error here, you probably need to run `make pluginapi` to
   102  // autogenerate RPC glue code
   103  var _ plugin.Plugin = &hooksPlugin{}
   104  var _ Hooks = &hooksRPCClient{}
   105  
   106  //
   107  // Below are specal cases for hooks or APIs that can not be auto generated
   108  //
   109  
   110  func (g *hooksRPCClient) Implemented() (impl []string, err error) {
   111  	err = g.client.Call("Plugin.Implemented", struct{}{}, &impl)
   112  	for _, hookName := range impl {
   113  		if hookId, ok := hookNameToId[hookName]; ok {
   114  			g.implemented[hookId] = true
   115  		}
   116  	}
   117  	return
   118  }
   119  
   120  // Implemented replies with the names of the hooks that are implemented.
   121  func (s *hooksRPCServer) Implemented(args struct{}, reply *[]string) error {
   122  	ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem()
   123  	implType := reflect.TypeOf(s.impl)
   124  	selfType := reflect.TypeOf(s)
   125  	var methods []string
   126  	for i := 0; i < ifaceType.NumMethod(); i++ {
   127  		method := ifaceType.Method(i)
   128  		if m, ok := implType.MethodByName(method.Name); !ok {
   129  			continue
   130  		} else if m.Type.NumIn() != method.Type.NumIn()+1 {
   131  			continue
   132  		} else if m.Type.NumOut() != method.Type.NumOut() {
   133  			continue
   134  		} else {
   135  			match := true
   136  			for j := 0; j < method.Type.NumIn(); j++ {
   137  				if m.Type.In(j+1) != method.Type.In(j) {
   138  					match = false
   139  					break
   140  				}
   141  			}
   142  			for j := 0; j < method.Type.NumOut(); j++ {
   143  				if m.Type.Out(j) != method.Type.Out(j) {
   144  					match = false
   145  					break
   146  				}
   147  			}
   148  			if !match {
   149  				continue
   150  			}
   151  		}
   152  		if _, ok := selfType.MethodByName(method.Name); !ok {
   153  			continue
   154  		}
   155  		methods = append(methods, method.Name)
   156  	}
   157  	*reply = methods
   158  	return encodableError(nil)
   159  }
   160  
   161  type Z_OnActivateArgs struct {
   162  	APIMuxId uint32
   163  }
   164  
   165  type Z_OnActivateReturns struct {
   166  	A error
   167  }
   168  
   169  func (g *hooksRPCClient) OnActivate() error {
   170  	muxId := g.muxBroker.NextId()
   171  	go g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{
   172  		impl: g.apiImpl,
   173  	})
   174  
   175  	_args := &Z_OnActivateArgs{
   176  		APIMuxId: muxId,
   177  	}
   178  	_returns := &Z_OnActivateReturns{}
   179  
   180  	if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil {
   181  		g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err))
   182  	}
   183  	return _returns.A
   184  }
   185  
   186  func (s *hooksRPCServer) OnActivate(args *Z_OnActivateArgs, returns *Z_OnActivateReturns) error {
   187  	connection, err := s.muxBroker.Dial(args.APIMuxId)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	s.apiRPCClient = &apiRPCClient{
   193  		client: rpc.NewClient(connection),
   194  	}
   195  
   196  	if mmplugin, ok := s.impl.(interface {
   197  		SetAPI(api API)
   198  		OnConfigurationChange() error
   199  	}); !ok {
   200  	} else {
   201  		mmplugin.SetAPI(s.apiRPCClient)
   202  		mmplugin.OnConfigurationChange()
   203  	}
   204  
   205  	// Capture output of standard logger because go-plugin
   206  	// redirects it.
   207  	log.SetOutput(os.Stderr)
   208  
   209  	if hook, ok := s.impl.(interface {
   210  		OnActivate() error
   211  	}); ok {
   212  		returns.A = encodableError(hook.OnActivate())
   213  	}
   214  	return nil
   215  }
   216  
   217  type Z_LoadPluginConfigurationArgsArgs struct {
   218  }
   219  
   220  type Z_LoadPluginConfigurationArgsReturns struct {
   221  	A []byte
   222  }
   223  
   224  func (g *apiRPCClient) LoadPluginConfiguration(dest interface{}) error {
   225  	_args := &Z_LoadPluginConfigurationArgsArgs{}
   226  	_returns := &Z_LoadPluginConfigurationArgsReturns{}
   227  	if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil {
   228  		log.Printf("RPC call to LoadPluginConfiguration API failed: %s", err.Error())
   229  	}
   230  	if err := json.Unmarshal(_returns.A, dest); err != nil {
   231  		log.Printf("LoadPluginConfiguration API failed to unmarshal: %s", err.Error())
   232  	}
   233  	return nil
   234  }
   235  
   236  func (s *apiRPCServer) LoadPluginConfiguration(args *Z_LoadPluginConfigurationArgsArgs, returns *Z_LoadPluginConfigurationArgsReturns) error {
   237  	var config interface{}
   238  	if hook, ok := s.impl.(interface {
   239  		LoadPluginConfiguration(dest interface{}) error
   240  	}); ok {
   241  		if err := hook.LoadPluginConfiguration(&config); err != nil {
   242  			return err
   243  		}
   244  	}
   245  	b, err := json.Marshal(config)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	returns.A = b
   250  	return nil
   251  }
   252  
   253  func init() {
   254  	hookNameToId["ServeHTTP"] = ServeHTTPId
   255  }
   256  
   257  type Z_ServeHTTPArgs struct {
   258  	ResponseWriterStream uint32
   259  	Request              *http.Request
   260  	Context              *Context
   261  	RequestBodyStream    uint32
   262  }
   263  
   264  func (g *hooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) {
   265  	if !g.implemented[ServeHTTPId] {
   266  		http.NotFound(w, r)
   267  		return
   268  	}
   269  
   270  	serveHTTPStreamId := g.muxBroker.NextId()
   271  	go func() {
   272  		connection, err := g.muxBroker.Accept(serveHTTPStreamId)
   273  		if err != nil {
   274  			g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't accept connection", mlog.Uint32("serve_http_stream_id", serveHTTPStreamId), mlog.Err(err))
   275  			http.Error(w, "500 internal server error", http.StatusInternalServerError)
   276  			return
   277  		}
   278  		defer connection.Close()
   279  
   280  		rpcServer := rpc.NewServer()
   281  		if err := rpcServer.RegisterName("Plugin", &httpResponseWriterRPCServer{w: w}); err != nil {
   282  			g.log.Error("Plugin failed to ServeHTTP, coulden't register RPC name", mlog.Err(err))
   283  			http.Error(w, "500 internal server error", http.StatusInternalServerError)
   284  			return
   285  		}
   286  		rpcServer.ServeConn(connection)
   287  	}()
   288  
   289  	requestBodyStreamId := uint32(0)
   290  	if r.Body != nil {
   291  		requestBodyStreamId = g.muxBroker.NextId()
   292  		go func() {
   293  			bodyConnection, err := g.muxBroker.Accept(requestBodyStreamId)
   294  			if err != nil {
   295  				g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't Accept request body connection", mlog.Err(err))
   296  				http.Error(w, "500 internal server error", http.StatusInternalServerError)
   297  				return
   298  			}
   299  			defer bodyConnection.Close()
   300  			serveIOReader(r.Body, bodyConnection)
   301  		}()
   302  	}
   303  
   304  	forwardedRequest := &http.Request{
   305  		Method:     r.Method,
   306  		URL:        r.URL,
   307  		Proto:      r.Proto,
   308  		ProtoMajor: r.ProtoMajor,
   309  		ProtoMinor: r.ProtoMinor,
   310  		Header:     r.Header,
   311  		Host:       r.Host,
   312  		RemoteAddr: r.RemoteAddr,
   313  		RequestURI: r.RequestURI,
   314  	}
   315  
   316  	if err := g.client.Call("Plugin.ServeHTTP", Z_ServeHTTPArgs{
   317  		Context:              c,
   318  		ResponseWriterStream: serveHTTPStreamId,
   319  		Request:              forwardedRequest,
   320  		RequestBodyStream:    requestBodyStreamId,
   321  	}, nil); err != nil {
   322  		g.log.Error("Plugin failed to ServeHTTP, RPC call failed", mlog.Err(err))
   323  		http.Error(w, "500 internal server error", http.StatusInternalServerError)
   324  	}
   325  }
   326  
   327  func (s *hooksRPCServer) ServeHTTP(args *Z_ServeHTTPArgs, returns *struct{}) error {
   328  	connection, err := s.muxBroker.Dial(args.ResponseWriterStream)
   329  	if err != nil {
   330  		fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error())
   331  		return err
   332  	}
   333  	w := connectHTTPResponseWriter(connection)
   334  	defer w.Close()
   335  
   336  	r := args.Request
   337  	if args.RequestBodyStream != 0 {
   338  		connection, err := s.muxBroker.Dial(args.RequestBodyStream)
   339  		if err != nil {
   340  			fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error())
   341  			return err
   342  		}
   343  		r.Body = connectIOReader(connection)
   344  	} else {
   345  		r.Body = ioutil.NopCloser(&bytes.Buffer{})
   346  	}
   347  	defer r.Body.Close()
   348  
   349  	if hook, ok := s.impl.(interface {
   350  		ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request)
   351  	}); ok {
   352  		hook.ServeHTTP(args.Context, w, r)
   353  	} else {
   354  		http.NotFound(w, r)
   355  	}
   356  
   357  	return nil
   358  }
   359  
   360  func init() {
   361  	hookNameToId["FileWillBeUploaded"] = FileWillBeUploadedId
   362  }
   363  
   364  type Z_FileWillBeUploadedArgs struct {
   365  	A                     *Context
   366  	B                     *model.FileInfo
   367  	UploadedFileStream    uint32
   368  	ReplacementFileStream uint32
   369  }
   370  
   371  type Z_FileWillBeUploadedReturns struct {
   372  	A *model.FileInfo
   373  	B string
   374  }
   375  
   376  func (g *hooksRPCClient) FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string) {
   377  	if !g.implemented[FileWillBeUploadedId] {
   378  		return info, ""
   379  	}
   380  
   381  	uploadedFileStreamId := g.muxBroker.NextId()
   382  	go func() {
   383  		uploadedFileConnection, err := g.muxBroker.Accept(uploadedFileStreamId)
   384  		if err != nil {
   385  			g.log.Error("Plugin failed to serve upload file stream. MuxBroker could not Accept connection", mlog.Err(err))
   386  			return
   387  		}
   388  		defer uploadedFileConnection.Close()
   389  		serveIOReader(file, uploadedFileConnection)
   390  	}()
   391  
   392  	replacementFileStreamId := g.muxBroker.NextId()
   393  	go func() {
   394  		replacementFileConnection, err := g.muxBroker.Accept(replacementFileStreamId)
   395  		if err != nil {
   396  			g.log.Error("Plugin failed to serve replacement file stream. MuxBroker could not Accept connection", mlog.Err(err))
   397  			return
   398  		}
   399  		defer replacementFileConnection.Close()
   400  		if _, err := io.Copy(output, replacementFileConnection); err != nil && err != io.EOF {
   401  			g.log.Error("Error reading replacement file.", mlog.Err(err))
   402  		}
   403  	}()
   404  
   405  	_args := &Z_FileWillBeUploadedArgs{c, info, uploadedFileStreamId, replacementFileStreamId}
   406  	_returns := &Z_FileWillBeUploadedReturns{}
   407  	if g.implemented[FileWillBeUploadedId] {
   408  		if err := g.client.Call("Plugin.FileWillBeUploaded", _args, _returns); err != nil {
   409  			g.log.Error("RPC call FileWillBeUploaded to plugin failed.", mlog.Err(err))
   410  		}
   411  	}
   412  	return _returns.A, _returns.B
   413  }
   414  
   415  func (s *hooksRPCServer) FileWillBeUploaded(args *Z_FileWillBeUploadedArgs, returns *Z_FileWillBeUploadedReturns) error {
   416  	uploadFileConnection, err := s.muxBroker.Dial(args.UploadedFileStream)
   417  	if err != nil {
   418  		fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote upload file stream, error: %v", err.Error())
   419  		return err
   420  	}
   421  	defer uploadFileConnection.Close()
   422  	fileReader := connectIOReader(uploadFileConnection)
   423  	defer fileReader.Close()
   424  
   425  	replacementFileConnection, err := s.muxBroker.Dial(args.ReplacementFileStream)
   426  	if err != nil {
   427  		fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote replacement file stream, error: %v", err.Error())
   428  		return err
   429  	}
   430  	defer replacementFileConnection.Close()
   431  	returnFileWriter := replacementFileConnection
   432  
   433  	if hook, ok := s.impl.(interface {
   434  		FileWillBeUploaded(c *Context, info *model.FileInfo, file io.Reader, output io.Writer) (*model.FileInfo, string)
   435  	}); ok {
   436  		returns.A, returns.B = hook.FileWillBeUploaded(args.A, args.B, fileReader, returnFileWriter)
   437  	} else {
   438  		return fmt.Errorf("Hook FileWillBeUploaded called but not implemented.")
   439  	}
   440  	return nil
   441  }