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 }