go-micro.dev/v5@v5.12.0/broker/rabbitmq/rabbitmq.go (about)

     1  // Package rabbitmq provides a RabbitMQ broker
     2  package rabbitmq
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"net/url"
     9  	"sync"
    10  	"time"
    11  
    12  	amqp "github.com/rabbitmq/amqp091-go"
    13  	"go-micro.dev/v5/broker"
    14  	"go-micro.dev/v5/logger"
    15  )
    16  
    17  type rbroker struct {
    18  	conn           *rabbitMQConn
    19  	addrs          []string
    20  	opts           broker.Options
    21  	prefetchCount  int
    22  	prefetchGlobal bool
    23  	mtx            sync.Mutex
    24  	wg             sync.WaitGroup
    25  }
    26  
    27  type subscriber struct {
    28  	mtx          sync.Mutex
    29  	unsub        chan bool
    30  	opts         broker.SubscribeOptions
    31  	topic        string
    32  	ch           *rabbitMQChannel
    33  	durableQueue bool
    34  	queueArgs    map[string]interface{}
    35  	r            *rbroker
    36  	fn           func(msg amqp.Delivery)
    37  	headers      map[string]interface{}
    38  	wg           sync.WaitGroup
    39  }
    40  
    41  type publication struct {
    42  	d   amqp.Delivery
    43  	m   *broker.Message
    44  	t   string
    45  	err error
    46  }
    47  
    48  func (p *publication) Ack() error {
    49  	return p.d.Ack(false)
    50  }
    51  
    52  func (p *publication) Error() error {
    53  	return p.err
    54  }
    55  
    56  func (p *publication) Topic() string {
    57  	return p.t
    58  }
    59  
    60  func (p *publication) Message() *broker.Message {
    61  	return p.m
    62  }
    63  
    64  func (s *subscriber) Options() broker.SubscribeOptions {
    65  	return s.opts
    66  }
    67  
    68  func (s *subscriber) Topic() string {
    69  	return s.topic
    70  }
    71  
    72  func (s *subscriber) Unsubscribe() error {
    73  	s.unsub <- true
    74  
    75  	// Need to wait on subscriber to exit if autoack is disabled
    76  	// since closing the channel will prevent the ack/nack from
    77  	// being sent upon handler completion.
    78  	if !s.opts.AutoAck {
    79  		s.wg.Wait()
    80  	}
    81  
    82  	s.mtx.Lock()
    83  	defer s.mtx.Unlock()
    84  	if s.ch != nil {
    85  		return s.ch.Close()
    86  	}
    87  	return nil
    88  }
    89  
    90  func (s *subscriber) resubscribe() {
    91  	s.wg.Add(1)
    92  	defer s.wg.Done()
    93  
    94  	minResubscribeDelay := 100 * time.Millisecond
    95  	maxResubscribeDelay := 30 * time.Second
    96  	expFactor := time.Duration(2)
    97  	reSubscribeDelay := minResubscribeDelay
    98  	// loop until unsubscribe
    99  	for {
   100  		select {
   101  		// unsubscribe case
   102  		case <-s.unsub:
   103  			return
   104  		// check shutdown case
   105  		case <-s.r.conn.close:
   106  			// yep, its shutdown case
   107  			return
   108  			// wait until we reconect to rabbit
   109  		case <-s.r.conn.waitConnection:
   110  			// When the connection is disconnected, the waitConnection will be re-assigned, so '<-s.r.conn.waitConnection' maybe blocked.
   111  			// Here, it returns once a second, and then the latest waitconnection will be used
   112  		case <-time.After(time.Second):
   113  			continue
   114  		}
   115  
   116  		// it may crash (panic) in case of Consume without connection, so recheck it
   117  		s.r.mtx.Lock()
   118  		if !s.r.conn.connected {
   119  			s.r.mtx.Unlock()
   120  			continue
   121  		}
   122  
   123  		ch, sub, err := s.r.conn.Consume(
   124  			s.opts.Queue,
   125  			s.topic,
   126  			s.headers,
   127  			s.queueArgs,
   128  			s.opts.AutoAck,
   129  			s.durableQueue,
   130  		)
   131  
   132  		s.r.mtx.Unlock()
   133  		switch err {
   134  		case nil:
   135  			reSubscribeDelay = minResubscribeDelay
   136  			s.mtx.Lock()
   137  			s.ch = ch
   138  			s.mtx.Unlock()
   139  		default:
   140  			if reSubscribeDelay > maxResubscribeDelay {
   141  				reSubscribeDelay = maxResubscribeDelay
   142  			}
   143  			time.Sleep(reSubscribeDelay)
   144  			reSubscribeDelay *= expFactor
   145  			continue
   146  		}
   147  
   148  	SubLoop:
   149  		for {
   150  			select {
   151  			case <-s.unsub:
   152  				return
   153  			case d, ok := <-sub:
   154  				if !ok {
   155  					break SubLoop
   156  				}
   157  				s.r.wg.Add(1)
   158  				s.fn(d)
   159  				s.r.wg.Done()
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  func (r *rbroker) Publish(topic string, msg *broker.Message, opts ...broker.PublishOption) error {
   166  	m := amqp.Publishing{
   167  		Body:    msg.Body,
   168  		Headers: amqp.Table{},
   169  	}
   170  
   171  	options := broker.PublishOptions{}
   172  	for _, o := range opts {
   173  		o(&options)
   174  	}
   175  
   176  	if options.Context != nil {
   177  		if value, ok := options.Context.Value(deliveryMode{}).(uint8); ok {
   178  			m.DeliveryMode = value
   179  		}
   180  
   181  		if value, ok := options.Context.Value(priorityKey{}).(uint8); ok {
   182  			m.Priority = value
   183  		}
   184  
   185  		if value, ok := options.Context.Value(contentType{}).(string); ok {
   186  			m.Headers["Content-Type"] = value
   187  			m.ContentType = value
   188  		}
   189  
   190  		if value, ok := options.Context.Value(contentEncoding{}).(string); ok {
   191  			m.ContentEncoding = value
   192  		}
   193  
   194  		if value, ok := options.Context.Value(correlationID{}).(string); ok {
   195  			m.CorrelationId = value
   196  		}
   197  
   198  		if value, ok := options.Context.Value(replyTo{}).(string); ok {
   199  			m.ReplyTo = value
   200  		}
   201  
   202  		if value, ok := options.Context.Value(expiration{}).(string); ok {
   203  			m.Expiration = value
   204  		}
   205  
   206  		if value, ok := options.Context.Value(messageID{}).(string); ok {
   207  			m.MessageId = value
   208  		}
   209  
   210  		if value, ok := options.Context.Value(timestamp{}).(time.Time); ok {
   211  			m.Timestamp = value
   212  		}
   213  
   214  		if value, ok := options.Context.Value(typeMsg{}).(string); ok {
   215  			m.Type = value
   216  		}
   217  
   218  		if value, ok := options.Context.Value(userID{}).(string); ok {
   219  			m.UserId = value
   220  		}
   221  
   222  		if value, ok := options.Context.Value(appID{}).(string); ok {
   223  			m.AppId = value
   224  		}
   225  	}
   226  
   227  	for k, v := range msg.Header {
   228  		m.Headers[k] = v
   229  	}
   230  
   231  	if r.getWithoutExchange() {
   232  		m.Headers["Micro-Topic"] = topic
   233  	}
   234  
   235  	if r.conn == nil {
   236  		return errors.New("connection is nil")
   237  	}
   238  
   239  	return r.conn.Publish(r.conn.exchange.Name, topic, m)
   240  }
   241  
   242  func (r *rbroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
   243  	var ackSuccess bool
   244  
   245  	if r.conn == nil {
   246  		return nil, errors.New("not connected")
   247  	}
   248  
   249  	opt := broker.SubscribeOptions{
   250  		AutoAck: true,
   251  	}
   252  
   253  	for _, o := range opts {
   254  		o(&opt)
   255  	}
   256  
   257  	// Make sure context is setup
   258  	if opt.Context == nil {
   259  		opt.Context = context.Background()
   260  	}
   261  
   262  	ctx := opt.Context
   263  	if subscribeContext, ok := ctx.Value(subscribeContextKey{}).(context.Context); ok && subscribeContext != nil {
   264  		ctx = subscribeContext
   265  	}
   266  
   267  	var requeueOnError bool
   268  	requeueOnError, _ = ctx.Value(requeueOnErrorKey{}).(bool)
   269  
   270  	var durableQueue bool
   271  	durableQueue, _ = ctx.Value(durableQueueKey{}).(bool)
   272  
   273  	var qArgs map[string]interface{}
   274  	if qa, ok := ctx.Value(queueArgumentsKey{}).(map[string]interface{}); ok {
   275  		qArgs = qa
   276  	}
   277  
   278  	var headers map[string]interface{}
   279  	if h, ok := ctx.Value(headersKey{}).(map[string]interface{}); ok {
   280  		headers = h
   281  	}
   282  
   283  	if bval, ok := ctx.Value(ackSuccessKey{}).(bool); ok && bval {
   284  		opt.AutoAck = false
   285  		ackSuccess = true
   286  	}
   287  
   288  	fn := func(msg amqp.Delivery) {
   289  		header := make(map[string]string)
   290  		for k, v := range msg.Headers {
   291  			header[k] = fmt.Sprintf("%v", v)
   292  		}
   293  
   294  		// Get rid of dependence on 'Micro-Topic'
   295  		msgTopic := header["Micro-Topic"]
   296  		if msgTopic == "" {
   297  			header["Micro-Topic"] = msg.RoutingKey
   298  		}
   299  
   300  		m := &broker.Message{
   301  			Header: header,
   302  			Body:   msg.Body,
   303  		}
   304  		p := &publication{d: msg, m: m, t: msg.RoutingKey}
   305  		p.err = handler(p)
   306  		if p.err == nil && ackSuccess && !opt.AutoAck {
   307  			msg.Ack(false)
   308  		} else if p.err != nil && !opt.AutoAck {
   309  			msg.Nack(false, requeueOnError)
   310  		}
   311  	}
   312  
   313  	sret := &subscriber{topic: topic, opts: opt, unsub: make(chan bool), r: r,
   314  		durableQueue: durableQueue, fn: fn, headers: headers, queueArgs: qArgs,
   315  		wg: sync.WaitGroup{}}
   316  
   317  	go sret.resubscribe()
   318  
   319  	return sret, nil
   320  }
   321  
   322  func (r *rbroker) Options() broker.Options {
   323  	return r.opts
   324  }
   325  
   326  func (r *rbroker) String() string {
   327  	return "rabbitmq"
   328  }
   329  
   330  func (r *rbroker) Address() string {
   331  	if len(r.addrs) > 0 {
   332  		u, err := url.Parse(r.addrs[0])
   333  		if err != nil {
   334  			return ""
   335  		}
   336  
   337  		return u.Redacted()
   338  	}
   339  	return ""
   340  }
   341  
   342  func (r *rbroker) Init(opts ...broker.Option) error {
   343  	for _, o := range opts {
   344  		o(&r.opts)
   345  	}
   346  	r.addrs = r.opts.Addrs
   347  	return nil
   348  }
   349  
   350  func (r *rbroker) Connect() error {
   351  	if r.conn == nil {
   352  		r.conn = newRabbitMQConn(
   353  			r.getExchange(),
   354  			r.opts.Addrs,
   355  			r.getPrefetchCount(),
   356  			r.getPrefetchGlobal(),
   357  			r.getConfirmPublish(),
   358  			r.getWithoutExchange(),
   359  			r.opts.Logger,
   360  		)
   361  	}
   362  
   363  	conf := defaultAmqpConfig
   364  
   365  	if auth, ok := r.opts.Context.Value(externalAuth{}).(ExternalAuthentication); ok {
   366  		conf.SASL = []amqp.Authentication{&auth}
   367  	}
   368  
   369  	conf.TLSClientConfig = r.opts.TLSConfig
   370  
   371  	return r.conn.Connect(r.opts.Secure, &conf)
   372  }
   373  
   374  func (r *rbroker) Disconnect() error {
   375  	if r.conn == nil {
   376  		return errors.New("connection is nil")
   377  	}
   378  	ret := r.conn.Close()
   379  	r.wg.Wait() // wait all goroutines
   380  	return ret
   381  }
   382  
   383  func NewBroker(opts ...broker.Option) broker.Broker {
   384  	options := broker.Options{
   385  		Context: context.Background(),
   386  		Logger:  logger.DefaultLogger,
   387  	}
   388  
   389  	for _, o := range opts {
   390  		o(&options)
   391  	}
   392  
   393  	return &rbroker{
   394  		addrs: options.Addrs,
   395  		opts:  options,
   396  	}
   397  }
   398  
   399  func (r *rbroker) getExchange() Exchange {
   400  	ex := DefaultExchange
   401  
   402  	if e, ok := r.opts.Context.Value(exchangeKey{}).(string); ok {
   403  		ex.Name = e
   404  	}
   405  
   406  	if t, ok := r.opts.Context.Value(exchangeTypeKey{}).(MQExchangeType); ok {
   407  		ex.Type = t
   408  	}
   409  
   410  	if d, ok := r.opts.Context.Value(durableExchange{}).(bool); ok {
   411  		ex.Durable = d
   412  	}
   413  
   414  	return ex
   415  }
   416  
   417  func (r *rbroker) getPrefetchCount() int {
   418  	if e, ok := r.opts.Context.Value(prefetchCountKey{}).(int); ok {
   419  		return e
   420  	}
   421  	return DefaultPrefetchCount
   422  }
   423  
   424  func (r *rbroker) getPrefetchGlobal() bool {
   425  	if e, ok := r.opts.Context.Value(prefetchGlobalKey{}).(bool); ok {
   426  		return e
   427  	}
   428  	return DefaultPrefetchGlobal
   429  }
   430  
   431  func (r *rbroker) getConfirmPublish() bool {
   432  	if e, ok := r.opts.Context.Value(confirmPublishKey{}).(bool); ok {
   433  		return e
   434  	}
   435  	return DefaultConfirmPublish
   436  }
   437  
   438  func (r *rbroker) getWithoutExchange() bool {
   439  	if e, ok := r.opts.Context.Value(withoutExchangeKey{}).(bool); ok {
   440  		return e
   441  	}
   442  	return DefaultWithoutExchange
   443  }