diff --git a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java index 8d8f0bfd..b9819698 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java +++ b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java @@ -18,12 +18,9 @@ package io.openvidu.server; import java.io.IOException; -import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; - import org.kurento.jsonrpc.internal.server.config.JsonRpcConfiguration; import org.kurento.jsonrpc.server.JsonRpcConfigurer; import org.kurento.jsonrpc.server.JsonRpcHandlerRegistry; @@ -38,8 +35,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.context.event.EventListener; -import io.openvidu.client.OpenViduException; -import io.openvidu.client.OpenViduException.Code; import io.openvidu.server.cdr.CDRLogger; import io.openvidu.server.cdr.CDRLoggerFile; import io.openvidu.server.cdr.CallDetailRecord; @@ -81,26 +76,45 @@ public class OpenViduServer implements JsonRpcConfigurer { private static final Logger log = LoggerFactory.getLogger(OpenViduServer.class); + public static final String WS_PATH = "/openvidu"; + public static String publicurlType; + public static String wsUrl; + public static String httpUrl; + @Autowired OpenviduConfig openviduConfig; - public static final String KMSS_URIS_PROPERTY = "kms.uris"; - - public static String wsUrl; - - public static String httpUrl; - @Bean @ConditionalOnMissingBean public KmsManager kmsManager() { if (openviduConfig.getKmsUris().isEmpty()) { - throw new IllegalArgumentException(KMSS_URIS_PROPERTY + " should contain at least one kms url"); + throw new IllegalArgumentException("'kms.uris' should contain at least one KMS url"); } String firstKmsWsUri = openviduConfig.getKmsUris().get(0); log.info("OpenVidu Server using one KMS: {}", firstKmsWsUri); return new FixedOneKmsManager(); } + @Bean + @ConditionalOnMissingBean + public CallDetailRecord cdr() { + List loggers = new ArrayList<>(); + if (openviduConfig.isCdrEnabled()) { + log.info("OpenVidu CDR is enabled"); + loggers.add(new CDRLoggerFile()); + } + if (openviduConfig.isWebhookEnabled()) { + loggers.add(new CDRLoggerWebhook(openviduConfig)); + } + return new CallDetailRecord(loggers); + } + + @Bean + @ConditionalOnMissingBean + public CoturnCredentialsService coturnCredentialsService() { + return new CoturnCredentialsServiceFactory().getCoturnCredentialsService(openviduConfig.getSpringProfile()); + } + @Bean @ConditionalOnMissingBean public LoadManager loadManager() { @@ -131,20 +145,6 @@ public class OpenViduServer implements JsonRpcConfigurer { return new KurentoSessionEventsHandler(); } - @Bean - @ConditionalOnMissingBean - public CallDetailRecord cdr() { - List loggers = new ArrayList<>(); - if (openviduConfig.isCdrEnabled()) { - log.info("OpenVidu CDR is enabled"); - loggers.add(new CDRLoggerFile()); - } - if (openviduConfig.isWebhookEnabled()) { - loggers.add(new CDRLoggerWebhook(openviduConfig)); - } - return new CallDetailRecord(loggers); - } - @Bean @ConditionalOnMissingBean public KurentoParticipantEndpointConfig kurentoEndpointConfig() { @@ -169,12 +169,6 @@ public class OpenViduServer implements JsonRpcConfigurer { return new DummyRecordingDownloader(); } - @Bean - @ConditionalOnMissingBean - public CoturnCredentialsService coturnCredentialsService() { - return new CoturnCredentialsServiceFactory().getCoturnCredentialsService(openviduConfig.getSpringProfile()); - } - @Bean @ConditionalOnMissingBean public GeoLocationByIp geoLocationByIp() { @@ -190,10 +184,10 @@ public class OpenViduServer implements JsonRpcConfigurer { @Override public void registerJsonRpcHandlers(JsonRpcHandlerRegistry registry) { registry.addHandler(rpcHandler().withPingWatchdog(true).withInterceptors(new HttpHandshakeInterceptor()), - "/openvidu"); + WS_PATH); } - private static String getContainerIp() throws IOException, InterruptedException { + public static String getContainerIp() throws IOException, InterruptedException { return CommandExecutor.execCommand("/bin/sh", "-c", "hostname -i | awk '{print $1}'"); } @@ -203,86 +197,14 @@ public class OpenViduServer implements JsonRpcConfigurer { SpringApplication.run(OpenViduServer.class, args); } - @PostConstruct - public void init() throws MalformedURLException, InterruptedException { - String publicUrl = this.openviduConfig.getOpenViduPublicUrl(); - String type = publicUrl; - - switch (publicUrl) { - case "docker": - try { - String containerIp = getContainerIp(); - OpenViduServer.wsUrl = "wss://" + containerIp + ":" + openviduConfig.getServerPort(); - } catch (Exception e) { - log.error("Docker container IP was configured, but there was an error obtaining IP: " - + e.getClass().getName() + " " + e.getMessage()); - log.error("Fallback to local URL"); - OpenViduServer.wsUrl = null; - } - break; - - case "local": - break; - - case "": - break; - - default: - - type = "custom"; - - if (publicUrl.startsWith("https://")) { - OpenViduServer.wsUrl = publicUrl.replace("https://", "wss://"); - } else if (publicUrl.startsWith("http://")) { - OpenViduServer.wsUrl = publicUrl.replace("http://", "wss://"); - } - - if (!OpenViduServer.wsUrl.startsWith("wss://")) { - OpenViduServer.wsUrl = "wss://" + OpenViduServer.wsUrl; - } - } - - if (OpenViduServer.wsUrl == null) { - type = "local"; - OpenViduServer.wsUrl = "wss://localhost:" + openviduConfig.getServerPort(); - } - - if (OpenViduServer.wsUrl.endsWith("/")) { - OpenViduServer.wsUrl = OpenViduServer.wsUrl.substring(0, OpenViduServer.wsUrl.length() - 1); - } - - if (this.openviduConfig.isRecordingModuleEnabled()) { - try { - this.recordingManager().initializeRecordingManager(); - } catch (OpenViduException e) { - String finalErrorMessage = ""; - if (e.getCodeValue() == Code.DOCKER_NOT_FOUND.getValue()) { - finalErrorMessage = "Error connecting to Docker daemon. Enabling OpenVidu recording module requires Docker"; - } else if (e.getCodeValue() == Code.RECORDING_PATH_NOT_VALID.getValue()) { - finalErrorMessage = "Error initializing recording path \"" - + this.openviduConfig.getOpenViduRecordingPath() - + "\" set with system property \"openvidu.recording.path\""; - } else if (e.getCodeValue() == Code.RECORDING_FILE_EMPTY_ERROR.getValue()) { - finalErrorMessage = "Error initializing recording custom layouts path \"" - + this.openviduConfig.getOpenviduRecordingCustomLayout() - + "\" set with system property \"openvidu.recording.custom-layout\""; - } - log.error(finalErrorMessage + ". Shutting down OpenVidu Server"); - System.exit(1); - } - } - - String finalUrl = OpenViduServer.wsUrl.replaceFirst("wss://", "https://").replaceFirst("ws://", "http://"); - openviduConfig.setFinalUrl(finalUrl); - httpUrl = openviduConfig.getFinalUrl(); - log.info("OpenVidu Server using " + type + " URL: [" + OpenViduServer.wsUrl + "]"); - } - @EventListener(ApplicationReadyEvent.class) public void whenReady() { + log.info("OpenVidu Server listening for client websocket connections on" + + (OpenViduServer.publicurlType.isEmpty() ? "" : (" " + OpenViduServer.publicurlType)) + " url " + + OpenViduServer.wsUrl + WS_PATH); final String NEW_LINE = System.lineSeparator(); - String str = NEW_LINE + NEW_LINE + " ACCESS IP " + NEW_LINE + "-------------------------" - + NEW_LINE + httpUrl + NEW_LINE + "-------------------------" + NEW_LINE; + String str = NEW_LINE + NEW_LINE + " OPENVIDU SERVER IP " + NEW_LINE + "--------------------------" + + NEW_LINE + httpUrl + NEW_LINE + "--------------------------" + NEW_LINE; log.info(str); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index 95804b31..5496ae2a 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -19,12 +19,18 @@ package io.openvidu.server.config; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.PostConstruct; @@ -46,18 +52,42 @@ import com.google.gson.JsonElement; import com.google.gson.JsonParser; import io.openvidu.java.client.OpenViduRole; +import io.openvidu.server.OpenViduServer; import io.openvidu.server.cdr.CDREventName; +import io.openvidu.server.recording.RecordingNotification; @Component public class OpenviduConfig { private static final Logger log = LoggerFactory.getLogger(OpenviduConfig.class); + public static final List OPENVIDU_STRING_PROPERTIES = Arrays.asList(new String[] { "openvidu.secret", + "openvidu.publicurl", "openvidu.recording.path", "openvidu.recording.notification", + "openvidu.recording.custom-layout", "openvidu.webhook.endpoint" }); + + public static final List OPENVIDU_INTEGER_PROPERTIES = Arrays + .asList(new String[] { "openvidu.recording.autostop-timeout", "openvidu.streams.video.max-recv-bandwidth", + "openvidu.streams.video.min-recv-bandwidth", "openvidu.streams.video.max-send-bandwidth", + "openvidu.streams.video.min-send-bandwidth" }); + + public static final List OPENVIDU_BOOLEAN_PROPERTIES = Arrays.asList(new String[] { "openvidu.cdr", + "openvidu.recording", "openvidu.recording.public-access", "openvidu.webhook", }); + + public static final List OPENVIDU_ARRAY_PROPERTIES = Arrays + .asList(new String[] { "kms.uris", "openvidu.webhook.headers", "openvidu.webhook.events", }); + + public static final List OPENVIDU_PROPERTIES = Stream.of(OPENVIDU_STRING_PROPERTIES, + OPENVIDU_INTEGER_PROPERTIES, OPENVIDU_BOOLEAN_PROPERTIES, OPENVIDU_ARRAY_PROPERTIES) + .flatMap(Collection::stream).collect(Collectors.toList()); + + public static final List OPENVIDU_VALID_PUBLICURL_VALUES = Arrays + .asList(new String[] { "local", "docker", "" }); + @Value("#{'${spring.config.additional-location:}'.length() > 0 ? '${spring.config.additional-location:}' : \"\"}") private String springConfigLocation; @Autowired - BuildProperties buildProperties; + private BuildProperties buildProperties; @Value("${kms.uris}") private String kmsUris; @@ -87,7 +117,7 @@ public class OpenviduConfig { private boolean openviduRecordingPublicAccess; @Value("${openvidu.recording.notification}") - private String openviduRecordingNotification; + private RecordingNotification openviduRecordingNotification; @Value("${openvidu.recording.custom-layout}") private String openviduRecordingCustomLayout; @@ -144,72 +174,9 @@ public class OpenviduConfig { private List kmsUrisList; private List
webhookHeadersList; private List webhookEventsList; + private Properties externalizedProperties; - @PostConstruct - public void init() { - - if (!this.springConfigLocation.isEmpty()) { - // Properties file has been manually configured in certain path - FileSystemResource resource = new FileSystemResource(this.springConfigLocation); - try { - this.externalizedProperties = PropertiesLoaderUtils.loadProperties(resource); - log.info("Properties file found at \"{}\". Content: {}", this.springConfigLocation, - externalizedProperties); - } catch (IOException e) { - log.error("Error in 'spring.config.additional-location' system property: {}", e.getMessage()); - log.error("Shutting down OpenVidu Server"); - System.exit(1); - } - // Check OpenVidu Server write permissions in properties path - if (!Files.isWritable(Paths.get(this.springConfigLocation))) { - log.warn( - "The properties path '{}' set with property 'spring.config.additional-location' is not valid. Reason: OpenVidu Server needs write permissions. Try running command \"sudo chmod 777 {}\". If not, OpenVidu won't be able to overwrite preexisting properties on reboot", - this.springConfigLocation, this.springConfigLocation); - } else { - log.info("OpenVidu Server has write permissions on properties path: {}", this.springConfigLocation); - } - } - - try { - this.kmsUrisList = this.kmsUrisStringToList(this.kmsUris); - } catch (Exception e) { - log.error("Error in 'kms.uris' system property: " + e.getMessage()); - log.error("Shutting down OpenVidu Server"); - System.exit(1); - } - if (this.isWebhookEnabled()) { - log.info("OpenVidu Webhook service enabled"); - try { - if (this.openviduWebhookEndpoint == null || this.openviduWebhookEndpoint.isEmpty()) { - log.error( - "If OpenVidu Webhook service is enabled property 'openvidu.webhook.endpoint' must be defined"); - log.error("Shutting down OpenVidu Server"); - System.exit(1); - } - this.initiateOpenViduWebhookEndpoint(this.openviduWebhookEndpoint); - } catch (Exception e) { - log.error("Error in 'openvidu.webhook.endpoint' system property. " + e.getMessage()); - log.error("Shutting down OpenVidu Server"); - System.exit(1); - } - try { - this.initiateOpenViduWebhookHeaders(this.openviduWebhookHeaders); - } catch (Exception e) { - log.error("Error in 'openvidu.webhook.headers' system property: " + e.getMessage()); - log.error("Shutting down OpenVidu Server"); - System.exit(1); - } - try { - this.initiateOpenViduWebhookEvents(this.openviduWebhookEvents); - } catch (Exception e) { - log.error("Error in 'openvidu.webhook.events' system property: " + e.getMessage()); - log.error("Shutting down OpenVidu Server"); - System.exit(1); - } - } - } - public List getKmsUris() { return this.kmsUrisList; } @@ -311,7 +278,7 @@ public class OpenviduConfig { return this.coturnRedisDbname; } - public String getOpenViduRecordingNotification() { + public RecordingNotification getOpenViduRecordingNotification() { return this.openviduRecordingNotification; } @@ -338,16 +305,16 @@ public class OpenviduConfig { public OpenViduRole[] getRolesFromRecordingNotification() { OpenViduRole[] roles; switch (this.openviduRecordingNotification) { - case "none": + case none: roles = new OpenViduRole[0]; break; - case "moderator": + case moderator: roles = new OpenViduRole[] { OpenViduRole.MODERATOR }; break; - case "publisher_moderator": + case publisher_moderator: roles = new OpenViduRole[] { OpenViduRole.PUBLISHER, OpenViduRole.MODERATOR }; break; - case "all": + case all: roles = new OpenViduRole[] { OpenViduRole.SUBSCRIBER, OpenViduRole.PUBLISHER, OpenViduRole.MODERATOR }; break; default: @@ -380,6 +347,256 @@ public class OpenviduConfig { return this.externalizedProperties; } + public void checkWebsocketUri(String uri) throws Exception { + try { + if (!uri.startsWith("ws://") || uri.startsWith("wss://")) { + throw new Exception("WebSocket protocol not found"); + } + String parsedUri = uri.replaceAll("^ws://", "http://").replaceAll("^wss://", "https://"); + new URL(parsedUri).toURI(); + } catch (Exception e) { + throw new Exception("URI '" + uri + "' has not a valid WebSocket endpoint format: " + e.getMessage()); + } + } + + public void checkConfigurationParameters(Map parameters, Collection validKeys) throws Exception { + + parameters = this.filterValidParameters(parameters, validKeys); + + log.info("Checking configuration parameters: {}", parameters.keySet()); + + for (String parameter : parameters.keySet()) { + switch (parameter) { + case "openvidu.secret": + String secret = checkString(parameters, parameter); + if (secret.isEmpty()) { + throw new Exception("Property 'openvidu.secret' cannot be empty"); + } + break; + case "openvidu.publicurl": + String publicurl = checkString(parameters, parameter); + if (!OPENVIDU_VALID_PUBLICURL_VALUES.contains(publicurl)) { + // Must be a valid URL + try { + new URL(publicurl).toURI(); + } catch (MalformedURLException | URISyntaxException e) { + throw new Exception( + "Property 'openvidu.publicurl' has not a valid URL format: " + e.getMessage()); + } + } + break; + case "openvidu.cdr": + checkBoolean(parameters, parameter); + break; + case "openvidu.recording": + checkBoolean(parameters, parameter); + break; + case "openvidu.recording.public-access": + checkBoolean(parameters, parameter); + break; + case "openvidu.recording.autostop-timeout": + checkIntegerNonNegative(parameters, parameter); + break; + case "openvidu.recording.notification": + String recordingNotif = checkString(parameters, parameter); + try { + RecordingNotification.valueOf(recordingNotif); + } catch (IllegalArgumentException e) { + throw new Exception("Property 'openvidu.recording.notification' has not a valid value ('" + + recordingNotif + "'). Must be one of " + Arrays.asList(RecordingNotification.values())); + } + break; + case "openvidu.webhook": + checkBoolean(parameters, parameter); + break; + case "openvidu.webhook.endpoint": + String webhookEndpoint = checkString(parameters, parameter); + try { + checkWebhookEndpoint(webhookEndpoint); + } catch (Exception e) { + throw new Exception("Property 'openvidu.webhook.endpoint' is not valid: " + e.getMessage()); + } + break; + case "openvidu.streams.video.max-recv-bandwidth": + checkIntegerNonNegative(parameters, parameter); + break; + case "openvidu.streams.video.min-recv-bandwidth": + checkIntegerNonNegative(parameters, parameter); + break; + case "openvidu.streams.video.max-send-bandwidth": + checkIntegerNonNegative(parameters, parameter); + break; + case "openvidu.streams.video.min-send-bandwidth": + checkIntegerNonNegative(parameters, parameter); + break; + case "kms.uris": + String kmsUris; + try { + // First check if castable to a List + List list = checkArray(parameters, parameter); + String elementString; + for (Object element : list) { + try { + // Check every object is a String value + elementString = (String) element; + } catch (ClassCastException e) { + throw new Exception("Property 'kms.uris' is an array, but contains a value (" + element + + ") that is not a string: " + e.getMessage()); + } + } + kmsUris = list.toString(); + } catch (Exception e) { + // If it is not a list, try casting to String + kmsUris = checkString(parameters, parameter); + } + // Finally check all strings have a valid WebSocket URI format + try { + kmsUrisStringToList(kmsUris); + } catch (Exception e) { + throw new Exception( + "Property 'kms.uris' is an array of strings, but contains some value that has not a valid WbeSocket URI format: " + + e.getMessage()); + } + break; + case "openvidu.webhook.headers": + String webhookHeaders; + try { + // First check if castable to a List + List list = checkArray(parameters, parameter); + String elementString; + for (Object element : list) { + try { + // Check every object is a String value + elementString = (String) element; + } catch (ClassCastException e) { + throw new Exception( + "Property 'openvidu.webhook.headers' is an array, but contains a value (" + element + + ") that is not a string: " + e.getMessage()); + } + } + webhookHeaders = list.toString(); + } catch (Exception e) { + // If it is not a list, try casting to String + webhookHeaders = checkString(parameters, parameter); + } + try { + checkWebhookHeaders(webhookHeaders); + } catch (Exception e) { + throw new Exception( + "Property 'openvidu.webhook.headers' contains a value not valid: " + e.getMessage()); + } + break; + case "openvidu.webhook.events": + String webhookEvents; + try { + // First check if castable to a List + List list = checkArray(parameters, parameter); + String elementString; + for (Object element : list) { + try { + // Check every object is a String value + elementString = (String) element; + } catch (ClassCastException e) { + throw new Exception("Property 'openvidu.webhook.events' is an array, but contains a value (" + + element + ") that is not a string: " + e.getMessage()); + } + } + webhookEvents = list.toString(); + } catch (Exception e) { + // If it is not a list, try casting to String + webhookEvents = checkString(parameters, parameter); + } + try { + checkWebhookEvents(webhookEvents); + } catch (Exception e) { + throw new Exception( + "Property 'openvidu.webhook.events' contains a value not valid: " + e.getMessage()); + } + break; + case "openvidu.recording.path": + checkString(parameters, parameter); + break; + case "openvidu.recording.custom-layout": + checkString(parameters, parameter); + break; + default: + log.warn("Unknown configuration parameter '{}'", parameter); + } + } + } + + public String checkString(Map parameters, String key) throws Exception { + try { + String stringValue = (String) parameters.get(key); + return stringValue; + } catch (ClassCastException e) { + throw new Exception("Property '" + key + "' must be a string: " + e.getMessage()); + } + } + + public boolean checkBoolean(Map parameters, String key) throws Exception { + try { + boolean booleanValue = Boolean.parseBoolean((String) parameters.get(key)); + return booleanValue; + } catch (ClassCastException e) { + throw new Exception("Property '" + key + "' must be a boolean: " + e.getMessage()); + } + } + + public Integer checkIntegerNonNegative(Map parameters, String key) throws Exception { + try { + Integer integerValue = Integer.parseInt((String) parameters.get(key)); + if (integerValue < 0) { + throw new Exception("Property '" + key + "' is an integer but cannot be less than 0 (current value: " + + integerValue + ")"); + } + return integerValue; + } catch (ClassCastException e) { + throw new Exception("Property '" + key + "' must be an integer: " + e.getMessage()); + } + } + + public List checkArray(Map parameters, String key) throws Exception { + try { + List list = (List) parameters.get(key); + return list; + } catch (ClassCastException e) { + throw new Exception("Property '" + key + "' must be an array: " + e.getMessage()); + } + } + + public Map filterValidParameters(Map parameters, Collection validKeys) { + return parameters.entrySet().stream().filter(x -> validKeys.contains(x.getKey())) + .collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue())); + } + + public Properties retrieveExternalizedProperties() throws Exception { + + if (this.springConfigLocation.isEmpty()) { + throw new Exception( + "Externalized properties file not found. Path must be set with configuration parameter 'spring.config.additional-location'"); + } + + Properties externalizedProps = null; + FileSystemResource resource = new FileSystemResource(this.springConfigLocation); + try { + externalizedProps = PropertiesLoaderUtils.loadProperties(resource); + log.info("Properties file found at \"{}\". Content: {}", this.springConfigLocation, externalizedProps); + } catch (IOException e) { + throw new Exception("Error in 'spring.config.additional-location' system property. Cannot load properties: " + + e.getMessage()); + } + // Check OpenVidu Server write permissions in properties path + if (!Files.isWritable(Paths.get(this.springConfigLocation))) { + log.warn( + "The properties path '{}' set with property 'spring.config.additional-location' is missconfigured. Reason: OpenVidu Server needs write permissions. Try running command \"sudo chmod 777 {}\". If not, OpenVidu won't be able to overwrite preexisting properties", + this.springConfigLocation, this.springConfigLocation); + } else { + log.info("OpenVidu Server has write permissions on properties path {}", this.springConfigLocation); + } + return externalizedProps; + } + public List kmsUrisStringToList(String kmsUris) throws Exception { kmsUris = kmsUris.replaceAll("\\s", ""); // Remove all white spaces kmsUris = kmsUris.replaceAll("\\\\", ""); // Remove previous escapes @@ -392,7 +609,6 @@ public class OpenviduConfig { List list = JsonUtils.toStringList(kmsUrisArray); if (list.size() == 1 && list.get(0).isEmpty()) { - log.warn("Array kms.uris is empty"); list = new ArrayList<>(); } else { for (String uri : list) { @@ -402,20 +618,20 @@ public class OpenviduConfig { return list; } - public void initiateOpenViduWebhookEndpoint(String endpoint) throws Exception { + private void checkWebhookEndpoint(String endpoint) throws Exception { try { - new URL(endpoint); + new URL(endpoint).toURI(); log.info("OpenVidu Webhook endpoint is {}", endpoint); - } catch (MalformedURLException e) { + } catch (MalformedURLException | URISyntaxException e) { throw new Exception("Webhook endpoint '" + endpoint + "' is not correct. Malformed URL: " + e.getMessage()); } } - public void initiateOpenViduWebhookHeaders(String headers) throws Exception { + private List
checkWebhookHeaders(String headers) throws Exception { JsonParser parser = new JsonParser(); JsonElement elem = parser.parse(headers); JsonArray headersJsonArray = elem.getAsJsonArray(); - this.webhookHeadersList = new ArrayList<>(); + List
headerList = new ArrayList<>(); for (JsonElement jsonElement : headersJsonArray) { String headerString = jsonElement.getAsString(); @@ -434,37 +650,112 @@ public class OpenviduConfig { throw new Exception( "HTTP header '" + headerString + "' syntax is not correct. Header value cannot be empty"); } - this.webhookHeadersList.add(new BasicHeader(headerName, headerValue)); + headerList.add(new BasicHeader(headerName, headerValue)); } - log.info("OpenVidu Webhook headers: {}", this.getOpenViduWebhookHeaders().toString()); + return headerList; } - public void initiateOpenViduWebhookEvents(String events) throws Exception { + private List checkWebhookEvents(String events) throws Exception { JsonParser parser = new JsonParser(); JsonElement elem = parser.parse(events); JsonArray eventsJsonArray = elem.getAsJsonArray(); - this.webhookEventsList = new ArrayList<>(); + List eventList = new ArrayList<>(); for (JsonElement jsonElement : eventsJsonArray) { String eventString = jsonElement.getAsString(); try { CDREventName.valueOf(eventString); } catch (IllegalArgumentException e) { - throw new Exception("Event name '" + eventString + "' does not exist"); + throw new Exception("Event '" + eventString + "' does not exist"); } - this.webhookEventsList.add(CDREventName.valueOf(eventString)); + eventList.add(CDREventName.valueOf(eventString)); } - log.info("OpenVidu Webhook events: {}", this.getOpenViduWebhookEvents().toString()); + return eventList; } - public void checkWebsocketUri(String uri) throws MalformedURLException { - try { - String parsedUri = uri.replaceAll("^ws://", "http://").replaceAll("^wss://", "https://"); - new URL(parsedUri); - } catch (MalformedURLException e) { - log.error("URI {} is not a valid WebSocket endpoint", uri); - throw e; + @PostConstruct + public void init() { + + // Check configuration parameters + Map props = null; + if (!this.springConfigLocation.isEmpty()) { + try { + this.externalizedProperties = this.retrieveExternalizedProperties(); + props = (Map) this.externalizedProperties; + } catch (Exception e) { + log.error(e.getMessage()); + log.error("Shutting down OpenVidu Server"); + System.exit(1); + } + } else { + props = (Map) System.getProperties(); } + + try { + this.checkConfigurationParameters(props, OPENVIDU_PROPERTIES); + } catch (Exception e) { + log.error(e.getMessage()); + log.error("Shutting down OpenVidu Server"); + System.exit(1); + } + + try { + this.kmsUrisList = this.kmsUrisStringToList(this.kmsUris); + if (this.isWebhookEnabled()) { + this.webhookHeadersList = this.checkWebhookHeaders(this.openviduWebhookHeaders); + this.webhookEventsList = this.checkWebhookEvents(this.openviduWebhookEvents); + log.info("OpenVidu Webhook endpoint: {}", this.openviduWebhookEndpoint); + log.info("OpenVidu Webhook headers: {}", this.getOpenViduWebhookHeaders().toString()); + log.info("OpenVidu Webhook events: {}", this.getOpenViduWebhookEvents().toString()); + } + } catch (Exception e) { + log.error("Unexpected exception when setting final value of configuration parameters: {}", e.getMessage()); + } + + // Generate final public url + String publicUrl = this.getOpenViduPublicUrl(); + String type = ""; + switch (publicUrl) { + case "docker": + try { + String containerIp = OpenViduServer.getContainerIp(); + OpenViduServer.wsUrl = "wss://" + containerIp + ":" + this.getServerPort(); + } catch (Exception e) { + log.error("Docker container IP was configured, but there was an error obtaining IP: " + + e.getClass().getName() + " " + e.getMessage()); + log.error("Fallback to local URL"); + OpenViduServer.wsUrl = null; + } + break; + case "local": + break; + case "": + break; + default: + if (publicUrl.startsWith("https://")) { + OpenViduServer.wsUrl = publicUrl.replace("https://", "wss://"); + } else if (publicUrl.startsWith("http://")) { + OpenViduServer.wsUrl = publicUrl.replace("http://", "wss://"); + } + + if (!OpenViduServer.wsUrl.startsWith("wss://")) { + OpenViduServer.wsUrl = "wss://" + OpenViduServer.wsUrl; + } + } + + if (OpenViduServer.wsUrl == null) { + type = "local"; + OpenViduServer.wsUrl = "wss://localhost:" + this.getServerPort(); + } + + if (OpenViduServer.wsUrl.endsWith("/")) { + OpenViduServer.wsUrl = OpenViduServer.wsUrl.substring(0, OpenViduServer.wsUrl.length() - 1); + } + + String finalUrl = OpenViduServer.wsUrl.replaceFirst("wss://", "https://").replaceFirst("ws://", "http://"); + this.setFinalUrl(finalUrl); + OpenViduServer.httpUrl = this.getFinalUrl(); + OpenViduServer.publicurlType = type; } } \ No newline at end of file diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/RecordingNotification.java b/openvidu-server/src/main/java/io/openvidu/server/recording/RecordingNotification.java new file mode 100644 index 00000000..7802e158 --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/RecordingNotification.java @@ -0,0 +1,31 @@ +package io.openvidu.server.recording; + +/** + * Defines which users should receive the Session recording notifications on the + * client side (recordingStarted, recordingStopped) + * + * @author Pablo Fuente (pablofuenteperez@gmail.com) + */ +public enum RecordingNotification { + + /* + * No user of the session will receive recording events + */ + none, + + /* + * Only users with role MODERATOR will receive recording events + */ + moderator, + + /* + * Users with role MODERATOR or PUBLISHER will receive recording events + */ + publisher_moderator, + + /* + * All users of to the session will receive recording events + */ + all + +} diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java index a53c8c2a..78564056 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java @@ -36,6 +36,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; + import org.apache.commons.io.FileUtils; import org.kurento.client.ErrorEvent; import org.kurento.client.EventListener; @@ -116,6 +118,30 @@ public class RecordingManager { .asList(new EndReason[] { EndReason.disconnect, EndReason.forceDisconnectByUser, EndReason.forceDisconnectByServer, EndReason.networkDisconnect }); + @PostConstruct + public void init() { + if (this.openviduConfig.isRecordingModuleEnabled()) { + try { + this.initializeRecordingManager(); + } catch (OpenViduException e) { + String finalErrorMessage = ""; + if (e.getCodeValue() == Code.DOCKER_NOT_FOUND.getValue()) { + finalErrorMessage = "Error connecting to Docker daemon. Enabling OpenVidu recording module requires Docker"; + } else if (e.getCodeValue() == Code.RECORDING_PATH_NOT_VALID.getValue()) { + finalErrorMessage = "Error initializing recording path \"" + + this.openviduConfig.getOpenViduRecordingPath() + + "\" set with system property \"openvidu.recording.path\""; + } else if (e.getCodeValue() == Code.RECORDING_FILE_EMPTY_ERROR.getValue()) { + finalErrorMessage = "Error initializing recording custom layouts path \"" + + this.openviduConfig.getOpenviduRecordingCustomLayout() + + "\" set with system property \"openvidu.recording.custom-layout\""; + } + log.error(finalErrorMessage + ". Shutting down OpenVidu Server"); + System.exit(1); + } + } + } + public void initializeRecordingManager() throws OpenViduException { RecordingManager.IMAGE_TAG = openviduConfig.getOpenViduRecordingVersion(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java index 961a6c16..24a9d391 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java @@ -48,7 +48,7 @@ public class ConfigRestController { private static final Logger log = LoggerFactory.getLogger(ConfigRestController.class); @Autowired - protected OpenviduConfig openviduConfig; + private OpenviduConfig openviduConfig; @RequestMapping(value = "/openvidu-version", method = RequestMethod.GET) public String getOpenViduServerVersion() { @@ -108,7 +108,7 @@ public class ConfigRestController { json.addProperty("openviduRecordingVersion", openviduConfig.getOpenViduRecordingVersion()); json.addProperty("openviduRecordingPath", openviduConfig.getOpenViduRecordingPath()); json.addProperty("openviduRecordingPublicAccess", openviduConfig.getOpenViduRecordingPublicAccess()); - json.addProperty("openviduRecordingNotification", openviduConfig.getOpenViduRecordingNotification()); + json.addProperty("openviduRecordingNotification", openviduConfig.getOpenViduRecordingNotification().name()); json.addProperty("openviduRecordingCustomLayout", openviduConfig.getOpenviduRecordingCustomLayout()); json.addProperty("openviduRecordingAutostopTimeout", openviduConfig.getOpenviduRecordingAutostopTimeout()); }