github.com/minio/console@v1.3.0/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 // Read goroutine 47 go func() { 48 for { 49 mType, message, err := wsc.conn.readMessage() 50 if err != nil { 51 LogInfo("Error while reading objectManager message", err) 52 close(done) 53 return 54 } 55 56 if mType == websocket.TextMessage { 57 // We get request data & review information 58 var messageRequest ObjectsRequest 59 60 err := json.Unmarshal(message, &messageRequest) 61 if err != nil { 62 LogInfo("Error on message request unmarshal") 63 64 close(done) 65 return 66 } 67 68 // new message, new context 69 ctx, cancel := context.WithCancel(context.Background()) 70 71 // We store the cancel func associated with this request 72 cancelContexts[messageRequest.RequestID] = cancel 73 74 const itemsPerBatch = 1000 75 switch messageRequest.Mode { 76 case "close": 77 close(done) 78 return 79 case "cancel": 80 // if we have that request id, cancel it 81 if cancelFunc, ok := cancelContexts[messageRequest.RequestID]; ok { 82 cancelFunc() 83 delete(cancelContexts, messageRequest.RequestID) 84 } 85 case "objects": 86 // cancel all previous open objects requests for listing 87 for rid, c := range cancelContexts { 88 if rid < messageRequest.RequestID { 89 // invoke cancel 90 c() 91 } 92 } 93 94 // start listing and writing to web socket 95 go func() { 96 objectRqConfigs, err := getObjectsOptionsFromReq(messageRequest) 97 if err != nil { 98 LogInfo(fmt.Sprintf("Error during Objects OptionsParse %s", err.Error())) 99 100 writeChannel <- WSResponse{ 101 RequestID: messageRequest.RequestID, 102 Error: ErrorWithContext(ctx, err), 103 Prefix: messageRequest.Prefix, 104 BucketName: messageRequest.BucketName, 105 } 106 107 return 108 } 109 var buffer []ObjectResponse 110 for lsObj := range startObjectsListing(ctx, wsc.client, objectRqConfigs) { 111 if cancelContexts[messageRequest.RequestID] == nil { 112 return 113 } 114 if lsObj.Err != nil { 115 writeChannel <- WSResponse{ 116 RequestID: messageRequest.RequestID, 117 Error: ErrorWithContext(ctx, lsObj.Err), 118 Prefix: messageRequest.Prefix, 119 BucketName: messageRequest.BucketName, 120 } 121 122 continue 123 } 124 objItem := ObjectResponse{ 125 Name: lsObj.Key, 126 Size: lsObj.Size, 127 LastModified: lsObj.LastModified.Format(time.RFC3339), 128 VersionID: lsObj.VersionID, 129 IsLatest: lsObj.IsLatest, 130 DeleteMarker: lsObj.IsDeleteMarker, 131 } 132 buffer = append(buffer, objItem) 133 134 if len(buffer) >= itemsPerBatch { 135 writeChannel <- WSResponse{ 136 RequestID: messageRequest.RequestID, 137 Data: buffer, 138 } 139 buffer = nil 140 } 141 } 142 if len(buffer) > 0 { 143 writeChannel <- WSResponse{ 144 RequestID: messageRequest.RequestID, 145 Data: buffer, 146 } 147 } 148 149 writeChannel <- WSResponse{ 150 RequestID: messageRequest.RequestID, 151 RequestEnd: true, 152 } 153 154 // remove the cancellation context 155 delete(cancelContexts, messageRequest.RequestID) 156 }() 157 case "rewind": 158 // cancel all previous open objects requests for listing 159 for rid, c := range cancelContexts { 160 if rid < messageRequest.RequestID { 161 // invoke cancel 162 c() 163 } 164 } 165 166 // start listing and writing to web socket 167 go func() { 168 objectRqConfigs, err := getObjectsOptionsFromReq(messageRequest) 169 if err != nil { 170 LogInfo(fmt.Sprintf("Error during Objects OptionsParse %s", err.Error())) 171 writeChannel <- WSResponse{ 172 RequestID: messageRequest.RequestID, 173 Error: ErrorWithContext(ctx, err), 174 Prefix: messageRequest.Prefix, 175 BucketName: messageRequest.BucketName, 176 } 177 178 return 179 } 180 181 clientIP := wsc.conn.remoteAddress() 182 183 s3Client, err := newS3BucketClient(session, objectRqConfigs.BucketName, objectRqConfigs.Prefix, clientIP) 184 if err != nil { 185 writeChannel <- WSResponse{ 186 RequestID: messageRequest.RequestID, 187 Error: ErrorWithContext(ctx, err), 188 Prefix: messageRequest.Prefix, 189 BucketName: messageRequest.BucketName, 190 } 191 192 cancel() 193 return 194 } 195 196 mcS3C := mcClient{client: s3Client} 197 198 var buffer []ObjectResponse 199 200 for lsObj := range startRewindListing(ctx, mcS3C, objectRqConfigs) { 201 if lsObj.Err != nil { 202 writeChannel <- WSResponse{ 203 RequestID: messageRequest.RequestID, 204 Error: ErrorWithContext(ctx, lsObj.Err.ToGoError()), 205 Prefix: messageRequest.Prefix, 206 BucketName: messageRequest.BucketName, 207 } 208 209 continue 210 } 211 212 name := strings.Replace(lsObj.URL.Path, fmt.Sprintf("/%s/", objectRqConfigs.BucketName), "", 1) 213 214 objItem := ObjectResponse{ 215 Name: name, 216 Size: lsObj.Size, 217 LastModified: lsObj.Time.Format(time.RFC3339), 218 VersionID: lsObj.VersionID, 219 IsLatest: lsObj.IsLatest, 220 DeleteMarker: lsObj.IsDeleteMarker, 221 } 222 buffer = append(buffer, objItem) 223 224 if len(buffer) >= itemsPerBatch { 225 writeChannel <- WSResponse{ 226 RequestID: messageRequest.RequestID, 227 Data: buffer, 228 } 229 buffer = nil 230 } 231 232 } 233 if len(buffer) > 0 { 234 writeChannel <- WSResponse{ 235 RequestID: messageRequest.RequestID, 236 Data: buffer, 237 } 238 } 239 240 writeChannel <- WSResponse{ 241 RequestID: messageRequest.RequestID, 242 RequestEnd: true, 243 } 244 245 // remove the cancellation context 246 delete(cancelContexts, messageRequest.RequestID) 247 }() 248 } 249 } 250 } 251 }() 252 253 // Write goroutine 254 go func() { 255 for { 256 select { 257 case <-done: 258 return 259 case writeM := <-writeChannel: 260 jsonData, err := json.Marshal(writeM) 261 if err != nil { 262 LogInfo("Error while marshaling the response", err) 263 return 264 } 265 266 err = wsc.conn.writeMessage(websocket.TextMessage, jsonData) 267 if err != nil { 268 LogInfo("Error while writing the message", err) 269 return 270 } 271 } 272 } 273 }() 274 275 <-done 276 }