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

     1  package writer
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"math/rand"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/Jeffail/benthos/v3/internal/impl/nats/auth"
    13  	btls "github.com/Jeffail/benthos/v3/lib/util/tls"
    14  	"github.com/nats-io/nats.go"
    15  
    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/stan.go"
    20  )
    21  
    22  //------------------------------------------------------------------------------
    23  
    24  // NATSStreamConfig contains configuration fields for the NATSStream output
    25  // type.
    26  type NATSStreamConfig struct {
    27  	URLs        []string    `json:"urls" yaml:"urls"`
    28  	ClusterID   string      `json:"cluster_id" yaml:"cluster_id"`
    29  	ClientID    string      `json:"client_id" yaml:"client_id"`
    30  	Subject     string      `json:"subject" yaml:"subject"`
    31  	MaxInFlight int         `json:"max_in_flight" yaml:"max_in_flight"`
    32  	TLS         btls.Config `json:"tls" yaml:"tls"`
    33  	Auth        auth.Config `json:"auth" yaml:"auth"`
    34  }
    35  
    36  // NewNATSStreamConfig creates a new NATSStreamConfig with default values.
    37  func NewNATSStreamConfig() NATSStreamConfig {
    38  	return NATSStreamConfig{
    39  		URLs:        []string{stan.DefaultNatsURL},
    40  		ClusterID:   "test-cluster",
    41  		ClientID:    "benthos_client",
    42  		Subject:     "benthos_messages",
    43  		MaxInFlight: 1,
    44  		TLS:         btls.NewConfig(),
    45  		Auth:        auth.New(),
    46  	}
    47  }
    48  
    49  //------------------------------------------------------------------------------
    50  
    51  // NATSStream is an output type that serves NATS messages.
    52  type NATSStream struct {
    53  	log log.Modular
    54  
    55  	stanConn stan.Conn
    56  	natsConn *nats.Conn
    57  	connMut  sync.RWMutex
    58  
    59  	urls    string
    60  	conf    NATSStreamConfig
    61  	tlsConf *tls.Config
    62  }
    63  
    64  // NewNATSStream creates a new NATS Stream output type.
    65  func NewNATSStream(conf NATSStreamConfig, log log.Modular, stats metrics.Type) (*NATSStream, error) {
    66  	if conf.ClientID == "" {
    67  		rgen := rand.New(rand.NewSource(time.Now().UnixNano()))
    68  
    69  		// Generate random client id if one wasn't supplied.
    70  		b := make([]byte, 16)
    71  		rgen.Read(b)
    72  		conf.ClientID = fmt.Sprintf("client-%x", b)
    73  	}
    74  
    75  	n := NATSStream{
    76  		log:  log,
    77  		conf: conf,
    78  	}
    79  	n.urls = strings.Join(conf.URLs, ",")
    80  	var err error
    81  	if conf.TLS.Enabled {
    82  		if n.tlsConf, err = conf.TLS.Get(); err != nil {
    83  			return nil, err
    84  		}
    85  	}
    86  
    87  	return &n, nil
    88  }
    89  
    90  //------------------------------------------------------------------------------
    91  
    92  // ConnectWithContext attempts to establish a connection to NATS servers.
    93  func (n *NATSStream) ConnectWithContext(ctx context.Context) error {
    94  	return n.Connect()
    95  }
    96  
    97  // Connect attempts to establish a connection to NATS servers.
    98  func (n *NATSStream) Connect() error {
    99  	n.connMut.Lock()
   100  	defer n.connMut.Unlock()
   101  
   102  	if n.natsConn != nil {
   103  		return nil
   104  	}
   105  
   106  	var opts []nats.Option
   107  	if n.tlsConf != nil {
   108  		opts = append(opts, nats.Secure(n.tlsConf))
   109  	}
   110  
   111  	opts = append(opts, auth.GetOptions(n.conf.Auth)...)
   112  
   113  	natsConn, err := nats.Connect(n.urls, opts...)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	stanConn, err := stan.Connect(
   119  		n.conf.ClusterID,
   120  		n.conf.ClientID,
   121  		stan.NatsConn(natsConn),
   122  	)
   123  	if err != nil {
   124  		natsConn.Close()
   125  		return err
   126  	}
   127  
   128  	n.stanConn = stanConn
   129  	n.natsConn = natsConn
   130  	n.log.Infof("Sending NATS messages to subject: %v\n", n.conf.Subject)
   131  	return nil
   132  }
   133  
   134  // WriteWithContext attempts to write a message.
   135  func (n *NATSStream) WriteWithContext(ctx context.Context, msg types.Message) error {
   136  	return n.Write(msg)
   137  }
   138  
   139  // Write attempts to write a message.
   140  func (n *NATSStream) Write(msg types.Message) error {
   141  	n.connMut.RLock()
   142  	conn := n.stanConn
   143  	n.connMut.RUnlock()
   144  
   145  	if conn == nil {
   146  		return types.ErrNotConnected
   147  	}
   148  
   149  	return IterateBatchedSend(msg, func(i int, p types.Part) error {
   150  		err := conn.Publish(n.conf.Subject, p.Get())
   151  		if err == stan.ErrConnectionClosed {
   152  			conn.Close()
   153  			n.connMut.Lock()
   154  			n.stanConn = nil
   155  			n.natsConn.Close()
   156  			n.natsConn = nil
   157  			n.connMut.Unlock()
   158  			return types.ErrNotConnected
   159  		}
   160  		return err
   161  	})
   162  }
   163  
   164  // CloseAsync shuts down the MQTT output and stops processing messages.
   165  func (n *NATSStream) CloseAsync() {
   166  	n.connMut.Lock()
   167  	if n.natsConn != nil {
   168  		n.natsConn.Close()
   169  		n.natsConn = nil
   170  	}
   171  	if n.stanConn != nil {
   172  		n.stanConn.Close()
   173  		n.stanConn = nil
   174  	}
   175  	n.connMut.Unlock()
   176  }
   177  
   178  // WaitForClose blocks until the NATS output has closed down.
   179  func (n *NATSStream) WaitForClose(timeout time.Duration) error {
   180  	return nil
   181  }
   182  
   183  //------------------------------------------------------------------------------