github.com/btccom/go-micro/v2@v2.9.3/broker/nats/nats.go (about)

     1  // Package nats provides a NATS broker
     2  package nats
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/btccom/go-micro/v2/broker"
    11  	"github.com/btccom/go-micro/v2/codec/json"
    12  	"github.com/btccom/go-micro/v2/logger"
    13  	"github.com/btccom/go-micro/v2/registry"
    14  	nats "github.com/nats-io/nats.go"
    15  )
    16  
    17  type natsBroker struct {
    18  	sync.Once
    19  	sync.RWMutex
    20  
    21  	// indicate if we're connected
    22  	connected bool
    23  
    24  	addrs []string
    25  	conn  *nats.Conn
    26  	opts  broker.Options
    27  	nopts nats.Options
    28  
    29  	// should we drain the connection
    30  	drain   bool
    31  	closeCh chan (error)
    32  }
    33  
    34  type subscriber struct {
    35  	s    *nats.Subscription
    36  	opts broker.SubscribeOptions
    37  }
    38  
    39  type publication struct {
    40  	t   string
    41  	err error
    42  	m   *broker.Message
    43  }
    44  
    45  func (p *publication) Topic() string {
    46  	return p.t
    47  }
    48  
    49  func (p *publication) Message() *broker.Message {
    50  	return p.m
    51  }
    52  
    53  func (p *publication) Ack() error {
    54  	// nats does not support acking
    55  	return nil
    56  }
    57  
    58  func (p *publication) Error() error {
    59  	return p.err
    60  }
    61  
    62  func (s *subscriber) Options() broker.SubscribeOptions {
    63  	return s.opts
    64  }
    65  
    66  func (s *subscriber) Topic() string {
    67  	return s.s.Subject
    68  }
    69  
    70  func (s *subscriber) Unsubscribe() error {
    71  	return s.s.Unsubscribe()
    72  }
    73  
    74  func (n *natsBroker) Address() string {
    75  	if n.conn != nil && n.conn.IsConnected() {
    76  		return n.conn.ConnectedUrl()
    77  	}
    78  
    79  	if len(n.addrs) > 0 {
    80  		return n.addrs[0]
    81  	}
    82  
    83  	return ""
    84  }
    85  
    86  func (n *natsBroker) setAddrs(addrs []string) []string {
    87  	//nolint:prealloc
    88  	var cAddrs []string
    89  	for _, addr := range addrs {
    90  		if len(addr) == 0 {
    91  			continue
    92  		}
    93  		if !strings.HasPrefix(addr, "nats://") {
    94  			addr = "nats://" + addr
    95  		}
    96  		cAddrs = append(cAddrs, addr)
    97  	}
    98  	if len(cAddrs) == 0 {
    99  		cAddrs = []string{nats.DefaultURL}
   100  	}
   101  	return cAddrs
   102  }
   103  
   104  func (n *natsBroker) Connect() error {
   105  	n.Lock()
   106  	defer n.Unlock()
   107  
   108  	if n.connected {
   109  		return nil
   110  	}
   111  
   112  	status := nats.CLOSED
   113  	if n.conn != nil {
   114  		status = n.conn.Status()
   115  	}
   116  
   117  	switch status {
   118  	case nats.CONNECTED, nats.RECONNECTING, nats.CONNECTING:
   119  		n.connected = true
   120  		return nil
   121  	default: // DISCONNECTED or CLOSED or DRAINING
   122  		opts := n.nopts
   123  		opts.Servers = n.addrs
   124  		opts.Secure = n.opts.Secure
   125  		opts.TLSConfig = n.opts.TLSConfig
   126  
   127  		// secure might not be set
   128  		if n.opts.TLSConfig != nil {
   129  			opts.Secure = true
   130  		}
   131  
   132  		c, err := opts.Connect()
   133  		if err != nil {
   134  			return err
   135  		}
   136  		n.conn = c
   137  		n.connected = true
   138  		return nil
   139  	}
   140  }
   141  
   142  func (n *natsBroker) Disconnect() error {
   143  	n.Lock()
   144  	defer n.Unlock()
   145  
   146  	// drain the connection if specified
   147  	if n.drain {
   148  		n.conn.Drain()
   149  		n.closeCh <- nil
   150  	}
   151  
   152  	// close the client connection
   153  	n.conn.Close()
   154  
   155  	// set not connected
   156  	n.connected = false
   157  
   158  	return nil
   159  }
   160  
   161  func (n *natsBroker) Init(opts ...broker.Option) error {
   162  	n.setOption(opts...)
   163  	return nil
   164  }
   165  
   166  func (n *natsBroker) Options() broker.Options {
   167  	return n.opts
   168  }
   169  
   170  func (n *natsBroker) Publish(topic string, msg *broker.Message, opts ...broker.PublishOption) error {
   171  	n.RLock()
   172  	defer n.RUnlock()
   173  
   174  	if n.conn == nil {
   175  		return errors.New("not connected")
   176  	}
   177  
   178  	b, err := n.opts.Codec.Marshal(msg)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	return n.conn.Publish(topic, b)
   183  }
   184  
   185  func (n *natsBroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
   186  	n.RLock()
   187  	if n.conn == nil {
   188  		n.RUnlock()
   189  		return nil, errors.New("not connected")
   190  	}
   191  	n.RUnlock()
   192  
   193  	opt := broker.SubscribeOptions{
   194  		AutoAck: true,
   195  		Context: context.Background(),
   196  	}
   197  
   198  	for _, o := range opts {
   199  		o(&opt)
   200  	}
   201  
   202  	fn := func(msg *nats.Msg) {
   203  		var m broker.Message
   204  		pub := &publication{t: msg.Subject}
   205  		eh := n.opts.ErrorHandler
   206  		err := n.opts.Codec.Unmarshal(msg.Data, &m)
   207  		pub.err = err
   208  		pub.m = &m
   209  		if err != nil {
   210  			m.Body = msg.Data
   211  			if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
   212  				logger.Error(err)
   213  			}
   214  			if eh != nil {
   215  				eh(pub)
   216  			}
   217  			return
   218  		}
   219  		if err := handler(pub); err != nil {
   220  			pub.err = err
   221  			if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
   222  				logger.Error(err)
   223  			}
   224  			if eh != nil {
   225  				eh(pub)
   226  			}
   227  		}
   228  	}
   229  
   230  	var sub *nats.Subscription
   231  	var err error
   232  
   233  	n.RLock()
   234  	if len(opt.Queue) > 0 {
   235  		sub, err = n.conn.QueueSubscribe(topic, opt.Queue, fn)
   236  	} else {
   237  		sub, err = n.conn.Subscribe(topic, fn)
   238  	}
   239  	n.RUnlock()
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	return &subscriber{s: sub, opts: opt}, nil
   244  }
   245  
   246  func (n *natsBroker) String() string {
   247  	return "nats"
   248  }
   249  
   250  func (n *natsBroker) setOption(opts ...broker.Option) {
   251  	for _, o := range opts {
   252  		o(&n.opts)
   253  	}
   254  
   255  	n.Once.Do(func() {
   256  		n.nopts = nats.GetDefaultOptions()
   257  	})
   258  
   259  	if nopts, ok := n.opts.Context.Value(optionsKey{}).(nats.Options); ok {
   260  		n.nopts = nopts
   261  	}
   262  
   263  	// broker.Options have higher priority than nats.Options
   264  	// only if Addrs, Secure or TLSConfig were not set through a broker.Option
   265  	// we read them from nats.Option
   266  	if len(n.opts.Addrs) == 0 {
   267  		n.opts.Addrs = n.nopts.Servers
   268  	}
   269  
   270  	if !n.opts.Secure {
   271  		n.opts.Secure = n.nopts.Secure
   272  	}
   273  
   274  	if n.opts.TLSConfig == nil {
   275  		n.opts.TLSConfig = n.nopts.TLSConfig
   276  	}
   277  	n.addrs = n.setAddrs(n.opts.Addrs)
   278  
   279  	if n.opts.Context.Value(drainConnectionKey{}) != nil {
   280  		n.drain = true
   281  		n.closeCh = make(chan error)
   282  		n.nopts.ClosedCB = n.onClose
   283  		n.nopts.AsyncErrorCB = n.onAsyncError
   284  		n.nopts.DisconnectedErrCB = n.onDisconnectedError
   285  	}
   286  }
   287  
   288  func (n *natsBroker) onClose(conn *nats.Conn) {
   289  	n.closeCh <- nil
   290  }
   291  
   292  func (n *natsBroker) onAsyncError(conn *nats.Conn, sub *nats.Subscription, err error) {
   293  	// There are kinds of different async error nats might callback, but we are interested
   294  	// in ErrDrainTimeout only here.
   295  	if err == nats.ErrDrainTimeout {
   296  		n.closeCh <- err
   297  	}
   298  }
   299  
   300  func (n *natsBroker) onDisconnectedError(conn *nats.Conn, err error) {
   301  	n.closeCh <- err
   302  }
   303  
   304  func NewBroker(opts ...broker.Option) broker.Broker {
   305  	options := broker.Options{
   306  		// Default codec
   307  		Codec:    json.Marshaler{},
   308  		Context:  context.Background(),
   309  		Registry: registry.DefaultRegistry,
   310  	}
   311  
   312  	n := &natsBroker{
   313  		opts: options,
   314  	}
   315  	n.setOption(opts...)
   316  
   317  	return n
   318  }