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  }