mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[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:
parent
b9c5a0d158
commit
944682d1f1
@ -42,12 +42,18 @@ The `server` thing has the following configuration parameters:
|
||||
| useHttps | no | Set to true if you want to use HTTPS to communicate with the remote openHAB server. Default is false. |
|
||||
| port | yes | The HTTP port to use to communicate with the remote openHAB server. Default is 8080. |
|
||||
| trustedCertificate | no | Set to true if you want to use HTTPS even without a valid SSL certificate provided by your remote server. |
|
||||
| restPath | yes | The subpath of the REST API on the remote openHAB server. Default is /rest |
|
||||
| restPath | yes | The subpath of the REST API on the remote openHAB server. Default is "/rest/" |
|
||||
| token | no | The token to use when the remote openHAB server is setup to require authorization to run its REST API. |
|
||||
| username | no | The username to use when the remote openHAB server is setup to require basic authorization to run its REST API. |
|
||||
| password | no | The password to use when the remote openHAB server is setup to require basic authorization to run its REST API. |
|
||||
| authenticateAnyway | no | 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. |
|
||||
| accessibilityInterval | no | Minutes between checking the remote server accessibility. 0 to disable the check. Default is 3. |
|
||||
| aliveInterval | no | Number of last minutes to consider when monitoring the receipt of events from the remote server. If an event is received during this interval, the remote server is considered alive and its accessibility will not be verified. Use 0 to disable this feature. Default is 5. |
|
||||
| restartIfNoActivity | no | Set it to true if you want to restart the connection (SSE) to the remote server when no events are received in the monitored interval. It is not necessary if the goal is to properly handle a short network outage (few seconds). This can be useful if you want to deal with a long network outage. Do not enable it if you remote server does not send events during the monitored interval under normal conditions, it will cause frequent restart of the connection and potential loss of events. Default is false. |
|
||||
|
||||
Please note that even though the default configuration is based on insecure communication over HTTP, it is recommended to adjust the configuration to be based on secure communication over HTTPS.
|
||||
This is of course essential if your connection to the remote openHAB server is over the Internet.
|
||||
|
||||
The `thing` thing has the following configuration parameters:
|
||||
|
||||
| Parameter | Required | Description |
|
||||
@ -55,7 +61,8 @@ The `thing` thing has the following configuration parameters:
|
||||
| thingUID | yes | The thing UID in the remote openHAB server. |
|
||||
| buildTriggerChannels | no | If set to true, a trigger channel will be automatically created and linked to each trigger channel from the remote thing. Default is true. |
|
||||
|
||||
Please note that if your remote server is an openHAB v3 server, you will need to define a valid token on your bridge thing to have your things correctly initialized.
|
||||
Please note that if your remote server is an openHAB v3 server, in order for all of your things to be properly initialized, you will need to define on your bridge thing a valid API token in the parameter `token` and also define the parameter `authenticateAnyway` to true in case you are using an unsecured connection (HTTP).
|
||||
This API token can be created on your remote server using Main UI.
|
||||
|
||||
Setting the `buildTriggerChannels` parameter to false is for the main following advanced usages:
|
||||
|
||||
|
@ -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,25 +492,40 @@ 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()) {
|
||||
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))
|
||||
&& contentType != null) {
|
||||
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>
|
||||
|
Loading…
Reference in New Issue
Block a user