github.com/philippseith/signalr@v0.6.3/hubconnection.go (about)

     1  package signalr
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  	"time"
    10  )
    11  
    12  // hubConnection is used by HubContext, Server and Client to realize the external API.
    13  // hubConnection uses a transport connection (of type Connection) and a hubProtocol to send and receive SignalR messages.
    14  type hubConnection interface {
    15  	ConnectionID() string
    16  	Receive() <-chan receiveResult
    17  	SendInvocation(id string, target string, args []interface{}) error
    18  	SendStreamInvocation(id string, target string, args []interface{}) error
    19  	SendInvocationWithStreamIds(id string, target string, args []interface{}, streamIds []string) error
    20  	StreamItem(id string, item interface{}) error
    21  	Completion(id string, result interface{}, error string) error
    22  	Close(error string, allowReconnect bool) error
    23  	Ping() error
    24  	LastWriteStamp() time.Time
    25  	Items() *sync.Map
    26  	Context() context.Context
    27  	Abort()
    28  }
    29  
    30  type receiveResult struct {
    31  	message interface{}
    32  	err     error
    33  }
    34  
    35  func newHubConnection(connection Connection, protocol hubProtocol, maximumReceiveMessageSize uint, info StructuredLogger) hubConnection {
    36  	ctx, cancelFunc := context.WithCancel(connection.Context())
    37  	c := &defaultHubConnection{
    38  		ctx:                       ctx,
    39  		cancelFunc:                cancelFunc,
    40  		protocol:                  protocol,
    41  		mx:                        sync.Mutex{},
    42  		connection:                connection,
    43  		maximumReceiveMessageSize: maximumReceiveMessageSize,
    44  		items:                     &sync.Map{},
    45  		info:                      info,
    46  	}
    47  	if connectionWithTransferMode, ok := connection.(ConnectionWithTransferMode); ok {
    48  		connectionWithTransferMode.SetTransferMode(protocol.transferMode())
    49  	}
    50  	return c
    51  }
    52  
    53  type defaultHubConnection struct {
    54  	ctx                       context.Context
    55  	cancelFunc                context.CancelFunc
    56  	protocol                  hubProtocol
    57  	mx                        sync.Mutex
    58  	connection                Connection
    59  	maximumReceiveMessageSize uint
    60  	items                     *sync.Map
    61  	lastWriteStamp            time.Time
    62  	info                      StructuredLogger
    63  }
    64  
    65  func (c *defaultHubConnection) Items() *sync.Map {
    66  	return c.items
    67  }
    68  
    69  func (c *defaultHubConnection) Close(errorText string, allowReconnect bool) error {
    70  	var closeMessage = closeMessage{
    71  		Type:           7,
    72  		Error:          errorText,
    73  		AllowReconnect: allowReconnect,
    74  	}
    75  	return c.protocol.WriteMessage(closeMessage, c.connection)
    76  }
    77  
    78  func (c *defaultHubConnection) ConnectionID() string {
    79  	return c.connection.ConnectionID()
    80  }
    81  
    82  func (c *defaultHubConnection) Context() context.Context {
    83  	return c.ctx
    84  }
    85  
    86  func (c *defaultHubConnection) Abort() {
    87  	c.cancelFunc()
    88  }
    89  
    90  func (c *defaultHubConnection) Receive() <-chan receiveResult {
    91  	recvChan := make(chan receiveResult, 20)
    92  	// Prepare cleanup
    93  	writerDone := make(chan struct{}, 1)
    94  	// the pipe connects the goroutine which reads from the connection and the goroutine which parses the read data
    95  	reader, writer := CtxPipe(c.ctx)
    96  	p := make([]byte, c.maximumReceiveMessageSize)
    97  	go func(ctx context.Context, connection io.Reader, writer io.Writer, recvChan chan<- receiveResult, writerDone chan<- struct{}) {
    98  	loop:
    99  		for {
   100  			select {
   101  			case <-ctx.Done():
   102  				break loop
   103  			default:
   104  				n, err := connection.Read(p)
   105  				if err != nil {
   106  					select {
   107  					case recvChan <- receiveResult{err: err}:
   108  					case <-ctx.Done():
   109  						break loop
   110  					}
   111  				}
   112  				if n > 0 {
   113  					_, err = writer.Write(p[:n])
   114  					if err != nil {
   115  						select {
   116  						case recvChan <- receiveResult{err: err}:
   117  						case <-ctx.Done():
   118  							break loop
   119  						}
   120  					}
   121  				}
   122  			}
   123  		}
   124  		// The pipe writer is done
   125  		close(writerDone)
   126  	}(c.ctx, c.connection, writer, recvChan, writerDone)
   127  	// parse
   128  	go func(ctx context.Context, reader io.Reader, recvChan chan<- receiveResult, writerDone <-chan struct{}) {
   129  		remainBuf := bytes.Buffer{}
   130  	loop:
   131  		for {
   132  			select {
   133  			case <-ctx.Done():
   134  				break loop
   135  			case <-writerDone:
   136  				break loop
   137  			default:
   138  				messages, err := c.protocol.ParseMessages(reader, &remainBuf)
   139  				if err != nil {
   140  					select {
   141  					case recvChan <- receiveResult{err: err}:
   142  					case <-ctx.Done():
   143  						break loop
   144  					case <-writerDone:
   145  						break loop
   146  					}
   147  				} else {
   148  					for _, message := range messages {
   149  						select {
   150  						case recvChan <- receiveResult{message: message}:
   151  						case <-ctx.Done():
   152  							break loop
   153  						case <-writerDone:
   154  							break loop
   155  						}
   156  					}
   157  				}
   158  			}
   159  		}
   160  	}(c.ctx, reader, recvChan, writerDone)
   161  	return recvChan
   162  }
   163  
   164  func (c *defaultHubConnection) SendInvocation(id string, target string, args []interface{}) error {
   165  	if args == nil {
   166  		args = make([]interface{}, 0)
   167  	}
   168  	var invocationMessage = invocationMessage{
   169  		Type:         1,
   170  		InvocationID: id,
   171  		Target:       target,
   172  		Arguments:    args,
   173  	}
   174  	return c.writeMessage(invocationMessage)
   175  }
   176  
   177  func (c *defaultHubConnection) SendStreamInvocation(id string, target string, args []interface{}) error {
   178  	if args == nil {
   179  		args = make([]interface{}, 0)
   180  	}
   181  	var invocationMessage = invocationMessage{
   182  		Type:         4,
   183  		InvocationID: id,
   184  		Target:       target,
   185  		Arguments:    args,
   186  	}
   187  	return c.writeMessage(invocationMessage)
   188  }
   189  
   190  func (c *defaultHubConnection) SendInvocationWithStreamIds(id string, target string, args []interface{}, streamIds []string) error {
   191  	var invocationMessage = invocationMessage{
   192  		Type:         1,
   193  		InvocationID: id,
   194  		Target:       target,
   195  		Arguments:    args,
   196  		StreamIds:    streamIds,
   197  	}
   198  	return c.writeMessage(invocationMessage)
   199  }
   200  
   201  func (c *defaultHubConnection) StreamItem(id string, item interface{}) error {
   202  	var streamItemMessage = streamItemMessage{
   203  		Type:         2,
   204  		InvocationID: id,
   205  		Item:         item,
   206  	}
   207  	return c.writeMessage(streamItemMessage)
   208  }
   209  
   210  func (c *defaultHubConnection) Completion(id string, result interface{}, error string) error {
   211  	var completionMessage = completionMessage{
   212  		Type:         3,
   213  		InvocationID: id,
   214  		Result:       result,
   215  		Error:        error,
   216  	}
   217  	return c.writeMessage(completionMessage)
   218  }
   219  
   220  func (c *defaultHubConnection) Ping() error {
   221  	var pingMessage = hubMessage{
   222  		Type: 6,
   223  	}
   224  	return c.writeMessage(pingMessage)
   225  }
   226  
   227  func (c *defaultHubConnection) LastWriteStamp() time.Time {
   228  	defer c.mx.Unlock()
   229  	c.mx.Lock()
   230  	return c.lastWriteStamp
   231  }
   232  
   233  func (c *defaultHubConnection) writeMessage(message interface{}) error {
   234  	c.mx.Lock()
   235  	c.lastWriteStamp = time.Now()
   236  	c.mx.Unlock()
   237  	err := func() error {
   238  		if c.ctx.Err() != nil {
   239  			return fmt.Errorf("hubConnection canceled: %w", c.ctx.Err())
   240  		}
   241  		e := make(chan error, 1)
   242  		go func() { e <- c.protocol.WriteMessage(message, c.connection) }()
   243  		select {
   244  		case <-c.ctx.Done():
   245  			return fmt.Errorf("hubConnection canceled: %w", c.ctx.Err())
   246  		case err := <-e:
   247  			if err != nil {
   248  				c.Abort()
   249  			}
   250  			return err
   251  		}
   252  	}()
   253  	if err != nil {
   254  		_ = c.info.Log(evt, msgSend, "message", fmtMsg(message), "error", err)
   255  	}
   256  	return err
   257  }