1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-01-29 18:04:17 +02:00
2021-01-20 21:01:10 +00:00

246 lines
5.6 KiB
Go

package stomp
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/url"
"time"
"github.com/go-stomp/stomp"
"github.com/go-stomp/stomp/frame"
"github.com/asim/go-micro/v3/broker"
"github.com/asim/go-micro/v3/cmd"
)
type rbroker struct {
opts broker.Options
stompConn *stomp.Conn
}
// init registers the STOMP broker
func init() {
cmd.DefaultBrokers["stomp"] = NewBroker
}
// stompHeaderToMap converts STOMP header to broker friendly header
func stompHeaderToMap(h *frame.Header) map[string]string {
m := map[string]string{}
for i := 0; i < h.Len(); i++ {
k, v := h.GetAt(i)
m[k] = v
}
return m
}
// defaults sets 'sane' STOMP default
func (r *rbroker) defaults() {
ConnectTimeout(30 * time.Second)(&r.opts)
VirtualHost("/")(&r.opts)
}
func (r *rbroker) Options() broker.Options {
if r.opts.Context == nil {
r.opts.Context = context.Background()
}
return r.opts
}
func (r *rbroker) Address() string {
if len(r.opts.Addrs) > 0 {
return r.opts.Addrs[0]
}
return ""
}
func (r *rbroker) Connect() error {
connectTimeOut := r.Options().Context.Value(connectTimeoutKey{}).(time.Duration)
// Decode address
url, err := url.Parse(r.Address())
if err != nil {
return err
}
// Make sure we are stomp
if url.Scheme != "stomp" {
return fmt.Errorf("Expected stomp:// protocol but was %s", url.Scheme)
}
// Retrieve user/pass if present
stompOpts := []func(*stomp.Conn) error{}
if url.User != nil && url.User.Username() != "" {
password, _ := url.User.Password()
stompOpts = append(stompOpts, stomp.ConnOpt.Login(url.User.Username(), password))
}
// Dial
netConn, err := net.DialTimeout("tcp", url.Host, connectTimeOut)
if err != nil {
return fmt.Errorf("Failed to dial %s: %v", url.Host, err)
}
// Set connect options
if auth, ok := r.Options().Context.Value(authKey{}).(*authRecord); ok && auth != nil {
stompOpts = append(stompOpts, stomp.ConnOpt.Login(auth.username, auth.password))
}
if headers, ok := r.Options().Context.Value(connectHeaderKey{}).(map[string]string); ok && headers != nil {
for k, v := range headers {
stompOpts = append(stompOpts, stomp.ConnOpt.Header(k, v))
}
}
if host, ok := r.Options().Context.Value(vHostKey{}).(string); ok && host != "" {
log.Printf("Adding host: %s", host)
stompOpts = append(stompOpts, stomp.ConnOpt.Host(host))
}
// STOMP Connect
r.stompConn, err = stomp.Connect(netConn, stompOpts...)
if err != nil {
netConn.Close()
return fmt.Errorf("Failed to connect to %s: %v", url.Host, err)
}
return nil
}
func (r *rbroker) Disconnect() error {
return r.stompConn.Disconnect()
}
func (r *rbroker) Init(opts ...broker.Option) error {
r.defaults()
for _, o := range opts {
o(&r.opts)
}
return nil
}
func (r *rbroker) Publish(topic string, msg *broker.Message, opts ...broker.PublishOption) error {
if r.stompConn == nil {
return errors.New("not connected")
}
// Set options
stompOpt := make([]func(*frame.Frame) error, 0, len(msg.Header))
for k, v := range msg.Header {
stompOpt = append(stompOpt, stomp.SendOpt.Header(k, v))
}
bOpt := broker.PublishOptions{}
for _, o := range opts {
o(&bOpt)
}
if withReceipt, ok := r.Options().Context.Value(receiptKey{}).(bool); ok && withReceipt {
stompOpt = append(stompOpt, stomp.SendOpt.Receipt)
}
if withoutContentLength, ok := r.Options().Context.Value(suppressContentLengthKey{}).(bool); ok && withoutContentLength {
stompOpt = append(stompOpt, stomp.SendOpt.NoContentLength)
}
// Send
if err := r.stompConn.Send(
topic,
"",
msg.Body,
stompOpt...); err != nil {
return err
}
return nil
}
func (r *rbroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
var ackSuccess bool
if r.stompConn == nil {
return nil, errors.New("not connected")
}
// Set options
stompOpt := make([]func(*frame.Frame) error, 0, len(opts))
bOpt := broker.SubscribeOptions{
AutoAck: true,
}
for _, o := range opts {
o(&bOpt)
}
// Make sure context is setup
if bOpt.Context == nil {
bOpt.Context = context.Background()
}
ctx := bOpt.Context
if subscribeContext, ok := ctx.Value(subscribeContextKey{}).(context.Context); ok && subscribeContext != nil {
ctx = subscribeContext
}
if durableQueue, ok := ctx.Value(durableQueueKey{}).(bool); ok && durableQueue {
stompOpt = append(stompOpt, stomp.SubscribeOpt.Header("persistent", "true"))
}
if headers, ok := ctx.Value(subscribeHeaderKey{}).(map[string]string); ok && len(headers) > 0 {
for k, v := range headers {
stompOpt = append(stompOpt, stomp.SubscribeOpt.Header(k, v))
}
}
if bval, ok := ctx.Value(ackSuccessKey{}).(bool); ok && bval {
bOpt.AutoAck = false
ackSuccess = true
}
var ackMode stomp.AckMode
if bOpt.AutoAck {
ackMode = stomp.AckAuto
} else {
ackMode = stomp.AckClientIndividual
}
// Subscribe now
sub, err := r.stompConn.Subscribe(topic, ackMode, stompOpt...)
if err != nil {
return nil, err
}
// Process messages
go func() {
for msg := range sub.C {
go func(msg *stomp.Message) {
// Transform message
m := &broker.Message{
Header: stompHeaderToMap(msg.Header),
Body: msg.Body,
}
p := &publication{msg: msg, m: m, topic: topic, broker: r}
// Handle the publication
p.err = handler(p)
if p.err == nil && !bOpt.AutoAck && ackSuccess {
msg.Conn.Ack(msg)
}
}(msg)
}
}()
// Return subs
return &subscriber{sub: sub, topic: topic, opts: bOpt}, nil
}
func (r *rbroker) String() string {
return "stomp"
}
// NewBroker returns a STOMP broker
func NewBroker(opts ...broker.Option) broker.Broker {
r := &rbroker{
opts: broker.Options{
Context: context.Background(),
},
}
r.Init(opts...)
return r
}