[remoteopenhab] Connection to the remote server through openHAB Cloud (#10138)
Fix #10055 Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
@@ -31,8 +31,11 @@ public class RemoteopenhabServerConfiguration {
|
||||
public boolean useHttps = false;
|
||||
public int port = 8080;
|
||||
public boolean trustedCertificate = false;
|
||||
public String restPath = "/rest";
|
||||
public String restPath = "/rest/";
|
||||
public String token = "";
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
public boolean authenticateAnyway = false;
|
||||
public int accessibilityInterval = 3;
|
||||
public int aliveInterval = 5;
|
||||
public boolean restartIfNoActivity = false;
|
||||
|
||||
@@ -150,13 +150,10 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
|
||||
}
|
||||
|
||||
String urlStr = url.toString();
|
||||
if (urlStr.endsWith("/")) {
|
||||
urlStr = urlStr.substring(0, urlStr.length() - 1);
|
||||
}
|
||||
logger.debug("REST URL = {}", urlStr);
|
||||
|
||||
restClient.setRestUrl(urlStr);
|
||||
restClient.setAccessToken(config.token);
|
||||
restClient.setAuthenticationData(config.authenticateAnyway, config.token, config.username, config.password);
|
||||
if (config.useHttps && config.trustedCertificate) {
|
||||
restClient.setHttpClient(httpClientTrustingCert);
|
||||
restClient.setTrustedCertificate(true);
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.io.EOFException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -89,7 +90,9 @@ public class RemoteopenhabRestClient {
|
||||
private @Nullable String restApiVersion;
|
||||
private Map<String, @Nullable String> apiEndPointsUrls = new HashMap<>();
|
||||
private @Nullable String topicNamespace;
|
||||
private boolean authenticateAnyway;
|
||||
private String accessToken;
|
||||
private String credentialToken;
|
||||
private boolean trustedCertificate;
|
||||
private boolean connected;
|
||||
private boolean completed;
|
||||
@@ -104,6 +107,7 @@ public class RemoteopenhabRestClient {
|
||||
this.eventSourceFactory = eventSourceFactory;
|
||||
this.jsonParser = jsonParser;
|
||||
this.accessToken = "";
|
||||
this.credentialToken = "";
|
||||
}
|
||||
|
||||
public void setHttpClient(HttpClient httpClient) {
|
||||
@@ -122,8 +126,16 @@ public class RemoteopenhabRestClient {
|
||||
this.restUrl = restUrl;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
public void setAuthenticationData(boolean authenticateAnyway, String accessToken, String username,
|
||||
String password) {
|
||||
this.authenticateAnyway = authenticateAnyway;
|
||||
this.accessToken = accessToken;
|
||||
if (username.isBlank() || password.isBlank()) {
|
||||
this.credentialToken = "";
|
||||
} else {
|
||||
String token = username + ":" + password;
|
||||
this.credentialToken = Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
public void setTrustedCertificate(boolean trustedCertificate) {
|
||||
@@ -132,7 +144,7 @@ public class RemoteopenhabRestClient {
|
||||
|
||||
public void tryApi() throws RemoteopenhabException {
|
||||
try {
|
||||
String jsonResponse = executeGetUrl(getRestUrl(), "application/json", false);
|
||||
String jsonResponse = executeGetUrl(getRestUrl(), "application/json", false, false);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
@@ -160,7 +172,7 @@ public class RemoteopenhabRestClient {
|
||||
url += "&fields=" + fields;
|
||||
}
|
||||
boolean asyncReading = fields == null || Arrays.asList(fields.split(",")).contains("state");
|
||||
String jsonResponse = executeGetUrl(url, "application/json", asyncReading);
|
||||
String jsonResponse = executeGetUrl(url, "application/json", false, asyncReading);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
@@ -174,7 +186,7 @@ public class RemoteopenhabRestClient {
|
||||
public String getRemoteItemState(String itemName) throws RemoteopenhabException {
|
||||
try {
|
||||
String url = String.format("%s/%s/state", getRestApiUrl("items"), itemName);
|
||||
return executeGetUrl(url, "text/plain", true);
|
||||
return executeGetUrl(url, "text/plain", false, true);
|
||||
} catch (RemoteopenhabException e) {
|
||||
throw new RemoteopenhabException("Failed to get the state of remote item " + itemName
|
||||
+ " using the items REST API: " + e.getMessage(), e);
|
||||
@@ -184,7 +196,8 @@ public class RemoteopenhabRestClient {
|
||||
public void sendCommandToRemoteItem(String itemName, Command command) throws RemoteopenhabException {
|
||||
try {
|
||||
String url = String.format("%s/%s", getRestApiUrl("items"), itemName);
|
||||
executeUrl(HttpMethod.POST, url, "application/json", command.toFullString(), "text/plain", false, true);
|
||||
executeUrl(HttpMethod.POST, url, "application/json", command.toFullString(), "text/plain", false, false,
|
||||
true);
|
||||
} catch (RemoteopenhabException e) {
|
||||
throw new RemoteopenhabException("Failed to send command to the remote item " + itemName
|
||||
+ " using the items REST API: " + e.getMessage(), e);
|
||||
@@ -193,7 +206,7 @@ public class RemoteopenhabRestClient {
|
||||
|
||||
public List<RemoteopenhabThing> getRemoteThings() throws RemoteopenhabException {
|
||||
try {
|
||||
String jsonResponse = executeGetUrl(getRestApiUrl("things"), "application/json", false);
|
||||
String jsonResponse = executeGetUrl(getRestApiUrl("things"), "application/json", true, false);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
@@ -207,7 +220,7 @@ public class RemoteopenhabRestClient {
|
||||
public RemoteopenhabThing getRemoteThing(String uid) throws RemoteopenhabException {
|
||||
try {
|
||||
String url = String.format("%s/%s", getRestApiUrl("things"), uid);
|
||||
String jsonResponse = executeGetUrl(url, "application/json", false);
|
||||
String jsonResponse = executeGetUrl(url, "application/json", true, false);
|
||||
if (jsonResponse.isEmpty()) {
|
||||
throw new RemoteopenhabException("JSON response is empty");
|
||||
}
|
||||
@@ -224,7 +237,14 @@ public class RemoteopenhabRestClient {
|
||||
|
||||
private String getRestApiUrl(String endPoint) throws RemoteopenhabException {
|
||||
String url = apiEndPointsUrls.get(endPoint);
|
||||
return url != null ? url : getRestUrl() + "/" + endPoint;
|
||||
if (url == null) {
|
||||
url = getRestUrl();
|
||||
if (!url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
url += endPoint;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getTopicNamespace() {
|
||||
@@ -250,6 +270,7 @@ public class RemoteopenhabRestClient {
|
||||
}
|
||||
|
||||
private SseEventSource createEventSource(String restSseUrl) {
|
||||
String credentialToken = restSseUrl.startsWith("https:") || authenticateAnyway ? this.credentialToken : "";
|
||||
Client client;
|
||||
// Avoid a timeout exception after 1 minute by setting the read timeout to 0 (infinite)
|
||||
if (trustedCertificate) {
|
||||
@@ -259,11 +280,11 @@ public class RemoteopenhabRestClient {
|
||||
public boolean verify(@Nullable String hostname, @Nullable SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
}).readTimeout(0, TimeUnit.SECONDS).register(new RemoteopenhabStreamingRequestFilter(accessToken))
|
||||
.build();
|
||||
}).readTimeout(0, TimeUnit.SECONDS)
|
||||
.register(new RemoteopenhabStreamingRequestFilter(credentialToken)).build();
|
||||
} else {
|
||||
client = clientBuilder.readTimeout(0, TimeUnit.SECONDS)
|
||||
.register(new RemoteopenhabStreamingRequestFilter(accessToken)).build();
|
||||
.register(new RemoteopenhabStreamingRequestFilter(credentialToken)).build();
|
||||
}
|
||||
SseEventSource eventSource = eventSourceFactory.newSource(client.target(restSseUrl));
|
||||
eventSource.register(this::onEvent, this::onError, this::onComplete);
|
||||
@@ -471,18 +492,31 @@ public class RemoteopenhabRestClient {
|
||||
return parts[2];
|
||||
}
|
||||
|
||||
public String executeGetUrl(String url, String acceptHeader, boolean asyncReading) throws RemoteopenhabException {
|
||||
return executeUrl(HttpMethod.GET, url, acceptHeader, null, null, asyncReading, true);
|
||||
public String executeGetUrl(String url, String acceptHeader, boolean provideAccessToken, boolean asyncReading)
|
||||
throws RemoteopenhabException {
|
||||
return executeUrl(HttpMethod.GET, url, acceptHeader, null, null, provideAccessToken, asyncReading, true);
|
||||
}
|
||||
|
||||
public String executeUrl(HttpMethod httpMethod, String url, String acceptHeader, @Nullable String content,
|
||||
@Nullable String contentType, boolean asyncReading, boolean retryIfEOF) throws RemoteopenhabException {
|
||||
final Request request = httpClient.newRequest(url).method(httpMethod).timeout(REQUEST_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
@Nullable String contentType, boolean provideAccessToken, boolean asyncReading, boolean retryIfEOF)
|
||||
throws RemoteopenhabException {
|
||||
final Request request = httpClient.newRequest(url).method(httpMethod)
|
||||
.timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).followRedirects(false)
|
||||
.header(HttpHeaders.ACCEPT, acceptHeader);
|
||||
|
||||
request.header(HttpHeaders.ACCEPT, acceptHeader);
|
||||
if (!accessToken.isEmpty()) {
|
||||
request.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
if (url.startsWith("https:") || authenticateAnyway) {
|
||||
boolean useAlternativeHeader = false;
|
||||
if (!credentialToken.isEmpty()) {
|
||||
request.header(HttpHeaders.AUTHORIZATION, "Basic " + credentialToken);
|
||||
useAlternativeHeader = true;
|
||||
}
|
||||
if (provideAccessToken && !accessToken.isEmpty()) {
|
||||
if (useAlternativeHeader) {
|
||||
request.header("X-OPENHAB-TOKEN", accessToken);
|
||||
} else {
|
||||
request.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (content != null && (HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod))
|
||||
@@ -490,6 +524,8 @@ public class RemoteopenhabRestClient {
|
||||
request.content(new StringContentProvider(content), contentType);
|
||||
}
|
||||
|
||||
logger.debug("Request {} {}", request.getMethod(), request.getURI());
|
||||
|
||||
try {
|
||||
if (asyncReading) {
|
||||
InputStreamResponseListener listener = new InputStreamResponseListener();
|
||||
@@ -509,7 +545,17 @@ public class RemoteopenhabRestClient {
|
||||
} else {
|
||||
ContentResponse response = request.send();
|
||||
int statusCode = response.getStatus();
|
||||
if (statusCode >= HttpStatus.BAD_REQUEST_400) {
|
||||
if (statusCode == HttpStatus.MOVED_PERMANENTLY_301 || statusCode == HttpStatus.FOUND_302) {
|
||||
String locationHeader = response.getHeaders().get(HttpHeaders.LOCATION);
|
||||
if (locationHeader != null && !locationHeader.isBlank()) {
|
||||
logger.debug("The remopte server redirected the request to this URL: {}", locationHeader);
|
||||
return executeUrl(httpMethod, locationHeader, acceptHeader, content, contentType,
|
||||
provideAccessToken, asyncReading, retryIfEOF);
|
||||
} else {
|
||||
String statusLine = statusCode + " " + response.getReason();
|
||||
throw new RemoteopenhabException("HTTP call failed: " + statusLine);
|
||||
}
|
||||
} else if (statusCode >= HttpStatus.BAD_REQUEST_400) {
|
||||
String statusLine = statusCode + " " + response.getReason();
|
||||
throw new RemoteopenhabException("HTTP call failed: " + statusLine);
|
||||
}
|
||||
@@ -525,7 +571,8 @@ public class RemoteopenhabRestClient {
|
||||
Throwable cause = e.getCause();
|
||||
if (retryIfEOF && cause instanceof EOFException) {
|
||||
logger.debug("EOFException - retry the request");
|
||||
return executeUrl(httpMethod, url, acceptHeader, content, contentType, asyncReading, false);
|
||||
return executeUrl(httpMethod, url, acceptHeader, content, contentType, provideAccessToken, asyncReading,
|
||||
false);
|
||||
} else {
|
||||
throw new RemoteopenhabException(e);
|
||||
}
|
||||
|
||||
@@ -30,18 +30,18 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
@NonNullByDefault
|
||||
public class RemoteopenhabStreamingRequestFilter implements ClientRequestFilter {
|
||||
|
||||
private final String accessToken;
|
||||
private final String credentialToken;
|
||||
|
||||
public RemoteopenhabStreamingRequestFilter(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
public RemoteopenhabStreamingRequestFilter(String credentialToken) {
|
||||
this.credentialToken = credentialToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(@Nullable ClientRequestContext requestContext) throws IOException {
|
||||
if (requestContext != null) {
|
||||
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
|
||||
if (!accessToken.isEmpty()) {
|
||||
headers.putSingle(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
|
||||
if (!credentialToken.isEmpty()) {
|
||||
headers.putSingle(HttpHeaders.AUTHORIZATION, "Basic " + credentialToken);
|
||||
}
|
||||
headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache");
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<parameter name="restPath" type="text" required="true">
|
||||
<label>REST API Path</label>
|
||||
<description>The subpath of the REST API on the remote openHAB server.</description>
|
||||
<default>/rest</default>
|
||||
<default>/rest/</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
@@ -54,6 +54,30 @@
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="username" type="text">
|
||||
<label>Username</label>
|
||||
<description>The username to use when the remote openHAB server is setup to require basic authorization to run its
|
||||
REST API.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="password" type="text">
|
||||
<context>password</context>
|
||||
<label>Password</label>
|
||||
<description>The password to use when the remote openHAB server is setup to require basic authorization to run its
|
||||
REST API.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="authenticateAnyway" type="boolean">
|
||||
<label>Authenticate Anyway</label>
|
||||
<description>Set it to true in case you want to pass authentication information even when the communicate with the
|
||||
remote openHAB server is not secured (only HTTP). This is of course not recommended especially if your connection
|
||||
is over the Internet. Default is false.</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="accessibilityInterval" type="integer" min="0" step="1" unit="min">
|
||||
<label>Accessibility Interval</label>
|
||||
<description>Minutes between checking the remote server accessibility. 0 to disable the check. Default is 3.</description>
|
||||
|
||||
Reference in New Issue
Block a user