mirror of https://github.com/OpenVidu/openvidu.git
316 lines
12 KiB
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();
|
|
}
|
|
|
|
}
|