github.com/optim-corp/cios-golang-sdk@v0.5.1/sdk/service/pubsub/messaging.go (about)

     1  package srvpubsub
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"log"
     8  	"net/http"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/optim-corp/cios-golang-sdk/sdk/enum"
    13  
    14  	ciosutil "github.com/optim-corp/cios-golang-sdk/util/cios"
    15  
    16  	"github.com/fcfcqloow/go-advance/check"
    17  	cnv "github.com/fcfcqloow/go-advance/convert"
    18  	"github.com/optim-corp/cios-golang-sdk/cios"
    19  	ciosctx "github.com/optim-corp/cios-golang-sdk/ctx"
    20  	sdkmodel "github.com/optim-corp/cios-golang-sdk/model"
    21  
    22  	_nethttp "net/http"
    23  	"net/url"
    24  	"strings"
    25  
    26  	"github.com/gorilla/websocket"
    27  )
    28  
    29  type (
    30  	CiosMessaging struct {
    31  		SubscribeFunc func([]byte) (bool, error)
    32  		CloseFunc     func()
    33  		Connection    *websocket.Conn
    34  		isUpdating    bool
    35  		wsUrl         string
    36  		isDebug       bool
    37  		token         string
    38  		closed        chan bool
    39  		readDeadTime  time.Duration
    40  		writeDeadTime time.Duration
    41  		refresh       *func() sdkmodel.AccessToken
    42  	}
    43  
    44  	// Deprecated: should not be used
    45  	ConnectWebSocketOptions struct {
    46  		PackerFormat  *string
    47  		SubscribeFunc *func(body []byte) (bool, error)
    48  		PublishStr    *chan *string
    49  		Setting       *func(*websocket.Conn)
    50  		Context       ciosctx.RequestCtx
    51  	}
    52  )
    53  
    54  func CreateCiosWsConn(isDebug bool, url, authorization string) (connection *websocket.Conn, err error) {
    55  	if isDebug {
    56  		log.Printf("Websocket URL: %s\nAuthorization: %s", url, authorization)
    57  	}
    58  	connection, _, err = (&websocket.Dialer{}).Dial(url, http.Header{"Authorization": []string{authorization}})
    59  	return
    60  }
    61  func CreateCiosWsMessagingURL(httpUrl, channelID, mode string, packerFormat *string) string {
    62  	_url, err := url.Parse(strings.Replace(httpUrl, "https", "wss", 1) + "/v2/messaging")
    63  	if err != nil {
    64  		return ""
    65  	}
    66  	q := _url.Query()
    67  	q.Set("channel_id", channelID)
    68  	q.Set("mode", mode)
    69  	if packerFormat != nil {
    70  		q.Set("packer_format", *packerFormat)
    71  	}
    72  	_url.RawQuery = q.Encode()
    73  	return _url.String()
    74  }
    75  func (self *CiosPubSub) NewMessaging(channelId string, mode enum.MessagingMode, packerFormat enum.PackerFormat) *CiosMessaging {
    76  	ref := func() (token string) {
    77  		self.refresh()
    78  		if !check.IsNil(self.token) {
    79  			token = *self.token
    80  		}
    81  		return
    82  	}
    83  	if packerFormat == "" {
    84  		packerFormat = "payload_only"
    85  	}
    86  	instance := CiosMessaging{}
    87  	instance.wsUrl = CreateCiosWsMessagingURL(self.Url, channelId, string(mode), cnv.StrPtr(string(packerFormat)))
    88  	instance.isDebug = self.debug
    89  	instance.refresh = &ref
    90  	instance.CloseFunc = func() {}
    91  	return &instance
    92  }
    93  
    94  func (self *CiosMessaging) SetWriteTimeout(t time.Duration) *CiosMessaging {
    95  	self.writeDeadTime = t
    96  	return self
    97  }
    98  func (self *CiosMessaging) SetReadTimeout(t time.Duration) *CiosMessaging {
    99  	self.readDeadTime = t
   100  	return self
   101  }
   102  func (self *CiosMessaging) debug(text ...interface{}) {
   103  	if self.isDebug {
   104  		result := ""
   105  		for _, t := range text {
   106  			result += cnv.MustStr(t) + " "
   107  		}
   108  		log.Println(result)
   109  	}
   110  }
   111  func (self *CiosMessaging) OnReceive(arg func([]byte) (bool, error)) error {
   112  	for {
   113  		body, err := self.Receive()
   114  		if err != nil {
   115  			return err
   116  		}
   117  		if ok, err := arg(body); !ok || err != nil {
   118  			return err
   119  		}
   120  	}
   121  }
   122  func (self *CiosMessaging) OnClose(arg func()) {
   123  	self.CloseFunc = arg
   124  }
   125  func (self *CiosMessaging) Send(message []byte) (err error) {
   126  	if check.IsNil(self.Connection) {
   127  		return fmt.Errorf("no connection used Start()")
   128  	}
   129  	defer self.debug("Send: " + string(message))
   130  	if self.writeDeadTime != 0 {
   131  		if err = self.Connection.SetWriteDeadline(time.Now().Add(self.writeDeadTime)); err != nil {
   132  			self.debug("Set Write Timeout", err)
   133  			return
   134  		}
   135  	}
   136  	if err = self.Connection.WriteMessage(websocket.TextMessage, message); err != nil && !check.IsNil(self.refresh) {
   137  		self.token = (*self.refresh)()
   138  		self.debug("Send Refresh")
   139  		if _connection, err := CreateCiosWsConn(self.isDebug, self.wsUrl, ciosutil.ParseAccessToken(self.token)); err == nil {
   140  			self.debug("Close err: ", self.Connection.Close())
   141  			self.Connection = _connection
   142  			return self.Send(message)
   143  		}
   144  	}
   145  	return
   146  }
   147  func (self *CiosMessaging) SendStr(message string) error {
   148  	return self.Send([]byte(message))
   149  }
   150  func (self *CiosMessaging) SendAny(message interface{}) error {
   151  	return self.SendStr(cnv.MustStr(message))
   152  }
   153  func (self *CiosMessaging) SendJson(message interface{}) error {
   154  	msg, err := json.Marshal(message)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	return self.Send(msg)
   159  }
   160  func (self *CiosMessaging) Publish(message interface{}) error {
   161  	return self.SendAny(message)
   162  }
   163  
   164  func (self *CiosMessaging) receive() (body []byte, err error) {
   165  	var messageType int
   166  	if check.IsNil(self.Connection) {
   167  		err = fmt.Errorf("no connection use Start()")
   168  		return
   169  	}
   170  	if self.readDeadTime != 0 {
   171  		if err = self.Connection.SetReadDeadline(time.Now().Add(self.readDeadTime)); err != nil {
   172  			self.debug("Set Read Timeout", err)
   173  			return
   174  		}
   175  	}
   176  	messageType, body, err = self.Connection.ReadMessage()
   177  	switch {
   178  	case websocket.IsCloseError(err, websocket.CloseNormalClosure):
   179  		err = nil
   180  		self.debug("Receive close err: ", fmt.Sprintf("%d, %s", messageType, cnv.MustStr(err)))
   181  	case websocket.IsUnexpectedCloseError(err):
   182  		self.debug("Receive unexpected close err: ", fmt.Sprintf("%d, %s", messageType, cnv.MustStr(err)))
   183  	case messageType == websocket.CloseMessage:
   184  		err = nil
   185  		self.debug("Receive CloseMessage: ", fmt.Sprintf("%d, %s", messageType, cnv.MustStr(err)))
   186  	case messageType == websocket.TextMessage:
   187  		self.debug(string(body))
   188  	}
   189  	return
   190  }
   191  func (self *CiosMessaging) Receive() (body []byte, err error) {
   192  	body, err = self.receive()
   193  	if err != nil && !check.IsNil(self.refresh) {
   194  		self.token = (*self.refresh)()
   195  		self.debug("Receive Refresh")
   196  		if _connection, err := CreateCiosWsConn(self.isDebug, self.wsUrl, ciosutil.ParseAccessToken(self.token)); err == nil {
   197  			self.debug("Close err: ", self.Connection.Close())
   198  			self.Connection = _connection
   199  			return self.Receive()
   200  		}
   201  	}
   202  	return
   203  }
   204  func (self *CiosMessaging) ReceiveStr() (string, error) {
   205  	res, err := self.Receive()
   206  	return string(res), err
   207  }
   208  func (self *CiosMessaging) MapReceived(stct interface{}) error {
   209  	res, err := self.Receive()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	return cnv.UnMarshalJson(res, stct)
   214  }
   215  
   216  func (self *CiosMessaging) Start(ctx ciosctx.RequestCtx) (err error) {
   217  	self.closed = make(chan bool)
   218  	if _token, ok := ctx.Value(cios.ContextAccessToken).(string); !ok && !check.IsNil(self.refresh) {
   219  		self.token = (*self.refresh)()
   220  	} else {
   221  		self.token = _token
   222  	}
   223  	if self.Connection, err = CreateCiosWsConn(self.isDebug, self.wsUrl, ciosutil.ParseAccessToken(self.token)); err != nil {
   224  		return
   225  	}
   226  	autoRefresh := func() {
   227  	Refresh:
   228  		for {
   229  			self.debug("Registration Refresh Loop")
   230  			select {
   231  			case <-time.After(time.Minute * 55):
   232  				if check.IsNil(self.refresh) {
   233  					break
   234  				}
   235  				self.token = (*self.refresh)()
   236  				self.debug("Auto Refresh")
   237  				if _connection, _err := CreateCiosWsConn(self.isDebug, self.wsUrl, ciosutil.ParseAccessToken(self.token)); _err == nil {
   238  					self.debug("Connection Close: ", self.Connection.Close())
   239  					self.Connection = _connection
   240  					self.debug("Reconnect Websocket")
   241  				}
   242  			case _, _ = <-self.closed:
   243  				self.debug("End Auto Refresh")
   244  				break Refresh
   245  			}
   246  		}
   247  	}
   248  	autoPing := func() {
   249  	Ping:
   250  		for {
   251  			self.debug("Registration Ping Loop")
   252  			select {
   253  			case <-time.After(time.Minute):
   254  				if !check.IsNil(self.Connection) {
   255  					self.debug("Ping")
   256  					if err := self.Connection.WriteMessage(websocket.PingMessage, nil); err != nil {
   257  						self.debug("Ping Err: ", err)
   258  						continue
   259  					}
   260  				}
   261  			case _, _ = <-self.closed:
   262  				self.debug("End Auto Ping")
   263  				break Ping
   264  			}
   265  		}
   266  	}
   267  	go autoRefresh()
   268  	go autoPing()
   269  	return
   270  }
   271  func (self *CiosMessaging) Close() (err error) {
   272  	self.debug("Close")
   273  	defer self.CloseFunc()
   274  	self.closed <- true
   275  	self.closed <- true
   276  	safeCloseChan(self.closed)
   277  	if !check.IsNil(self.Connection) {
   278  		return self.Connection.Close()
   279  	}
   280  	return nil
   281  }
   282  
   283  func (self *CiosPubSub) PublishMessage(ctx ciosctx.RequestCtx, id string, body interface{}, packerFormat *string) (*_nethttp.Response, error) {
   284  	if err := self.refresh(); err != nil {
   285  		return nil, err
   286  	}
   287  	request := self.ApiClient.PublishSubscribeApi.PublishMessage(self.withHost(ctx)).ChannelId(id).Body(body)
   288  	request.P_packerFormat = packerFormat
   289  	return request.Execute()
   290  }
   291  func (self *CiosPubSub) PublishMessagePackerOnly(ctx ciosctx.RequestCtx, id string, body interface{}) (*_nethttp.Response, error) {
   292  	return self.PublishMessage(ctx, id, &body, nil)
   293  }
   294  func (self *CiosPubSub) PublishMessageJSON(ctx ciosctx.RequestCtx, id string, body cios.PackerFormatJson) (*_nethttp.Response, error) {
   295  	return self.PublishMessage(ctx, id, &body, cnv.StrPtr("json"))
   296  }
   297  
   298  // Deprecated: should not be used
   299  func (self *CiosPubSub) ConnectWebSocket(channelID string, done chan bool, params ConnectWebSocketOptions) (err error) {
   300  	if params.SubscribeFunc == nil && params.PublishStr == nil {
   301  		return errors.New("no publish str and subscribe func")
   302  	}
   303  	mode, _token, ok := "subscribe", "", true
   304  	if params.SubscribeFunc == nil && params.PublishStr != nil {
   305  		mode = "publish"
   306  	} else if params.SubscribeFunc != nil && params.PublishStr != nil {
   307  		mode = "pubsub"
   308  	}
   309  	if params.Context != nil {
   310  		_token, ok = params.Context.Value(cios.ContextAccessToken).(string)
   311  	}
   312  	if _token == "" {
   313  		_ = self.refresh()
   314  		_token = cnv.MustStr(self.token)
   315  	}
   316  
   317  	var (
   318  		wsUrl                     = self.CreateMessagingURL(channelID, mode, params.PackerFormat)
   319  		connection, connectionErr = self.CreateCIOSWebsocketConnection(wsUrl, ciosutil.ParseAccessToken(_token))
   320  		wg                        sync.WaitGroup
   321  		finish                    = false
   322  		beUpdating                = false
   323  		beCompletedS              = make(chan bool, 1)
   324  		beCompletedP              = make(chan bool, 1)
   325  		debug                     = func(text ...interface{}) {
   326  			if self.debug {
   327  				log.Println(text...)
   328  			}
   329  		}
   330  		closeConnection = func() {
   331  			if connection != nil {
   332  				if _err := connection.Close(); _err != nil {
   333  					debug("\nClosed Connection\n")
   334  				}
   335  			}
   336  		}
   337  		cleanBeCompleted = func() {
   338  			for {
   339  				select {
   340  				case <-beCompletedP:
   341  					debug("ignore publish update")
   342  					continue
   343  				case <-beCompletedS:
   344  					debug("ignore subscribe update")
   345  					continue
   346  				default:
   347  				}
   348  				break
   349  			}
   350  		}
   351  		reconnection = func() error {
   352  			_ = self.refresh()
   353  			_connection, _err := self.CreateCIOSWebsocketConnection(wsUrl, ciosutil.ParseAccessToken(cnv.MustStr(self.token)))
   354  			closeConnection()
   355  			if _err != nil {
   356  				return _err
   357  			}
   358  			connection = _connection
   359  			return nil
   360  		}
   361  		closeLogic = func() {
   362  			if params.PublishStr != nil {
   363  				isClosed := func() (f bool) {
   364  					select {
   365  					case _, ok = <-*params.PublishStr:
   366  						f = !ok
   367  					default:
   368  					}
   369  					return
   370  				}
   371  				if !isClosed() {
   372  					close(*params.PublishStr)
   373  				}
   374  			}
   375  			beUpdating = false
   376  			finish = true
   377  			closeConnection()
   378  			safeSendChan(done, false)
   379  			safeCloseChan(done)
   380  			safeCloseChan(beCompletedS)
   381  			safeCloseChan(beCompletedP)
   382  		}
   383  		updateConnection = func() {
   384  			for {
   385  				select {
   386  				case <-time.After(time.Second * 270):
   387  					if !check.IsNil(self.refresh) {
   388  						cleanBeCompleted()
   389  						beUpdating = true
   390  						debug("Updating access token and connection")
   391  						if err = reconnection(); err != nil {
   392  							closeLogic()
   393  							beUpdating = false
   394  							break
   395  						}
   396  						beCompletedS <- true
   397  						beCompletedP <- true
   398  						debug("Completed reconnection")
   399  						beUpdating = false
   400  					}
   401  					break
   402  				case isClosing := <-done:
   403  					if isClosing {
   404  						closeLogic()
   405  					}
   406  					break
   407  				}
   408  				if finish {
   409  					break
   410  				}
   411  			}
   412  		}
   413  		subscribe = func() {
   414  			for {
   415  				if finish {
   416  					break
   417  				}
   418  				if _, body, _err := connection.ReadMessage(); _err != nil {
   419  					if beUpdating {
   420  						if isConnecting, ok := <-beCompletedS; ok && isConnecting {
   421  							continue
   422  						}
   423  					}
   424  					err = errors.New("close connection")
   425  					break
   426  				} else if finish, err = (*params.SubscribeFunc)(body); err != nil || finish {
   427  					break
   428  				} else {
   429  					debug("Receive text: " + string(body))
   430  				}
   431  			}
   432  			closeLogic()
   433  		}
   434  		publish = func() {
   435  			for {
   436  				text, ok := <-(*params.PublishStr)
   437  				if text == nil || !ok {
   438  					break
   439  				}
   440  				if beUpdating {
   441  					<-beCompletedP
   442  				}
   443  				if _err := connection.WriteMessage(websocket.TextMessage, []byte(*text)); _err != nil {
   444  					if _err = connection.WriteMessage(websocket.TextMessage, []byte(*text)); _err != nil {
   445  						err = errors.New("close connection")
   446  						break
   447  					}
   448  				}
   449  				debug("Publish text: " + *text)
   450  			}
   451  			closeLogic()
   452  		}
   453  	)
   454  	if err = connectionErr; err != nil {
   455  		safeCloseChan(beCompletedS)
   456  		safeCloseChan(beCompletedP)
   457  		return err
   458  	}
   459  	if params.Setting != nil {
   460  		(*params.Setting)(connection)
   461  	}
   462  	wg.Add(1)
   463  	go func() {
   464  		updateConnection()
   465  		wg.Done()
   466  	}()
   467  	if params.SubscribeFunc != nil {
   468  		wg.Add(1)
   469  		go func() {
   470  			subscribe()
   471  			wg.Done()
   472  		}()
   473  	}
   474  	if params.PublishStr != nil {
   475  		wg.Add(1)
   476  		go func() {
   477  			publish()
   478  			wg.Done()
   479  		}()
   480  	}
   481  	wg.Wait()
   482  	debug(err)
   483  	return err
   484  }
   485  
   486  // Deprecated: should not be used
   487  func (self *CiosPubSub) CreateMessagingURL(channelID string, mode string, packerFormat *string) string {
   488  	_url, err := url.Parse(strings.Replace(self.Url, "https", "wss", 1) + "/v2/messaging")
   489  	if err != nil {
   490  		return ""
   491  	}
   492  	q := _url.Query()
   493  	q.Set("channel_id", channelID)
   494  	q.Set("mode", mode)
   495  	if packerFormat != nil {
   496  		q.Set("packer_format", *packerFormat)
   497  	}
   498  	_url.RawQuery = q.Encode()
   499  	return _url.String()
   500  }
   501  
   502  // Deprecated: should not be used
   503  func (self *CiosPubSub) CreateCIOSWebsocketConnection(url string, authorization string) (connection *websocket.Conn, err error) {
   504  	if self.debug {
   505  		log.Printf("Websocket URL: %s\nAuthorization: %s", url, authorization)
   506  	}
   507  	connection, _, err = (&websocket.Dialer{}).Dial(url, http.Header{"Authorization": []string{authorization}})
   508  	return
   509  }
   510  
   511  func safeCloseChan(ch chan bool) {
   512  	isClosed := func() bool {
   513  		select {
   514  		case _, ok := <-ch:
   515  			return !ok
   516  		default:
   517  			return false
   518  		}
   519  	}
   520  	if !isClosed() {
   521  		close(ch)
   522  	}
   523  }
   524  func safeSendChan(ch chan bool, v bool) {
   525  	isClosed := func() bool {
   526  		select {
   527  		case _, ok := <-ch:
   528  			return !ok
   529  		default:
   530  			return false
   531  		}
   532  	}
   533  	if !isClosed() {
   534  		ch <- v
   535  	}
   536  }