openvidu/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java

316 lines
12 KiB
Java

package io.openvidu.server.rpc;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.kurento.jsonrpc.DefaultJsonRpcHandler;
import org.kurento.jsonrpc.Session;
import org.kurento.jsonrpc.Transaction;
import org.kurento.jsonrpc.message.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.gson.JsonObject;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.SessionManager;
import io.openvidu.server.core.Token;
public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
private static final Logger log = LoggerFactory.getLogger(RpcHandler.class);
@Autowired
OpenviduConfig openviduConfig;
@Autowired
SessionManager sessionManager;
@Autowired
RpcNotificationService notificationService;
private ConcurrentMap<String, Boolean> webSocketTransportError = new ConcurrentHashMap<>();
@Override
public void handleRequest(Transaction transaction, Request<JsonObject> request) throws Exception {
String participantPrivateId = null;
try {
participantPrivateId = transaction.getSession().getSessionId();
} catch (Throwable e) {
log.error("Error getting WebSocket session ID from transaction {}", transaction, e);
throw e;
}
log.debug("WebSocket session #{} - Request: {}", participantPrivateId, request);
RpcConnection rpcConnection = notificationService.addTransaction(transaction, request);
transaction.startAsync();
switch (request.getMethod()) {
case ProtocolElements.JOINROOM_METHOD:
joinRoom(rpcConnection, request);
break;
case ProtocolElements.LEAVEROOM_METHOD:
leaveRoom(rpcConnection, request);
break;
case ProtocolElements.PUBLISHVIDEO_METHOD:
publishVideo(rpcConnection, request);
break;
case ProtocolElements.ONICECANDIDATE_METHOD:
onIceCandidate(rpcConnection, request);
break;
case ProtocolElements.RECEIVEVIDEO_METHOD:
receiveVideoFrom(rpcConnection, request);
break;
case ProtocolElements.UNSUBSCRIBEFROMVIDEO_METHOD:
unsubscribeFromVideo(rpcConnection, request);
break;
case ProtocolElements.SENDMESSAGE_ROOM_METHOD:
sendMessage(rpcConnection, request);
break;
case ProtocolElements.UNPUBLISHVIDEO_METHOD:
unpublishVideo(rpcConnection, request);
break;
default:
log.error("Unrecognized request {}", request);
break;
}
}
public void joinRoom(RpcConnection rpcConnection, Request<JsonObject> request) {
String sessionId = getStringParam(request, ProtocolElements.JOINROOM_ROOM_PARAM);
String token = getStringParam(request, ProtocolElements.JOINROOM_TOKEN_PARAM);
String secret = getStringParam(request, ProtocolElements.JOINROOM_SECRET_PARAM);
String participantPrivatetId = rpcConnection.getParticipantPrivateId();
boolean recorder = false;
try {
recorder = getBooleanParam(request, ProtocolElements.JOINROOM_RECORDER_PARAM);
} catch (RuntimeException e) {
// Nothing happens. 'recorder' param to false
}
boolean generateRecorderParticipant = false;
if (openviduConfig.isOpenViduSecret(secret)) {
sessionManager.newInsecureParticipant(participantPrivatetId);
if (recorder) {
generateRecorderParticipant = true;
}
}
if (sessionManager.isTokenValidInSession(token, sessionId, participantPrivatetId)) {
String clientMetadata = getStringParam(request, ProtocolElements.JOINROOM_METADATA_PARAM);
if (sessionManager.isMetadataFormatCorrect(clientMetadata)) {
Token tokenObj = sessionManager.consumeToken(sessionId, participantPrivatetId, token);
Participant participant;
if (generateRecorderParticipant) {
participant = sessionManager.newRecorderParticipant(sessionId, participantPrivatetId, tokenObj,
clientMetadata);
} else {
participant = sessionManager.newParticipant(sessionId, participantPrivatetId, tokenObj,
clientMetadata);
}
rpcConnection.setSessionId(sessionId);
sessionManager.joinRoom(participant, sessionId, request.getId());
} else {
log.error("ERROR: Metadata format is incorrect");
throw new OpenViduException(Code.USER_METADATA_FORMAT_INVALID_ERROR_CODE,
"Unable to join room. The metadata received has an invalid format");
}
} else {
log.error("ERROR: sessionId or token not valid");
throw new OpenViduException(Code.USER_UNAUTHORIZED_ERROR_CODE,
"Unable to join room. The user is not authorized");
}
}
private void leaveRoom(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
if (sessionId == null) { // null when afterConnectionClosed
log.warn("No session information found for participant with privateId {}. "
+ "Using the admin method to evict the user.", participantPrivateId);
leaveRoomAfterConnClosed(participantPrivateId);
} else {
// Sanity check: don't call leaveRoom unless the id checks out
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
if (participant != null) {
log.info("Participant {} is leaving session {}", participant.getParticipantPublicId(), sessionId);
sessionManager.leaveRoom(participant, request.getId());
log.info("Participant {} has left session {}", participant.getParticipantPublicId(), sessionId);
} else {
log.warn("Participant with private id {} not found in session {}. "
+ "Using the admin method to evict the user.", participantPrivateId, sessionId);
leaveRoomAfterConnClosed(participantPrivateId);
}
}
}
private void publishVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
if (sessionManager.isPublisherInSession(sessionId, participant)) {
MediaOptions options = sessionManager.generateMediaOptions(request);
sessionManager.publishVideo(participant, options, request.getId());
} else {
log.error("Error: participant {} is not a publisher", participant.getParticipantPublicId());
throw new OpenViduException(Code.USER_UNAUTHORIZED_ERROR_CODE,
"Unable to publish video. The user does not have a valid token");
}
}
private void receiveVideoFrom(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
String senderName = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
senderName = senderName.substring(0, senderName.indexOf("_"));
String sdpOffer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM);
sessionManager.subscribe(participant, senderName, sdpOffer, request.getId());
}
private void unsubscribeFromVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
String senderName = getStringParam(request, ProtocolElements.UNSUBSCRIBEFROMVIDEO_SENDER_PARAM);
sessionManager.unsubscribe(participant, senderName, request.getId());
}
private void onIceCandidate(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
String endpointName = getStringParam(request, ProtocolElements.ONICECANDIDATE_EPNAME_PARAM);
String candidate = getStringParam(request, ProtocolElements.ONICECANDIDATE_CANDIDATE_PARAM);
String sdpMid = getStringParam(request, ProtocolElements.ONICECANDIDATE_SDPMIDPARAM);
int sdpMLineIndex = getIntParam(request, ProtocolElements.ONICECANDIDATE_SDPMLINEINDEX_PARAM);
sessionManager.onIceCandidate(participant, endpointName, candidate, sdpMLineIndex, sdpMid, request.getId());
}
private void sendMessage(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
String message = getStringParam(request, ProtocolElements.SENDMESSAGE_MESSAGE_PARAM);
sessionManager.sendMessage(participant, message, request.getId());
}
private void unpublishVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
String participantPrivateId = rpcConnection.getParticipantPrivateId();
String sessionId = rpcConnection.getSessionId();
Participant participant = sessionManager.getParticipant(sessionId, participantPrivateId);
sessionManager.unpublishVideo(participant, request.getId());
}
public void leaveRoomAfterConnClosed(String participantPrivateId) {
try {
sessionManager.evictParticipant(participantPrivateId);
log.info("Evicted participant with privateId {}", participantPrivateId);
} catch (OpenViduException e) {
log.warn("Unable to evict: {}", e.getMessage());
log.trace("Unable to evict user", e);
}
}
@Override
public void afterConnectionEstablished(Session rpcSession) throws Exception {
log.info("Connection established for WebSocket session: {}", rpcSession.getSessionId());
}
@Override
public void afterConnectionClosed(Session rpcSession, String status) throws Exception {
log.info("Connection closed for WebSocket session: {} - Status: {}", rpcSession.getSessionId(), status);
this.notificationService.showRpcConnections();
String rpcSessionId = rpcSession.getSessionId();
if (this.webSocketTransportError.get(rpcSessionId) != null) {
log.warn(
"Evicting participant with private id {} because a transport error took place and its web socket connection is now closed",
rpcSession.getSessionId());
this.leaveRoomAfterConnClosed(rpcSessionId);
this.webSocketTransportError.remove(rpcSessionId);
}
}
@Override
public void handleTransportError(Session rpcSession, Throwable exception) throws Exception {
log.error("Transport exception for WebSocket session: {} - Exception: {}", rpcSession.getSessionId(),
exception);
if ("EOFException".equals(exception.getClass().getSimpleName())) {
// Store WebSocket connection interrupted exception for this web socket to
// automatically evict the participant on "afterConnectionClosed" event
this.webSocketTransportError.put(rpcSession.getSessionId(), true);
}
}
@Override
public void handleUncaughtException(Session rpcSession, Exception exception) {
log.error("Uncaught exception for WebSocket session: {} - Exception: {}", rpcSession.getSessionId(), exception);
}
@Override
public List<String> allowedOrigins() {
return Arrays.asList("*");
}
public static String getStringParam(Request<JsonObject> request, String key) {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request.getMethod() + "'. CHECK THAT 'openvidu-server' AND 'openvidu-browser' SHARE THE SAME VERSION NUMBER");
}
return request.getParams().get(key).getAsString();
}
public static int getIntParam(Request<JsonObject> request, String key) {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request.getMethod() + "'. CHECK THAT 'openvidu-server' AND 'openvidu-browser' SHARE THE SAME VERSION NUMBER");
}
return request.getParams().get(key).getAsInt();
}
public static boolean getBooleanParam(Request<JsonObject> request, String key) {
if (request.getParams() == null || request.getParams().get(key) == null) {
throw new RuntimeException("Request element '" + key + "' is missing in method '" + request.getMethod() + "'. CHECK THAT 'openvidu-server' AND 'openvidu-browser' SHARE THE SAME VERSION NUMBER");
}
return request.getParams().get(key).getAsBoolean();
}
}