github.com/philippseith/signalr@v0.6.3/server.go (about) 1 package signalr 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "net/http" 10 "os" 11 "reflect" 12 "runtime/debug" 13 14 "github.com/go-kit/log" 15 ) 16 17 // Server is a SignalR server for one type of hub. 18 // 19 // MapHTTP(mux *http.ServeMux, path string) 20 // 21 // maps the servers' hub to a path on a http.ServeMux. 22 // 23 // Serve(conn Connection) 24 // 25 // serves the hub of the server on one connection. 26 // The same server might serve different connections in parallel. Serve does not return until the connection is closed 27 // or the servers' context is canceled. 28 // 29 // HubClients() 30 // allows to call all HubClients of the server from server-side, non-hub code. 31 // Note that HubClients.Caller() returns nil, because there is no real caller which can be reached over a HubConnection. 32 type Server interface { 33 Party 34 MapHTTP(routerFactory func() MappableRouter, path string) 35 Serve(conn Connection) error 36 HubClients() HubClients 37 availableTransports() []TransportType 38 } 39 40 type server struct { 41 partyBase 42 newHub func() HubInterface 43 lifetimeManager HubLifetimeManager 44 defaultHubClients *defaultHubClients 45 groupManager GroupManager 46 reconnectAllowed bool 47 transports []TransportType 48 } 49 50 // NewServer creates a new server for one type of hub. The hub type is set by one of the 51 // options UseHub, HubFactory or SimpleHubFactory 52 func NewServer(ctx context.Context, options ...func(Party) error) (Server, error) { 53 info, dbg := buildInfoDebugLogger(log.NewLogfmtLogger(os.Stderr), false) 54 lifetimeManager := newLifeTimeManager(info) 55 server := &server{ 56 lifetimeManager: &lifetimeManager, 57 defaultHubClients: &defaultHubClients{ 58 lifetimeManager: &lifetimeManager, 59 allCache: allClientProxy{lifetimeManager: &lifetimeManager}, 60 }, 61 groupManager: &defaultGroupManager{ 62 lifetimeManager: &lifetimeManager, 63 }, 64 partyBase: newPartyBase(ctx, info, dbg), 65 reconnectAllowed: true, 66 } 67 for _, option := range options { 68 if option != nil { 69 if err := option(server); err != nil { 70 return nil, err 71 } 72 } 73 } 74 if server.transports == nil { 75 server.transports = []TransportType{TransportWebSockets, TransportServerSentEvents} 76 } 77 if server.newHub == nil { 78 return server, errors.New("cannot determine hub type. Neither UseHub, HubFactory or SimpleHubFactory given as option") 79 } 80 return server, nil 81 } 82 83 // MappableRouter encapsulates the methods used by server.MapHTTP to configure the 84 // handlers required by the signalr protocol. this abstraction removes the explicit 85 // binding to http.ServerMux and allows use of any mux which implements those basic 86 // Handle and HandleFunc methods. 87 type MappableRouter interface { 88 HandleFunc(string, func(w http.ResponseWriter, r *http.Request)) 89 Handle(string, http.Handler) 90 } 91 92 // WithHTTPServeMux is a MappableRouter factory for MapHTTP which converts a 93 // http.ServeMux to a MappableRouter. 94 // For factories for other routers, see github.com/philippseith/signalr/router 95 func WithHTTPServeMux(serveMux *http.ServeMux) func() MappableRouter { 96 return func() MappableRouter { 97 return serveMux 98 } 99 } 100 101 // MapHTTP maps the servers' hub to a path in a MappableRouter 102 func (s *server) MapHTTP(routerFactory func() MappableRouter, path string) { 103 httpMux := newHTTPMux(s) 104 router := routerFactory() 105 router.HandleFunc(fmt.Sprintf("%s/negotiate", path), httpMux.negotiate) 106 router.Handle(path, httpMux) 107 } 108 109 // Serve serves the hub of the server on one connection. 110 // The same server might serve different connections in parallel. Serve does not return until the connection is closed 111 // or the servers' context is canceled. 112 func (s *server) Serve(conn Connection) error { 113 114 protocol, err := s.processHandshake(conn) 115 if err != nil { 116 info, _ := s.prefixLoggers("") 117 _ = info.Log(evt, "processHandshake", "connectionId", conn.ConnectionID(), "error", err, react, "do not connect") 118 return err 119 } 120 121 return newLoop(s, conn, protocol).Run(make(chan struct{}, 1)) 122 } 123 124 func (s *server) HubClients() HubClients { 125 return s.defaultHubClients 126 } 127 128 func (s *server) availableTransports() []TransportType { 129 return s.transports 130 } 131 132 func (s *server) onConnected(hc hubConnection) { 133 s.lifetimeManager.OnConnected(hc) 134 go func() { 135 defer s.recoverHubLifeCyclePanic() 136 s.invocationTarget(hc).(HubInterface).OnConnected(hc.ConnectionID()) 137 }() 138 } 139 140 func (s *server) onDisconnected(hc hubConnection) { 141 go func() { 142 defer s.recoverHubLifeCyclePanic() 143 s.invocationTarget(hc).(HubInterface).OnDisconnected(hc.ConnectionID()) 144 }() 145 s.lifetimeManager.OnDisconnected(hc) 146 147 } 148 149 func (s *server) invocationTarget(conn hubConnection) interface{} { 150 hub := s.newHub() 151 hub.Initialize(s.newConnectionHubContext(conn)) 152 return hub 153 } 154 155 func (s *server) allowReconnect() bool { 156 return s.reconnectAllowed 157 } 158 159 func (s *server) recoverHubLifeCyclePanic() { 160 if err := recover(); err != nil { 161 s.reconnectAllowed = false 162 info, dbg := s.prefixLoggers("") 163 _ = info.Log(evt, "panic in hub lifecycle", "error", err, react, "close connection, allow no reconnect") 164 _ = dbg.Log(evt, "panic in hub lifecycle", "error", err, react, "close connection, allow no reconnect", "stack", string(debug.Stack())) 165 s.cancel() 166 } 167 } 168 169 func (s *server) prefixLoggers(connectionID string) (info StructuredLogger, dbg StructuredLogger) { 170 return log.WithPrefix(s.info, "ts", log.DefaultTimestampUTC, 171 "class", "Server", 172 "connection", connectionID, 173 "hub", reflect.ValueOf(s.newHub()).Elem().Type()), 174 log.WithPrefix(s.dbg, "ts", log.DefaultTimestampUTC, 175 "class", "Server", 176 "connection", connectionID, 177 "hub", reflect.ValueOf(s.newHub()).Elem().Type()) 178 } 179 180 func (s *server) newConnectionHubContext(hubConn hubConnection) HubContext { 181 return &connectionHubContext{ 182 abort: hubConn.Abort, 183 clients: &callerHubClients{ 184 defaultHubClients: s.defaultHubClients, 185 connectionID: hubConn.ConnectionID(), 186 }, 187 groups: s.groupManager, 188 connection: hubConn, 189 info: s.info, 190 dbg: s.dbg, 191 } 192 } 193 194 func (s *server) processHandshake(conn Connection) (hubProtocol, error) { 195 if request, err := s.receiveHandshakeRequest(conn); err != nil { 196 return nil, err 197 } else { 198 return s.sendHandshakeResponse(conn, request) 199 } 200 } 201 202 func (s *server) receiveHandshakeRequest(conn Connection) (handshakeRequest, error) { 203 _, dbg := s.prefixLoggers(conn.ConnectionID()) 204 ctx, cancelRead := context.WithTimeout(s.context(), s.HandshakeTimeout()) 205 defer cancelRead() 206 readJSONFramesChan := make(chan []interface{}, 1) 207 go func() { 208 var remainBuf bytes.Buffer 209 rawHandshake, err := readJSONFrames(conn, &remainBuf) 210 readJSONFramesChan <- []interface{}{rawHandshake, err} 211 }() 212 request := handshakeRequest{} 213 select { 214 case result := <-readJSONFramesChan: 215 if result[1] != nil { 216 return request, result[1].(error) 217 } 218 rawHandshake := result[0].([][]byte) 219 _ = dbg.Log(evt, "handshake received", "msg", string(rawHandshake[0])) 220 return request, json.Unmarshal(rawHandshake[0], &request) 221 case <-ctx.Done(): 222 return request, ctx.Err() 223 } 224 } 225 226 func (s *server) sendHandshakeResponse(conn Connection, request handshakeRequest) (protocol hubProtocol, err error) { 227 info, dbg := s.prefixLoggers(conn.ConnectionID()) 228 ctx, cancelWrite := context.WithTimeout(s.context(), s.HandshakeTimeout()) 229 defer cancelWrite() 230 var ok bool 231 if protocol, ok = protocolMap[request.Protocol]; ok { 232 // Send the handshake response 233 const handshakeResponse = "{}\u001e" 234 if _, err = ReadWriteWithContext(ctx, 235 func() (int, error) { 236 return conn.Write([]byte(handshakeResponse)) 237 }, func() {}); err != nil { 238 _ = dbg.Log(evt, "handshake sent", "error", err) 239 } else { 240 _ = dbg.Log(evt, "handshake sent", "msg", handshakeResponse) 241 } 242 } else { 243 err = fmt.Errorf("protocol %v not supported", request.Protocol) 244 _ = info.Log(evt, "protocol requested", "error", err) 245 if _, respErr := ReadWriteWithContext(ctx, 246 func() (int, error) { 247 const errorHandshakeResponse = "{\"error\":\"%s\"}\u001e" 248 return conn.Write([]byte(fmt.Sprintf(errorHandshakeResponse, err))) 249 }, func() {}); respErr != nil { 250 _ = dbg.Log(evt, "handshake sent", "error", respErr) 251 err = respErr 252 } 253 } 254 return protocol, err 255 } 256 257 var protocolMap = map[string]hubProtocol{ 258 "json": &jsonHubProtocol{}, 259 "messagepack": &messagePackHubProtocol{}, 260 } 261 262 // const for logging 263 const evt string = "event" 264 const msgRecv string = "message received" 265 const msgSend string = "message send" 266 const msg string = "message" 267 const react string = "reaction"