mirror of
https://github.com/go-micro/go-micro.git
synced 2025-01-29 18:04:17 +02:00
281 lines
6.3 KiB
Go
281 lines
6.3 KiB
Go
package rabbitmq
|
|
|
|
//
|
|
// All credit to Mondo
|
|
//
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"go-micro.dev/v4/logger"
|
|
"github.com/streadway/amqp"
|
|
)
|
|
|
|
var (
|
|
DefaultExchange = Exchange{
|
|
Name: "micro",
|
|
}
|
|
DefaultRabbitURL = "amqp://guest:guest@127.0.0.1:5672"
|
|
DefaultPrefetchCount = 0
|
|
DefaultPrefetchGlobal = false
|
|
DefaultRequeueOnError = false
|
|
|
|
// The amqp library does not seem to set these when using amqp.DialConfig
|
|
// (even though it says so in the comments) so we set them manually to make
|
|
// sure to not brake any existing functionality
|
|
defaultHeartbeat = 10 * time.Second
|
|
defaultLocale = "en_US"
|
|
|
|
defaultAmqpConfig = amqp.Config{
|
|
Heartbeat: defaultHeartbeat,
|
|
Locale: defaultLocale,
|
|
}
|
|
|
|
dial = amqp.Dial
|
|
dialTLS = amqp.DialTLS
|
|
dialConfig = amqp.DialConfig
|
|
)
|
|
|
|
type rabbitMQConn struct {
|
|
Connection *amqp.Connection
|
|
Channel *rabbitMQChannel
|
|
ExchangeChannel *rabbitMQChannel
|
|
exchange Exchange
|
|
url string
|
|
prefetchCount int
|
|
prefetchGlobal bool
|
|
|
|
sync.Mutex
|
|
connected bool
|
|
close chan bool
|
|
|
|
waitConnection chan struct{}
|
|
}
|
|
|
|
// Exchange is the rabbitmq exchange
|
|
type Exchange struct {
|
|
// Name of the exchange
|
|
Name string
|
|
// Whether its persistent
|
|
Durable bool
|
|
}
|
|
|
|
func newRabbitMQConn(ex Exchange, urls []string, prefetchCount int, prefetchGlobal bool) *rabbitMQConn {
|
|
var url string
|
|
|
|
if len(urls) > 0 && regexp.MustCompile("^amqp(s)?://.*").MatchString(urls[0]) {
|
|
url = urls[0]
|
|
} else {
|
|
url = DefaultRabbitURL
|
|
}
|
|
|
|
ret := &rabbitMQConn{
|
|
exchange: ex,
|
|
url: url,
|
|
prefetchCount: prefetchCount,
|
|
prefetchGlobal: prefetchGlobal,
|
|
close: make(chan bool),
|
|
waitConnection: make(chan struct{}),
|
|
}
|
|
// its bad case of nil == waitConnection, so close it at start
|
|
close(ret.waitConnection)
|
|
return ret
|
|
}
|
|
|
|
func (r *rabbitMQConn) connect(secure bool, config *amqp.Config) error {
|
|
// try connect
|
|
if err := r.tryConnect(secure, config); err != nil {
|
|
return err
|
|
}
|
|
|
|
// connected
|
|
r.Lock()
|
|
r.connected = true
|
|
r.Unlock()
|
|
|
|
// create reconnect loop
|
|
go r.reconnect(secure, config)
|
|
return nil
|
|
}
|
|
|
|
func (r *rabbitMQConn) reconnect(secure bool, config *amqp.Config) {
|
|
// skip first connect
|
|
var connect bool
|
|
|
|
for {
|
|
if connect {
|
|
// try reconnect
|
|
if err := r.tryConnect(secure, config); err != nil {
|
|
time.Sleep(1 * time.Second)
|
|
continue
|
|
}
|
|
|
|
// connected
|
|
r.Lock()
|
|
r.connected = true
|
|
r.Unlock()
|
|
//unblock resubscribe cycle - close channel
|
|
//at this point channel is created and unclosed - close it without any additional checks
|
|
close(r.waitConnection)
|
|
}
|
|
|
|
connect = true
|
|
notifyClose := make(chan *amqp.Error)
|
|
r.Connection.NotifyClose(notifyClose)
|
|
chanNotifyClose := make(chan *amqp.Error)
|
|
channel := r.ExchangeChannel.channel
|
|
channel.NotifyClose(chanNotifyClose)
|
|
channelNotifyReturn := make(chan amqp.Return)
|
|
channel.NotifyReturn(channelNotifyReturn)
|
|
|
|
// block until closed
|
|
select {
|
|
case result, ok := <-channelNotifyReturn:
|
|
if !ok {
|
|
// Channel closed, probably also the channel or connection.
|
|
return
|
|
}
|
|
// Do what you need with messageFailing.
|
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
|
logger.Errorf("notify error reason: %s, description: %s", result.ReplyText, result.Exchange)
|
|
}
|
|
case err := <-chanNotifyClose:
|
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
|
logger.Error(err)
|
|
}
|
|
// block all resubscribe attempt - they are useless because there is no connection to rabbitmq
|
|
// create channel 'waitConnection' (at this point channel is nil or closed, create it without unnecessary checks)
|
|
r.Lock()
|
|
r.connected = false
|
|
r.waitConnection = make(chan struct{})
|
|
r.Unlock()
|
|
case err := <-notifyClose:
|
|
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
|
logger.Error(err)
|
|
}
|
|
// block all resubscribe attempt - they are useless because there is no connection to rabbitmq
|
|
// create channel 'waitConnection' (at this point channel is nil or closed, create it without unnecessary checks)
|
|
r.Lock()
|
|
r.connected = false
|
|
r.waitConnection = make(chan struct{})
|
|
r.Unlock()
|
|
case <-r.close:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *rabbitMQConn) Connect(secure bool, config *amqp.Config) error {
|
|
r.Lock()
|
|
|
|
// already connected
|
|
if r.connected {
|
|
r.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// check it was closed
|
|
select {
|
|
case <-r.close:
|
|
r.close = make(chan bool)
|
|
default:
|
|
// no op
|
|
// new conn
|
|
}
|
|
|
|
r.Unlock()
|
|
|
|
return r.connect(secure, config)
|
|
}
|
|
|
|
func (r *rabbitMQConn) Close() error {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
select {
|
|
case <-r.close:
|
|
return nil
|
|
default:
|
|
close(r.close)
|
|
r.connected = false
|
|
}
|
|
|
|
return r.Connection.Close()
|
|
}
|
|
|
|
func (r *rabbitMQConn) tryConnect(secure bool, config *amqp.Config) error {
|
|
var err error
|
|
|
|
if config == nil {
|
|
config = &defaultAmqpConfig
|
|
}
|
|
|
|
url := r.url
|
|
|
|
if secure || config.TLSClientConfig != nil || strings.HasPrefix(r.url, "amqps://") {
|
|
if config.TLSClientConfig == nil {
|
|
config.TLSClientConfig = &tls.Config{
|
|
InsecureSkipVerify: true,
|
|
}
|
|
}
|
|
|
|
url = strings.Replace(r.url, "amqp://", "amqps://", 1)
|
|
}
|
|
|
|
r.Connection, err = dialConfig(url, *config)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if r.Channel, err = newRabbitChannel(r.Connection, r.prefetchCount, r.prefetchGlobal); err != nil {
|
|
return err
|
|
}
|
|
|
|
if r.exchange.Durable {
|
|
r.Channel.DeclareDurableExchange(r.exchange.Name)
|
|
} else {
|
|
r.Channel.DeclareExchange(r.exchange.Name)
|
|
}
|
|
r.ExchangeChannel, err = newRabbitChannel(r.Connection, r.prefetchCount, r.prefetchGlobal)
|
|
|
|
return err
|
|
}
|
|
|
|
func (r *rabbitMQConn) Consume(queue, key string, headers amqp.Table, qArgs amqp.Table, autoAck, durableQueue bool) (*rabbitMQChannel, <-chan amqp.Delivery, error) {
|
|
consumerChannel, err := newRabbitChannel(r.Connection, r.prefetchCount, r.prefetchGlobal)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if durableQueue {
|
|
err = consumerChannel.DeclareDurableQueue(queue, qArgs)
|
|
} else {
|
|
err = consumerChannel.DeclareQueue(queue, qArgs)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
deliveries, err := consumerChannel.ConsumeQueue(queue, autoAck)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
err = consumerChannel.BindQueue(queue, key, r.exchange.Name, headers)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return consumerChannel, deliveries, nil
|
|
}
|
|
|
|
func (r *rabbitMQConn) Publish(exchange, key string, msg amqp.Publishing) error {
|
|
return r.ExchangeChannel.Publish(exchange, key, msg)
|
|
}
|