github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/listen-notification-handlers.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "net/http" 24 "strconv" 25 "time" 26 27 "github.com/minio/minio/internal/event" 28 "github.com/minio/minio/internal/grid" 29 "github.com/minio/minio/internal/logger" 30 "github.com/minio/minio/internal/pubsub" 31 "github.com/minio/mux" 32 "github.com/minio/pkg/v2/policy" 33 ) 34 35 func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r *http.Request) { 36 ctx := newContext(r, w, "ListenNotification") 37 38 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 39 40 // Validate if bucket exists. 41 objAPI := api.ObjectAPI() 42 if objAPI == nil { 43 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) 44 return 45 } 46 47 vars := mux.Vars(r) 48 bucketName := vars["bucket"] 49 50 if bucketName == "" { 51 if s3Error := checkRequestAuthType(ctx, r, policy.ListenNotificationAction, bucketName, ""); s3Error != ErrNone { 52 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 53 return 54 } 55 } else { 56 if s3Error := checkRequestAuthType(ctx, r, policy.ListenBucketNotificationAction, bucketName, ""); s3Error != ErrNone { 57 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL) 58 return 59 } 60 } 61 62 values := r.Form 63 64 var prefix string 65 if len(values[peerRESTListenPrefix]) > 1 { 66 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNamePrefix), r.URL) 67 return 68 } 69 70 if len(values[peerRESTListenPrefix]) == 1 { 71 if err := event.ValidateFilterRuleValue(values[peerRESTListenPrefix][0]); err != nil { 72 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 73 return 74 } 75 76 prefix = values[peerRESTListenPrefix][0] 77 } 78 79 var suffix string 80 if len(values[peerRESTListenSuffix]) > 1 { 81 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNameSuffix), r.URL) 82 return 83 } 84 85 if len(values[peerRESTListenSuffix]) == 1 { 86 if err := event.ValidateFilterRuleValue(values[peerRESTListenSuffix][0]); err != nil { 87 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 88 return 89 } 90 91 suffix = values[peerRESTListenSuffix][0] 92 } 93 94 pattern := event.NewPattern(prefix, suffix) 95 96 var eventNames []event.Name 97 var mask pubsub.Mask 98 for _, s := range values[peerRESTListenEvents] { 99 eventName, err := event.ParseName(s) 100 if err != nil { 101 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 102 return 103 } 104 mask.MergeMaskable(eventName) 105 eventNames = append(eventNames, eventName) 106 } 107 108 if bucketName != "" { 109 if _, err := objAPI.GetBucketInfo(ctx, bucketName, BucketOptions{}); err != nil { 110 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 111 return 112 } 113 } 114 115 rulesMap := event.NewRulesMap(eventNames, pattern, event.TargetID{ID: mustGetUUID()}) 116 117 setEventStreamHeaders(w) 118 119 // Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers. 120 // Use buffered channel to take care of burst sends or slow w.Write() 121 mergeCh := make(chan []byte, globalAPIConfig.getRequestsPoolCapacity()*len(globalEndpoints.Hostnames())) 122 localCh := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity()) 123 124 // Convert local messages to JSON and send to mergeCh 125 go func() { 126 buf := bytes.NewBuffer(grid.GetByteBuffer()[:0]) 127 enc := json.NewEncoder(buf) 128 tmpEvt := struct{ Records []event.Event }{[]event.Event{{}}} 129 for { 130 select { 131 case ev := <-localCh: 132 buf.Reset() 133 tmpEvt.Records[0] = ev 134 if err := enc.Encode(tmpEvt); err != nil { 135 logger.LogOnceIf(ctx, err, "event: Encode failed") 136 continue 137 } 138 mergeCh <- append(grid.GetByteBuffer()[:0], buf.Bytes()...) 139 case <-ctx.Done(): 140 grid.PutByteBuffer(buf.Bytes()) 141 return 142 } 143 } 144 }() 145 peers, _ := newPeerRestClients(globalEndpoints) 146 err := globalHTTPListen.Subscribe(mask, localCh, ctx.Done(), func(ev event.Event) bool { 147 if ev.S3.Bucket.Name != "" && bucketName != "" { 148 if ev.S3.Bucket.Name != bucketName { 149 return false 150 } 151 } 152 return rulesMap.MatchSimple(ev.EventName, ev.S3.Object.Key) 153 }) 154 if err != nil { 155 writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) 156 return 157 } 158 if bucketName != "" { 159 values.Set(peerRESTListenBucket, bucketName) 160 } 161 for _, peer := range peers { 162 if peer == nil { 163 continue 164 } 165 peer.Listen(ctx, mergeCh, values) 166 } 167 168 var ( 169 emptyEventTicker <-chan time.Time 170 keepAliveTicker <-chan time.Time 171 ) 172 173 if p := values.Get("ping"); p != "" { 174 pingInterval, err := strconv.Atoi(p) 175 if err != nil { 176 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidQueryParams), r.URL) 177 return 178 } 179 if pingInterval < 1 { 180 writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidQueryParams), r.URL) 181 return 182 } 183 t := time.NewTicker(time.Duration(pingInterval) * time.Second) 184 defer t.Stop() 185 emptyEventTicker = t.C 186 } else { 187 // Deprecated Apr 2023 188 t := time.NewTicker(500 * time.Millisecond) 189 defer t.Stop() 190 keepAliveTicker = t.C 191 } 192 193 enc := json.NewEncoder(w) 194 for { 195 select { 196 case ev := <-mergeCh: 197 _, err := w.Write(ev) 198 if err != nil { 199 return 200 } 201 if len(mergeCh) == 0 { 202 // Flush if nothing is queued 203 w.(http.Flusher).Flush() 204 } 205 grid.PutByteBuffer(ev) 206 case <-emptyEventTicker: 207 if err := enc.Encode(struct{ Records []event.Event }{}); err != nil { 208 return 209 } 210 w.(http.Flusher).Flush() 211 case <-keepAliveTicker: 212 if _, err := w.Write([]byte(" ")); err != nil { 213 return 214 } 215 w.(http.Flusher).Flush() 216 case <-ctx.Done(): 217 return 218 } 219 } 220 }