[neeo] Convert to OH jetty HttpClient (#15571)
* Convert to OH jetty HttpClient * Adds a Stack for HttpClient storage * Add synchronized to prevent exception * fix binding * Stop stack on close * Resolves exception on registring forward actions * Reduces client count to 5 to align to expected thread pool limit of 5 --------- Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com> Co-authored-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
@@ -20,9 +20,8 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.neeo.internal.models.ExecuteResult;
|
||||
import org.openhab.binding.neeo.internal.models.NeeoBrain;
|
||||
@@ -52,8 +51,7 @@ public class NeeoBrainApi implements AutoCloseable {
|
||||
/** The {@link HttpRequest} used for making requests */
|
||||
private final AtomicReference<HttpRequest> request;
|
||||
|
||||
/** The {@link ClientBuilder} to use */
|
||||
private final ClientBuilder clientBuilder;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/** The IP address of the neeo brain */
|
||||
private final NeeoUrlBuilder urlBuilder;
|
||||
@@ -63,14 +61,14 @@ public class NeeoBrainApi implements AutoCloseable {
|
||||
*
|
||||
* @param ipAddress the non-empty ip address
|
||||
*/
|
||||
public NeeoBrainApi(String ipAddress, ClientBuilder clientBuilder) {
|
||||
public NeeoBrainApi(String ipAddress, HttpClient httpClient) {
|
||||
NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
|
||||
|
||||
this.urlBuilder = new NeeoUrlBuilder(
|
||||
NeeoConstants.PROTOCOL + ipAddress + ":" + NeeoConstants.DEFAULT_BRAIN_PORT);
|
||||
this.clientBuilder = clientBuilder;
|
||||
this.httpClient = httpClient;
|
||||
|
||||
request = new AtomicReference<>(new HttpRequest(clientBuilder));
|
||||
request = new AtomicReference<>(new HttpRequest(httpClient));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,7 +244,7 @@ public class NeeoBrainApi implements AutoCloseable {
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
NeeoUtil.close(request.getAndSet(new HttpRequest(clientBuilder)));
|
||||
NeeoUtil.close(request.getAndSet(new HttpRequest(httpClient)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,10 +29,10 @@ import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainApi;
|
||||
import org.openhab.binding.neeo.internal.NeeoBrainConfig;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
@@ -72,8 +72,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
|
||||
/** The {@link NetworkAddressService} to use */
|
||||
private final NetworkAddressService networkAddressService;
|
||||
|
||||
/** The {@link ClientBuilder} to use */
|
||||
private final ClientBuilder clientBuilder;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/** GSON implementation - only used to deserialize {@link NeeoAction} */
|
||||
private final Gson gson = new Gson();
|
||||
@@ -114,7 +113,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
|
||||
* @param networkAddressService the non-null {@link NetworkAddressService}
|
||||
*/
|
||||
NeeoBrainHandler(Bridge bridge, int servicePort, HttpService httpService,
|
||||
NetworkAddressService networkAddressService, ClientBuilder clientBuilder) {
|
||||
NetworkAddressService networkAddressService, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
|
||||
Objects.requireNonNull(bridge, "bridge cannot be null");
|
||||
@@ -124,7 +123,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
|
||||
this.servicePort = servicePort;
|
||||
this.httpService = httpService;
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.clientBuilder = clientBuilder;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +168,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
|
||||
"Brain IP Address must be specified");
|
||||
return;
|
||||
}
|
||||
final NeeoBrainApi api = new NeeoBrainApi(ipAddress, clientBuilder);
|
||||
final NeeoBrainApi api = new NeeoBrainApi(ipAddress, httpClient);
|
||||
final NeeoBrain brain = api.getBrain();
|
||||
final String brainId = getNeeoBrainId();
|
||||
|
||||
@@ -195,13 +194,17 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
|
||||
final NeeoAction action = Objects.requireNonNull(gson.fromJson(json, NeeoAction.class));
|
||||
getThing().getThings().stream().map(Thing::getHandler).filter(NeeoRoomHandler.class::isInstance)
|
||||
.forEach(h -> ((NeeoRoomHandler) h).processAction(action));
|
||||
}, config.getForwardChain(), clientBuilder);
|
||||
}, config.getForwardChain(), httpClient);
|
||||
|
||||
NeeoUtil.checkInterrupt();
|
||||
try {
|
||||
servletPath = NeeoConstants.WEBAPP_FORWARDACTIONS.replace("{brainid}", brainId);
|
||||
String servletPath = NeeoConstants.WEBAPP_FORWARDACTIONS.replace("{brainid}", brainId);
|
||||
this.servletPath = servletPath;
|
||||
|
||||
httpService.registerServlet(servletPath, forwardActionServlet, new Hashtable<>(),
|
||||
Hashtable<Object, Object> initParams = new Hashtable<>();
|
||||
initParams.put("servlet-name", servletPath);
|
||||
|
||||
httpService.registerServlet(servletPath, forwardActionServlet, initParams,
|
||||
httpService.createDefaultHttpContext());
|
||||
|
||||
final URL callbackURL = createCallbackUrl(brainId, config);
|
||||
|
||||
@@ -20,10 +20,10 @@ import java.util.stream.Collectors;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.neeo.internal.net.HttpRequest;
|
||||
import org.openhab.binding.neeo.internal.net.HttpResponse;
|
||||
@@ -50,8 +50,7 @@ public class NeeoForwardActionsServlet extends HttpServlet {
|
||||
/** The forwarding chain */
|
||||
private final @Nullable String forwardChain;
|
||||
|
||||
/** The {@link ClientBuilder} to use */
|
||||
private final ClientBuilder clientBuilder;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/** The scheduler to use to schedule recipe execution */
|
||||
private final ScheduledExecutorService scheduler;
|
||||
@@ -64,13 +63,13 @@ public class NeeoForwardActionsServlet extends HttpServlet {
|
||||
* @param forwardChain a possibly null, possibly empty forwarding chain
|
||||
*/
|
||||
NeeoForwardActionsServlet(ScheduledExecutorService scheduler, Consumer<String> callback,
|
||||
@Nullable String forwardChain, ClientBuilder clientBuilder) {
|
||||
@Nullable String forwardChain, HttpClient httpClient) {
|
||||
super();
|
||||
|
||||
this.scheduler = scheduler;
|
||||
this.callback = callback;
|
||||
this.forwardChain = forwardChain;
|
||||
this.clientBuilder = clientBuilder;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,9 +91,10 @@ public class NeeoForwardActionsServlet extends HttpServlet {
|
||||
|
||||
callback.accept(json);
|
||||
|
||||
String forwardChain = this.forwardChain;
|
||||
if (forwardChain != null && !forwardChain.isEmpty()) {
|
||||
scheduler.execute(() -> {
|
||||
try (final HttpRequest request = new HttpRequest(clientBuilder)) {
|
||||
try (final HttpRequest request = new HttpRequest(httpClient)) {
|
||||
for (final String forwardUrl : forwardChain.split(",")) {
|
||||
if (!forwardUrl.isEmpty()) {
|
||||
final HttpResponse httpResponse = request.sendPostJsonCommand(forwardUrl, json);
|
||||
|
||||
@@ -17,14 +17,14 @@ import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.neeo.internal.NeeoConstants;
|
||||
import org.openhab.binding.neeo.internal.discovery.NeeoDeviceDiscoveryService;
|
||||
import org.openhab.binding.neeo.internal.discovery.NeeoRoomDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.HttpServiceUtil;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@@ -56,18 +56,17 @@ public class NeeoHandlerFactory extends BaseThingHandlerFactory {
|
||||
/** The {@link NetworkAddressService} used for ip lookup */
|
||||
private final NetworkAddressService networkAddressService;
|
||||
|
||||
/** The {@link ClientBuilder} used in HttpRequest */
|
||||
private final ClientBuilder clientBuilder;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/** The discovery services created by this class (one per room and one for each device) */
|
||||
private final ConcurrentMap<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new ConcurrentHashMap<>();
|
||||
|
||||
@Activate
|
||||
public NeeoHandlerFactory(@Reference HttpService httpService,
|
||||
@Reference NetworkAddressService networkAddressService, @Reference ClientBuilder clientBuilder) {
|
||||
@Reference NetworkAddressService networkAddressService, @Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpService = httpService;
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.clientBuilder = clientBuilder;
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +86,7 @@ public class NeeoHandlerFactory extends BaseThingHandlerFactory {
|
||||
final int port = HttpServiceUtil.getHttpServicePort(this.bundleContext);
|
||||
final NeeoBrainHandler handler = new NeeoBrainHandler((Bridge) thing,
|
||||
port < 0 ? NeeoConstants.DEFAULT_BRAIN_HTTP_PORT : port, httpService, networkAddressService,
|
||||
clientBuilder);
|
||||
httpClient);
|
||||
registerRoomDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_ROOM)) {
|
||||
|
||||
@@ -16,18 +16,19 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.ws.rs.ProcessingException;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.ClientBuilder;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.Invocation.Builder;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.glassfish.jersey.filter.LoggingFilter;
|
||||
import org.openhab.binding.neeo.internal.NeeoUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -44,17 +45,13 @@ public class HttpRequest implements AutoCloseable {
|
||||
private final Logger logger = LoggerFactory.getLogger(HttpRequest.class);
|
||||
|
||||
/** The client to use */
|
||||
private final Client client;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/**
|
||||
* Instantiates a new request
|
||||
*/
|
||||
public HttpRequest(ClientBuilder clientBuilder) {
|
||||
client = clientBuilder.build();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
client.register(new LoggingFilter(new Slf4LoggingAdapter(logger), true));
|
||||
}
|
||||
public HttpRequest(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,17 +63,17 @@ public class HttpRequest implements AutoCloseable {
|
||||
public HttpResponse sendGetCommand(String uri) {
|
||||
NeeoUtil.requireNotEmpty(uri, "uri cannot be empty");
|
||||
try {
|
||||
final Builder request = client.target(uri).request();
|
||||
|
||||
final Response content = request.get();
|
||||
|
||||
try {
|
||||
return new HttpResponse(content);
|
||||
} finally {
|
||||
content.close();
|
||||
}
|
||||
final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(uri);
|
||||
request.method(HttpMethod.GET);
|
||||
request.timeout(10, TimeUnit.SECONDS);
|
||||
ContentResponse refreshResponse = request.send();
|
||||
return new HttpResponse(refreshResponse);
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage());
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("An exception occurred while invoking a HTTP request: '{}'", e.getMessage());
|
||||
String message = e.getMessage();
|
||||
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,26 +96,27 @@ public class HttpRequest implements AutoCloseable {
|
||||
logger.warn("Absolute URI required but provided URI '{}' is non-absolute. ", uriString);
|
||||
return new HttpResponse(HttpStatus.NOT_ACCEPTABLE_406, "Absolute URI required");
|
||||
}
|
||||
final Builder request = client.target(targetUri).request(MediaType.APPLICATION_JSON);
|
||||
|
||||
final Response content = request.post(Entity.entity(body, MediaType.APPLICATION_JSON));
|
||||
|
||||
try {
|
||||
return new HttpResponse(content);
|
||||
} finally {
|
||||
content.close();
|
||||
}
|
||||
final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(targetUri);
|
||||
request.content(new StringContentProvider(body));
|
||||
request.header(HttpHeader.CONTENT_TYPE, "application/json");
|
||||
request.method(HttpMethod.POST);
|
||||
request.timeout(10, TimeUnit.SECONDS);
|
||||
ContentResponse refreshResponse = request.send();
|
||||
return new HttpResponse(refreshResponse);
|
||||
// IllegalArgumentException/ProcessingException catches issues with the URI being invalid
|
||||
// as well
|
||||
} catch (IOException | IllegalStateException | IllegalArgumentException | ProcessingException e) {
|
||||
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage());
|
||||
} catch (URISyntaxException e) {
|
||||
return new HttpResponse(HttpStatus.NOT_ACCEPTABLE_406, e.getMessage());
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("An exception occurred while invoking a HTTP request: '{}'", e.getMessage());
|
||||
String message = e.getMessage();
|
||||
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
package org.openhab.binding.neeo.internal.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -23,6 +22,7 @@ import javax.ws.rs.core.Response;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
|
||||
/**
|
||||
* This class represents an {@link HttpRequest} response
|
||||
@@ -47,24 +47,18 @@ public class HttpResponse {
|
||||
/**
|
||||
* Instantiates a new http response from the {@link Response}.
|
||||
*
|
||||
* @param response the non-null response
|
||||
* @param refreshResponse the non-null response
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
HttpResponse(Response response) throws IOException {
|
||||
Objects.requireNonNull(response, "response cannot be null");
|
||||
HttpResponse(ContentResponse refreshResponse) throws IOException {
|
||||
Objects.requireNonNull(refreshResponse, "response cannot be null");
|
||||
|
||||
httpStatus = response.getStatus();
|
||||
httpReason = response.getStatusInfo().getReasonPhrase();
|
||||
httpStatus = refreshResponse.getStatus();
|
||||
httpReason = refreshResponse.getReason();
|
||||
contents = refreshResponse.getContent();
|
||||
|
||||
if (response.hasEntity()) {
|
||||
InputStream is = response.readEntity(InputStream.class);
|
||||
contents = is.readAllBytes();
|
||||
} else {
|
||||
contents = null;
|
||||
}
|
||||
|
||||
for (String key : response.getHeaders().keySet()) {
|
||||
headers.put(key, response.getHeaderString(key));
|
||||
for (String key : refreshResponse.getHeaders().getFieldNamesCollection()) {
|
||||
headers.put(key, refreshResponse.getHeaders().getField(key).toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user