github.com/futurehomeno/fimpgo@v1.14.0/sync_client.go (about)

     1  package fimpgo
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/google/uuid"
     7  	log "github.com/sirupsen/logrus"
     8  )
     9  
    10  // SyncClient allows sync interaction over async channel.
    11  type SyncClient struct {
    12  	mqttTransport         *MqttTransport
    13  	mqttConnPool          *MqttConnectionPool
    14  	isConnPoolEnabled     bool
    15  	transactionPoolSize   int // Max transaction pool size
    16  	inboundBufferSize     int // Inbound message channel buffer size
    17  	inboundMsgChannel     MessageCh
    18  	inboundChannelName    string
    19  	stopSignalCh          chan bool
    20  	isStartedUsingConnect bool
    21  	globalPrefix          string
    22  }
    23  
    24  // SetGlobalPrefix configures global prefix/site_id . Most be used from backend services.
    25  func (sc *SyncClient) SetGlobalPrefix(globalPrefix string) {
    26  	sc.globalPrefix = globalPrefix
    27  }
    28  
    29  func (sc *SyncClient) SetTransactionPoolSize(transactionPoolSize int) {
    30  	sc.transactionPoolSize = transactionPoolSize
    31  }
    32  
    33  // NewSyncClient creates sync client using existing mqtt connection
    34  func NewSyncClient(mqttTransport *MqttTransport) *SyncClient {
    35  	sc := SyncClient{mqttTransport: mqttTransport}
    36  	sc.transactionPoolSize = 20
    37  	sc.inboundBufferSize = 10
    38  	sc.init()
    39  	return &sc
    40  }
    41  
    42  // NewSyncClientV2 creates new sync client using existing mqtt connection and configures transactionPool size and inboundBufferSize
    43  func NewSyncClientV2(mqttTransport *MqttTransport, transactionPoolSize int, inboundBuffSize int) *SyncClient {
    44  	sc := SyncClient{mqttTransport: mqttTransport}
    45  	sc.transactionPoolSize = transactionPoolSize
    46  	sc.inboundBufferSize = inboundBuffSize
    47  	sc.init()
    48  	return &sc
    49  }
    50  
    51  func (sc *SyncClient) SetConfigs(transactionPoolSize int, inboundBuffSize int) {
    52  	if transactionPoolSize == 0 {
    53  		transactionPoolSize = 20
    54  	}
    55  	if inboundBuffSize == 0 {
    56  		inboundBuffSize = 10
    57  	}
    58  }
    59  
    60  func (sc *SyncClient) init() {
    61  	sc.stopSignalCh = make(chan bool)
    62  }
    63  
    64  // Connect establishes internal connection to mqtt broker and initializes mqtt
    65  // Should be used if MqttTransport instance is not provided in constructor .
    66  func (sc *SyncClient) Connect(serverURI string, clientID string, username string, password string, cleanSession bool, subQos byte, pubQos byte) error {
    67  	if sc.mqttTransport == nil {
    68  		log.Info("<SyncClient> Connecting to mqtt broker")
    69  		sc.mqttTransport = NewMqttTransport(serverURI, clientID, username, password, cleanSession, subQos, pubQos)
    70  		err := sc.mqttTransport.Start()
    71  		if err != nil {
    72  			log.Error("<SyncClient> Error connecting to broker :", err)
    73  			return err
    74  		}
    75  		sc.isStartedUsingConnect = true
    76  	} else {
    77  		log.Info("<SyncClient> Already connected")
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  // Stop has to be invoked to stop message listener
    84  func (sc *SyncClient) Stop() {
    85  	if sc.isStartedUsingConnect {
    86  		sc.mqttTransport.Stop()
    87  	}
    88  
    89  }
    90  
    91  // AddSubscription has to be invoked before Send methods
    92  func (sc *SyncClient) AddSubscription(topic string) {
    93  	if err := sc.mqttTransport.Subscribe(topic); err != nil {
    94  		log.Error("<SyncClient> error subscribing to topic:", err)
    95  	}
    96  }
    97  
    98  // RemoveSubscription
    99  func (sc *SyncClient) RemoveSubscription(topic string) {
   100  	if err := sc.mqttTransport.Unsubscribe(topic); err != nil {
   101  		log.Error("<SyncClient> error unsubscribing from topic:", err)
   102  	}
   103  }
   104  
   105  // SendFimpWithTopicResponse send message over mqtt and awaits response from responseTopic with responseService and responseMsgType
   106  func (sc *SyncClient) sendFimpWithTopicResponse(topic string, fimpMsg *FimpMessage, responseTopic string, responseService string, responseMsgType string, timeout int64, autoSubscribe bool) (*FimpMessage, error) {
   107  	//log.Debug("Registering request uid = ",fimpMsg.UID)
   108  	var conId int
   109  	var conn *MqttTransport
   110  	var inboundCh = make(MessageCh, 10)
   111  	var responseChannel chan *FimpMessage
   112  	var err error
   113  	var chanName = uuid.New().String()
   114  
   115  	defer func() {
   116  		if autoSubscribe && responseTopic != "" && conn != nil {
   117  			if err := conn.Unsubscribe(responseTopic); err != nil {
   118  				log.Error("<SyncClient> error unsubscribing from topic:", err)
   119  			}
   120  		}
   121  		if conn != nil {
   122  			conn.UnregisterChannel(chanName)
   123  			close(inboundCh)
   124  			if sc.isConnPoolEnabled {
   125  				// force unset global prefix
   126  				conn.SetGlobalTopicPrefix("")
   127  				sc.mqttConnPool.ReturnConnection(conId)
   128  			}
   129  		}
   130  	}()
   131  
   132  	if sc.isConnPoolEnabled {
   133  		conId, conn, err = sc.mqttConnPool.BorrowConnection()
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  	} else {
   138  		conn = sc.mqttTransport
   139  	}
   140  	conn.RegisterChannel(chanName, inboundCh)
   141  
   142  	responseChannel = sc.startResponseListener(fimpMsg, responseMsgType, responseService, responseTopic, inboundCh, timeout)
   143  
   144  	// force the global prefix -> this is useful for per-site operations
   145  	if sc.globalPrefix != "" {
   146  		conn.SetGlobalTopicPrefix(sc.globalPrefix)
   147  	}
   148  
   149  	if autoSubscribe && responseTopic != "" {
   150  		if err := conn.Subscribe(responseTopic); err != nil {
   151  			log.Error("<SyncClient> error subscribing to topic:", err)
   152  		}
   153  	} else if responseTopic != "" {
   154  		if err := conn.Subscribe(responseTopic); err != nil {
   155  			log.Error("<SyncClient> error subscribing to topic:", err)
   156  			return nil, errSubscribe
   157  		}
   158  	}
   159  
   160  	if err := conn.PublishToTopic(topic, fimpMsg); err != nil {
   161  		log.Error("<SyncClient> error publishing to topic:", err)
   162  		return nil, errPublish
   163  	}
   164  
   165  	select {
   166  	case fimpResponse := <-responseChannel:
   167  		return fimpResponse, nil
   168  	case <-time.After(time.Second * time.Duration(timeout)):
   169  		log.Info("<SyncClient> No response from queue for ", timeout)
   170  		return nil, errTimeout
   171  	}
   172  }
   173  
   174  // SendReqRespFimp sends msg to topic and expects to receive response on response topic . If autoSubscribe is set to true , the system will automatically subscribe and unsubscribe from response topic.
   175  func (sc *SyncClient) SendReqRespFimp(cmdTopic, responseTopic string, reqMsg *FimpMessage, timeout int64, autoSubscribe bool) (*FimpMessage, error) {
   176  	return sc.sendFimpWithTopicResponse(cmdTopic, reqMsg, responseTopic, "", "", timeout, autoSubscribe)
   177  }
   178  
   179  // SendFimp sends message over mqtt and blocks until request is received or timeout is reached .
   180  // messages are correlated using uid->corid
   181  func (sc *SyncClient) SendFimp(topic string, fimpMsg *FimpMessage, timeout int64) (*FimpMessage, error) {
   182  	return sc.SendFimpWithTopicResponse(topic, fimpMsg, "", "", "", timeout)
   183  }
   184  
   185  // SendFimpWithTopicResponse send message over mqtt and awaits response from responseTopic with responseService and responseMsgType (the method is for backward compatibility)
   186  func (sc *SyncClient) SendFimpWithTopicResponse(topic string, fimpMsg *FimpMessage, responseTopic string, responseService string, responseMsgType string, timeout int64) (*FimpMessage, error) {
   187  	return sc.sendFimpWithTopicResponse(topic, fimpMsg, responseTopic, responseService, responseMsgType, timeout, false)
   188  }
   189  
   190  // startResponseListener starts response listener , it blocks callers proc until response is received or timeout.
   191  func (sc *SyncClient) startResponseListener(requestMsg *FimpMessage, respMsgType, respService, respTopic string, inboundCh MessageCh, timeout int64) chan *FimpMessage {
   192  	log.Debug("<SyncClient> Msg listener is started")
   193  	respChan := make(chan *FimpMessage)
   194  	go func() {
   195  		for msg := range inboundCh {
   196  			if (respMsgType == msg.Payload.Type && respService == msg.Payload.Service && respTopic == msg.Topic) || requestMsg.UID == msg.Payload.CorrelationID {
   197  				select {
   198  				case respChan <- msg.Payload:
   199  				case <-time.After(time.Second * time.Duration(timeout)):
   200  				}
   201  				return
   202  			}
   203  		}
   204  	}()
   205  	return respChan
   206  }