All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kurento.jsonrpc.internal.ws.JsonRpcWebSocketHandler Maven / Gradle / Ivy

/*
 * (C) Copyright 2013 Kurento (http://kurento.org/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.kurento.jsonrpc.internal.ws;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;

import org.kurento.commons.PropertiesManager;
import org.kurento.jsonrpc.internal.client.TransactionImpl.ResponseSender;
import org.kurento.jsonrpc.internal.server.ProtocolManager;
import org.kurento.jsonrpc.internal.server.ProtocolManager.ServerSessionFactory;
import org.kurento.jsonrpc.internal.server.ServerSession;
import org.kurento.jsonrpc.internal.server.SessionsManager;
import org.kurento.jsonrpc.message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class JsonRpcWebSocketHandler extends TextWebSocketHandler {

  public class MaxNumberWsConnectionsReachedException extends Exception {

    private static final long serialVersionUID = -6621614523181088993L;
  }

  private static final long MAX_WS_CONNECTIONS =
      PropertiesManager.getProperty("ws.maxSessions", Long.MAX_VALUE);

  private static final AtomicLong numConnections = new AtomicLong();

  private static final Logger log = LoggerFactory.getLogger(JsonRpcWebSocketHandler.class);

  private final ProtocolManager protocolManager;

  private String label = "";

  public JsonRpcWebSocketHandler(ProtocolManager protocolManager) {
    this.protocolManager = protocolManager;
  }

  public void setLabel(String label) {
    this.label = "[" + label + "] ";
    this.protocolManager.setLabel(label);
  }

  @Override
  public void afterConnectionEstablished(WebSocketSession session) throws Exception {

    try {
      incNumConnectionsIfAllowed();
    } catch (MaxNumberWsConnectionsReachedException e) {
      log.warn("Closed a WS connection because MAX_WS_CONNECTIONS={} limit reached",
          MAX_WS_CONNECTIONS);
      session.close();
    }

    try {
      // We send this notification to the JsonRpcHandler when the JsonRpc
      // session is established, not when websocket session is established
      log.debug(
          "{} Client connection established from session={} uri={} headers={} acceptedProtocol={} attributes={}",
          label, session.getRemoteAddress(), session.getUri(), session.getHandshakeHeaders(),
          session.getAcceptedProtocol(), session.getAttributes());

    } catch (Throwable t) {
      log.error("{} Exception processing afterConnectionEstablished in session={}", label,
          session.getId(), t);
    }
  }

  private void incNumConnectionsIfAllowed() throws MaxNumberWsConnectionsReachedException {

    while (true) {

      long curNumConn = numConnections.get();
      if (curNumConn >= MAX_WS_CONNECTIONS) {
        throw new MaxNumberWsConnectionsReachedException();
      }

      // Try updating the value, but only if it's equal to the
      // one we've just seen. If it is different, we have to check again if now
      // there are room for a new client.
      boolean setSuccessful = numConnections.compareAndSet(curNumConn, curNumConn + 1);

      if (setSuccessful) {
        // We have incremented numConnections. Exiting.
        break;
      }

      // Another thread updated the numConnections between our get and
      // compareAndSet calls. It is possible that we check again
    }
  }

  @Override
  public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status)
      throws Exception {

    numConnections.decrementAndGet();

    try {
      ServerSession session =
          (ServerSession) protocolManager.getSessionByTransportId(wsSession.getId());

      if (session != null) {

        if (session.isGracefullyClosed()) {

          log.debug("{} WebSocket session {} with transportId {} closed gracefully", label,
              session.getSessionId(), wsSession.getId());

        } else {

          log.debug(
              "{} WebSocket session {} with transportId {} closed for {} (code {}, reason '{}')",
              label, session.getSessionId(), wsSession.getId(),
              CloseStatusHelper.getCloseStatusType(status.getCode()), status.getCode(),
              status.getReason());

          protocolManager.closeSessionIfTimeout(wsSession.getId(), status.getReason());
        }
      } else {
        log.debug(
            "{} WebSocket session not associated to any jsonRpcSession "
                + "with transportId {} closed for {} (code {}, reason '{}')",
            label, wsSession.getId(), CloseStatusHelper.getCloseStatusType(status.getCode()),
            status.getCode(), status.getReason());
      }

    } catch (Throwable t) {
      log.error("{} Exception processing afterConnectionClosed in session={}", label,
          wsSession.getId(), t);
    }
  }

  @Override
  public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {

    try {
      protocolManager.processTransportError(session.getId(), exception);
    } catch (Throwable t) {
      log.error(label + "Exception processing transportError in session={}", session.getId(), t);
    }
  }

  @Override
  public void handleTextMessage(final WebSocketSession wsSession, TextMessage message)
      throws Exception {

    try {

      String messageJson = message.getPayload();

      // TODO Ensure only one register message per websocket session.
      ServerSessionFactory factory = new ServerSessionFactory() {
        @Override
        public ServerSession createSession(String sessionId, Object registerInfo,
            SessionsManager sessionsManager) {
          return new WebSocketServerSession(sessionId, registerInfo, sessionsManager, wsSession);
        }

        @Override
        public void updateSessionOnReconnection(ServerSession session) {
          ((WebSocketServerSession) session).updateWebSocketSession(wsSession);
        }
      };

      protocolManager.processMessage(messageJson, factory, new ResponseSender() {
        @Override
        public void sendResponse(Message message) throws IOException {

          String jsonMessage = message.toString();
          log.debug("{} Res<- {}", label, jsonMessage);
          sendJsonMessage(jsonMessage);
        }

        @Override
        public void sendPingResponse(Message message) throws IOException {

          String jsonMessage = message.toString();
          log.trace("{} Res<- {}", label, jsonMessage);
          sendJsonMessage(jsonMessage);
        }

        private void sendJsonMessage(String jsonMessage) throws IOException {
          synchronized (wsSession) {
            if (wsSession.isOpen()) {
              wsSession.sendMessage(new TextMessage(jsonMessage));
            } else {
              log.error("Trying to send a message to a closed session");
            }
          }
        }
      }, wsSession.getId());

    } catch (Throwable t) {
      log.error("{} Exception processing request {}.", label, message.getPayload(), t);
    }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy