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 }