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 //------------------------------------------------------------------------------