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  }