Class DefaultMatsSocketServer

java.lang.Object
io.mats3.matssocket.impl.DefaultMatsSocketServer
All Implemented Interfaces:
MatsSocketStatics, MatsSocketServer

public class DefaultMatsSocketServer extends Object implements MatsSocketServer, MatsSocketStatics
  • Field Details

    • ALPHABET

      public static final String ALPHABET
      A-Z, a-z, 0-9, which is 62 chars.
      See Also:
    • ALPHABET_JSON_ID

      public static final String ALPHABET_JSON_ID
      All chars from 20-7f, except:
      • Space (32, 0x20) - since spaces are always annoying to use in Ids
      • " (34, 0x22) - since this is the start and end symbol of a String
      • \ (92 0x5c) - since this is the escape char
      • DEL (127, 0x7f) - since this is a control char
      This is 92 chars.
  • Method Details

    • createMatsSocketServer

      public static MatsSocketServer createMatsSocketServer(javax.websocket.server.ServerContainer serverContainer, io.mats3.MatsFactory matsFactory, ClusterStoreAndForward clusterStoreAndForward, AuthenticationPlugin authenticationPlugin, String websocketPath)
      Variant of the full method that uses the appName that the MatsFactory is configured with as the 'instanceName' parameter.
      Parameters:
      serverContainer - the WebSocket ServerContainer, typically gotten from the Servlet Container.
      matsFactory - The MatsFactory which we should hook into for both sending requests and setting up endpoints to receive replies.
      clusterStoreAndForward - an implementation of ClusterStoreAndForward which temporarily holds replies while finding the right node that holds the WebSocket connection - and hold them till the client reconnects in case he has disconnected in the mean time.
      authenticationPlugin - the piece of code that turns an Authorization String into a Principal. Must be pretty fast, as it is invoked synchronously - keep any IPC fast, otherwise all your threads of the container might be used up. If the function throws or returns null, authorization did not go through.
      websocketPath - The path onto which the WebSocket Server Endpoint will be mounted. Suggestion: "/matssocket". If you need multiple MatsSocketServers, e.g. because you need two types of authentication, they need to be mounted on different paths.
      Returns:
      a MatsSocketServer instance, now hooked into both the WebSocket ServerContainer and the MatsFactory.
    • createMatsSocketServer

      public static MatsSocketServer createMatsSocketServer(javax.websocket.server.ServerContainer serverContainer, io.mats3.MatsFactory matsFactory, ClusterStoreAndForward clusterStoreAndForward, AuthenticationPlugin authenticationPlugin, String instanceName, String websocketPath)
      Create a MatsSocketServer, piecing together necessary bits.
      Parameters:
      serverContainer - the WebSocket ServerContainer, typically gotten from the Servlet Container.
      matsFactory - The MatsFactory which we should hook into for both sending requests and setting up endpoints to receive replies.
      clusterStoreAndForward - an implementation of ClusterStoreAndForward which temporarily holds replies while finding the right node that holds the WebSocket connection - and hold them till the client reconnects in case he has disconnected in the mean time.
      authenticationPlugin - the piece of code that turns an Authorization String into a Principal. Must be pretty fast, as it is invoked synchronously - keep any IPC fast, otherwise all your threads of the container might be used up. If the function throws or returns null, authorization did not go through.
      instanceName - a unique name of this MatsSocketServer setup, at least within the MQ system the MatsFactory is connected to, as it is used to postfix/uniquify the endpoints that the MatsSocketServer creates on the MatsFactory. To illustrate: The variant of this factory method that does not take 'instanceName' uses the appName that the MatsFactory is configured with.
      websocketPath - The path onto which the WebSocket Server Endpoint will be mounted. Suggestion: "/matssocket". If you need multiple MatsSocketServers, e.g. because you need two types of authentication, they need to be mounted on different paths.
      Returns:
      a MatsSocketServer instance, now hooked into both the WebSocket ServerContainer and the MatsFactory.
    • getWebSocketOutgoingEnvelopes

      public WebSocketOutgoingEnvelopes getWebSocketOutgoingEnvelopes()
    • getIncomingSrrMsgHandler

      public IncomingSrrMsgHandler getIncomingSrrMsgHandler()
    • matsSocketEndpoint

      public <I, MR, R> MatsSocketServer.MatsSocketEndpoint<I,MR,R> matsSocketEndpoint(String matsSocketEndpointId, Class<I> incomingClass, Class<MR> matsReplyClass, Class<R> msReplyClass, MatsSocketServer.IncomingAuthorizationAndAdapter<I,MR,R> incomingAuthEval, MatsSocketServer.ReplyAdapter<I,MR,R> replyAdapter)
      Description copied from interface: MatsSocketServer
      Registers a MatsSocket Endpoint, including a MatsSocketServer.ReplyAdapter which can adapt the reply from the Mats endpoint before being fed back to the MatsSocket - and also decide whether to resolve or reject the waiting Client Promise.

      NOTE: If you supply MatsObject as the type 'MR', you will get such an instance, and can decide yourself what to deserialize it to - it will be like having a Java method taking Object as argument. However, there is no "instanceof" functionality, so you will need to know what type of object it is by other means, e.g. by putting some up-flow information on the Mats ProcessContext as a TraceProperty.

      NOTE: You need not be specific with the 'R' type being created in the MatsSocketServer.ReplyAdapter - it can be any superclass of your intended Reply DTO(s), up to Object. However, the introspection aspects will take a hit, i.e. when listing all MatsSocketEndpoints on some monitoring/introspection page. This is also a bit like with Java: Methods returning Object as return type are annoying, but can potentially be of value in certain convoluted scenarios.

      Specified by:
      matsSocketEndpoint in interface MatsSocketServer
    • send

      public void send(String sessionId, String traceId, String clientTerminatorId, Object messageDto) throws MatsSocketServer.DataStoreException
      Description copied from interface: MatsSocketServer
      Sends a message to the specified MatsSocketSession, to the specified Client TerminatorId. This is "fire into the void" style messaging, where you have no idea of whether the client received the message. Usage scenarios include "New information about order progress" which may or may not include said information (if not included, the client must do a request to update) - but where the server does not really care if the client gets the information, only that if he actually has the webpage/app open at the time, he will get the message and thus update his view of the changed world.

      Note: If the specified session is closed when this method is invoked, the message will (effectively) silently be dropped. Even if you just got hold of the sessionId and it was active then, it might asynchronously close while you invoke this method.

      Note: The message is put in the outbox, and if the session is actually connected, it will be delivered ASAP, otherwise it will rest in the outbox for delivery once the session reconnects. If the session then closes or times out while the message is in the outbox, it will be deleted.

      Note: Given that the session actually is live and the client is connected or connects before the session is closed or times out, the guaranteed delivery and exactly-once features are in effect, and this still holds in face of session reconnects.

      Specified by:
      send in interface MatsSocketServer
      Throws:
      MatsSocketServer.DataStoreException - if the ClusterStoreAndForward makes any problems when putting the outgoing message in the outbox.
    • request

      public void request(String sessionId, String traceId, String clientEndpointId, Object requestDto, String replyToMatsSocketTerminatorId, String correlationString, byte[] correlationBinary) throws MatsSocketServer.DataStoreException
      Description copied from interface: MatsSocketServer
      Initiates a request to the specified MatsSocketSession, to the specified Client EndpointId, with a replyTo specified to (typically) a MatsSocket terminator - which includes a String "correlationString" and byte array "correlationBinary" which can be used to correlate the reply to the request (available here and here for the reply processing). Do note that since you have no control of when the Client decides to close the browser or terminate the app, you have no guarantee that a reply will ever come - so code accordingly.

      Note: the correlationString and correlationBinary are not sent over to the client, but stored server side in the ClusterStoreAndForward. This both means that you do not need to be afraid of size (but storing megabytes is silly anyway), but more importantly, this data cannot be tampered with client side - you can be safe that what you gave in here is what you get out in the context.getCorrelationString() and context.getCorrelationBinary().

      Note: To check whether the client Resolved or Rejected the request, use MatsSocketServer.MatsSocketEndpointIncomingContext.getMessageType().

      Note: If the specified session is closed when this method is invoked, the message will (effectively) silently be dropped. Even if you just got hold of the sessionId and it was active then, it might asynchronously close while you invoke this method.

      Note: The message is put in the outbox, and if the session is actually connected, it will be delivered ASAP, otherwise it will rest in the outbox for delivery once the session reconnects. If the session then closes or times out while the message is in the outbox, it will be deleted.

      Note: Given that the session actually is live and the client is connected or connects before the session is closed or times out, the guaranteed delivery and exactly-once features are in effect, and this still holds in face of session reconnects.

      Specified by:
      request in interface MatsSocketServer
      Throws:
      MatsSocketServer.DataStoreException - if the ClusterStoreAndForward makes any problems when putting the outgoing message in the outbox.
    • publish

      public void publish(String traceId, String topicId, Object messageDto) throws io.mats3.MatsInitiator.MatsBackendRuntimeException
      Description copied from interface: MatsSocketServer
      Publish a Message to the specified Topic, with the specified TraceId. This is pretty much a direct invocation of MatsInitiator.MatsInitiate.publish(Object) on the MatsFactory, and thus you might get the MatsInitiator.MatsBackendRuntimeException which MatsInitiator.initiateUnchecked(InitiateLambda) raises.

      Note: A published message will be broadcast to all nodes in the MatsSocketServer instance (where each instance then evaluates if it have subscribers to the topic and forwards to those). In addition, a certain number of messages per topic will be retained in memory to support "replay of lost messages" when a Client looses connection and must reconnect. You should consider these facts when designing usage of pub/sub. Messages over topics should generally be of interest to more than one party. While it is certainly feasible to have user-specific, or even session-specific topics, which could be authorized to only be subscribable by the "owning user" or even "owning session" (by use of the AuthenticationPlugin), the current implementation of pub/sub will result in quite a bit of overhead with extensive use of such an approach. Also, even for messages that are of interest to multiple parties, you should consider the size of the messages: Maybe not send large PDFs or the entire ISO-images of "newly arrived BlueRays" over a topic - instead send a small notification about the fresh BlueRay availability including just essential information and an id, and then the client can decide whether he wants to download it.

      Specified by:
      publish in interface MatsSocketServer
      Parameters:
      traceId - traceId for the flow.
      topicId - which Topic to Publish on.
      messageDto - the message to Publish.
      Throws:
      io.mats3.MatsInitiator.MatsBackendRuntimeException - if the Mats implementation cannot connect to the underlying message broker, or are having problems interacting with it.
    • getMatsSocketEndpoints

      public SortedMap<String,MatsSocketServer.MatsSocketEndpoint<?,?,?>> getMatsSocketEndpoints()
      Specified by:
      getMatsSocketEndpoints in interface MatsSocketServer
      Returns:
      all registered MatsSocketEndpoints, as a SortedMap[endpointId, endpoint].
    • getMatsSocketSessions

      public List<MatsSocketServer.MatsSocketSessionDto> getMatsSocketSessions(boolean onlyActive, String userId, String appName, String appVersionAtOrAbove) throws MatsSocketServer.DataStoreException
      Description copied from interface: MatsSocketServer
      Unless restricted by the "constraint parameters", this method returns all MatsSocketSessions on this MatsSocketServer instance, regardless of whether the session currently is connected, and if connected, which node it is connected to. This is done by reading from the data store, as opposed to methods MatsSocketServer.getActiveMatsSocketSessions() and MatsSocketServer.getLiveMatsSocketSessions(), which returns result from this node's internal structures - and therefore only returns sessions that are connected right now, and are connected to this node. This means that you will get returned both connected sessions, and sessions that are not currently connected (unless restricting this via parameter 'onlyActive'). The latter implies that they are state=MatsSocketServer.ActiveMatsSocketSession.MatsSocketSessionState.DEREGISTERED, and the MatsSocketServer.MatsSocketSession.getNodeName() returns Optional.empty().

      The parameters are constraints - if a parameter is null or false, that parameter is not used in the search criteria, while if it is set, that parameter will constrain the search.

      Specified by:
      getMatsSocketSessions in interface MatsSocketServer
      Parameters:
      onlyActive - If true, only returns "active" MatsSocketSessions, currently being connected to some node, i.e. having MatsSocketServer.MatsSocketSession.getNodeName() NOT returning Optional.empty().
      userId - If non-null, restricts the results to sessions for this particular userId
      appName - If non-null, restricts the results to sessions for this particular app-name. Do realize that it is the Client that specifies this value, there is no restriction and you cannot trust that this String falls within your expected values.
      appVersionAtOrAbove - If non-null, restricts the results to sessions having app-version at or above the specified value, using ordinary alphanum comparison. Do realize that it is the Client that specifies this value, there is no restriction and you cannot trust that this String falls within your expected values.
      Returns:
      the list of all MatsSocketSessions currently registered with this MatsSocketServer instance matching the constraints if set - as read from the data store.
      Throws:
      MatsSocketServer.DataStoreException - if the ClusterStoreAndForward makes any problems when reading sessions from it.
    • getMatsSocketSessionsCount

      public int getMatsSocketSessionsCount(boolean onlyActive, String userId, String appName, String appVersionAtOrAbove)
      Description copied from interface: MatsSocketServer
      Like MatsSocketServer.getMatsSocketSessions(boolean, String, String, String), only returning the count - this might be interesting if there are very many sessions, and you do not need the full DTOs of every Session, just the count for a metric to graph or similar.
      Specified by:
      getMatsSocketSessionsCount in interface MatsSocketServer
      Returns:
      the count of all MatsSocketSessions currently registered with this MatsSocketServer instance matching the constraints if set - as read from the data store.
    • getActiveMatsSocketSessions

      public SortedMap<String,MatsSocketServer.ActiveMatsSocketSessionDto> getActiveMatsSocketSessions()
      Description copied from interface: MatsSocketServer
      This returns static, frozen-in-time, "copied-out" DTO-variants of the LiveMatsSocketSessions. Please observe the difference between MatsSocketServer.ActiveMatsSocketSession and MatsSocketServer.LiveMatsSocketSession. If you have a massive amount of sessions, and only need the sessions for appName="MegaCorpWebBank", then you should consider not employing this method, but instead do a variant of what this method does, where you restrict the "copy out" to the relevant sessions:
       SortedMap<String, ActiveMatsSocketSessionDto> ret = new TreeMap<>();
       for (LiveMatsSocketSession liveSession : getLiveMatsSocketSessions().values()) {
           // === HERE YOU MAY ADD CRITERIA on the LiveMatsSocketSession, doing 'continue' if not matched ===
           // :: "Copy it out"
           ActiveMatsSocketSessionDto activeSession = liveSession.toActiveMatsSocketSession();
           // ?: Check that the LiveSession is still SESSION_ESTABLISHED
           if (liveSession.getState() != MatsSocketSessionState.SESSION_ESTABLISHED) {
               // -> No, it changed during copying, so then we drop this.
               continue;
           }
           // Add to result Map
           ret.put(activeSession.getMatsSocketSessionId(), activeSession);
       }
       return ret;
       
      Specified by:
      getActiveMatsSocketSessions in interface MatsSocketServer
      Returns:
      a current snapshot of ActiveMatsSocketSessions - these are the active MatsSocketSessions which are active right now on this node of the set of nodes (i.e. cluster) that represents this instance of MatsSocketServer. Notice that all returned instances had state=SESSION_ESTABLISHED at the time of capture.
      See Also:
    • getLiveMatsSocketSessions

      public Map<String,MatsSocketServer.LiveMatsSocketSession> getLiveMatsSocketSessions()
      Description copied from interface: MatsSocketServer
      Imagine that the MatsSocketServer uses a ConcurrentMap to keep its set of local, live, currently connected MatsSocketSessions. This method then returns an unmodifiable view of this Map. This means that you can get session instances, and iterate over it, but the contents will change over time as Clients come and go, i.e. connects and disconnects. It also means that you can get this Map instance once, and keep a local copy of it, and it will always be current. It again also means that if you want a "static list" of these sessions, either use MatsSocketServer.getActiveMatsSocketSessions() which gives you a snapshot, "frozen-in-time" view of the active sessions, where both the sessions, and the contents of the sessions, are static. Or you may copy the values of this returned Map into another container - but in the latter case, the contents of those LiveMatsSocketSession instances are still live. Please observe the difference between MatsSocketServer.ActiveMatsSocketSession and MatsSocketServer.LiveMatsSocketSession.
      Specified by:
      getLiveMatsSocketSessions in interface MatsSocketServer
      Returns:
      an unmodifiable concurrent live view of LiveMatsSocketSessions - these are the live MatsSocketSessions which are active right now on this node of the set of nodes (i.e. cluster) that represents this instance of MatsSocketServer.
      See Also:
    • closeSession

      public void closeSession(String matsSocketSessionId, String reason)
      Description copied from interface: MatsSocketServer
      Closes the specified MatsSocketSession - can be used to forcibly close an active MatsSocketSession (i.e. "kick it off"), and can also used to perform out-of-band closing of Session if the WebSocket is down (this is used in the MatsSocket.js Client, where an "onunload"-listener is attached, so that if the user navigates away, every effort is done to get the MatsSocketSession closed).

      Note: An invocation of any SessionRemoved listeners with type CLOSE will be issued.

      Note: This can be done on any node of the MatsSocketServer-instance cluster, as the instruction will be forwarded to the active node if the MatsSocketSession is not active on this node. If it is not active on any node, it will nevertheless be closed in the data store (i.e. the session cannot reconnect again).

      Specified by:
      closeSession in interface MatsSocketServer
      Parameters:
      matsSocketSessionId - the id of the Session to close.
      reason - a short descriptive String of why it was closed.
    • addSessionEstablishedEventListener

      public void addSessionEstablishedEventListener(MatsSocketServer.SessionEstablishedEventListener listener)
      Description copied from interface: MatsSocketServer
      SessionEstablishedEvent listeners will be invoked when an LiveMatsSocketSession is established on this node of the MatsSocketServer instance cluster, i.e. the authentication/authorization is accepted, HELLO message from Client is processed and MatsSocketSessionId is established. Note that this means that in a fairly load balanced 3-node MatsSocketServer cluster, you should get approximately 1/3 of the SessionEstablishedEvents on "this" node, while 2/3 of them will come on the "other" two nodes.

      Note: A specific MatsSocketSession with a specific MatsSocketSessionId can be established multiple times, due to RECONNECT.

      NOTE: You are advised against keeping hold of the LiveMatsSocketSession instance that is provided in the SessionEstablishedEvent. You can instead get a view of the currently live sessions for this node by means of MatsSocketServer.getLiveMatsSocketSessions(). If you still decide to hold on to these active sessions instances, you must be very certain to remove it from your held instances when getting any SessionRemovedEvent, meaning that you must remove it for any of the DEREGISTER, CLOSE and TIMEOUT event types: The live session instance is dead for all of these events. If you were to remove it only on CLOSE or TIMEOUT, believing that a DEREGISTER is a "softer" removal, you have basically misunderstood! You could then get a DEREGISTER (which actually is the server informing you that it has ditched this LiveMatsSocketSession and the session is now solely represented in the data store, while you still stubbornly hold on to it!), and then not get a corresponding TIMEOUT for the same MatsSocketSessionId until many hours, or days, later. If you fail to remove it at all, you will eventually get an OutOfMemory situation. The reason here is that a MatsSocketSession instance is never "reanimated", even if the MatsSocketSession is just DEREGISTERed: A new LiveMatsSocketSession instance is always created upon a SessionEstablishedEvent, both for NEW and RECONNECT

      Specified by:
      addSessionEstablishedEventListener in interface MatsSocketServer
      Parameters:
      listener - the SessionEstablishedListener that shall get invoked when MatsSocketSessions are established.
      See Also:
    • addSessionRemovedEventListener

      public void addSessionRemovedEventListener(MatsSocketServer.SessionRemovedEventListener listener)
      Description copied from interface: MatsSocketServer
      MatsSocketServer.SessionRemovedEvent listeners will be invoked when an MatsSocketServer.LiveMatsSocketSession is removed from this node of the MatsSocketServer instance cluster - this is both when a MatsSocketSession is DEREGISTERed, in which case the Client can still RECONNECT to the same MatsSocketSessionId, and when a MatsSocketSession is CLOSEd or TIMEOUTed. In the latter cases, any information of the MatsSocketSession and its MatsSocketSessionId are deleted from the MatsSocketServer, and the session cannot be reconnected again.

      Note: A specific MatsSocketSession can DEREGISTER multiple times, due to it can RECONNECT again after each DEREGISTER. However, once it has CLOSE or TIMEOUT, the session cannot RECONNECT ever again, and hence those events are terminal wrt. to that specific MatsSocketSessionId.

      Specified by:
      addSessionRemovedEventListener in interface MatsSocketServer
      Parameters:
      listener - the SessionEstablishedListener that shall get invoked when MatsSocketSessions are removed (either deregistered, closed or timed out).
      See Also:
    • addMessageEventListener

      public void addMessageEventListener(MatsSocketServer.MessageEventListener listener)
      Description copied from interface: MatsSocketServer
      MessageEventListeners will be invoked for every processed incoming and outgoing message for any session. It will be invoked after the message is processed OK on incoming, and after the message is sent for outgoing. Note that the MatsSocketServer.MatsSocketEnvelopeWithMetaDto contains more information than is sent over the wire, this is the "WithMeta" aspect which holds processing metadata - the wire-part is what is contained in MatsSocketServer.MatsSocketEnvelopeDto.

      Note wrt. modifications on the MatsSocketEnvelopeWithMetaDto! All fields are public and non-final, so you can modify it before e.g. sending it over Mats (e.g. nulling out the 'msg' field). However, read the JavaDoc comment on the class: There is only one single instance for all listeners and MatsSocketServer.ActiveMatsSocketSession.getLastEnvelopes() "last envelopes"}, so clone it before modifying!

      Note: The last messages per MatsSocketServer.ActiveMatsSocketSession is available via MatsSocketServer.ActiveMatsSocketSession.getLastEnvelopes().

      Specified by:
      addMessageEventListener in interface MatsSocketServer
      Parameters:
      listener - the MatsSocketServer.MessageEventListener that will be invoked for every processed incoming and outgoing envelope for any session.
      See Also:
    • stop

      public void stop(int gracefulShutdownMillis)
      Description copied from interface: MatsSocketServer
      Closes all MatsSocketServer.ActiveMatsSocketSession on this node, closing the WebSocket with CloseReason.CloseCodes.SERVICE_RESTART (assuming that a MatsSocket service will never truly go down, thus effectively asking the client to reconnect, hopefully to another instance). Should be invoked at application shutdown.
      Specified by:
      stop in interface MatsSocketServer