[VolvoOnCall] Backported fix for #8554 from 3.x

User-Agent HTTP header is no longer sent in the requests
- as Jetty/9.4.20.v20190813 is blacklisted server-side.

The backported fix uses a shim wrapper for HttpUtil, allowing to inject
a customized Jetty's HttpClient, while keeping the rest of the interface
intact (goal was to minimize changes: 3.x branch has a more elegant fix
for the same, but coupled with other changes and refactorings that are
not part of this backport).
Resolves #8554

Signed-off-by: Mateusz Bronk <bronkm+gh@gmail.com>
This commit is contained in:
Mateusz Bronk 2020-10-04 15:02:41 +02:00 committed by Kai Kreuzer
parent 156492c5e1
commit a514e1de1a
4 changed files with 144 additions and 6 deletions

View File

@ -28,6 +28,7 @@ import org.eclipse.smarthome.core.thing.ThingUID;
import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
import org.eclipse.smarthome.core.thing.binding.ThingHandler;
import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
import org.eclipse.smarthome.io.net.http.HttpClientFactory;
import org.openhab.binding.volvooncall.internal.discovery.VolvoOnCallDiscoveryService;
import org.openhab.binding.volvooncall.internal.handler.VehicleHandler;
import org.openhab.binding.volvooncall.internal.handler.VehicleStateDescriptionProvider;
@ -51,10 +52,13 @@ public class VolvoOnCallHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(VolvoOnCallHandlerFactory.class);
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final VehicleStateDescriptionProvider stateDescriptionProvider;
private final HttpClientFactory defaultHttpClientFactory;
@Activate
public VolvoOnCallHandlerFactory(@Reference VehicleStateDescriptionProvider provider) {
public VolvoOnCallHandlerFactory(@Reference VehicleStateDescriptionProvider provider,
@Reference HttpClientFactory httpClientFactory) {
this.stateDescriptionProvider = provider;
this.defaultHttpClientFactory = httpClientFactory;
}
@Override
@ -66,7 +70,8 @@ public class VolvoOnCallHandlerFactory extends BaseThingHandlerFactory {
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (APIBRIDGE_THING_TYPE.equals(thingTypeUID)) {
VolvoOnCallBridgeHandler bridgeHandler = new VolvoOnCallBridgeHandler((Bridge) thing);
VolvoOnCallBridgeHandler bridgeHandler = new VolvoOnCallBridgeHandler((Bridge) thing,
this.defaultHttpClientFactory);
registerDeviceDiscoveryService(bridgeHandler);
return bridgeHandler;
} else if (VEHICLE_THING_TYPE.equals(thingTypeUID)) {

View File

@ -25,8 +25,10 @@ import java.util.Stack;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.smarthome.core.library.types.OnOffType;
import org.eclipse.smarthome.core.library.types.OpenClosedType;
import org.eclipse.smarthome.core.thing.Bridge;
@ -35,13 +37,15 @@ import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.io.net.http.HttpUtil;
import org.eclipse.smarthome.io.net.http.HttpClientFactory;
import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
import org.openhab.binding.volvooncall.internal.VolvoOnCallException.ErrorType;
import org.openhab.binding.volvooncall.internal.config.VolvoOnCallBridgeConfiguration;
import org.openhab.binding.volvooncall.internal.dto.CustomerAccounts;
import org.openhab.binding.volvooncall.internal.dto.PostResponse;
import org.openhab.binding.volvooncall.internal.dto.VocAnswer;
import org.openhab.binding.volvooncall.internal.http.HttpClientProvider_VOC;
import org.openhab.binding.volvooncall.internal.http.HttpUtil_VOC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -55,6 +59,7 @@ import com.google.gson.JsonSyntaxException;
* sent to one of the channels.
*
* @author Gaël L'hopital - Initial contribution
* @author Mateusz Bronk - Backported (to 2.5.x line) a fix for #8554 (User-Agent header removed)
*/
@NonNullByDefault
public class VolvoOnCallBridgeHandler extends BaseBridgeHandler {
@ -66,9 +71,15 @@ public class VolvoOnCallBridgeHandler extends BaseBridgeHandler {
private @NonNullByDefault({}) CustomerAccounts customerAccount;
public VolvoOnCallBridgeHandler(Bridge bridge) {
public VolvoOnCallBridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory) {
super(bridge);
final int MAX_HTTP_CLIENT_NAME_LENGTH = 20;
HttpClient customHttpClient = httpClientFactory.createHttpClient(
StringUtils.left("voc_bridge_" + bridge.getUID().getId(), MAX_HTTP_CLIENT_NAME_LENGTH));
customHttpClient.setUserAgentField(null); // This causes Jetty not to send User-Agent header, fixing #8554
HttpUtil_VOC.SetHttpClientFactory(new HttpClientProvider_VOC(customHttpClient));
httpHeader.put("cache-control", "no-cache");
httpHeader.put("content-type", JSON_CONTENT_TYPE);
httpHeader.put("x-device-id", "Device");
@ -129,7 +140,8 @@ public class VolvoOnCallBridgeHandler extends BaseBridgeHandler {
public <T extends VocAnswer> T getURL(String url, Class<T> objectClass) throws VolvoOnCallException {
try {
String jsonResponse = HttpUtil.executeUrl("GET", url, httpHeader, null, JSON_CONTENT_TYPE, REQUEST_TIMEOUT);
String jsonResponse = HttpUtil_VOC.executeUrl("GET", url, httpHeader, null, JSON_CONTENT_TYPE,
REQUEST_TIMEOUT);
logger.debug("Request for : {}", url);
logger.debug("Received : {}", jsonResponse);
T response = gson.fromJson(jsonResponse, objectClass);
@ -177,7 +189,7 @@ public class VolvoOnCallBridgeHandler extends BaseBridgeHandler {
void postURL(String URL, @Nullable String body) throws VolvoOnCallException {
InputStream inputStream = body != null ? new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)) : null;
try {
String jsonString = HttpUtil.executeUrl("POST", URL, httpHeader, inputStream, null, REQUEST_TIMEOUT);
String jsonString = HttpUtil_VOC.executeUrl("POST", URL, httpHeader, inputStream, null, REQUEST_TIMEOUT);
logger.debug("Post URL: {} Attributes {}", URL, httpHeader);
PostResponse postResponse = gson.fromJson(jsonString, PostResponse.class);
String error = postResponse.getErrorLabel();

View File

@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.volvooncall.internal.http;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.smarthome.io.net.http.HttpClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link HttpClientProvider_VOC} is a *FAKE* HttpClient factory
* which always serves the same instance (provided by the user).
* Only the {@link getCommonHttpClient} method is implemented.
*
* @implNote The intent is to allow usage of the full implementation of HTTP methods
* implemented by {@link HttpUtil} class, while allowing to override User-Agent header,
* which is not possible via the current interface of HttpUtil.
* This class was created for the sole purpose of backporting a 'User-Agent' fix (#8554)
* to 2.5.x branch, without the need of refactoring VOC binding or openhab-core.
* A full/proper change would likely involve changes to HttpClient's interface
* ##This should not be merged into OH3##
*
* @author Mateusz Bronk - Initial contribution for the purpose of backporting #8554 fix.
*/
@NonNullByDefault
public class HttpClientProvider_VOC implements HttpClientFactory {
private static final Logger logger = LoggerFactory.getLogger(HttpClientProvider_VOC.class);
private final HttpClient httpClient;
public HttpClientProvider_VOC(final HttpClient theClient) {
this.httpClient = theClient;
if (!this.httpClient.isRunning()) {
try {
this.httpClient.start();
} catch (Exception e) {
logger.error("Could not start Jetty http client", e);
}
}
}
public void dispose() throws Exception {
this.httpClient.stop();
}
@Override
public HttpClient createHttpClient(String consumerName) {
throw new UnsupportedOperationException();
}
@Override
public HttpClient createHttpClient(String consumerName, String endpoint) {
throw new UnsupportedOperationException();
}
@Override
public HttpClient getCommonHttpClient() {
logger.trace("Using custom HttpClient: {}", this.httpClient);
return this.httpClient;
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.volvooncall.internal.http;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.io.net.http.HttpClientFactory;
import org.eclipse.smarthome.io.net.http.HttpUtil;
/**
* The {@link HttpUtil_VOC} is basically a clone of {@link HttpUtil}, (from opehnhab-core)
* though it allows external entities to override its private static copy of HttpClientFactory.
*
* @implNote This class was created for the sole purpose of backporting a 'User-Agent' fix (#8554)
* to 2.5.x branch, without the need of refactoring VOC binding or openhab-core.
* ##This should not be merged into OH3##
*
* @author Mateusz Bronk - Initial contribution for the purpose of backporting #8554 fix.
*/
@NonNullByDefault
public class HttpUtil_VOC extends HttpUtil {
/**
* @implNote Instantiating this class is not really required for any purpose, as all the fields
* are static. It is there just to access the protected 'super.setHttpClientFactory()'
* and to avoid reflection or forcing clients of this class to use a stray 'new'
*/
private static final HttpUtil_VOC instance = new HttpUtil_VOC();
private HttpUtil_VOC() {
}
/**
* Overrides HttpClientFactory used by {@link HttpUtil_VOC} static methods
*
* @param httpClientFactory The new HttpClientFactory to use
*/
public static void SetHttpClientFactory(@Nullable final HttpClientFactory httpClientFactory) {
instance.setHttpClientFactory(httpClientFactory);
}
}