github.com/minio/console@v1.4.1/api/ws_objects.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2023 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/minio/console/models"
    27  	"github.com/minio/websocket"
    28  )
    29  
    30  func (wsc *wsMinioClient) objectManager(session *models.Principal) {
    31  	// Storage of Cancel Contexts for this connection
    32  	cancelContexts := make(map[int64]context.CancelFunc)
    33  	// Initial goroutine
    34  	defer func() {
    35  		// We close socket at the end of requests
    36  		wsc.conn.close()
    37  		for _, c := range cancelContexts {
    38  			// invoke cancel
    39  			c()
    40  		}
    41  	}()
    42  
    43  	writeChannel := make(chan WSResponse)
    44  	done := make(chan interface{})
    45  
    46  	sendWSResponse := func(r WSResponse) {
    47  		select {
    48  		case writeChannel <- r:
    49  		case <-done:
    50  		}
    51  	}
    52  
    53  	// Read goroutine
    54  	go func() {
    55  		defer close(writeChannel)
    56  		for {
    57  			select {
    58  			case <-done:
    59  				return
    60  			default:
    61  			}
    62  
    63  			mType, message, err := wsc.conn.readMessage()
    64  			if err != nil {
    65  				LogInfo("Error while reading objectManager message", err)
    66  				return
    67  			}
    68  
    69  			if mType == websocket.TextMessage {
    70  				// We get request data & review information
    71  				var messageRequest ObjectsRequest
    72  
    73  				err := json.Unmarshal(message, &messageRequest)
    74  				if err != nil {
    75  					LogInfo("Error on message request unmarshal")
    76  					return
    77  				}
    78  
    79  				// new message, new context
    80  				ctx, cancel := context.WithCancel(context.Background())
    81  
    82  				// We store the cancel func associated with this request
    83  				cancelContexts[messageRequest.RequestID] = cancel
    84  
    85  				const itemsPerBatch = 1000
    86  				switch messageRequest.Mode {
    87  				case "close":
    88  					return
    89  				case "cancel":
    90  					// if we have that request id, cancel it
    91  					if cancelFunc, ok := cancelContexts[messageRequest.RequestID]; ok {
    92  						cancelFunc()
    93  						delete(cancelContexts, messageRequest.RequestID)
    94  					}
    95  				case "objects":
    96  					// cancel all previous open objects requests for listing
    97  					for rid, c := range cancelContexts {
    98  						if rid < messageRequest.RequestID {
    99  							// invoke cancel
   100  							c()
   101  						}
   102  					}
   103  
   104  					// start listing and writing to web socket
   105  					go func() {
   106  						objectRqConfigs, err := getObjectsOptionsFromReq(messageRequest)
   107  						if err != nil {
   108  							LogInfo(fmt.Sprintf("Error during Objects OptionsParse %s", err.Error()))
   109  
   110  							sendWSResponse(WSResponse{
   111  								RequestID:  messageRequest.RequestID,
   112  								Error:      ErrorWithContext(ctx, err),
   113  								Prefix:     messageRequest.Prefix,
   114  								BucketName: messageRequest.BucketName,
   115  							})
   116  
   117  							return
   118  						}
   119  						var buffer []ObjectResponse
   120  						for lsObj := range startObjectsListing(ctx, wsc.client, objectRqConfigs) {
   121  							if cancelContexts[messageRequest.RequestID] == nil {
   122  								return
   123  							}
   124  							if lsObj.Err != nil {
   125  								sendWSResponse(WSResponse{
   126  									RequestID:  messageRequest.RequestID,
   127  									Error:      ErrorWithContext(ctx, lsObj.Err),
   128  									Prefix:     messageRequest.Prefix,
   129  									BucketName: messageRequest.BucketName,
   130  								})
   131  
   132  								continue
   133  							}
   134  							objItem := ObjectResponse{
   135  								Name:         lsObj.Key,
   136  								Size:         lsObj.Size,
   137  								LastModified: lsObj.LastModified.Format(time.RFC3339),
   138  								VersionID:    lsObj.VersionID,
   139  								IsLatest:     lsObj.IsLatest,
   140  								DeleteMarker: lsObj.IsDeleteMarker,
   141  							}
   142  							buffer = append(buffer, objItem)
   143  
   144  							if len(buffer) >= itemsPerBatch {
   145  								sendWSResponse(WSResponse{
   146  									RequestID: messageRequest.RequestID,
   147  									Data:      buffer,
   148  								})
   149  								buffer = nil
   150  							}
   151  						}
   152  						if len(buffer) > 0 {
   153  							sendWSResponse(WSResponse{
   154  								RequestID: messageRequest.RequestID,
   155  								Data:      buffer,
   156  							})
   157  						}
   158  
   159  						sendWSResponse(WSResponse{
   160  							RequestID:  messageRequest.RequestID,
   161  							RequestEnd: true,
   162  						})
   163  
   164  						// remove the cancellation context
   165  						delete(cancelContexts, messageRequest.RequestID)
   166  					}()
   167  				case "rewind":
   168  					// cancel all previous open objects requests for listing
   169  					for rid, c := range cancelContexts {
   170  						if rid < messageRequest.RequestID {
   171  							// invoke cancel
   172  							c()
   173  						}
   174  					}
   175  
   176  					// start listing and writing to web socket
   177  					go func() {
   178  						objectRqConfigs, err := getObjectsOptionsFromReq(messageRequest)
   179  						if err != nil {
   180  							LogInfo(fmt.Sprintf("Error during Objects OptionsParse %s", err.Error()))
   181  							sendWSResponse(WSResponse{
   182  								RequestID:  messageRequest.RequestID,
   183  								Error:      ErrorWithContext(ctx, err),
   184  								Prefix:     messageRequest.Prefix,
   185  								BucketName: messageRequest.BucketName,
   186  							})
   187  
   188  							return
   189  						}
   190  
   191  						clientIP := wsc.conn.remoteAddress()
   192  
   193  						s3Client, err := newS3BucketClient(session, objectRqConfigs.BucketName, objectRqConfigs.Prefix, clientIP)
   194  						if err != nil {
   195  							sendWSResponse(WSResponse{
   196  								RequestID:  messageRequest.RequestID,
   197  								Error:      ErrorWithContext(ctx, err),
   198  								Prefix:     messageRequest.Prefix,
   199  								BucketName: messageRequest.BucketName,
   200  							})
   201  
   202  							cancel()
   203  							return
   204  						}
   205  
   206  						mcS3C := mcClient{client: s3Client}
   207  
   208  						var buffer []ObjectResponse
   209  
   210  						for lsObj := range startRewindListing(ctx, mcS3C, objectRqConfigs) {
   211  							if lsObj.Err != nil {
   212  								sendWSResponse(WSResponse{
   213  									RequestID:  messageRequest.RequestID,
   214  									Error:      ErrorWithContext(ctx, lsObj.Err.ToGoError()),
   215  									Prefix:     messageRequest.Prefix,
   216  									BucketName: messageRequest.BucketName,
   217  								})
   218  
   219  								continue
   220  							}
   221  
   222  							name := strings.Replace(lsObj.URL.Path, fmt.Sprintf("/%s/", objectRqConfigs.BucketName), "", 1)
   223  
   224  							objItem := ObjectResponse{
   225  								Name:         name,
   226  								Size:         lsObj.Size,
   227  								LastModified: lsObj.Time.Format(time.RFC3339),
   228  								VersionID:    lsObj.VersionID,
   229  								IsLatest:     lsObj.IsLatest,
   230  								DeleteMarker: lsObj.IsDeleteMarker,
   231  							}
   232  							buffer = append(buffer, objItem)
   233  
   234  							if len(buffer) >= itemsPerBatch {
   235  								sendWSResponse(WSResponse{
   236  									RequestID: messageRequest.RequestID,
   237  									Data:      buffer,
   238  								})
   239  								buffer = nil
   240  							}
   241  
   242  						}
   243  						if len(buffer) > 0 {
   244  							sendWSResponse(WSResponse{
   245  								RequestID: messageRequest.RequestID,
   246  								Data:      buffer,
   247  							})
   248  						}
   249  
   250  						sendWSResponse(WSResponse{
   251  							RequestID:  messageRequest.RequestID,
   252  							RequestEnd: true,
   253  						})
   254  
   255  						// remove the cancellation context
   256  						delete(cancelContexts, messageRequest.RequestID)
   257  					}()
   258  				}
   259  			}
   260  		}
   261  	}()
   262  
   263  	defer close(done)
   264  
   265  	for writeM := range writeChannel {
   266  		jsonData, err := json.Marshal(writeM)
   267  		if err != nil {
   268  			LogInfo("Error while marshaling the response", err)
   269  			return
   270  		}
   271  
   272  		err = wsc.conn.writeMessage(websocket.TextMessage, jsonData)
   273  		if err != nil {
   274  			LogInfo("Error while writing the message", err)
   275  			return
   276  		}
   277  	}
   278  }