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"