github.com/vmware/transport-go@v1.3.4/bridge/bridge_client.go (about)

     1  // Copyright 2019-2020 VMware, Inc.
     2  // SPDX-License-Identifier: BSD-2-Clause
     3  
     4  package bridge
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"github.com/go-stomp/stomp/v3"
    12  	"github.com/go-stomp/stomp/v3/frame"
    13  	"github.com/google/uuid"
    14  	"github.com/gorilla/websocket"
    15  	"github.com/vmware/transport-go/model"
    16  	"log"
    17  	"net/url"
    18  	"os"
    19  	"strconv"
    20  	"sync"
    21  )
    22  
    23  // BridgeClient encapsulates all subscriptions and io to and from brokers.
    24  type BridgeClient struct {
    25  	WSc              *websocket.Conn // WebSocket connection
    26  	TCPc             *stomp.Conn     // STOMP TCP Connection
    27  	ConnectedChan    chan bool
    28  	disconnectedChan chan bool
    29  	connected        bool
    30  	inboundChan      chan *frame.Frame
    31  	stompConnected   bool
    32  	Subscriptions    map[string]*BridgeClientSub
    33  	logger           *log.Logger
    34  	lock             sync.Mutex
    35  	sendLock         sync.Mutex
    36  }
    37  
    38  // NewBridgeWsClient Create a new WebSocket client.
    39  func NewBridgeWsClient(enableLogging bool) *BridgeClient {
    40  	return newBridgeWsClient(enableLogging)
    41  }
    42  
    43  func newBridgeWsClient(enableLogging bool) *BridgeClient {
    44  	var l *log.Logger = nil
    45  	if enableLogging {
    46  		l = log.New(os.Stderr, "WebSocket Client: ", 2)
    47  	}
    48  	return &BridgeClient{
    49  		WSc:              nil,
    50  		TCPc:             nil,
    51  		stompConnected:   false,
    52  		connected:        false,
    53  		logger:           l,
    54  		lock:             sync.Mutex{},
    55  		sendLock:         sync.Mutex{},
    56  		Subscriptions:    make(map[string]*BridgeClientSub),
    57  		ConnectedChan:    make(chan bool),
    58  		disconnectedChan: make(chan bool),
    59  		inboundChan:      make(chan *frame.Frame)}
    60  }
    61  
    62  // Connect to broker endpoint.
    63  func (ws *BridgeClient) Connect(url *url.URL, config *BrokerConnectorConfig) error {
    64  	ws.lock.Lock()
    65  	defer ws.lock.Unlock()
    66  	if ws.logger != nil {
    67  		ws.logger.Printf("connecting to fabric endpoint over %s", url.String())
    68  	}
    69  
    70  	dialer := websocket.DefaultDialer
    71  	if config.WebSocketConfig.UseTLS {
    72  
    73  		// if the cert and key are not set, we're acting as a client, not a server so we have to
    74  		// allow these values to be empty when connecting vs serving over TLS.
    75  		if config.WebSocketConfig.CertFile != "" || config.WebSocketConfig.KeyFile != "" {
    76  			if err := config.WebSocketConfig.LoadX509KeyPairFromFiles(
    77  				config.WebSocketConfig.CertFile,
    78  				config.WebSocketConfig.KeyFile); err != nil {
    79  				return err
    80  			}
    81  		}
    82  		dialer.TLSClientConfig = config.WebSocketConfig.TLSConfig
    83  	}
    84  
    85  	c, _, err := dialer.Dial(url.String(), config.HttpHeader)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	ws.WSc = c
    90  
    91  	// handle incoming STOMP frames.
    92  	go ws.handleIncomingSTOMPFrames()
    93  
    94  	// go listen to the websocket
    95  	go ws.listenSocket()
    96  
    97  	stompHeaders := []string{
    98  		frame.AcceptVersion,
    99  		string(stomp.V12),
   100  		frame.Login,
   101  		config.Username,
   102  		frame.Passcode,
   103  		config.Password,
   104  		frame.HeartBeat,
   105  		fmt.Sprintf("%d,%d", config.HeartBeatOut.Milliseconds(), config.HeartBeatIn.Milliseconds())}
   106  	for key, value := range config.STOMPHeader {
   107  		stompHeaders = append(stompHeaders, key, value)
   108  	}
   109  
   110  	// send connect frame.
   111  	ws.SendFrame(frame.New(frame.CONNECT, stompHeaders...))
   112  
   113  	// wait to be connected
   114  	<-ws.ConnectedChan
   115  	return nil
   116  }
   117  
   118  // Disconnect from broker endpoint
   119  func (ws *BridgeClient) Disconnect() error {
   120  	if ws.WSc != nil {
   121  		defer ws.WSc.Close()
   122  		ws.disconnectedChan <- true
   123  	} else {
   124  		return fmt.Errorf("cannot disconnect, no connection defined")
   125  	}
   126  	return nil
   127  }
   128  
   129  // Subscribe to destination
   130  func (ws *BridgeClient) Subscribe(destination string) *BridgeClientSub {
   131  	ws.lock.Lock()
   132  	defer ws.lock.Unlock()
   133  	id := uuid.New()
   134  	s := &BridgeClientSub{
   135  		C:           make(chan *model.Message),
   136  		Id:          &id,
   137  		Client:      ws,
   138  		Destination: destination,
   139  		subscribed:  true}
   140  
   141  	ws.Subscriptions[destination] = s
   142  
   143  	// create subscription frame.
   144  	subscribeFrame := frame.New(frame.SUBSCRIBE,
   145  		frame.Id, id.String(),
   146  		frame.Destination, destination,
   147  		frame.Ack, stomp.AckAuto.String())
   148  
   149  	// send subscription frame.
   150  	ws.SendFrame(subscribeFrame)
   151  	return s
   152  }
   153  
   154  // Send a payload to a destination
   155  func (ws *BridgeClient) Send(destination, contentType string, payload []byte, opts ...func(fr *frame.Frame) error) {
   156  	ws.lock.Lock()
   157  	defer ws.lock.Unlock()
   158  
   159  	// create send frame.
   160  	sendFrame := frame.New(frame.SEND,
   161  		frame.Destination, destination,
   162  		frame.ContentLength, strconv.Itoa(len(payload)),
   163  		frame.ContentType, contentType)
   164  
   165  	// apply extra frame options such as adding extra headers
   166  	for _, frameOpt := range opts {
   167  		_ = frameOpt(sendFrame)
   168  	}
   169  	// add payload
   170  	sendFrame.Body = payload
   171  
   172  	// send frame
   173  	go ws.SendFrame(sendFrame)
   174  
   175  }
   176  
   177  // SendFrame fire a STOMP frame down the WebSocket
   178  func (ws *BridgeClient) SendFrame(f *frame.Frame) {
   179  	ws.sendLock.Lock()
   180  	defer ws.sendLock.Unlock()
   181  	var b bytes.Buffer
   182  	br := bufio.NewWriter(&b)
   183  	sw := frame.NewWriter(br)
   184  
   185  	// write frame to buffer
   186  	sw.Write(f)
   187  	w, _ := ws.WSc.NextWriter(websocket.TextMessage)
   188  	defer w.Close()
   189  
   190  	w.Write(b.Bytes())
   191  
   192  }
   193  
   194  func (ws *BridgeClient) listenSocket() {
   195  	for {
   196  		// read each incoming message from websocket
   197  		_, p, err := ws.WSc.ReadMessage()
   198  		b := bytes.NewReader(p)
   199  		sr := frame.NewReader(b)
   200  		f, _ := sr.Read()
   201  
   202  		if err != nil {
   203  			break // socket can't be read anymore, exit.
   204  		}
   205  		if f != nil {
   206  			ws.inboundChan <- f
   207  		}
   208  	}
   209  }
   210  
   211  func (ws *BridgeClient) handleIncomingSTOMPFrames() {
   212  	for {
   213  		select {
   214  		case <-ws.disconnectedChan:
   215  			return
   216  		case f := <-ws.inboundChan:
   217  			switch f.Command {
   218  			case frame.CONNECTED:
   219  				if ws.logger != nil {
   220  					ws.logger.Printf("STOMP Client connected")
   221  				}
   222  				ws.stompConnected = true
   223  				ws.connected = true
   224  				ws.ConnectedChan <- true
   225  
   226  			case frame.MESSAGE:
   227  				for _, sub := range ws.Subscriptions {
   228  					if sub.Destination == f.Header.Get(frame.Destination) {
   229  						c := &model.MessageConfig{Payload: f.Body, Destination: sub.Destination}
   230  						sub.lock.RLock()
   231  						if sub.subscribed {
   232  							ws.sendResponseSafe(sub.C, model.GenerateResponse(c))
   233  						}
   234  						sub.lock.RUnlock()
   235  					}
   236  				}
   237  
   238  			case frame.ERROR:
   239  				if ws.logger != nil {
   240  					ws.logger.Printf("STOMP ErrorDir received")
   241  				}
   242  
   243  				for _, sub := range ws.Subscriptions {
   244  					if sub.Destination == f.Header.Get(frame.Destination) {
   245  						c := &model.MessageConfig{Payload: f.Body, Err: errors.New("STOMP ErrorDir " + string(f.Body))}
   246  						sub.E <- model.GenerateError(c)
   247  					}
   248  				}
   249  			}
   250  		}
   251  	}
   252  }
   253  
   254  func (ws *BridgeClient) sendResponseSafe(C chan *model.Message, m *model.Message) {
   255  	defer func() {
   256  		if r := recover(); r != nil {
   257  			if ws.logger != nil {
   258  				ws.logger.Println("channel is closed, message undeliverable to closed channel.")
   259  			}
   260  		}
   261  	}()
   262  	C <- m
   263  }