github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/events/handler/stream.go (about)

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/google/uuid"
     9  	pb "github.com/tickoalcantara12/micro/v3/proto/events"
    10  	"github.com/tickoalcantara12/micro/v3/service/errors"
    11  	"github.com/tickoalcantara12/micro/v3/service/events"
    12  	"github.com/tickoalcantara12/micro/v3/service/events/util"
    13  	"github.com/tickoalcantara12/micro/v3/service/logger"
    14  	"github.com/tickoalcantara12/micro/v3/util/auth/namespace"
    15  )
    16  
    17  type Stream struct{}
    18  
    19  func (s *Stream) Publish(ctx context.Context, req *pb.PublishRequest, rsp *pb.PublishResponse) error {
    20  	// authorize the request
    21  	if err := namespace.AuthorizeAdmin(ctx, namespace.DefaultNamespace, "events.Stream.Publish"); err != nil {
    22  		return err
    23  	}
    24  
    25  	// validate the request
    26  	if len(req.Topic) == 0 {
    27  		return errors.BadRequest("events.Stream.Publish", events.ErrMissingTopic.Error())
    28  	}
    29  
    30  	// parse options
    31  	var opts []events.PublishOption
    32  	if req.Timestamp > 0 {
    33  		opts = append(opts, events.WithTimestamp(time.Unix(req.Timestamp, 0)))
    34  	}
    35  	if req.Metadata != nil {
    36  		opts = append(opts, events.WithMetadata(req.Metadata))
    37  	}
    38  
    39  	// publish the event
    40  	if err := events.Publish(req.Topic, req.Payload, opts...); err != nil {
    41  		return errors.InternalServerError("events.Stream.Publish", err.Error())
    42  	}
    43  
    44  	// write the event to the store
    45  	event := events.Event{
    46  		ID:        uuid.New().String(),
    47  		Metadata:  req.Metadata,
    48  		Payload:   req.Payload,
    49  		Topic:     req.Topic,
    50  		Timestamp: time.Unix(req.Timestamp, 0),
    51  	}
    52  
    53  	if err := events.DefaultStore.Write(&event, events.WithTTL(time.Hour*24)); err != nil {
    54  		logger.Errorf("Error writing event %v to store: %v", event.ID, err)
    55  	}
    56  
    57  	return nil
    58  }
    59  
    60  func (s *Stream) Consume(ctx context.Context, req *pb.ConsumeRequest, rsp pb.Stream_ConsumeStream) error {
    61  	// authorize the request
    62  	if err := namespace.AuthorizeAdmin(ctx, namespace.DefaultNamespace, "events.Stream.Consume"); err != nil {
    63  		return err
    64  	}
    65  
    66  	// parse options
    67  	opts := []events.ConsumeOption{}
    68  	if req.Offset > 0 {
    69  		opts = append(opts, events.WithOffset(time.Unix(req.Offset, 0)))
    70  	}
    71  	if len(req.Group) > 0 {
    72  		opts = append(opts, events.WithGroup(req.Group))
    73  	}
    74  	if !req.AutoAck {
    75  		opts = append(opts, events.WithAutoAck(req.AutoAck, time.Duration(req.AckWait)/time.Nanosecond))
    76  	}
    77  	if req.RetryLimit > -1 {
    78  		opts = append(opts, events.WithRetryLimit(int(req.RetryLimit)))
    79  	}
    80  
    81  	// append the context
    82  	opts = append(opts, events.WithContext(ctx))
    83  
    84  	// create the subscriber
    85  	evChan, err := events.Consume(req.Topic, opts...)
    86  	if err != nil {
    87  		return errors.InternalServerError("events.Stream.Consume", err.Error())
    88  	}
    89  
    90  	type eventSent struct {
    91  		sent  time.Time
    92  		event events.Event
    93  	}
    94  	ackMap := map[string]eventSent{}
    95  	mutex := sync.RWMutex{}
    96  	recvErrChan := make(chan error)
    97  	sendErrChan := make(chan error)
    98  
    99  	go func() {
   100  		// process messages from the consumer (probably just ACK messages
   101  		defer close(recvErrChan)
   102  		for {
   103  			select {
   104  			case <-ctx.Done():
   105  				return
   106  			case <-sendErrChan:
   107  				return
   108  			default:
   109  			}
   110  
   111  			req := pb.AckRequest{}
   112  			if err := rsp.RecvMsg(&req); err != nil {
   113  				return
   114  			}
   115  			mutex.RLock()
   116  			ev, ok := ackMap[req.Id]
   117  			mutex.RUnlock()
   118  			if !ok {
   119  				// not found, probably timed out after ackWait
   120  				continue
   121  			}
   122  			if req.Success {
   123  				ev.event.Ack()
   124  			} else {
   125  				ev.event.Nack()
   126  			}
   127  			mutex.Lock()
   128  			delete(ackMap, req.Id)
   129  			mutex.Unlock()
   130  		}
   131  	}()
   132  
   133  	go func() {
   134  		// process messages coming from the stream
   135  		defer close(sendErrChan)
   136  		for {
   137  			// Do any clean up of ackMap where we haven't got a response
   138  			now := time.Now()
   139  
   140  			mutex.Lock()
   141  			for k, v := range ackMap {
   142  				if v.sent.Add(2 * time.Duration(req.AckWait)).Before(now) {
   143  					delete(ackMap, k)
   144  				}
   145  			}
   146  			mutex.Unlock()
   147  			var ev events.Event
   148  			var ok bool
   149  			select {
   150  			case <-recvErrChan:
   151  			case <-rsp.Context().Done():
   152  			case <-ctx.Done():
   153  			case ev, ok = <-evChan:
   154  			}
   155  			if !ok {
   156  				return
   157  			}
   158  			if len(ev.ID) == 0 {
   159  				// ignore
   160  				continue
   161  			}
   162  			if !req.AutoAck {
   163  				// track the acks
   164  				mutex.Lock()
   165  				ackMap[ev.ID] = eventSent{event: ev, sent: time.Now()}
   166  				mutex.Unlock()
   167  			}
   168  			if err := rsp.Send(util.SerializeEvent(&ev)); err != nil {
   169  				return
   170  			}
   171  		}
   172  	}()
   173  
   174  	select {
   175  	case <-ctx.Done():
   176  	case <-recvErrChan:
   177  	case <-sendErrChan:
   178  	case <-rsp.Context().Done():
   179  	}
   180  	return nil
   181  
   182  }