github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/remotecluster/sendmsg.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package remotecluster
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"path"
    16  	"time"
    17  
    18  	"github.com/wiggin77/merror"
    19  
    20  	"github.com/masterhung0112/hk_server/v5/model"
    21  	"github.com/masterhung0112/hk_server/v5/shared/mlog"
    22  )
    23  
    24  type SendMsgResultFunc func(msg model.RemoteClusterMsg, rc *model.RemoteCluster, resp *Response, err error)
    25  
    26  type sendMsgTask struct {
    27  	rc  *model.RemoteCluster
    28  	msg model.RemoteClusterMsg
    29  	f   SendMsgResultFunc
    30  }
    31  
    32  // BroadcastMsg asynchronously sends a message to all remote clusters interested in the message's topic.
    33  //
    34  // `ctx` determines behaviour when the outbound queue is full. A timeout or deadline context will return a
    35  // BufferFullError if the message cannot be enqueued before the timeout. A background context will block indefinitely.
    36  //
    37  // An optional callback can be provided that receives the success or fail result of sending to each remote cluster.
    38  // Success or fail is regarding message delivery only.  If a callback is provided it should return quickly.
    39  func (rcs *Service) BroadcastMsg(ctx context.Context, msg model.RemoteClusterMsg, f SendMsgResultFunc) error {
    40  	// get list of interested remotes.
    41  	filter := model.RemoteClusterQueryFilter{
    42  		Topic: msg.Topic,
    43  	}
    44  	list, err := rcs.server.GetStore().RemoteCluster().GetAll(filter)
    45  	if err != nil {
    46  		return err
    47  	}
    48  
    49  	errs := merror.New()
    50  
    51  	for _, rc := range list {
    52  		if err := rcs.SendMsg(ctx, msg, rc, f); err != nil {
    53  			errs.Append(err)
    54  		}
    55  	}
    56  	return errs.ErrorOrNil()
    57  }
    58  
    59  // SendMsg asynchronously sends a message to a remote cluster.
    60  //
    61  // `ctx` determines behaviour when the outbound queue is full. A timeout or deadline context will return a
    62  // BufferFullError if the message cannot be enqueued before the timeout. A background context will block indefinitely.
    63  //
    64  // Nil or error return indicates success or failure of message enqueue only.
    65  //
    66  // An optional callback can be provided that receives the response from the remote cluster. The `err` provided to the
    67  // callback is regarding response decoding only. The `resp` contains the decoded bytes returned from the remote.
    68  // If a callback is provided it should return quickly.
    69  func (rcs *Service) SendMsg(ctx context.Context, msg model.RemoteClusterMsg, rc *model.RemoteCluster, f SendMsgResultFunc) error {
    70  	task := sendMsgTask{
    71  		rc:  rc,
    72  		msg: msg,
    73  		f:   f,
    74  	}
    75  	return rcs.enqueueTask(ctx, rc.RemoteId, task)
    76  }
    77  
    78  // sendMsg is called when a sendMsgTask is popped from the send channel.
    79  func (rcs *Service) sendMsg(task sendMsgTask) {
    80  	var errResp error
    81  	var response Response
    82  
    83  	// Ensure a panic from the callback does not exit the pool goroutine.
    84  	defer func() {
    85  		if errResp != nil {
    86  			response.Err = errResp.Error()
    87  		}
    88  
    89  		// If callback provided then call it with the results.
    90  		if task.f != nil {
    91  			task.f(task.msg, task.rc, &response, errResp)
    92  		}
    93  	}()
    94  
    95  	frame := &model.RemoteClusterFrame{
    96  		RemoteId: task.rc.RemoteId,
    97  		Msg:      task.msg,
    98  	}
    99  
   100  	u, err := url.Parse(task.rc.SiteURL)
   101  	if err != nil {
   102  		rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceError, "Invalid siteURL while sending message to remote",
   103  			mlog.String("remote", task.rc.DisplayName),
   104  			mlog.String("msgId", task.msg.Id),
   105  			mlog.Err(err),
   106  		)
   107  		errResp = err
   108  		return
   109  	}
   110  	u.Path = path.Join(u.Path, SendMsgURL)
   111  
   112  	respJSON, err := rcs.sendFrameToRemote(SendTimeout, task.rc, frame, u.String())
   113  
   114  	if err != nil {
   115  		rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceError, "Remote Cluster send message failed",
   116  			mlog.String("remote", task.rc.DisplayName),
   117  			mlog.String("msgId", task.msg.Id),
   118  			mlog.Err(err),
   119  		)
   120  		errResp = err
   121  	} else {
   122  		rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceDebug, "Remote Cluster message sent successfully",
   123  			mlog.String("remote", task.rc.DisplayName),
   124  			mlog.String("msgId", task.msg.Id),
   125  		)
   126  
   127  		if err = json.Unmarshal(respJSON, &response); err != nil {
   128  			rcs.server.GetLogger().Error("Invalid response sending message to remote cluster",
   129  				mlog.String("remote", task.rc.DisplayName),
   130  				mlog.Err(err),
   131  			)
   132  			errResp = err
   133  		}
   134  	}
   135  }
   136  
   137  func (rcs *Service) sendFrameToRemote(timeout time.Duration, rc *model.RemoteCluster, frame *model.RemoteClusterFrame, url string) ([]byte, error) {
   138  	body, err := json.Marshal(frame)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   144  	defer cancel()
   145  
   146  	req, err := http.NewRequest("POST", url, bytes.NewReader(body))
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	req.Header.Set("Content-Type", "application/json")
   151  	req.Header.Set(model.HEADER_REMOTECLUSTER_ID, rc.RemoteId)
   152  	req.Header.Set(model.HEADER_REMOTECLUSTER_TOKEN, rc.RemoteToken)
   153  
   154  	resp, err := rcs.httpClient.Do(req.WithContext(ctx))
   155  	if metrics := rcs.server.GetMetrics(); metrics != nil {
   156  		if err != nil || resp.StatusCode != http.StatusOK {
   157  			metrics.IncrementRemoteClusterMsgErrorsCounter(frame.RemoteId, os.IsTimeout(err))
   158  		} else {
   159  			metrics.IncrementRemoteClusterMsgSentCounter(frame.RemoteId)
   160  		}
   161  	}
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	defer resp.Body.Close()
   166  	body, err = ioutil.ReadAll(resp.Body)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	if resp.StatusCode != http.StatusOK {
   172  		return body, fmt.Errorf("unexpected response: %d - %s", resp.StatusCode, resp.Status)
   173  	}
   174  	return body, nil
   175  }