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