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 }