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

     1  package signalr
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sync"
     7  	"time"
     8  )
     9  
    10  func newStreamClient(protocol hubProtocol, chanReceiveTimeout time.Duration, streamBufferCapacity uint) *streamClient {
    11  	return &streamClient{
    12  		mx:                   sync.Mutex{},
    13  		upstreamChannels:     make(map[string]reflect.Value),
    14  		runningStreams:       make(map[string]bool),
    15  		chanReceiveTimeout:   chanReceiveTimeout,
    16  		streamBufferCapacity: streamBufferCapacity,
    17  		protocol:             protocol,
    18  	}
    19  }
    20  
    21  type streamClient struct {
    22  	mx                   sync.Mutex
    23  	upstreamChannels     map[string]reflect.Value
    24  	runningStreams       map[string]bool
    25  	chanReceiveTimeout   time.Duration
    26  	streamBufferCapacity uint
    27  	protocol             hubProtocol
    28  }
    29  
    30  func (c *streamClient) buildChannelArgument(invocation invocationMessage, argType reflect.Type, chanCount int) (arg reflect.Value, canClientStreaming bool, err error) {
    31  	c.mx.Lock()
    32  	defer c.mx.Unlock()
    33  	if argType.Kind() != reflect.Chan || argType.ChanDir() == reflect.SendDir {
    34  		return reflect.Value{}, false, nil
    35  	} else if len(invocation.StreamIds) > chanCount {
    36  		// MakeChan does only accept bidirectional channels, and we need to Send to this channel anyway
    37  		arg = reflect.MakeChan(reflect.ChanOf(reflect.BothDir, argType.Elem()), int(c.streamBufferCapacity))
    38  		c.upstreamChannels[invocation.StreamIds[chanCount]] = arg
    39  		return arg, true, nil
    40  	} else {
    41  		// To many channel parameters arguments this method. The client will not send streamItems for these
    42  		return reflect.Value{}, true, fmt.Errorf("method %s has more chan parameters than the client will stream", invocation.Target)
    43  	}
    44  }
    45  
    46  func (c *streamClient) newUpstreamChannel(invocationID string) <-chan interface{} {
    47  	c.mx.Lock()
    48  	defer c.mx.Unlock()
    49  	upChan := make(chan interface{}, c.streamBufferCapacity)
    50  	c.upstreamChannels[invocationID] = reflect.ValueOf(upChan)
    51  	return upChan
    52  }
    53  
    54  func (c *streamClient) deleteUpstreamChannel(invocationID string) {
    55  	c.mx.Lock()
    56  	if upChan, ok := c.upstreamChannels[invocationID]; ok {
    57  		upChan.Close()
    58  		delete(c.upstreamChannels, invocationID)
    59  	}
    60  	c.mx.Unlock()
    61  }
    62  
    63  func (c *streamClient) receiveStreamItem(streamItem streamItemMessage) error {
    64  	c.mx.Lock()
    65  	defer c.mx.Unlock()
    66  	if upChan, ok := c.upstreamChannels[streamItem.InvocationID]; ok {
    67  		// Mark the stream as running to detect illegal completion with result on this id
    68  		c.runningStreams[streamItem.InvocationID] = true
    69  		chanVal := reflect.New(upChan.Type().Elem())
    70  		err := c.protocol.UnmarshalArgument(streamItem.Item, chanVal.Interface())
    71  		if err != nil {
    72  			return err
    73  		}
    74  		return c.sendChanValSave(upChan, chanVal.Elem())
    75  	}
    76  	return fmt.Errorf(`unknown stream id "%v"`, streamItem.InvocationID)
    77  }
    78  
    79  func (c *streamClient) sendChanValSave(upChan reflect.Value, chanVal reflect.Value) error {
    80  	done := make(chan error)
    81  	go func() {
    82  		defer func() {
    83  			if r := recover(); r != nil {
    84  				done <- fmt.Errorf("%v", r)
    85  			}
    86  		}()
    87  		upChan.Send(chanVal)
    88  		done <- nil
    89  	}()
    90  	select {
    91  	case err := <-done:
    92  		return err
    93  	case <-time.After(c.chanReceiveTimeout):
    94  		return &hubChanTimeoutError{fmt.Sprintf("timeout (%v) waiting for hub to receive client streamed value", c.chanReceiveTimeout)}
    95  	}
    96  }
    97  
    98  type hubChanTimeoutError struct {
    99  	msg string
   100  }
   101  
   102  func (h *hubChanTimeoutError) Error() string {
   103  	return h.msg
   104  }
   105  
   106  func (c *streamClient) handlesInvocationID(invocationID string) bool {
   107  	c.mx.Lock()
   108  	defer c.mx.Unlock()
   109  	_, ok := c.upstreamChannels[invocationID]
   110  	return ok
   111  }
   112  
   113  func (c *streamClient) receiveCompletionItem(completion completionMessage, invokeClient *invokeClient) error {
   114  	c.mx.Lock()
   115  	channel, ok := c.upstreamChannels[completion.InvocationID]
   116  	c.mx.Unlock()
   117  	if ok {
   118  		var err error
   119  		if completion.Error != "" {
   120  			// Push error to the error channel
   121  			err = invokeClient.receiveCompletionItem(completion)
   122  		} else {
   123  			if completion.Result != nil {
   124  				c.mx.Lock()
   125  				running := c.runningStreams[completion.InvocationID]
   126  				c.mx.Unlock()
   127  				if running {
   128  					err = fmt.Errorf("client side streaming: received completion with result %v", completion)
   129  				} else {
   130  					// handle result like a stream item
   131  					err = c.receiveStreamItem(streamItemMessage{
   132  						Type:         2,
   133  						InvocationID: completion.InvocationID,
   134  						Item:         completion.Result,
   135  					})
   136  				}
   137  			}
   138  		}
   139  		channel.Close()
   140  		// Close error channel
   141  		invokeClient.deleteInvocation(completion.InvocationID)
   142  		c.mx.Lock()
   143  		delete(c.upstreamChannels, completion.InvocationID)
   144  		delete(c.runningStreams, completion.InvocationID)
   145  		c.mx.Unlock()
   146  		return err
   147  	}
   148  	return fmt.Errorf("received completion with unknown id %v", completion.InvocationID)
   149  }