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 }