github.com/Jeffail/benthos/v3@v3.65.0/lib/output/writer/nats.go (about)

     1  package writer
     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/interop"
    13  	btls "github.com/Jeffail/benthos/v3/lib/util/tls"
    14  
    15  	"github.com/Jeffail/benthos/v3/internal/bloblang/field"
    16  	"github.com/Jeffail/benthos/v3/lib/log"
    17  	"github.com/Jeffail/benthos/v3/lib/metrics"
    18  	"github.com/Jeffail/benthos/v3/lib/types"
    19  	"github.com/nats-io/nats.go"
    20  )
    21  
    22  //------------------------------------------------------------------------------
    23  
    24  // NATSConfig contains configuration fields for the NATS output type.
    25  type NATSConfig struct {
    26  	URLs        []string          `json:"urls" yaml:"urls"`
    27  	Subject     string            `json:"subject" yaml:"subject"`
    28  	Headers     map[string]string `json:"headers" yaml:"headers"`
    29  	MaxInFlight int               `json:"max_in_flight" yaml:"max_in_flight"`
    30  	TLS         btls.Config       `json:"tls" yaml:"tls"`
    31  	Auth        auth.Config       `json:"auth" yaml:"auth"`
    32  }
    33  
    34  // NewNATSConfig creates a new NATSConfig with default values.
    35  func NewNATSConfig() NATSConfig {
    36  	return NATSConfig{
    37  		URLs:        []string{nats.DefaultURL},
    38  		Subject:     "benthos_messages",
    39  		MaxInFlight: 1,
    40  		TLS:         btls.NewConfig(),
    41  		Auth:        auth.New(),
    42  	}
    43  }
    44  
    45  //------------------------------------------------------------------------------
    46  
    47  // NATS is an output type that serves NATS messages.
    48  type NATS struct {
    49  	log log.Modular
    50  
    51  	natsConn *nats.Conn
    52  	connMut  sync.RWMutex
    53  
    54  	urls       string
    55  	conf       NATSConfig
    56  	headers    map[string]*field.Expression
    57  	subjectStr *field.Expression
    58  	tlsConf    *tls.Config
    59  }
    60  
    61  // NewNATS creates a new NATS output type.
    62  //
    63  // Deprecated: use the V2 API instead.
    64  func NewNATS(conf NATSConfig, log log.Modular, stats metrics.Type) (*NATS, error) {
    65  	return NewNATSV2(conf, types.NoopMgr(), log, stats)
    66  }
    67  
    68  // NewNATSV2 creates a new NATS output type.
    69  func NewNATSV2(conf NATSConfig, mgr types.Manager, log log.Modular, stats metrics.Type) (*NATS, error) {
    70  	n := NATS{
    71  		log:     log,
    72  		conf:    conf,
    73  		headers: make(map[string]*field.Expression),
    74  	}
    75  	var err error
    76  	if n.subjectStr, err = interop.NewBloblangField(mgr, conf.Subject); err != nil {
    77  		return nil, fmt.Errorf("failed to parse subject expression: %v", err)
    78  	}
    79  	for k, v := range conf.Headers {
    80  		if n.headers[k], err = interop.NewBloblangField(mgr, v); err != nil {
    81  			return nil, fmt.Errorf("failed to parse header '%s' expresion: %v", k, err)
    82  		}
    83  	}
    84  	n.urls = strings.Join(conf.URLs, ",")
    85  
    86  	if conf.TLS.Enabled {
    87  		if n.tlsConf, err = conf.TLS.Get(); err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  
    92  	return &n, nil
    93  }
    94  
    95  //------------------------------------------------------------------------------
    96  
    97  // ConnectWithContext attempts to establish a connection to NATS servers.
    98  func (n *NATS) ConnectWithContext(ctx context.Context) error {
    99  	return n.Connect()
   100  }
   101  
   102  // Connect attempts to establish a connection to NATS servers.
   103  func (n *NATS) Connect() error {
   104  	n.connMut.Lock()
   105  	defer n.connMut.Unlock()
   106  
   107  	if n.natsConn != nil {
   108  		return nil
   109  	}
   110  
   111  	var err error
   112  	var opts []nats.Option
   113  
   114  	if n.tlsConf != nil {
   115  		opts = append(opts, nats.Secure(n.tlsConf))
   116  	}
   117  
   118  	opts = append(opts, auth.GetOptions(n.conf.Auth)...)
   119  
   120  	if n.natsConn, err = nats.Connect(n.urls, opts...); err != nil {
   121  		return err
   122  	}
   123  
   124  	if err == nil {
   125  		n.log.Infof("Sending NATS messages to subject: %v\n", n.conf.Subject)
   126  	}
   127  	return err
   128  }
   129  
   130  // WriteWithContext attempts to write a message.
   131  func (n *NATS) WriteWithContext(ctx context.Context, msg types.Message) error {
   132  	return n.Write(msg)
   133  }
   134  
   135  // Write attempts to write a message.
   136  func (n *NATS) Write(msg types.Message) error {
   137  	n.connMut.RLock()
   138  	conn := n.natsConn
   139  	n.connMut.RUnlock()
   140  
   141  	if conn == nil {
   142  		return types.ErrNotConnected
   143  	}
   144  
   145  	return IterateBatchedSend(msg, func(i int, p types.Part) error {
   146  		subject := n.subjectStr.String(i, msg)
   147  		n.log.Debugf("Writing NATS message to topic %s", subject)
   148  		// fill message data
   149  		nMsg := nats.NewMsg(subject)
   150  		nMsg.Data = p.Get()
   151  		if conn.HeadersSupported() {
   152  			// fill bloblang headers
   153  			for k, v := range n.headers {
   154  				nMsg.Header.Add(k, v.String(i, msg))
   155  			}
   156  		}
   157  		err := conn.PublishMsg(nMsg)
   158  		if err == nats.ErrConnectionClosed {
   159  			conn.Close()
   160  			n.connMut.Lock()
   161  			n.natsConn = nil
   162  			n.connMut.Unlock()
   163  			return types.ErrNotConnected
   164  		}
   165  		return err
   166  	})
   167  }
   168  
   169  // CloseAsync shuts down the MQTT output and stops processing messages.
   170  func (n *NATS) CloseAsync() {
   171  	go func() {
   172  		n.connMut.Lock()
   173  		if n.natsConn != nil {
   174  			n.natsConn.Close()
   175  			n.natsConn = nil
   176  		}
   177  		n.connMut.Unlock()
   178  	}()
   179  }
   180  
   181  // WaitForClose blocks until the NATS output has closed down.
   182  func (n *NATS) WaitForClose(timeout time.Duration) error {
   183  	return nil
   184  }
   185  
   186  //------------------------------------------------------------------------------