[miele] Use framework's HTTP client (#12545)

* Refactor to use framework's Jetty HTTP client
* Do not try to parse HTML as JSON on failed HTTP calls
* Improve handler initialization log messages

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
Jacob Laursen 2022-03-30 07:03:46 +02:00 committed by GitHub
parent a2a5c2e6ff
commit 8c6534300a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 94 deletions

View File

@ -12,21 +12,19 @@
*/ */
package org.openhab.binding.miele.internal; package org.openhab.binding.miele.internal;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader; import java.io.StringReader;
import java.net.HttpURLConnection; import java.net.URI;
import java.net.MalformedURLException; import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.zip.GZIPInputStream; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.miele.internal.exceptions.MieleRpcException; import org.openhab.binding.miele.internal.exceptions.MieleRpcException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -47,13 +45,15 @@ import com.google.gson.JsonParser;
@NonNullByDefault @NonNullByDefault
public class MieleGatewayCommunicationController { public class MieleGatewayCommunicationController {
private final URL url; private final URI uri;
private final Random rand = new Random(); private final Random rand = new Random();
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private final Logger logger = LoggerFactory.getLogger(MieleGatewayCommunicationController.class); private final Logger logger = LoggerFactory.getLogger(MieleGatewayCommunicationController.class);
private final HttpClient httpClient;
public MieleGatewayCommunicationController(String host) throws MalformedURLException { public MieleGatewayCommunicationController(HttpClient httpClient, String host) throws URISyntaxException {
url = new URL("http://" + host + "/remote/json-rpc"); uri = new URI("http://" + host + "/remote/json-rpc");
this.httpClient = httpClient;
} }
public JsonElement invokeOperation(FullyQualifiedApplianceIdentifier applianceIdentifier, String modelID, public JsonElement invokeOperation(FullyQualifiedApplianceIdentifier applianceIdentifier, String modelID,
@ -69,27 +69,43 @@ public class MieleGatewayCommunicationController {
public JsonElement invokeRPC(String methodName, Object[] args) throws MieleRpcException { public JsonElement invokeRPC(String methodName, Object[] args) throws MieleRpcException {
JsonElement result = null; JsonElement result = null;
JsonObject req = new JsonObject(); JsonObject requestBodyAsJson = new JsonObject();
int id = rand.nextInt(Integer.MAX_VALUE); int id = rand.nextInt(Integer.MAX_VALUE);
req.addProperty("jsonrpc", "2.0"); requestBodyAsJson.addProperty("jsonrpc", "2.0");
req.addProperty("id", id); requestBodyAsJson.addProperty("id", id);
req.addProperty("method", methodName); requestBodyAsJson.addProperty("method", methodName);
JsonArray params = new JsonArray(); JsonArray params = new JsonArray();
for (Object o : args) { for (Object o : args) {
params.add(gson.toJsonTree(o)); params.add(gson.toJsonTree(o));
} }
req.add("params", params); requestBodyAsJson.add("params", params);
String requestBody = requestBodyAsJson.toString();
Request request = httpClient.newRequest(uri).method(HttpMethod.POST)
.content(new StringContentProvider(requestBody), "application/json");
String requestData = req.toString();
String responseData = null; String responseData = null;
try { try {
responseData = post(url, Collections.emptyMap(), requestData); final ContentResponse contentResponse = request.send();
} catch (IOException e) { final int httpStatus = contentResponse.getStatus();
throw new MieleRpcException("Exception occurred while posting data", e); if (httpStatus != 200) {
if (httpStatus == 503) {
throw new MieleRpcException("Gateway is temporarily unavailable");
}
throw new MieleRpcException("Unexpected HTTP status code " + httpStatus);
}
responseData = contentResponse.getContentAsString();
} catch (TimeoutException e) {
throw new MieleRpcException("Timeout when calling gateway", e);
} catch (ExecutionException e) {
throw new MieleRpcException("Failure when calling gateway", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MieleRpcException("Interrupted while calling gateway", e);
} }
logger.trace("The request '{}' yields '{}'", requestData, responseData); logger.trace("The request '{}' yields '{}'", requestBody, responseData);
JsonObject parsedResponse = null; JsonObject parsedResponse = null;
try { try {
parsedResponse = (JsonObject) JsonParser.parseReader(new StringReader(responseData)); parsedResponse = (JsonObject) JsonParser.parseReader(new StringReader(responseData));
@ -107,7 +123,7 @@ public class MieleGatewayCommunicationController {
String message = (o.has("message") ? o.get("message").getAsString() : null); String message = (o.has("message") ? o.get("message").getAsString() : null);
String data = (o.has("data") String data = (o.has("data")
? (o.get("data") instanceof JsonObject ? o.get("data").toString() : o.get("data").getAsString()) ? (o.get("data") instanceof JsonObject ? o.get("data").toString() : o.get("data").getAsString())
: null); : "");
throw new MieleRpcException( throw new MieleRpcException(
"Remote exception occurred: '" + code + "':'" + message + "':'" + data + "'"); "Remote exception occurred: '" + code + "':'" + message + "':'" + data + "'");
} else { } else {
@ -122,63 +138,4 @@ public class MieleGatewayCommunicationController {
return result; return result;
} }
private String post(URL url, Map<String, String> headers, String data) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
for (Map.Entry<String, String> entry : headers.entrySet()) {
connection.addRequestProperty(entry.getKey(), entry.getValue());
}
connection.addRequestProperty("Accept-Encoding", "gzip");
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.connect();
OutputStream out = null;
try {
out = connection.getOutputStream();
out.write(data.getBytes());
out.flush();
int statusCode = connection.getResponseCode();
if (statusCode != HttpURLConnection.HTTP_OK) {
logger.debug("An unexpected status code was returned: '{}'", statusCode);
}
} finally {
if (out != null) {
out.close();
}
}
String responseEncoding = connection.getHeaderField("Content-Encoding");
responseEncoding = (responseEncoding == null ? "" : responseEncoding.trim());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream in = connection.getInputStream();
try {
in = connection.getInputStream();
if ("gzip".equalsIgnoreCase(responseEncoding)) {
in = new GZIPInputStream(in);
}
in = new BufferedInputStream(in);
byte[] buff = new byte[1024];
int n;
while ((n = in.read(buff)) > 0) {
bos.write(buff, 0, n);
}
bos.flush();
bos.close();
} finally {
if (in != null) {
in.close();
}
}
return bos.toString();
}
} }

View File

@ -23,6 +23,7 @@ import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.miele.internal.discovery.MieleApplianceDiscoveryService; import org.openhab.binding.miele.internal.discovery.MieleApplianceDiscoveryService;
import org.openhab.binding.miele.internal.handler.CoffeeMachineHandler; import org.openhab.binding.miele.internal.handler.CoffeeMachineHandler;
import org.openhab.binding.miele.internal.handler.DishWasherHandler; import org.openhab.binding.miele.internal.handler.DishWasherHandler;
@ -39,6 +40,7 @@ import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocaleProvider; import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
@ -57,6 +59,7 @@ import org.osgi.service.component.annotations.Reference;
* handlers. * handlers.
* *
* @author Karel Goderis - Initial contribution * @author Karel Goderis - Initial contribution
* @author Jacob Laursen - Refactored to use framework's HTTP client
*/ */
@NonNullByDefault @NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele") @Component(service = ThingHandlerFactory.class, configurationPid = "binding.miele")
@ -67,14 +70,17 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
MieleApplianceHandler.SUPPORTED_THING_TYPES.stream()) MieleApplianceHandler.SUPPORTED_THING_TYPES.stream())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
private final HttpClient httpClient;
private final TranslationProvider i18nProvider; private final TranslationProvider i18nProvider;
private final LocaleProvider localeProvider; private final LocaleProvider localeProvider;
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>(); private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Activate @Activate
public MieleHandlerFactory(final @Reference TranslationProvider i18nProvider, public MieleHandlerFactory(@Reference final HttpClientFactory httpClientFactory,
final @Reference LocaleProvider localeProvider, ComponentContext componentContext) { final @Reference TranslationProvider i18nProvider, final @Reference LocaleProvider localeProvider,
ComponentContext componentContext) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.i18nProvider = i18nProvider; this.i18nProvider = i18nProvider;
this.localeProvider = localeProvider; this.localeProvider = localeProvider;
} }
@ -102,7 +108,7 @@ public class MieleHandlerFactory extends BaseThingHandlerFactory {
@Override @Override
protected @Nullable ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { if (MieleBridgeHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
MieleBridgeHandler handler = new MieleBridgeHandler((Bridge) thing); MieleBridgeHandler handler = new MieleBridgeHandler((Bridge) thing, httpClient);
registerApplianceDiscoveryService(handler); registerApplianceDiscoveryService(handler);
return handler; return handler;
} else if (MieleApplianceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) { } else if (MieleApplianceHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {

View File

@ -127,7 +127,7 @@ public abstract class MieleApplianceHandler<E extends Enum<E> & ApplianceChannel
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing Miele appliance handler."); logger.debug("Initializing handler for thing {}", getThing().getUID());
final String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID); final String applianceId = (String) getThing().getConfiguration().getProperties().get(APPLIANCE_ID);
if (applianceId == null || applianceId.isBlank()) { if (applianceId == null || applianceId.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,

View File

@ -17,9 +17,9 @@ import static org.openhab.binding.miele.internal.MieleBindingConstants.*;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.MulticastSocket; import java.net.MulticastSocket;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.IllformedLocaleException; import java.util.IllformedLocaleException;
@ -39,6 +39,7 @@ import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier; import org.openhab.binding.miele.internal.FullyQualifiedApplianceIdentifier;
import org.openhab.binding.miele.internal.MieleGatewayCommunicationController; import org.openhab.binding.miele.internal.MieleGatewayCommunicationController;
import org.openhab.binding.miele.internal.api.dto.DeviceClassObject; import org.openhab.binding.miele.internal.api.dto.DeviceClassObject;
@ -87,7 +88,8 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private boolean lastBridgeConnectionState = false; private boolean lastBridgeConnectionState = false;
private Gson gson = new Gson(); private final HttpClient httpClient;
private final Gson gson = new Gson();
private @NonNullByDefault({}) MieleGatewayCommunicationController gatewayCommunication; private @NonNullByDefault({}) MieleGatewayCommunicationController gatewayCommunication;
private Set<DiscoveryListener> discoveryListeners = ConcurrentHashMap.newKeySet(); private Set<DiscoveryListener> discoveryListeners = ConcurrentHashMap.newKeySet();
@ -99,21 +101,22 @@ public class MieleBridgeHandler extends BaseBridgeHandler {
private Map<String, HomeDevice> cachedHomeDevicesByApplianceId = new ConcurrentHashMap<>(); private Map<String, HomeDevice> cachedHomeDevicesByApplianceId = new ConcurrentHashMap<>();
private Map<String, HomeDevice> cachedHomeDevicesByRemoteUid = new ConcurrentHashMap<>(); private Map<String, HomeDevice> cachedHomeDevicesByRemoteUid = new ConcurrentHashMap<>();
public MieleBridgeHandler(Bridge bridge) { public MieleBridgeHandler(Bridge bridge, HttpClient httpClient) {
super(bridge); super(bridge);
this.httpClient = httpClient;
} }
@Override @Override
public void initialize() { public void initialize() {
logger.debug("Initializing the Miele bridge handler."); logger.debug("Initializing handler for bridge {}", getThing().getUID());
if (!validateConfig(getConfig())) { if (!validateConfig(getConfig())) {
return; return;
} }
try { try {
gatewayCommunication = new MieleGatewayCommunicationController((String) getConfig().get(HOST)); gatewayCommunication = new MieleGatewayCommunicationController(httpClient, (String) getConfig().get(HOST));
} catch (MalformedURLException e) { } catch (URISyntaxException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
return; return;
} }