github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/services/remotecluster/sendfile.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 "context" 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "net/http" 12 "net/url" 13 "path" 14 "time" 15 16 "github.com/masterhung0112/hk_server/v5/model" 17 "github.com/masterhung0112/hk_server/v5/shared/filestore" 18 "github.com/masterhung0112/hk_server/v5/shared/mlog" 19 ) 20 21 type SendFileResultFunc func(us *model.UploadSession, rc *model.RemoteCluster, resp *Response, err error) 22 23 type sendFileTask struct { 24 rc *model.RemoteCluster 25 us *model.UploadSession 26 fi *model.FileInfo 27 rp ReaderProvider 28 f SendFileResultFunc 29 } 30 31 type ReaderProvider interface { 32 FileReader(path string) (filestore.ReadCloseSeeker, *model.AppError) 33 } 34 35 // SendFile asynchronously sends a file to a remote cluster. 36 // 37 // `ctx` determines behaviour when the outbound queue is full. A timeout or deadline context will return a 38 // BufferFullError if the task cannot be enqueued before the timeout. A background context will block indefinitely. 39 // 40 // Nil or error return indicates success or failure of task enqueue only. 41 // 42 // An optional callback can be provided that receives the response from the remote cluster. The `err` provided to the 43 // callback is regarding file delivery only. The `resp` contains the decoded bytes returned from the remote. 44 // If a callback is provided it should return quickly. 45 func (rcs *Service) SendFile(ctx context.Context, us *model.UploadSession, fi *model.FileInfo, rc *model.RemoteCluster, rp ReaderProvider, f SendFileResultFunc) error { 46 task := sendFileTask{ 47 rc: rc, 48 us: us, 49 fi: fi, 50 rp: rp, 51 f: f, 52 } 53 return rcs.enqueueTask(ctx, rc.RemoteId, task) 54 } 55 56 // sendFile is called when a sendFileTask is popped from the send channel. 57 func (rcs *Service) sendFile(task sendFileTask) { 58 fi, err := rcs.sendFileToRemote(SendTimeout, task) 59 var response Response 60 61 if err != nil { 62 rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceError, "Remote Cluster send file failed", 63 mlog.String("remote", task.rc.DisplayName), 64 mlog.String("uploadId", task.us.Id), 65 mlog.Err(err), 66 ) 67 response.Status = ResponseStatusFail 68 response.Err = err.Error() 69 } else { 70 rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceDebug, "Remote Cluster file sent successfully", 71 mlog.String("remote", task.rc.DisplayName), 72 mlog.String("uploadId", task.us.Id), 73 ) 74 response.Status = ResponseStatusOK 75 response.SetPayload(fi) 76 } 77 78 // If callback provided then call it with the results. 79 if task.f != nil { 80 task.f(task.us, task.rc, &response, err) 81 } 82 } 83 84 func (rcs *Service) sendFileToRemote(timeout time.Duration, task sendFileTask) (*model.FileInfo, error) { 85 rcs.server.GetLogger().Log(mlog.LvlRemoteClusterServiceDebug, "sending file to remote...", 86 mlog.String("remote", task.rc.DisplayName), 87 mlog.String("uploadId", task.us.Id), 88 mlog.String("file_path", task.us.Path), 89 ) 90 91 r, appErr := task.rp.FileReader(task.fi.Path) // get Reader for the file 92 if appErr != nil { 93 return nil, fmt.Errorf("error opening file while sending file to remote %s: %w", task.rc.RemoteId, appErr) 94 } 95 defer r.Close() 96 97 u, err := url.Parse(task.rc.SiteURL) 98 if err != nil { 99 return nil, fmt.Errorf("invalid siteURL while sending file to remote %s: %w", task.rc.RemoteId, err) 100 } 101 u.Path = path.Join(u.Path, model.API_URL_SUFFIX, "remotecluster", "upload", task.us.Id) 102 103 req, err := http.NewRequest("POST", u.String(), r) 104 if err != nil { 105 return nil, err 106 } 107 108 req.Header.Set(model.HEADER_REMOTECLUSTER_ID, task.rc.RemoteId) 109 req.Header.Set(model.HEADER_REMOTECLUSTER_TOKEN, task.rc.RemoteToken) 110 111 ctx, cancel := context.WithTimeout(context.Background(), timeout) 112 defer cancel() 113 114 resp, err := rcs.httpClient.Do(req.WithContext(ctx)) 115 if err != nil { 116 return nil, err 117 } 118 defer resp.Body.Close() 119 120 body, err := ioutil.ReadAll(resp.Body) 121 if err != nil { 122 return nil, err 123 } 124 125 if resp.StatusCode != http.StatusOK { 126 return nil, fmt.Errorf("unexpected response: %d - %s", resp.StatusCode, resp.Status) 127 } 128 129 // body should be a FileInfo 130 var fi model.FileInfo 131 if err := json.Unmarshal(body, &fi); err != nil { 132 return nil, fmt.Errorf("unexpected response body: %w", err) 133 } 134 135 return &fi, nil 136 }