github.com/Jeffail/benthos/v3@v3.65.0/internal/impl/nats/input_jetstream.go (about) 1 package nats 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/Jeffail/benthos/v3/internal/impl/nats/auth" 12 "github.com/Jeffail/benthos/v3/internal/shutdown" 13 "github.com/Jeffail/benthos/v3/lib/input" 14 "github.com/Jeffail/benthos/v3/public/service" 15 "github.com/nats-io/nats.go" 16 ) 17 18 func natsJetStreamInputConfig() *service.ConfigSpec { 19 return service.NewConfigSpec(). 20 // Stable(). TODO 21 Categories("Services"). 22 Version("3.46.0"). 23 Summary("Reads messages from NATS JetStream subjects."). 24 Description(` 25 ### Metadata 26 27 This input adds the following metadata fields to each message: 28 29 ` + "```text" + ` 30 - nats_subject 31 ` + "```" + ` 32 33 You can access these metadata fields using 34 [function interpolation](/docs/configuration/interpolation#metadata). 35 36 ` + auth.Description()). 37 Field(service.NewStringListField("urls"). 38 Description("A list of URLs to connect to. If an item of the list contains commas it will be expanded into multiple URLs."). 39 Example([]string{"nats://127.0.0.1:4222"}). 40 Example([]string{"nats://username:password@127.0.0.1:4222"})). 41 Field(service.NewStringField("queue"). 42 Description("An optional queue group to consume as."). 43 Optional()). 44 Field(service.NewStringField("subject"). 45 Description("A subject to consume from. Supports wildcards for consuming multiple subjects."). 46 Example("foo.bar.baz").Example("foo.*.baz").Example("foo.bar.*").Example("foo.>")). 47 Field(service.NewStringField("durable"). 48 Description("Preserve the state of your consumer under a durable name."). 49 Optional()). 50 Field(service.NewStringAnnotatedEnumField("deliver", map[string]string{ 51 "all": "Deliver all available messages.", 52 "last": "Deliver starting with the last published messages.", 53 }). 54 Description("Determines which messages to deliver when consuming without a durable subscriber."). 55 Default("all")). 56 Field(service.NewStringField("ack_wait"). 57 Description("The maximum amount of time NATS server should wait for an ack from consumer."). 58 Advanced(). 59 Default("30s"). 60 Example("100ms"). 61 Example("5m")). 62 Field(service.NewIntField("max_ack_pending"). 63 Description("The maximum number of outstanding acks to be allowed before consuming is halted."). 64 Advanced(). 65 Default(1024)). 66 Field(service.NewTLSToggledField("tls")). 67 Field(service.NewInternalField(auth.FieldSpec())) 68 } 69 70 func init() { 71 err := service.RegisterInput( 72 input.TypeNATSJetStream, natsJetStreamInputConfig(), 73 func(conf *service.ParsedConfig, mgr *service.Resources) (service.Input, error) { 74 return newJetStreamReaderFromConfig(conf, mgr.Logger()) 75 }) 76 77 if err != nil { 78 panic(err) 79 } 80 } 81 82 //------------------------------------------------------------------------------ 83 84 type jetStreamReader struct { 85 urls string 86 deliverOpt nats.SubOpt 87 subject string 88 queue string 89 durable string 90 ackWait time.Duration 91 maxAckPending int 92 authConf auth.Config 93 tlsConf *tls.Config 94 95 log *service.Logger 96 97 connMut sync.Mutex 98 natsConn *nats.Conn 99 natsSub *nats.Subscription 100 101 shutSig *shutdown.Signaller 102 } 103 104 func newJetStreamReaderFromConfig(conf *service.ParsedConfig, log *service.Logger) (*jetStreamReader, error) { 105 j := jetStreamReader{ 106 log: log, 107 shutSig: shutdown.NewSignaller(), 108 } 109 110 urlList, err := conf.FieldStringList("urls") 111 if err != nil { 112 return nil, err 113 } 114 j.urls = strings.Join(urlList, ",") 115 116 deliver, err := conf.FieldString("deliver") 117 if err != nil { 118 return nil, err 119 } 120 switch deliver { 121 case "all": 122 j.deliverOpt = nats.DeliverAll() 123 case "last": 124 j.deliverOpt = nats.DeliverLast() 125 default: 126 return nil, fmt.Errorf("deliver option %v was not recognised", deliver) 127 } 128 129 if j.subject, err = conf.FieldString("subject"); err != nil { 130 return nil, err 131 } 132 if conf.Contains("queue") { 133 if j.queue, err = conf.FieldString("queue"); err != nil { 134 return nil, err 135 } 136 } 137 if conf.Contains("durable") { 138 if j.durable, err = conf.FieldString("durable"); err != nil { 139 return nil, err 140 } 141 } 142 143 ackWaitStr, err := conf.FieldString("ack_wait") 144 if err != nil { 145 return nil, err 146 } 147 if ackWaitStr != "" { 148 j.ackWait, err = time.ParseDuration(ackWaitStr) 149 if err != nil { 150 return nil, fmt.Errorf("failed to parse ack wait duration: %v", err) 151 } 152 } 153 154 if j.maxAckPending, err = conf.FieldInt("max_ack_pending"); err != nil { 155 return nil, err 156 } 157 158 tlsConf, tlsEnabled, err := conf.FieldTLSToggled("tls") 159 if err != nil { 160 return nil, err 161 } 162 if tlsEnabled { 163 j.tlsConf = tlsConf 164 } 165 166 if j.authConf, err = AuthFromParsedConfig(conf.Namespace("auth")); err != nil { 167 return nil, err 168 } 169 170 return &j, nil 171 } 172 173 //------------------------------------------------------------------------------ 174 175 func (j *jetStreamReader) Connect(ctx context.Context) error { 176 j.connMut.Lock() 177 defer j.connMut.Unlock() 178 179 if j.natsConn != nil { 180 return nil 181 } 182 183 var natsConn *nats.Conn 184 var natsSub *nats.Subscription 185 var err error 186 187 defer func() { 188 if err != nil { 189 if natsSub != nil { 190 _ = natsSub.Drain() 191 } 192 if natsConn != nil { 193 natsConn.Close() 194 } 195 } 196 }() 197 198 var opts []nats.Option 199 if j.tlsConf != nil { 200 opts = append(opts, nats.Secure(j.tlsConf)) 201 } 202 opts = append(opts, auth.GetOptions(j.authConf)...) 203 if natsConn, err = nats.Connect(j.urls, opts...); err != nil { 204 return err 205 } 206 207 jCtx, err := natsConn.JetStream() 208 if err != nil { 209 return err 210 } 211 212 options := []nats.SubOpt{ 213 nats.ManualAck(), 214 } 215 if j.durable != "" { 216 options = append(options, nats.Durable(j.durable)) 217 } 218 options = append(options, j.deliverOpt) 219 if j.ackWait > 0 { 220 options = append(options, nats.AckWait(j.ackWait)) 221 } 222 if j.maxAckPending != 0 { 223 options = append(options, nats.MaxAckPending(j.maxAckPending)) 224 } 225 226 if j.queue == "" { 227 natsSub, err = jCtx.SubscribeSync(j.subject, options...) 228 } else { 229 natsSub, err = jCtx.QueueSubscribeSync(j.subject, j.queue, options...) 230 } 231 if err != nil { 232 return err 233 } 234 235 j.log.Infof("Receiving NATS messages from JetStream subject: %v", j.subject) 236 237 j.natsConn = natsConn 238 j.natsSub = natsSub 239 return nil 240 } 241 242 func (j *jetStreamReader) disconnect() { 243 j.connMut.Lock() 244 defer j.connMut.Unlock() 245 246 if j.natsSub != nil { 247 _ = j.natsSub.Drain() 248 j.natsSub = nil 249 } 250 if j.natsConn != nil { 251 j.natsConn.Close() 252 j.natsConn = nil 253 } 254 } 255 256 func (j *jetStreamReader) Read(ctx context.Context) (*service.Message, service.AckFunc, error) { 257 j.connMut.Lock() 258 natsSub := j.natsSub 259 j.connMut.Unlock() 260 if natsSub == nil { 261 return nil, nil, service.ErrNotConnected 262 } 263 264 nmsg, err := natsSub.NextMsgWithContext(ctx) 265 if err != nil { 266 // TODO: Any errors need capturing here to signal a lost connection? 267 return nil, nil, err 268 } 269 270 msg := service.NewMessage(nmsg.Data) 271 msg.MetaSet("nats_subject", nmsg.Subject) 272 273 return msg, func(ctx context.Context, res error) error { 274 if res == nil { 275 return nmsg.Ack() 276 } 277 return nmsg.Nak() 278 }, nil 279 } 280 281 func (j *jetStreamReader) Close(ctx context.Context) error { 282 go func() { 283 j.disconnect() 284 j.shutSig.ShutdownComplete() 285 }() 286 select { 287 case <-j.shutSig.HasClosedChan(): 288 case <-ctx.Done(): 289 return ctx.Err() 290 } 291 return nil 292 }