storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/listen-notification-handlers.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "encoding/json" 21 "net/http" 22 "time" 23 24 "github.com/gorilla/mux" 25 26 "storj.io/minio/cmd/logger" 27 policy "storj.io/minio/pkg/bucket/policy" 28 "storj.io/minio/pkg/event" 29 ) 30 31 func (api ObjectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r *http.Request) { 32 ctx := NewContext(r, w, "ListenNotification") 33 34 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 35 36 // Validate if bucket exists. 37 objAPI := api.ObjectAPI() 38 if objAPI == nil { 39 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 40 return 41 } 42 43 if !objAPI.IsNotificationSupported() { 44 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) 45 return 46 } 47 48 if !objAPI.IsListenSupported() { 49 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) 50 return 51 } 52 53 vars := mux.Vars(r) 54 bucketName := vars["bucket"] 55 56 if bucketName == "" { 57 if s3Error := checkRequestAuthType(ctx, r, policy.ListenNotificationAction, bucketName, ""); s3Error != ErrNone { 58 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 59 return 60 } 61 } else { 62 if s3Error := checkRequestAuthType(ctx, r, policy.ListenBucketNotificationAction, bucketName, ""); s3Error != ErrNone { 63 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 64 return 65 } 66 } 67 68 values := r.URL.Query() 69 70 var prefix string 71 if len(values[peerRESTListenPrefix]) > 1 { 72 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNamePrefix), r.URL, guessIsBrowserReq(r)) 73 return 74 } 75 76 if len(values[peerRESTListenPrefix]) == 1 { 77 if err := event.ValidateFilterRuleValue(values[peerRESTListenPrefix][0]); err != nil { 78 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 79 return 80 } 81 82 prefix = values[peerRESTListenPrefix][0] 83 } 84 85 var suffix string 86 if len(values[peerRESTListenSuffix]) > 1 { 87 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrFilterNameSuffix), r.URL, guessIsBrowserReq(r)) 88 return 89 } 90 91 if len(values[peerRESTListenSuffix]) == 1 { 92 if err := event.ValidateFilterRuleValue(values[peerRESTListenSuffix][0]); err != nil { 93 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 94 return 95 } 96 97 suffix = values[peerRESTListenSuffix][0] 98 } 99 100 pattern := event.NewPattern(prefix, suffix) 101 102 var eventNames []event.Name 103 for _, s := range values[peerRESTListenEvents] { 104 eventName, err := event.ParseName(s) 105 if err != nil { 106 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 107 return 108 } 109 110 eventNames = append(eventNames, eventName) 111 } 112 113 if bucketName != "" { 114 if _, err := objAPI.GetBucketInfo(ctx, bucketName); err != nil { 115 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 116 return 117 } 118 } 119 120 rulesMap := event.NewRulesMap(eventNames, pattern, event.TargetID{ID: mustGetUUID()}) 121 122 setEventStreamHeaders(w) 123 124 // Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers. 125 // Use buffered channel to take care of burst sends or slow w.Write() 126 listenCh := make(chan interface{}, 4000) 127 128 peers, _ := newPeerRestClients(globalEndpoints) 129 130 globalHTTPListen.Subscribe(listenCh, ctx.Done(), func(evI interface{}) bool { 131 ev, ok := evI.(event.Event) 132 if !ok { 133 return false 134 } 135 if ev.S3.Bucket.Name != "" && bucketName != "" { 136 if ev.S3.Bucket.Name != bucketName { 137 return false 138 } 139 } 140 return rulesMap.MatchSimple(ev.EventName, ev.S3.Object.Key) 141 }) 142 143 if bucketName != "" { 144 values.Set(peerRESTListenBucket, bucketName) 145 } 146 for _, peer := range peers { 147 if peer == nil { 148 continue 149 } 150 peer.Listen(listenCh, ctx.Done(), values) 151 } 152 153 keepAliveTicker := time.NewTicker(500 * time.Millisecond) 154 defer keepAliveTicker.Stop() 155 156 enc := json.NewEncoder(w) 157 for { 158 select { 159 case evI := <-listenCh: 160 ev, ok := evI.(event.Event) 161 if ok { 162 if err := enc.Encode(struct{ Records []event.Event }{[]event.Event{ev}}); err != nil { 163 return 164 } 165 } else { 166 if _, err := w.Write([]byte(" ")); err != nil { 167 return 168 } 169 } 170 w.(http.Flusher).Flush() 171 case <-keepAliveTicker.C: 172 if _, err := w.Write([]byte(" ")); err != nil { 173 return 174 } 175 w.(http.Flusher).Flush() 176 case <-ctx.Done(): 177 return 178 } 179 } 180 }