openvidu-java-client: refactoring to use Java HttpClient. Allow custom client

pull/786/head
pabloFuente 2023-01-27 16:15:20 +01:00
parent 836fa84cd1
commit b78b127447
15 changed files with 929 additions and 618 deletions

View File

@ -63,11 +63,6 @@
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@ -106,6 +101,7 @@
<version>${version.javadoc.plugin}</version>
<configuration>
<show>public</show>
<excludePackageNames>io.openvidu.java.client.utils</excludePackageNames>
</configuration>
</plugin>
</plugins>
@ -142,6 +138,7 @@
<configuration>
<show>public</show>
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
<excludePackageNames>io.openvidu.java.client.utils</excludePackageNames>
</configuration>
<executions>
<execution>

View File

@ -17,19 +17,23 @@
package io.openvidu.java.client;
import com.google.gson.JsonObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.validator.routines.DomainValidator;
import org.apache.commons.validator.routines.InetAddressValidator;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.Inet6Address;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.validator.routines.DomainValidator;
import org.apache.commons.validator.routines.InetAddressValidator;
import com.google.gson.JsonObject;
/**
* See
@ -37,331 +41,352 @@ import java.util.*;
*/
public class IceServerProperties {
private String url;
private String username;
private String credential;
private String url;
private String username;
private String credential;
/**
* Returns the defined ICE Server url for this {@link IceServerProperties} object.
*/
public String getUrl() {
return url;
}
/**
* Returns the defined ICE Server url for this {@link IceServerProperties}
* object.
*/
public String getUrl() {
return url;
}
/**
* Returns the Username to be used for TURN connections at the defined {@link IceServerProperties#getUrl()}
* and {@link IceServerProperties#getCredential()} for this {@link IceServerProperties} object.
*/
public String getUsername() {
return username;
}
/**
* Returns the Username to be used for TURN connections at the defined
* {@link IceServerProperties#getUrl()} and
* {@link IceServerProperties#getCredential()} for this
* {@link IceServerProperties} object.
*/
public String getUsername() {
return username;
}
/**
* Returns the credential to be used for TURN connections at the defined {@link IceServerProperties#getUrl()}
* and {@link IceServerProperties#getUsername()} for this {@link IceServerProperties} object.
*/
public String getCredential() {
return credential;
}
/**
* Returns the credential to be used for TURN connections at the defined
* {@link IceServerProperties#getUrl()} and
* {@link IceServerProperties#getUsername()} for this
* {@link IceServerProperties} object.
*/
public String getCredential() {
return credential;
}
private IceServerProperties(String url, String username, String credential) {
this.url = url;
this.username = username;
this.credential = credential;
}
private IceServerProperties(String url, String username, String credential) {
this.url = url;
this.username = username;
this.credential = credential;
}
/**
* @hidden
*/
public JsonObject toJson() {
JsonObject json = new JsonObject();
json.addProperty("url", getUrl());
if (getUsername() != null && !getUsername().isEmpty()) {
json.addProperty("username", getUsername());
}
if (getCredential() != null && !getCredential().isEmpty()) {
json.addProperty("credential", getCredential());
}
return json;
}
/**
* @hidden
*/
public JsonObject toJson() {
JsonObject json = new JsonObject();
json.addProperty("url", getUrl());
if (getUsername() != null && !getUsername().isEmpty()) {
json.addProperty("username", getUsername());
}
if (getCredential() != null && !getCredential().isEmpty()) {
json.addProperty("credential", getCredential());
}
return json;
}
/**
* Builder for {@link IceServerProperties}
*/
public static class Builder {
/**
* Builder for {@link IceServerProperties}
*/
public static class Builder {
private String url;
private String username;
private String credential;
private String staticAuthSecret;
private boolean ignoreEmptyUrl = false;
private String url;
private String username;
private String credential;
private String staticAuthSecret;
private boolean ignoreEmptyUrl = false;
/**
* Set the url for the ICE Server you want to use.
* It should follow a valid format:
* <ul>
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7065#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7065#section-3.1</a></li>
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7064#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7064#section-3.1</a></li>
* </ul>
*/
public IceServerProperties.Builder url(String url) {
this.url = url;
return this;
}
/**
* Set the url for the ICE Server you want to use. It should follow a valid
* format:
* <ul>
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7065#section-3.1"
* target=
* "_blank">https://datatracker.ietf.org/doc/html/rfc7065#section-3.1</a></li>
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7064#section-3.1"
* target=
* "_blank">https://datatracker.ietf.org/doc/html/rfc7064#section-3.1</a></li>
* </ul>
*/
public IceServerProperties.Builder url(String url) {
this.url = url;
return this;
}
/**
* Set a username for the ICE Server you want to use.
* This parameter should be defined only for TURN, not for STUN ICE Servers.
*/
public IceServerProperties.Builder username(String userName) {
this.username = userName;
return this;
}
/**
* Set a username for the ICE Server you want to use. This parameter should be
* defined only for TURN, not for STUN ICE Servers.
*/
public IceServerProperties.Builder username(String userName) {
this.username = userName;
return this;
}
/**
* Set a credential for the ICE Server you want to use.
* This parameter should be defined only for TURN, not for STUN ICE Servers.
*/
public IceServerProperties.Builder credential(String credential) {
this.credential = credential;
return this;
}
/**
* Set a credential for the ICE Server you want to use. This parameter should be
* defined only for TURN, not for STUN ICE Servers.
*/
public IceServerProperties.Builder credential(String credential) {
this.credential = credential;
return this;
}
/**
* Secret for TURN authentication based on:
* <ul>
* <li><a href="https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00" target="_blank">https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00</a></li>
* <li><a href="https://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf" target="_blank">https://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf</a></li>
* </ul>
* This will generate credentials valid for 24 hours which is the recommended value. You need to setup in your TURN service this same secret value
* which uses HMAC SHA1 as encryption algorithm. A TURN implementation which by default uses this is COTURN with static-auth-secret parameter.
*/
public IceServerProperties.Builder staticAuthSecret(String staticAuthSecret) {
this.staticAuthSecret = staticAuthSecret;
return this;
}
/**
* Secret for TURN authentication based on:
* <ul>
* <li><a href="https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00"
* target=
* "_blank">https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00</a></li>
* <li><a href=
* "https://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf" target=
* "_blank">https://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf</a></li>
* </ul>
* This will generate credentials valid for 24 hours which is the recommended
* value. You need to setup in your TURN service this same secret value which
* uses HMAC SHA1 as encryption algorithm. A TURN implementation which by
* default uses this is COTURN with static-auth-secret parameter.
*/
public IceServerProperties.Builder staticAuthSecret(String staticAuthSecret) {
this.staticAuthSecret = staticAuthSecret;
return this;
}
public IceServerProperties.Builder ignoreEmptyUrl(boolean ignore) {
this.ignoreEmptyUrl = true;
return this;
}
public IceServerProperties.Builder ignoreEmptyUrl(boolean ignore) {
this.ignoreEmptyUrl = true;
return this;
}
public IceServerProperties.Builder clone() {
return new Builder().url(this.url)
.username(this.username)
.credential(this.credential)
.staticAuthSecret(this.staticAuthSecret);
}
public IceServerProperties.Builder clone() {
return new Builder().url(this.url).username(this.username).credential(this.credential)
.staticAuthSecret(this.staticAuthSecret);
}
/**
* Builder for {@link io.openvidu.java.client.RecordingProperties}
* @throws IllegalArgumentException if the defined properties does not follows
* common STUN/TURN RFCs:
* <ul>
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7065#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7065#section-3.1</a></li>
* <li><a href="https://datatracker.ietf.org/doc/html/rfc7064#section-3.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc7064#section-3.1</a></li>
* </ul>
*/
public IceServerProperties build() throws IllegalArgumentException {
if (this.ignoreEmptyUrl) {
if (this.staticAuthSecret != null && this.username == null && this.credential == null) {
try {
this.generateTURNCredentials();
return new IceServerProperties(this.url, this.username, this.credential);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage());
}
} else {
throw new IllegalArgumentException("ignoreEmptyUrl=true can only be used with staticAuthSecret defined");
}
}
if (this.url == null) {
throw new IllegalArgumentException("External turn url cannot be null");
}
this.checkValidStunTurn(this.url);
if (this.url.startsWith("turn")) {
if (this.staticAuthSecret != null) {
if (this.username != null || this.credential != null) {
throw new IllegalArgumentException("You can't define username or credential if staticAuthSecret is defined");
}
try {
this.generateTURNCredentials();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage());
}
}
if ((this.username == null || this.credential == null)) {
throw new IllegalArgumentException("Credentials must be defined while using turn");
}
} else if (this.url.startsWith("stun")) {
if (this.username != null || this.credential != null) {
// Credentials can not be defined using stun
throw new IllegalArgumentException("Credentials can not be defined while using stun.");
}
}
return new IceServerProperties(this.url, this.username, this.credential);
}
/**
* Builder for {@link io.openvidu.java.client.RecordingProperties}
*
* @throws IllegalArgumentException if the defined properties does not follows
* common STUN/TURN RFCs:
* <ul>
* <li><a href=
* "https://datatracker.ietf.org/doc/html/rfc7065#section-3.1"
* target=
* "_blank">https://datatracker.ietf.org/doc/html/rfc7065#section-3.1</a></li>
* <li><a href=
* "https://datatracker.ietf.org/doc/html/rfc7064#section-3.1"
* target=
* "_blank">https://datatracker.ietf.org/doc/html/rfc7064#section-3.1</a></li>
* </ul>
*/
public IceServerProperties build() throws IllegalArgumentException {
if (this.ignoreEmptyUrl) {
if (this.staticAuthSecret != null && this.username == null && this.credential == null) {
try {
this.generateTURNCredentials();
return new IceServerProperties(this.url, this.username, this.credential);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage());
}
} else {
throw new IllegalArgumentException(
"ignoreEmptyUrl=true can only be used with staticAuthSecret defined");
}
}
if (this.url == null) {
throw new IllegalArgumentException("External turn url cannot be null");
}
this.checkValidStunTurn(this.url);
if (this.url.startsWith("turn")) {
if (this.staticAuthSecret != null) {
if (this.username != null || this.credential != null) {
throw new IllegalArgumentException(
"You can't define username or credential if staticAuthSecret is defined");
}
try {
this.generateTURNCredentials();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage());
}
}
if ((this.username == null || this.credential == null)) {
throw new IllegalArgumentException("Credentials must be defined while using turn");
}
} else if (this.url.startsWith("stun")) {
if (this.username != null || this.credential != null) {
// Credentials can not be defined using stun
throw new IllegalArgumentException("Credentials can not be defined while using stun.");
}
}
return new IceServerProperties(this.url, this.username, this.credential);
}
private void checkValidStunTurn(String uri) throws IllegalArgumentException {
final String TCP_TRANSPORT_SUFFIX = "?transport=tcp";
final String UDP_TRANSPORT_SUFFIX = "?transport=udp";
private void checkValidStunTurn(String uri) throws IllegalArgumentException {
final String TCP_TRANSPORT_SUFFIX = "?transport=tcp";
final String UDP_TRANSPORT_SUFFIX = "?transport=udp";
// Protocols which accepts transport=tcp and transport=udp
final Set<String> TURN_PROTOCOLS = new HashSet<>(Arrays.asList(
"turn",
"turns"
));
final Set<String> STUN_PROTOCOLS = new HashSet<>(Arrays.asList(
"stun",
"stuns"
));
// Protocols which accepts transport=tcp and transport=udp
final Set<String> TURN_PROTOCOLS = new HashSet<>(Arrays.asList("turn", "turns"));
final Set<String> STUN_PROTOCOLS = new HashSet<>(Arrays.asList("stun", "stuns"));
// Fails if no colons
int firstColonPos = uri.indexOf(':');
if (firstColonPos == -1) {
throw new IllegalArgumentException("Not a valid TURN/STUN uri provided. " +
"No colons found in: '" + uri + "'");
}
// Fails if no colons
int firstColonPos = uri.indexOf(':');
if (firstColonPos == -1) {
throw new IllegalArgumentException(
"Not a valid TURN/STUN uri provided. " + "No colons found in: '" + uri + "'");
}
// Get protocol and check
String protocol = uri.substring(0, firstColonPos);
if (!TURN_PROTOCOLS.contains(protocol) && !STUN_PROTOCOLS.contains(protocol)) {
throw new IllegalArgumentException("The protocol '" + protocol + "' is invalid. Only valid values are: "
+ TURN_PROTOCOLS + " " + STUN_PROTOCOLS);
}
// Get protocol and check
String protocol = uri.substring(0, firstColonPos);
if (!TURN_PROTOCOLS.contains(protocol) && !STUN_PROTOCOLS.contains(protocol)) {
throw new IllegalArgumentException("The protocol '" + protocol + "' is invalid. Only valid values are: "
+ TURN_PROTOCOLS + " " + STUN_PROTOCOLS);
}
// Check if query param with transport exist
int qmarkPos = uri.indexOf('?');
String hostAndPort = uri.substring(firstColonPos + 1);
if (qmarkPos != -1) {
if (TURN_PROTOCOLS.contains(protocol)) {
// Only Turn uses transport arg
String rawTransportType = uri.substring(qmarkPos);
hostAndPort = uri.substring(firstColonPos + 1, qmarkPos);
if (!TCP_TRANSPORT_SUFFIX.equals(rawTransportType) && !UDP_TRANSPORT_SUFFIX.equals(rawTransportType)) {
// If other argument rather than transport is specified, it is a wrong query for a STUN/TURN uri
throw new IllegalArgumentException("Wrong value specified in STUN/TURN uri: '"
+ uri + "'. " + "Unique valid arguments after '?' are '"
+ TCP_TRANSPORT_SUFFIX + "' or '" + UDP_TRANSPORT_SUFFIX);
}
} else {
throw new IllegalArgumentException("STUN uri can't have any '?' query param");
}
}
// Check if query param with transport exist
int qmarkPos = uri.indexOf('?');
String hostAndPort = uri.substring(firstColonPos + 1);
if (qmarkPos != -1) {
if (TURN_PROTOCOLS.contains(protocol)) {
// Only Turn uses transport arg
String rawTransportType = uri.substring(qmarkPos);
hostAndPort = uri.substring(firstColonPos + 1, qmarkPos);
if (!TCP_TRANSPORT_SUFFIX.equals(rawTransportType)
&& !UDP_TRANSPORT_SUFFIX.equals(rawTransportType)) {
// If other argument rather than transport is specified, it is a wrong query for
// a STUN/TURN uri
throw new IllegalArgumentException("Wrong value specified in STUN/TURN uri: '" + uri + "'. "
+ "Unique valid arguments after '?' are '" + TCP_TRANSPORT_SUFFIX + "' or '"
+ UDP_TRANSPORT_SUFFIX);
}
} else {
throw new IllegalArgumentException("STUN uri can't have any '?' query param");
}
}
// Check if port is defined
int portColon = hostAndPort.indexOf(':');
// IPv6 are defined between brackets
int startIpv6Index = hostAndPort.indexOf('[');
int endIpv6Index = hostAndPort.indexOf(']');
if (startIpv6Index == -1 ^ endIpv6Index == -1) {
throw new IllegalArgumentException("Not closed bracket '[' or ']' in uri: " + uri);
}
// Check if port is defined
int portColon = hostAndPort.indexOf(':');
// IPv6 are defined between brackets
int startIpv6Index = hostAndPort.indexOf('[');
int endIpv6Index = hostAndPort.indexOf(']');
if (startIpv6Index == -1 ^ endIpv6Index == -1) {
throw new IllegalArgumentException("Not closed bracket '[' or ']' in uri: " + uri);
}
if (portColon != -1) {
if (startIpv6Index == -1 && endIpv6Index == -1) {
// If Ipv4 and port defined
String[] splittedHostAndPort = hostAndPort.split(":");
if (splittedHostAndPort.length != 2) {
throw new IllegalArgumentException("Host or port are not correctly " +
"defined in STUN/TURN uri: '" + uri + "'");
}
String host = splittedHostAndPort[0];
String port = splittedHostAndPort[1];
if (portColon != -1) {
if (startIpv6Index == -1 && endIpv6Index == -1) {
// If Ipv4 and port defined
String[] splittedHostAndPort = hostAndPort.split(":");
if (splittedHostAndPort.length != 2) {
throw new IllegalArgumentException(
"Host or port are not correctly " + "defined in STUN/TURN uri: '" + uri + "'");
}
String host = splittedHostAndPort[0];
String port = splittedHostAndPort[1];
// Check if host is defined. Valid Host (Domain or IP) will be done at server side
checkHostAndPort(uri, host, port);
} else {
// If portColon is found and Ipv6
String ipv6 = hostAndPort.substring(startIpv6Index + 1, endIpv6Index);
String auxPort = hostAndPort.substring(endIpv6Index + 1);
if (auxPort.startsWith(":")) {
if (auxPort.length() == 1) {
throw new IllegalArgumentException("Host or port are not correctly defined in STUN/TURN uri: " + uri);
}
// If port is defined
// Get port without colon and check host and port
String host = ipv6;
String port = auxPort.substring(1);
checkHostAndPort(uri, host, port);
} else if (auxPort.length() > 0) {
// If auxPort = 0, no port is defined
throw new IllegalArgumentException("Port is not specified correctly after IPv6 in uri: '" + uri + "'");
}
}
} else {
// If portColon not found, only host is defined
String host = hostAndPort;
checkHost(uri, host);
}
}
// Check if host is defined. Valid Host (Domain or IP) will be done at server
// side
checkHostAndPort(uri, host, port);
} else {
// If portColon is found and Ipv6
String ipv6 = hostAndPort.substring(startIpv6Index + 1, endIpv6Index);
String auxPort = hostAndPort.substring(endIpv6Index + 1);
if (auxPort.startsWith(":")) {
if (auxPort.length() == 1) {
throw new IllegalArgumentException(
"Host or port are not correctly defined in STUN/TURN uri: " + uri);
}
// If port is defined
// Get port without colon and check host and port
String host = ipv6;
String port = auxPort.substring(1);
checkHostAndPort(uri, host, port);
} else if (auxPort.length() > 0) {
// If auxPort = 0, no port is defined
throw new IllegalArgumentException(
"Port is not specified correctly after IPv6 in uri: '" + uri + "'");
}
}
} else {
// If portColon not found, only host is defined
String host = hostAndPort;
checkHost(uri, host);
}
}
private void checkHost(String uri, String host) {
if (host == null || host.isEmpty()) {
throw new IllegalArgumentException("Host defined in '" + uri + "' is empty or null");
}
if (DomainValidator.getInstance().isValid(host)) {
return;
}
InetAddressValidator ipValidator = InetAddressValidator.getInstance();
if (ipValidator.isValid(host)) {
return;
}
try {
Inet6Address.getByName(host).getHostAddress();
return;
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Is not a valid Internet Address (IP or Domain Name): '" + host + "'");
}
}
private void checkHost(String uri, String host) {
if (host == null || host.isEmpty()) {
throw new IllegalArgumentException("Host defined in '" + uri + "' is empty or null");
}
if (DomainValidator.getInstance().isValid(host)) {
return;
}
InetAddressValidator ipValidator = InetAddressValidator.getInstance();
if (ipValidator.isValid(host)) {
return;
}
try {
Inet6Address.getByName(host).getHostAddress();
return;
} catch (UnknownHostException e) {
throw new IllegalArgumentException(
"Is not a valid Internet Address (IP or Domain Name): '" + host + "'");
}
}
private void checkPort(String uri, String port) {
if (port == null || port.isEmpty()) {
throw new IllegalArgumentException("Port defined in '" + uri + "' is empty or null");
}
private void checkPort(String uri, String port) {
if (port == null || port.isEmpty()) {
throw new IllegalArgumentException("Port defined in '" + uri + "' is empty or null");
}
try {
int parsedPort = Integer.parseInt(port);
if (parsedPort <= 0 || parsedPort > 65535) {
throw new IllegalArgumentException("The port defined in '" + uri + "' is not a valid port number (0-65535)");
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The port defined in '" + uri + "' is not a number (0-65535)");
}
}
try {
int parsedPort = Integer.parseInt(port);
if (parsedPort <= 0 || parsedPort > 65535) {
throw new IllegalArgumentException(
"The port defined in '" + uri + "' is not a valid port number (0-65535)");
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The port defined in '" + uri + "' is not a number (0-65535)");
}
}
private void checkHostAndPort(String uri, String host, String port) {
this.checkHost(uri, host);
this.checkPort(uri, port);
}
private void checkHostAndPort(String uri, String host, String port) {
this.checkHost(uri, host);
this.checkPort(uri, port);
}
private void generateTURNCredentials() throws NoSuchAlgorithmException, InvalidKeyException {
// 1. Generate random username
char[] ALPHANUMERIC = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJK" + "LMNOPQRSTUVWXYZ0123456789").toCharArray();
int MAX_LENGTH = 8;
StringBuilder randomUsername = new StringBuilder();
for (int i = 0; i < MAX_LENGTH; i++) {
int index = new SecureRandom().nextInt(ALPHANUMERIC.length);
randomUsername.append(ALPHANUMERIC[index]);
}
// 2. Get unix timestamp adding 24 hours to define max credential valid time
String unixTimestamp = Long.toString((System.currentTimeMillis() / 1000) + 24 * 3600);
private void generateTURNCredentials() throws NoSuchAlgorithmException, InvalidKeyException {
// 1. Generate random username
char[] ALPHANUMERIC =("abcdefghijklmnopqrstuvwxyzABCDEFGHIJK" +
"LMNOPQRSTUVWXYZ0123456789").toCharArray();
int MAX_LENGTH = 8;
StringBuilder randomUsername = new StringBuilder();
for(int i =0; i < MAX_LENGTH; i++) {
int index = new SecureRandom().nextInt(ALPHANUMERIC.length);
randomUsername.append(ALPHANUMERIC[index]);
}
// 2. Get unix timestamp adding 24 hours to define max credential valid time
String unixTimestamp = Long.toString((System.currentTimeMillis() / 1000) + 24*3600);
// 3. Generate TURN username
String username = unixTimestamp + ":" + randomUsername;
// 3. Generate TURN username
String username = unixTimestamp + ":" + randomUsername;
// 4. Generate HMAC SHA-1 password
SecretKeySpec signingKey = new SecretKeySpec(staticAuthSecret.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
String credential = new String(Base64.getEncoder().encode(mac.doFinal(username.getBytes())));
// 4. Generate HMAC SHA-1 password
SecretKeySpec signingKey = new SecretKeySpec(staticAuthSecret.getBytes(), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
String credential = new String(Base64.encodeBase64(mac.doFinal(username.getBytes())));
// Set credentials in builder
this.username = username;
this.credential = credential;
}
}
// Set credentials in builder
this.username = username;
this.credential = credential;
}
}
}

View File

@ -18,55 +18,49 @@
package io.openvidu.java.client;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Builder;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
public class OpenVidu {
private static final Logger log = LoggerFactory.getLogger(OpenVidu.class);
private String secret;
protected String hostname;
protected HttpClient httpClient;
protected Map<String, Session> activeSessions = new ConcurrentHashMap<>();
protected long requestTimeout = 30000;
protected Map<String, String> headers = new HashMap<>();
protected final static String API_PATH = "openvidu/api";
protected final static String API_SESSIONS = API_PATH + "/sessions";
@ -75,49 +69,149 @@ public class OpenVidu {
protected final static String API_RECORDINGS_START = API_RECORDINGS + "/start";
protected final static String API_RECORDINGS_STOP = API_RECORDINGS + "/stop";
private String defaultBasicAuth;
/**
* @param hostname URL where your instance of OpenVidu Server is up an running.
* It must be the full URL (e.g.
* <code>https://12.34.56.78:1234/</code>)
* @param hostname URL where your OpenVidu deployment is up an running. It must
* be the full URL (e.g. <code>https://12.34.56.78:1234/</code>)
*
* @param secret Secret used on OpenVidu Server initialization
* @param secret Secret configured in your OpenVidu deployment
*/
public OpenVidu(String hostname, String secret) {
this.hostname = hostname;
testHostname(hostname);
setDefaultBasicAuth(secret);
this.hostname = hostname;
if (!this.hostname.endsWith("/")) {
this.hostname += "/";
}
this.secret = secret;
TrustStrategy trustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
};
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("OPENVIDUAPP", this.secret);
provider.setCredentials(AuthScope.ANY, credentials);
SSLContext sslContext;
try {
sslContext = new SSLContextBuilder().loadTrustMaterial(null, trustStrategy).build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { new X509ExtendedTrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type) {
}
public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type) {
}
public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final Socket a_socket) {
}
public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final Socket a_socket) {
}
public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final SSLEngine a_engine) {
}
public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final SSLEngine a_engine) {
}
} }, null);
} catch (KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
RequestConfig.Builder requestBuilder = RequestConfig.custom();
requestBuilder = requestBuilder.setConnectTimeout(30000);
requestBuilder = requestBuilder.setConnectionRequestTimeout(30000);
Builder b = HttpClient.newBuilder();
b.sslContext(sslContext);
b.connectTimeout(Duration.ofSeconds(30));
this.httpClient = b.build();
}
this.httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestBuilder.build())
.setConnectionTimeToLive(30, TimeUnit.SECONDS).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setSSLContext(sslContext).setDefaultCredentialsProvider(provider).build();
/**
* @param hostname URL where your OpenVidu deployment is up an running. It
* must be the full URL (e.g.
* <code>https://12.34.56.78:1234/</code>)
* @param httpClient Object of class <a target="_blank" href=
* "https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html">java.net.http.HttpClient</a>.
* This overrides the internal HTTP client in use. This method
* allows you to custom configure the HTTP client to your
* needs. This may be interesting for many reasons, including:
* <ul>
* <li>Adding proxy configuration</li>
* <li>Customizing the SSLContext</li>
* <li>Modifying the connection timeout</li>
* <li>Adding a cookie handler</li>
* </ul>
*/
public OpenVidu(String hostname, String secret, HttpClient httpClient) {
testHostname(hostname);
setDefaultBasicAuth(secret);
this.hostname = hostname;
if (!this.hostname.endsWith("/")) {
this.hostname += "/";
}
Builder b = HttpClient.newBuilder();
if (httpClient.authenticator().isPresent()) {
log.warn(
"The provided HttpClient contains a custom java.net.Authenticator. OpenVidu deployments require all HTTP requests to have an Authorization header with Basic Auth, with username \"OPENVIDUAPP\" and password your OpenVidu deployment's secret (configuration parameter OPENVIDU_SECRET). The default Authenticator adds this header. Make sure that your custom Authenticator does so as well or that you add it explicitly via OpenVidu#setRequestHeaders, or you will receive 401 responses");
b.authenticator(httpClient.authenticator().get());
}
if (httpClient.connectTimeout().isPresent()) {
b.connectTimeout(httpClient.connectTimeout().get());
}
if (httpClient.cookieHandler().isPresent()) {
b.cookieHandler(httpClient.cookieHandler().get());
}
if (httpClient.executor().isPresent()) {
b.executor(httpClient.executor().get());
}
if (httpClient.proxy().isPresent()) {
b.proxy(httpClient.proxy().get());
}
b.followRedirects(httpClient.followRedirects());
b.sslContext(httpClient.sslContext());
b.sslParameters(httpClient.sslParameters());
b.version(httpClient.version());
this.httpClient = b.build();
}
/**
* Returns the current request timeout configured for all requests.
*/
public long getRequestTimeout() {
return this.requestTimeout;
}
/**
* Sets the request timeout for all HTTP requests. This is the same as setting
* the <a target="_blank" href=
* "https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.html#timeout()">HttpRequest.timeout()</a>
* property for all requests. The default value is 30000 ms.
*
* @param requestTimeout Timeout in milliseconds
*/
public void setRequestTimeout(long requestTimeout) {
this.requestTimeout = requestTimeout;
}
/**
* Returns the current collection of custom headers configured for all requests.
*/
public Map<String, String> getRequestHeaders() {
return this.headers;
}
/**
* Sets custom HTTP headers for all requests. Will override any previous value.
*
* @param headers Header names and values
*/
public void setRequestHeaders(Map<String, String> headers) {
this.headers = headers;
}
/**
@ -183,23 +277,20 @@ public class OpenVidu {
public Recording startRecording(String sessionId, RecordingProperties properties)
throws OpenViduJavaClientException, OpenViduHttpException {
HttpPost request = new HttpPost(this.hostname + API_RECORDINGS_START);
JsonObject json = properties.toJson();
json.addProperty("session", sessionId);
StringEntity params = new StringEntity(json.toString(), "UTF-8");
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params);
HttpResponse response = null;
try {
response = this.httpClient.execute(request);
JsonObject json = properties.toJson();
json.addProperty("session", sessionId);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
Recording r = new Recording(httpResponseToJson(response));
HttpRequest request = addHeaders(
HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(json.toString()))
.uri(new URI(this.hostname + API_RECORDINGS_START))
.setHeader("Content-Type", "application/json").timeout(Duration.ofMillis(requestTimeout)))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if ((response.statusCode() == HttpURLConnection.HTTP_OK)) {
Recording r = new Recording(Utils.httpResponseToJson(response));
Session activeSession = this.activeSessions.get(r.getSessionId());
if (activeSession != null) {
activeSession.setIsBeingRecorded(true);
@ -210,15 +301,11 @@ public class OpenVidu {
}
return r;
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -288,14 +375,16 @@ public class OpenVidu {
* API</a>)
*/
public Recording stopRecording(String recordingId) throws OpenViduJavaClientException, OpenViduHttpException {
HttpPost request = new HttpPost(this.hostname + API_RECORDINGS_STOP + "/" + recordingId);
HttpResponse response = null;
try {
response = this.httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
Recording r = new Recording(httpResponseToJson(response));
try {
HttpRequest request = addHeaders(HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.noBody())
.uri(new URI(this.hostname + API_RECORDINGS_STOP + "/" + recordingId))
.timeout(Duration.ofMillis(requestTimeout))).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
Recording r = new Recording(Utils.httpResponseToJson(response));
Session activeSession = this.activeSessions.get(r.getSessionId());
if (activeSession != null) {
activeSession.setIsBeingRecorded(false);
@ -306,14 +395,10 @@ public class OpenVidu {
}
return r;
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -331,24 +416,22 @@ public class OpenVidu {
* API</a>)
*/
public Recording getRecording(String recordingId) throws OpenViduJavaClientException, OpenViduHttpException {
HttpGet request = new HttpGet(this.hostname + API_RECORDINGS + "/" + recordingId);
HttpResponse response = null;
try {
response = this.httpClient.execute(request);
HttpRequest request = addHeaders(
HttpRequest.newBuilder().GET().uri(new URI(this.hostname + API_RECORDINGS + "/" + recordingId))
.timeout(Duration.ofMillis(requestTimeout)))
.build();
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
return new Recording(httpResponseToJson(response));
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
return new Recording(Utils.httpResponseToJson(response));
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -361,30 +444,26 @@ public class OpenVidu {
* @throws OpenViduHttpException
*/
public List<Recording> listRecordings() throws OpenViduJavaClientException, OpenViduHttpException {
HttpGet request = new HttpGet(this.hostname + API_RECORDINGS);
HttpResponse response = null;
try {
response = this.httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
try {
HttpRequest request = addHeaders(HttpRequest.newBuilder().GET().uri(new URI(this.hostname + API_RECORDINGS))
.timeout(Duration.ofMillis(requestTimeout))).build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
List<Recording> recordings = new ArrayList<>();
JsonObject json = httpResponseToJson(response);
JsonObject json = Utils.httpResponseToJson(response);
JsonArray array = json.get("items").getAsJsonArray();
array.forEach(item -> {
recordings.add(new Recording(item.getAsJsonObject()));
});
return recordings;
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -405,22 +484,20 @@ public class OpenVidu {
* API</a>)
*/
public void deleteRecording(String recordingId) throws OpenViduJavaClientException, OpenViduHttpException {
HttpDelete request = new HttpDelete(this.hostname + API_RECORDINGS + "/" + recordingId);
HttpResponse response = null;
try {
response = this.httpClient.execute(request);
HttpRequest request = addHeaders(
HttpRequest.newBuilder().DELETE().uri(new URI(this.hostname + API_RECORDINGS + "/" + recordingId))
.timeout(Duration.ofMillis(requestTimeout)))
.build();
int statusCode = response.getStatusLine().getStatusCode();
if (!(statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) {
throw new OpenViduHttpException(statusCode);
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != HttpURLConnection.HTTP_NO_CONTENT) {
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -488,16 +565,17 @@ public class OpenVidu {
* @throws OpenViduJavaClientException
*/
public boolean fetch() throws OpenViduJavaClientException, OpenViduHttpException {
HttpGet request = new HttpGet(this.hostname + API_SESSIONS + "?pendingConnections=true");
HttpResponse response = null;
try {
response = this.httpClient.execute(request);
HttpRequest request = addHeaders(HttpRequest.newBuilder().GET()
.uri(new URI(this.hostname + API_SESSIONS + "?pendingConnections=true"))
.timeout(Duration.ofMillis(requestTimeout))).build();
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
JsonObject jsonSessions = httpResponseToJson(response);
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
JsonObject jsonSessions = Utils.httpResponseToJson(response);
JsonArray jsonArraySessions = jsonSessions.get("content").getAsJsonArray();
// Boolean to store if any Session has changed
@ -546,26 +624,35 @@ public class OpenVidu {
return hasChanged[0];
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException {
protected HttpRequest.Builder addHeaders(HttpRequest.Builder builder) {
// HTTP header names are case insensitive
Map<String, String> headersLowerCase = new HashMap<>();
this.headers.forEach((k, v) -> headersLowerCase.put(k.toLowerCase(), v));
if (!headersLowerCase.containsKey("authorization")) {
// There is no custom Authorization header. Add default Basic Auth for OpenVidu
headersLowerCase.put("authorization", this.defaultBasicAuth);
}
headersLowerCase.forEach((k, v) -> builder.setHeader(k, v));
return builder;
}
private void testHostname(String hostnameStr) {
try {
JsonObject json = new Gson().fromJson(EntityUtils.toString(response.getEntity(), "UTF-8"),
JsonObject.class);
return json;
} catch (JsonSyntaxException | ParseException | IOException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
new URL(hostnameStr);
} catch (MalformedURLException e) {
throw new RuntimeException("The hostname \"" + hostnameStr + "\" is not a valid URL: " + e.getMessage());
}
}
private void setDefaultBasicAuth(String secret) {
this.defaultBasicAuth = "Basic " + Base64.getEncoder().encodeToString(("OPENVIDUAPP:" + secret).getBytes());
}
}

View File

@ -238,7 +238,7 @@ public class Recording {
* URL of the recording. You can access the file from there. It is
* <code>null</code> until recording reaches "ready" or "failed" status. If
* <a href="https://docs.openvidu.io/en/stable/reference-docs/openvidu-config/">
* OpenVidu Server configuration </a> property
* OpenVidu configuration </a> property
* <code>OPENVIDU_RECORDING_PUBLIC_ACCESS</code> is false, this path will be
* secured with OpenVidu credentials
*/

View File

@ -22,7 +22,6 @@ import java.util.Map;
import com.google.gson.JsonObject;
import io.openvidu.java.client.Recording.OutputMode;
import io.openvidu.java.client.utils.FormatChecker;
/**
* See
@ -559,7 +558,7 @@ public class RecordingProperties {
}
if (nameParam != null && !nameParam.isEmpty()) {
if (!FormatChecker.isValidRecordingName(nameParam)) {
if (!Utils.isValidRecordingName(nameParam)) {
throw new IllegalArgumentException(
"Parameter 'name' is wrong. Must be an alphanumeric string [a-zA-Z0-9_-~]+");
}
@ -620,7 +619,7 @@ public class RecordingProperties {
}
if (resolutionParam != null) {
if (!FormatChecker.isAcceptableRecordingResolution(resolutionParam)) {
if (!Utils.isAcceptableRecordingResolution(resolutionParam)) {
throw new IllegalStateException(
"Wrong 'resolution' parameter. Acceptable values from 100 to 1999 for both width and height");
}
@ -630,7 +629,7 @@ public class RecordingProperties {
}
if (frameRateParam != null) {
if (!FormatChecker.isAcceptableRecordingFrameRate(frameRateParam.intValue())) {
if (!Utils.isAcceptableRecordingFrameRate(frameRateParam.intValue())) {
throw new IllegalStateException(
"Wrong 'frameRate' parameter. Acceptable values are within range [1,120]");
}
@ -640,7 +639,7 @@ public class RecordingProperties {
}
if (shmSizeParam != null) {
if (!FormatChecker.isAcceptableRecordingShmSize(shmSizeParam)) {
if (!Utils.isAcceptableRecordingShmSize(shmSizeParam)) {
throw new IllegalStateException("Wrong 'shmSize' parameter. Must be 134217728 (128 MB) minimum");
}
shmSizeFinal = shmSizeParam;

View File

@ -18,6 +18,12 @@
package io.openvidu.java.client;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -25,14 +31,6 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -40,7 +38,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
public class Session {
@ -116,35 +113,34 @@ public class Session {
*/
@Deprecated
public String generateToken(TokenOptions tokenOptions) throws OpenViduJavaClientException, OpenViduHttpException {
if (!this.hasSessionId()) {
this.getSessionId();
}
HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_TOKENS);
StringEntity params = new StringEntity(tokenOptions.toJsonObject(sessionId).toString(), "UTF-8");
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params);
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
JsonObject json = tokenOptions.toJsonObject(sessionId);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
String token = httpResponseToJson(response).get("id").getAsString();
HttpRequest request = this.openVidu
.addHeaders(HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(json.toString()))
.uri(new URI(this.openVidu.hostname + OpenVidu.API_TOKENS))
.setHeader("Content-Type", "application/json")
.timeout(Duration.ofMillis(this.openVidu.requestTimeout)))
.build();
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
String token = Utils.httpResponseToJson(response).get("id").getAsString();
log.info("Returning a TOKEN: {}", token);
return token;
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -179,36 +175,33 @@ public class Session {
*/
public Connection createConnection(ConnectionProperties connectionProperties)
throws OpenViduJavaClientException, OpenViduHttpException {
if (!this.hasSessionId()) {
this.getSessionId();
}
HttpPost request = new HttpPost(
this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection");
StringEntity params = new StringEntity(connectionProperties.toJson(sessionId).toString(), "UTF-8");
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params);
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
JsonObject json = connectionProperties.toJson(sessionId);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
Connection connection = new Connection(httpResponseToJson(response));
HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(json.toString()))
.uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection"))
.setHeader("Content-Type", "application/json")
.timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build();
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
Connection connection = new Connection(Utils.httpResponseToJson(response));
this.connections.put(connection.getConnectionId(), connection);
return connection;
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -220,27 +213,24 @@ public class Session {
* @throws OpenViduHttpException
*/
public void close() throws OpenViduJavaClientException, OpenViduHttpException {
HttpDelete request = new HttpDelete(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId);
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder().DELETE()
.uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId))
.timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build();
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) {
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
this.openVidu.activeSessions.remove(this.sessionId);
log.info("Session {} closed", this.sessionId);
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -266,32 +256,30 @@ public class Session {
* @throws OpenViduJavaClientException
*/
public boolean fetch() throws OpenViduJavaClientException, OpenViduHttpException {
final String beforeJSON = this.toJson();
HttpGet request = new HttpGet(
this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "?pendingConnections=true");
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
final String beforeJSON = this.toJson();
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
this.resetWithJson(httpResponseToJson(response));
HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder().GET()
.uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId
+ "?pendingConnections=true"))
.timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build();
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
this.resetWithJson(Utils.httpResponseToJson(response));
final String afterJSON = this.toJson();
boolean hasChanged = !beforeJSON.equals(afterJSON);
log.info("Session info fetched for session '{}'. Any change: {}", this.sessionId, hasChanged);
return hasChanged;
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -340,16 +328,18 @@ public class Session {
* @throws OpenViduHttpException
*/
public void forceDisconnect(String connectionId) throws OpenViduJavaClientException, OpenViduHttpException {
HttpDelete request = new HttpDelete(
this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/" + connectionId);
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) {
HttpRequest request = this.openVidu.addHeaders(HttpRequest
.newBuilder().DELETE().uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/"
+ this.sessionId + "/connection/" + connectionId))
.timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build();
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
// Remove connection from activeConnections map
Connection connectionClosed = this.connections.remove(connectionId);
// Remove every Publisher of the closed connection from every subscriber list of
@ -368,15 +358,11 @@ public class Session {
}
log.info("Connection {} closed", connectionId);
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -422,16 +408,20 @@ public class Session {
* @throws OpenViduHttpException
*/
public void forceUnpublish(String streamId) throws OpenViduJavaClientException, OpenViduHttpException {
HttpDelete request = new HttpDelete(
this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/stream/" + streamId);
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) {
HttpRequest request = this.openVidu
.addHeaders(HttpRequest.newBuilder().DELETE()
.uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId
+ "/stream/" + streamId))
.timeout(Duration.ofMillis(this.openVidu.requestTimeout)))
.build();
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
for (Connection connection : this.connections.values()) {
// Try to remove the Publisher from the Connection publishers collection
if (connection.publishers.remove(streamId) != null) {
@ -442,15 +432,11 @@ public class Session {
}
log.info("Stream {} unpublished", streamId);
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -495,26 +481,28 @@ public class Session {
public Connection updateConnection(String connectionId, ConnectionProperties connectionProperties)
throws OpenViduJavaClientException, OpenViduHttpException {
HttpPatch request = new HttpPatch(
this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/" + connectionId);
StringEntity params = new StringEntity(connectionProperties.toJson(this.sessionId).toString(), "UTF-8");
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params);
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
JsonObject jsonPayload = connectionProperties.toJson(this.sessionId);
HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder()
.method("PATCH", HttpRequest.BodyPublishers.ofString(jsonPayload.toString()))
.uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/"
+ connectionId))
.setHeader("Content-Type", "application/json")
.timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build();
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
log.info("Connection {} updated", connectionId);
} else if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) {
} else if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) {
log.info("Properties of Connection {} remain the same", connectionId);
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
JsonObject json = httpResponseToJson(response);
JsonObject json = Utils.httpResponseToJson(response);
// Update the actual Connection object with the new options
Connection existingConnection = this.connections.get(connectionId);
@ -530,12 +518,8 @@ public class Session {
return existingConnection;
}
} catch (IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
@ -642,23 +626,27 @@ public class Session {
}
private void getSessionHttp() throws OpenViduJavaClientException, OpenViduHttpException {
if (this.hasSessionId()) {
return;
}
HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_SESSIONS);
StringEntity params = new StringEntity(properties.toJson().toString(), "UTF-8");
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params);
HttpResponse response = null;
try {
response = this.openVidu.httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if ((statusCode == org.apache.http.HttpStatus.SC_OK)) {
JsonObject responseJson = httpResponseToJson(response);
JsonObject json = properties.toJson();
HttpRequest request = this.openVidu
.addHeaders(HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(json.toString()))
.uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS))
.setHeader("Content-Type", "application/json")
.timeout(Duration.ofMillis(this.openVidu.requestTimeout)))
.build();
HttpResponse<String> response = this.openVidu.httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HttpURLConnection.HTTP_OK) {
JsonObject responseJson = Utils.httpResponseToJson(response);
this.sessionId = responseJson.get("id").getAsString();
this.createdAt = responseJson.get("createdAt").getAsLong();
@ -675,31 +663,17 @@ public class Session {
this.properties = responseProperties;
log.info("Session '{}' created", this.sessionId);
} else if (statusCode == org.apache.http.HttpStatus.SC_CONFLICT) {
} else if (response.statusCode() == HttpURLConnection.HTTP_CONFLICT) {
// 'customSessionId' already existed
this.sessionId = properties.customSessionId();
this.fetch();
} else {
throw new OpenViduHttpException(statusCode);
throw new OpenViduHttpException(response.statusCode());
}
} catch (IOException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} finally {
if (response != null) {
EntityUtils.consumeQuietly(response.getEntity());
}
}
}
private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException {
JsonObject json;
try {
json = new Gson().fromJson(EntityUtils.toString(response.getEntity(), "UTF-8"), JsonObject.class);
} catch (JsonSyntaxException | IOException e) {
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
}
return json;
}
protected void setIsBeingRecorded(boolean recording) {

View File

@ -1,9 +1,24 @@
package io.openvidu.java.client.utils;
package io.openvidu.java.client;
import java.net.http.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
/**
* @hidden
*/
public final class FormatChecker {
public class Utils {
public static JsonObject httpResponseToJson(HttpResponse<String> response) throws OpenViduJavaClientException {
try {
JsonObject json = new Gson().fromJson(response.body(), JsonObject.class);
return json;
} catch (JsonSyntaxException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
}
}
public static boolean isAcceptableRecordingResolution(String stringResolution) {
// Matches every string with format "AxB", being A and B any number not starting

View File

@ -0,0 +1,70 @@
package io.openvidu.java.client.test;
import java.net.Authenticator;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.Executors;
import javax.net.ssl.SSLContext;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.openvidu.java.client.OpenVidu;
public class OpenViduConstructorTest {
@Test
public void wrongHostname() {
RuntimeException thrown = Assertions.assertThrows(RuntimeException.class, () -> {
new OpenVidu("WRONG_URL", "MY_SECRET");
});
Assertions.assertEquals("The hostname \"WRONG_URL\" is not a valid URL: no protocol: WRONG_URL",
thrown.getMessage());
}
@Test
public void buildWithHttpClientWithoutAuthenticator() {
HttpClient.Builder builder = HttpClient.newBuilder();
builder.connectTimeout(Duration.ofMillis(10000));
ProxySelector proxy = ProxySelector.of(new InetSocketAddress("https://my.proxy.hostname/", 4444));
builder.proxy(proxy);
builder.followRedirects(Redirect.ALWAYS);
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
} catch (Exception e) {
}
builder.sslContext(sslContext);
builder.executor(Executors.newFixedThreadPool(1));
builder.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
OpenVidu OV = new OpenVidu("https://localhost:4443/", "MY_SECRET", builder.build());
Assertions.assertEquals(30000, OV.getRequestTimeout());
Assertions.assertTrue(OV.getRequestHeaders().isEmpty());
OV.setRequestTimeout(5000);
OV.setRequestHeaders(Map.of("header1", "value1", "header2", "value2"));
Assertions.assertEquals(5000, OV.getRequestTimeout());
Assertions.assertEquals(2, OV.getRequestHeaders().size());
}
@Test
public void buildWithHttpClientWithAuthenticator() {
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("OPENVIDUAPP", "secret".toCharArray());
}
};
HttpClient.Builder builder = HttpClient.newBuilder().authenticator(authenticator);
new OpenVidu("https://localhost:4443/", "MY_SECRET", builder.build());
}
}

View File

@ -6,9 +6,9 @@ import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.openvidu.java.client.utils.FormatChecker;
import io.openvidu.java.client.Utils;
public class FormatCheckerTest {
public class UtilsFormatCheckerTest {
@Test
public void testCustomSessionIdFormat() {
@ -23,9 +23,9 @@ public class FormatCheckerTest {
"session-_", "123_session-1");
for (String id : invalidCustomSessionIds)
Assertions.assertFalse(FormatChecker.isValidCustomSessionId(id));
Assertions.assertFalse(Utils.isValidCustomSessionId(id));
for (String id : validCustomSessionIds)
Assertions.assertTrue(FormatChecker.isValidCustomSessionId(id));
Assertions.assertTrue(Utils.isValidCustomSessionId(id));
}
@Test
@ -37,9 +37,9 @@ public class FormatCheckerTest {
List<String> validResolutions = Arrays.asList("1920x1080", "1280x720", "100x1999");
for (String resolution : invalidResolutions)
Assertions.assertFalse(FormatChecker.isAcceptableRecordingResolution(resolution));
Assertions.assertFalse(Utils.isAcceptableRecordingResolution(resolution));
for (String resolution : validResolutions)
Assertions.assertTrue(FormatChecker.isAcceptableRecordingResolution(resolution));
Assertions.assertTrue(Utils.isAcceptableRecordingResolution(resolution));
}
@Test
@ -50,9 +50,9 @@ public class FormatCheckerTest {
List<Integer> validFramerates = Arrays.asList(1, 2, 30, 60, 119, 120);
for (int framerate : invalidFrameRates)
Assertions.assertFalse(FormatChecker.isAcceptableRecordingFrameRate(framerate));
Assertions.assertFalse(Utils.isAcceptableRecordingFrameRate(framerate));
for (int framerate : validFramerates)
Assertions.assertTrue(FormatChecker.isAcceptableRecordingFrameRate(framerate));
Assertions.assertTrue(Utils.isAcceptableRecordingFrameRate(framerate));
}
}

View File

@ -87,10 +87,10 @@ export class OpenVidu {
activeSessions: Session[] = [];
/**
* @param hostname URL where your instance of OpenVidu Server is up an running.
* @param hostname URL where your OpenVidu deployment is up an running.
* It must be the full URL (e.g. `https://12.34.56.78:1234/`)
*
* @param secret Secret used on OpenVidu Server initialization
* @param secret Secret configured in your OpenVidu deployment
*/
constructor(private hostname: string, secret: string) {
this.setHostnameAndPort();

View File

@ -53,7 +53,6 @@ import io.openvidu.java.client.KurentoOptions;
import io.openvidu.java.client.OpenViduRole;
import io.openvidu.java.client.Recording;
import io.openvidu.java.client.SessionProperties;
import io.openvidu.java.client.utils.FormatChecker;
import io.openvidu.server.cdr.CDREventRecordingStatusChanged;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.coturn.CoturnCredentialsService;
@ -194,7 +193,7 @@ public abstract class SessionManager {
String connectionId);
public abstract void stopRtmpIfNecessary(Session session);
public void onEcho(String participantPrivateId, Integer requestId) {
sessionEventsHandler.onEcho(participantPrivateId, requestId);
}
@ -338,7 +337,7 @@ public abstract class SessionManager {
public Token newToken(Session session, OpenViduRole role, String serverMetadata, boolean record,
KurentoOptions kurentoOptions, List<IceServerProperties> customIceServers) throws Exception {
if (!FormatChecker.isServerMetadataFormatCorrect(serverMetadata)) {
if (!io.openvidu.java.client.Utils.isServerMetadataFormatCorrect(serverMetadata)) {
log.error("Data invalid format");
throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format");
}

View File

@ -49,7 +49,6 @@ import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.java.client.ConnectionProperties;
import io.openvidu.java.client.ConnectionType;
import io.openvidu.java.client.OpenViduRole;
import io.openvidu.java.client.utils.FormatChecker;
import io.openvidu.server.config.OpenviduBuildInfo;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.EndReason;
@ -304,7 +303,7 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
if (tokenObj != null) {
String clientMetadata = getStringParam(request, ProtocolElements.JOINROOM_METADATA_PARAM);
if (FormatChecker.isServerMetadataFormatCorrect(clientMetadata)) {
if (io.openvidu.java.client.Utils.isServerMetadataFormatCorrect(clientMetadata)) {
// While closing a session users can't join
if (session.closingLock.readLock().tryLock()) {

View File

@ -102,6 +102,11 @@
<artifactId>java-client</artifactId>
<version>${version.appium}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
</dependencies>
<profiles>

View File

@ -112,6 +112,11 @@
<artifactId>java-string-similarity</artifactId>
<version>${version.stringsimilarity}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
</dependencies>
</project>

View File

@ -20,6 +20,13 @@ package io.openvidu.test.e2e;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.File;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.Socket;
import java.net.http.HttpClient;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collection;
@ -34,6 +41,12 @@ import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@ -2427,6 +2440,129 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
gracefullyLeaveParticipants(user, 2);
}
@Test
@DisplayName("openvidu-java-client custom HttpClient test")
void openViduJavaClientCustomHttpClientTest() throws Exception {
// Test all possible combinations: custom Authenticator present and valid,
// present and wrong and no present; in combination with custom Authorization
// header present and valid, present and wrong and no present
HttpClient.Builder builder = HttpClient.newBuilder();
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { new X509ExtendedTrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type) {
}
public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type) {
}
public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final Socket a_socket) {
}
public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final Socket a_socket) {
}
public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final SSLEngine a_engine) {
}
public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type,
final SSLEngine a_engine) {
}
} }, null);
} catch (KeyManagementException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
builder.sslContext(sslContext);
final String BASIC_AUTH = "Basic "
+ Base64.getEncoder().encodeToString(("OPENVIDUAPP:" + OPENVIDU_SECRET).getBytes());
final String WRONG_SECRET = "WRONG_SECRET_" + RandomStringUtils.randomAlphanumeric(10);
// 1. No authenticator, no header, 200
OpenVidu customHttpClientOV1 = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET, builder.build());
customHttpClientOV1.fetch();
// 2. No authenticator, wrong header, 401
customHttpClientOV1.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER"));
OpenViduHttpException thrown = Assertions.assertThrows(OpenViduHttpException.class, () -> {
customHttpClientOV1.fetch();
});
Assertions.assertEquals(401, thrown.getStatus());
// 3. No authenticator and valid header, 200
customHttpClientOV1.setRequestHeaders(Map.of("Authorization", BASIC_AUTH));
customHttpClientOV1.fetch();
// 4. Wrong authenticator and no header, 401
builder.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("OPENVIDUAPP", WRONG_SECRET.toCharArray());
}
});
OpenVidu customHttpClientOV2 = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET, builder.build());
OpenViduJavaClientException thrown2 = Assertions.assertThrows(OpenViduJavaClientException.class, () -> {
customHttpClientOV2.fetch();
});
Assertions.assertTrue(thrown2.getMessage().contains("too many authentication attempts"));
// 5. Wrong authenticator and wrong header, 401
customHttpClientOV2.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER"));
thrown2 = Assertions.assertThrows(OpenViduJavaClientException.class, () -> {
customHttpClientOV2.fetch();
});
Assertions.assertTrue(thrown2.getMessage().contains("too many authentication attempts"));
// 6. Wrong authenticator and valid header, 401
customHttpClientOV2.setRequestHeaders(Map.of("Authorization", BASIC_AUTH));
thrown2 = Assertions.assertThrows(OpenViduJavaClientException.class, () -> {
customHttpClientOV2.fetch();
});
Assertions.assertTrue(thrown2.getMessage().contains("too many authentication attempts"));
// 7. Valid authenticator and no header, 200
builder.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("OPENVIDUAPP", OPENVIDU_SECRET.toCharArray());
}
});
OpenVidu customHttpClientOV3 = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET, builder.build());
customHttpClientOV3.fetch();
// 8. Valid authenticator and wrong header, 200
customHttpClientOV3.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER"));
customHttpClientOV3.fetch();
// 9. Valid authenticator and valid header, 200
customHttpClientOV3.setRequestHeaders(Map.of("Authorization", BASIC_AUTH));
customHttpClientOV3.fetch();
// 10. Wrong secret, valid authenticator, no header, 200
OpenVidu customHttpClientOV4 = new OpenVidu(OPENVIDU_URL, WRONG_SECRET, builder.build());
customHttpClientOV4.fetch();
// 11. Wrong secret, valid authenticator, wrong header, 200
customHttpClientOV4.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER"));
customHttpClientOV4.fetch();
// 12. Wrong secret, no authenticator, valid header, 200
builder = HttpClient.newBuilder().sslContext(sslContext);
customHttpClientOV4 = new OpenVidu(OPENVIDU_URL, WRONG_SECRET, builder.build());
customHttpClientOV4.setRequestHeaders(Map.of("Authorization", BASIC_AUTH));
customHttpClientOV4.fetch();
}
@Test
@DisplayName("openvidu-java-client test")
void openViduJavaClientTest() throws Exception {