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  }