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 }