[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:
morph166955 2023-10-08 03:22:53 -05:00 committed by GitHub
parent 7e53167967
commit 58d20839c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 211 additions and 168 deletions

View File

@ -20,9 +20,8 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.neeo.internal.models.ExecuteResult; import org.openhab.binding.neeo.internal.models.ExecuteResult;
import org.openhab.binding.neeo.internal.models.NeeoBrain; import org.openhab.binding.neeo.internal.models.NeeoBrain;
@ -52,8 +51,7 @@ public class NeeoBrainApi implements AutoCloseable {
/** The {@link HttpRequest} used for making requests */ /** The {@link HttpRequest} used for making requests */
private final AtomicReference<HttpRequest> request; private final AtomicReference<HttpRequest> request;
/** The {@link ClientBuilder} to use */ private final HttpClient httpClient;
private final ClientBuilder clientBuilder;
/** The IP address of the neeo brain */ /** The IP address of the neeo brain */
private final NeeoUrlBuilder urlBuilder; private final NeeoUrlBuilder urlBuilder;
@ -63,14 +61,14 @@ public class NeeoBrainApi implements AutoCloseable {
* *
* @param ipAddress the non-empty ip address * @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"); NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
this.urlBuilder = new NeeoUrlBuilder( this.urlBuilder = new NeeoUrlBuilder(
NeeoConstants.PROTOCOL + ipAddress + ":" + NeeoConstants.DEFAULT_BRAIN_PORT); 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 @Override
public void close() throws Exception { public void close() throws Exception {
NeeoUtil.close(request.getAndSet(new HttpRequest(clientBuilder))); NeeoUtil.close(request.getAndSet(new HttpRequest(httpClient)));
} }
/** /**

View File

@ -29,10 +29,10 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.ws.rs.client.ClientBuilder;
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.neeo.internal.NeeoBrainApi; import org.openhab.binding.neeo.internal.NeeoBrainApi;
import org.openhab.binding.neeo.internal.NeeoBrainConfig; import org.openhab.binding.neeo.internal.NeeoBrainConfig;
import org.openhab.binding.neeo.internal.NeeoConstants; import org.openhab.binding.neeo.internal.NeeoConstants;
@ -72,8 +72,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
/** The {@link NetworkAddressService} to use */ /** The {@link NetworkAddressService} to use */
private final NetworkAddressService networkAddressService; private final NetworkAddressService networkAddressService;
/** The {@link ClientBuilder} to use */ private final HttpClient httpClient;
private final ClientBuilder clientBuilder;
/** GSON implementation - only used to deserialize {@link NeeoAction} */ /** GSON implementation - only used to deserialize {@link NeeoAction} */
private final Gson gson = new Gson(); private final Gson gson = new Gson();
@ -114,7 +113,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
* @param networkAddressService the non-null {@link NetworkAddressService} * @param networkAddressService the non-null {@link NetworkAddressService}
*/ */
NeeoBrainHandler(Bridge bridge, int servicePort, HttpService httpService, NeeoBrainHandler(Bridge bridge, int servicePort, HttpService httpService,
NetworkAddressService networkAddressService, ClientBuilder clientBuilder) { NetworkAddressService networkAddressService, HttpClient httpClient) {
super(bridge); super(bridge);
Objects.requireNonNull(bridge, "bridge cannot be null"); Objects.requireNonNull(bridge, "bridge cannot be null");
@ -124,7 +123,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
this.servicePort = servicePort; this.servicePort = servicePort;
this.httpService = httpService; this.httpService = httpService;
this.networkAddressService = networkAddressService; this.networkAddressService = networkAddressService;
this.clientBuilder = clientBuilder; this.httpClient = httpClient;
} }
/** /**
@ -169,7 +168,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
"Brain IP Address must be specified"); "Brain IP Address must be specified");
return; return;
} }
final NeeoBrainApi api = new NeeoBrainApi(ipAddress, clientBuilder); final NeeoBrainApi api = new NeeoBrainApi(ipAddress, httpClient);
final NeeoBrain brain = api.getBrain(); final NeeoBrain brain = api.getBrain();
final String brainId = getNeeoBrainId(); final String brainId = getNeeoBrainId();
@ -195,13 +194,17 @@ public class NeeoBrainHandler extends BaseBridgeHandler {
final NeeoAction action = Objects.requireNonNull(gson.fromJson(json, NeeoAction.class)); final NeeoAction action = Objects.requireNonNull(gson.fromJson(json, NeeoAction.class));
getThing().getThings().stream().map(Thing::getHandler).filter(NeeoRoomHandler.class::isInstance) getThing().getThings().stream().map(Thing::getHandler).filter(NeeoRoomHandler.class::isInstance)
.forEach(h -> ((NeeoRoomHandler) h).processAction(action)); .forEach(h -> ((NeeoRoomHandler) h).processAction(action));
}, config.getForwardChain(), clientBuilder); }, config.getForwardChain(), httpClient);
NeeoUtil.checkInterrupt(); NeeoUtil.checkInterrupt();
try { 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()); httpService.createDefaultHttpContext());
final URL callbackURL = createCallbackUrl(brainId, config); final URL callbackURL = createCallbackUrl(brainId, config);

View File

@ -20,10 +20,10 @@ import java.util.stream.Collectors;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.ClientBuilder;
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.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.neeo.internal.net.HttpRequest; import org.openhab.binding.neeo.internal.net.HttpRequest;
import org.openhab.binding.neeo.internal.net.HttpResponse; import org.openhab.binding.neeo.internal.net.HttpResponse;
@ -50,8 +50,7 @@ public class NeeoForwardActionsServlet extends HttpServlet {
/** The forwarding chain */ /** The forwarding chain */
private final @Nullable String forwardChain; private final @Nullable String forwardChain;
/** The {@link ClientBuilder} to use */ private final HttpClient httpClient;
private final ClientBuilder clientBuilder;
/** The scheduler to use to schedule recipe execution */ /** The scheduler to use to schedule recipe execution */
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
@ -64,13 +63,13 @@ public class NeeoForwardActionsServlet extends HttpServlet {
* @param forwardChain a possibly null, possibly empty forwarding chain * @param forwardChain a possibly null, possibly empty forwarding chain
*/ */
NeeoForwardActionsServlet(ScheduledExecutorService scheduler, Consumer<String> callback, NeeoForwardActionsServlet(ScheduledExecutorService scheduler, Consumer<String> callback,
@Nullable String forwardChain, ClientBuilder clientBuilder) { @Nullable String forwardChain, HttpClient httpClient) {
super(); super();
this.scheduler = scheduler; this.scheduler = scheduler;
this.callback = callback; this.callback = callback;
this.forwardChain = forwardChain; this.forwardChain = forwardChain;
this.clientBuilder = clientBuilder; this.httpClient = httpClient;
} }
/** /**
@ -92,9 +91,10 @@ public class NeeoForwardActionsServlet extends HttpServlet {
callback.accept(json); callback.accept(json);
String forwardChain = this.forwardChain;
if (forwardChain != null && !forwardChain.isEmpty()) { if (forwardChain != null && !forwardChain.isEmpty()) {
scheduler.execute(() -> { scheduler.execute(() -> {
try (final HttpRequest request = new HttpRequest(clientBuilder)) { try (final HttpRequest request = new HttpRequest(httpClient)) {
for (final String forwardUrl : forwardChain.split(",")) { for (final String forwardUrl : forwardChain.split(",")) {
if (!forwardUrl.isEmpty()) { if (!forwardUrl.isEmpty()) {
final HttpResponse httpResponse = request.sendPostJsonCommand(forwardUrl, json); final HttpResponse httpResponse = request.sendPostJsonCommand(forwardUrl, json);

View File

@ -17,14 +17,14 @@ import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import javax.ws.rs.client.ClientBuilder;
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.neeo.internal.NeeoConstants; import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.discovery.NeeoDeviceDiscoveryService; import org.openhab.binding.neeo.internal.discovery.NeeoDeviceDiscoveryService;
import org.openhab.binding.neeo.internal.discovery.NeeoRoomDiscoveryService; import org.openhab.binding.neeo.internal.discovery.NeeoRoomDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService; 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.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService; import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
@ -56,18 +56,17 @@ public class NeeoHandlerFactory extends BaseThingHandlerFactory {
/** The {@link NetworkAddressService} used for ip lookup */ /** The {@link NetworkAddressService} used for ip lookup */
private final NetworkAddressService networkAddressService; private final NetworkAddressService networkAddressService;
/** The {@link ClientBuilder} used in HttpRequest */ private final HttpClient httpClient;
private final ClientBuilder clientBuilder;
/** The discovery services created by this class (one per room and one for each device) */ /** The discovery services created by this class (one per room and one for each device) */
private final ConcurrentMap<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new ConcurrentHashMap<>(); private final ConcurrentMap<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new ConcurrentHashMap<>();
@Activate @Activate
public NeeoHandlerFactory(@Reference HttpService httpService, public NeeoHandlerFactory(@Reference HttpService httpService,
@Reference NetworkAddressService networkAddressService, @Reference ClientBuilder clientBuilder) { @Reference NetworkAddressService networkAddressService, @Reference HttpClientFactory httpClientFactory) {
this.httpService = httpService; this.httpService = httpService;
this.networkAddressService = networkAddressService; this.networkAddressService = networkAddressService;
this.clientBuilder = clientBuilder; this.httpClient = httpClientFactory.getCommonHttpClient();
} }
@Override @Override
@ -87,7 +86,7 @@ public class NeeoHandlerFactory extends BaseThingHandlerFactory {
final int port = HttpServiceUtil.getHttpServicePort(this.bundleContext); final int port = HttpServiceUtil.getHttpServicePort(this.bundleContext);
final NeeoBrainHandler handler = new NeeoBrainHandler((Bridge) thing, final NeeoBrainHandler handler = new NeeoBrainHandler((Bridge) thing,
port < 0 ? NeeoConstants.DEFAULT_BRAIN_HTTP_PORT : port, httpService, networkAddressService, port < 0 ? NeeoConstants.DEFAULT_BRAIN_HTTP_PORT : port, httpService, networkAddressService,
clientBuilder); httpClient);
registerRoomDiscoveryService(handler); registerRoomDiscoveryService(handler);
return handler; return handler;
} else if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_ROOM)) { } else if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_ROOM)) {

View File

@ -16,18 +16,19 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Objects; 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.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.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.eclipse.jetty.http.HttpStatus;
import org.glassfish.jersey.filter.LoggingFilter;
import org.openhab.binding.neeo.internal.NeeoUtil; import org.openhab.binding.neeo.internal.NeeoUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -44,17 +45,13 @@ public class HttpRequest implements AutoCloseable {
private final Logger logger = LoggerFactory.getLogger(HttpRequest.class); private final Logger logger = LoggerFactory.getLogger(HttpRequest.class);
/** The client to use */ /** The client to use */
private final Client client; private final HttpClient httpClient;
/** /**
* Instantiates a new request * Instantiates a new request
*/ */
public HttpRequest(ClientBuilder clientBuilder) { public HttpRequest(HttpClient httpClient) {
client = clientBuilder.build(); this.httpClient = httpClient;
if (logger.isDebugEnabled()) {
client.register(new LoggingFilter(new Slf4LoggingAdapter(logger), true));
}
} }
/** /**
@ -66,17 +63,17 @@ public class HttpRequest implements AutoCloseable {
public HttpResponse sendGetCommand(String uri) { public HttpResponse sendGetCommand(String uri) {
NeeoUtil.requireNotEmpty(uri, "uri cannot be empty"); NeeoUtil.requireNotEmpty(uri, "uri cannot be empty");
try { try {
final Builder request = client.target(uri).request(); final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(uri);
request.method(HttpMethod.GET);
final Response content = request.get(); request.timeout(10, TimeUnit.SECONDS);
ContentResponse refreshResponse = request.send();
try { return new HttpResponse(refreshResponse);
return new HttpResponse(content);
} finally {
content.close();
}
} catch (IOException | IllegalStateException e) { } catch (IOException | IllegalStateException e) {
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage()); 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); logger.warn("Absolute URI required but provided URI '{}' is non-absolute. ", uriString);
return new HttpResponse(HttpStatus.NOT_ACCEPTABLE_406, "Absolute URI required"); return new HttpResponse(HttpStatus.NOT_ACCEPTABLE_406, "Absolute URI required");
} }
final Builder request = client.target(targetUri).request(MediaType.APPLICATION_JSON); final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(targetUri);
request.content(new StringContentProvider(body));
final Response content = request.post(Entity.entity(body, MediaType.APPLICATION_JSON)); request.header(HttpHeader.CONTENT_TYPE, "application/json");
request.method(HttpMethod.POST);
try { request.timeout(10, TimeUnit.SECONDS);
return new HttpResponse(content); ContentResponse refreshResponse = request.send();
} finally { return new HttpResponse(refreshResponse);
content.close();
}
// IllegalArgumentException/ProcessingException catches issues with the URI being invalid // IllegalArgumentException/ProcessingException catches issues with the URI being invalid
// as well // as well
} catch (IOException | IllegalStateException | IllegalArgumentException | ProcessingException e) { } catch (IOException | IllegalStateException | IllegalArgumentException | ProcessingException e) {
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage()); return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage());
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
return new HttpResponse(HttpStatus.NOT_ACCEPTABLE_406, e.getMessage()); 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 @Override
public void close() { public void close() {
client.close();
} }
} }

View File

@ -13,7 +13,6 @@
package org.openhab.binding.neeo.internal.net; package org.openhab.binding.neeo.internal.net;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.ContentResponse;
/** /**
* This class represents an {@link HttpRequest} response * This class represents an {@link HttpRequest} response
@ -47,24 +47,18 @@ public class HttpResponse {
/** /**
* Instantiates a new http response from the {@link Response}. * 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. * @throws IOException Signals that an I/O exception has occurred.
*/ */
HttpResponse(Response response) throws IOException { HttpResponse(ContentResponse refreshResponse) throws IOException {
Objects.requireNonNull(response, "response cannot be null"); Objects.requireNonNull(refreshResponse, "response cannot be null");
httpStatus = response.getStatus(); httpStatus = refreshResponse.getStatus();
httpReason = response.getStatusInfo().getReasonPhrase(); httpReason = refreshResponse.getReason();
contents = refreshResponse.getContent();
if (response.hasEntity()) { for (String key : refreshResponse.getHeaders().getFieldNamesCollection()) {
InputStream is = response.readEntity(InputStream.class); headers.put(key, refreshResponse.getHeaders().getField(key).toString());
contents = is.readAllBytes();
} else {
contents = null;
}
for (String key : response.getHeaders().keySet()) {
headers.put(key, response.getHeaderString(key));
} }
} }

View File

@ -23,16 +23,17 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.ws.rs.client.ClientBuilder;
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.core.addon.AddonInfoRegistry; import org.openhab.core.addon.AddonInfoRegistry;
import org.openhab.core.config.core.ConfigurableService; import org.openhab.core.config.core.ConfigurableService;
import org.openhab.core.events.Event; import org.openhab.core.events.Event;
import org.openhab.core.events.EventFilter; import org.openhab.core.events.EventFilter;
import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventPublisher;
import org.openhab.core.events.EventSubscriber; import org.openhab.core.events.EventSubscriber;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.transport.mdns.MDNSClient; import org.openhab.core.io.transport.mdns.MDNSClient;
import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemStateChangedEvent; import org.openhab.core.items.events.ItemStateChangedEvent;
@ -95,7 +96,8 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene
private final MDNSClient mdnsClient; private final MDNSClient mdnsClient;
private final EventPublisher eventPublisher; private final EventPublisher eventPublisher;
private final NetworkAddressService networkAddressService; private final NetworkAddressService networkAddressService;
private final ClientBuilder clientBuilder; private final HttpClientFactory httpClientFactory;
private final HttpClient httpClient;
/** The main dashboard servlet. Only created in the activate method (and disposed of in the deactivate method) */ /** The main dashboard servlet. Only created in the activate method (and disposed of in the deactivate method) */
private @Nullable NeeoDashboardServlet dashboardServlet; private @Nullable NeeoDashboardServlet dashboardServlet;
@ -143,7 +145,7 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene
@Reference AddonInfoRegistry addonInfoRegistry, @Reference ChannelTypeRegistry channelTypeRegistry, @Reference AddonInfoRegistry addonInfoRegistry, @Reference ChannelTypeRegistry channelTypeRegistry,
@Reference ThingTypeRegistry thingTypeRegistry, @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, @Reference ThingTypeRegistry thingTypeRegistry, @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
@Reference MDNSClient mdnsClient, @Reference EventPublisher eventPublisher, @Reference MDNSClient mdnsClient, @Reference EventPublisher eventPublisher,
@Reference NetworkAddressService networkAddressService, @Reference ClientBuilder clientBuilder) { @Reference NetworkAddressService networkAddressService, @Reference HttpClientFactory httpClientFactory) {
this.httpService = httpService; this.httpService = httpService;
this.itemRegistry = itemRegistry; this.itemRegistry = itemRegistry;
this.bindingInfoRegistry = addonInfoRegistry; this.bindingInfoRegistry = addonInfoRegistry;
@ -154,7 +156,8 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene
this.mdnsClient = mdnsClient; this.mdnsClient = mdnsClient;
this.eventPublisher = eventPublisher; this.eventPublisher = eventPublisher;
this.networkAddressService = networkAddressService; this.networkAddressService = networkAddressService;
this.clientBuilder = clientBuilder; this.httpClientFactory = httpClientFactory;
this.httpClient = httpClientFactory.getCommonHttpClient();
logger.debug("Neeo Service activated"); logger.debug("Neeo Service activated");
final ServiceContext localContext = new ServiceContext(componentContext, validate(httpService, "httpService"), final ServiceContext localContext = new ServiceContext(componentContext, validate(httpService, "httpService"),
@ -165,7 +168,7 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene
validate(eventPublisher, "eventPublisher"), validate(networkAddressService, "networkAddressService")); validate(eventPublisher, "eventPublisher"), validate(networkAddressService, "networkAddressService"));
context = localContext; context = localContext;
discovery = new MdnsBrainDiscovery(localContext, clientBuilder); discovery = new MdnsBrainDiscovery(localContext, httpClient);
discovery.addListener(discoveryListener); discovery.addListener(discoveryListener);
try { try {
@ -279,7 +282,7 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene
servletUrl); servletUrl);
try { try {
final NeeoBrainServlet newServlet = NeeoBrainServlet.create(localContext, servletUrl, final NeeoBrainServlet newServlet = NeeoBrainServlet.create(localContext, servletUrl,
sysInfo.getHostname(), ipAddress, clientBuilder); sysInfo.getHostname(), ipAddress, httpClient, httpClientFactory);
servlets.add(newServlet); servlets.add(newServlet);
Hashtable<Object, Object> initParams = new Hashtable<>(); Hashtable<Object, Object> initParams = new Hashtable<>();

View File

@ -20,6 +20,7 @@ import java.net.Socket;
import java.net.URL; import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Stack;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
@ -27,13 +28,13 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.client.ClientBuilder;
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.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.id.InstanceUUID; import org.openhab.core.id.InstanceUUID;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.net.HttpServiceUtil; import org.openhab.core.net.HttpServiceUtil;
import org.openhab.io.neeo.internal.models.NeeoAdapterRegistration; import org.openhab.io.neeo.internal.models.NeeoAdapterRegistration;
import org.openhab.io.neeo.internal.models.NeeoRecipe; import org.openhab.io.neeo.internal.models.NeeoRecipe;
@ -69,7 +70,13 @@ public class NeeoApi implements AutoCloseable {
/** The brain's IP address */ /** The brain's IP address */
private final String brainIpAddress; private final String brainIpAddress;
private final ClientBuilder clientBuilder; private final HttpClient httpClient;
private final HttpClientFactory httpClientFactory;
private Stack httpClientStack = new Stack<HttpClient>();
private int httpClientId = 0;
/** The URL of the brain */ /** The URL of the brain */
private final String brainUrl; private final String brainUrl;
@ -122,25 +129,26 @@ public class NeeoApi implements AutoCloseable {
* @param context the non-null {@link ServiceContext} * @param context the non-null {@link ServiceContext}
* @throws IOException if an exception occurs connecting to the brain * @throws IOException if an exception occurs connecting to the brain
*/ */
public NeeoApi(String ipAddress, String brainId, ServiceContext context, ClientBuilder clientBuilder) public NeeoApi(String ipAddress, String brainId, ServiceContext context, HttpClient httpClient,
throws IOException { HttpClientFactory httpClientFactory) throws IOException {
NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty"); NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty"); NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty");
Objects.requireNonNull(context, "context cannot be null"); Objects.requireNonNull(context, "context cannot be null");
this.brainIpAddress = ipAddress; this.brainIpAddress = ipAddress;
this.brainId = brainId; this.brainId = brainId;
this.clientBuilder = clientBuilder; this.httpClient = httpClient;
this.httpClientFactory = httpClientFactory;
this.brainUrl = NeeoConstants.PROTOCOL + (ipAddress.startsWith("/") ? ipAddress.substring(1) : ipAddress) + ":" this.brainUrl = NeeoConstants.PROTOCOL + (ipAddress.startsWith("/") ? ipAddress.substring(1) : ipAddress) + ":"
+ NeeoConstants.DEFAULT_BRAIN_PORT; + NeeoConstants.DEFAULT_BRAIN_PORT;
deviceKeys = new NeeoDeviceKeys(brainUrl, clientBuilder); deviceKeys = new NeeoDeviceKeys(brainUrl, httpClient);
request = new AtomicReference<>(new HttpRequest(clientBuilder)); request = new AtomicReference<>(new HttpRequest(httpClient));
this.systemInfo = getSystemInfo(ipAddress, clientBuilder); this.systemInfo = getSystemInfo(ipAddress, httpClient);
String name = brainId; String name = brainId;
try (HttpRequest request = new HttpRequest(clientBuilder)) { try (HttpRequest request = new HttpRequest(httpClient)) {
logger.debug("Getting existing device mappings from {}{}", brainUrl, NeeoConstants.PROJECTS_HOME); logger.debug("Getting existing device mappings from {}{}", brainUrl, NeeoConstants.PROJECTS_HOME);
final HttpResponse resp = request.sendGetCommand(brainUrl + NeeoConstants.PROJECTS_HOME); final HttpResponse resp = request.sendGetCommand(brainUrl + NeeoConstants.PROJECTS_HOME);
if (resp.getHttpCode() != HttpStatus.OK_200) { if (resp.getHttpCode() != HttpStatus.OK_200) {
@ -201,12 +209,12 @@ public class NeeoApi implements AutoCloseable {
* @return the non-null {@link NeeoSystemInfo} for the address * @return the non-null {@link NeeoSystemInfo} for the address
* @throws IOException Signals that an I/O exception has occurred or the URL is not a brain * @throws IOException Signals that an I/O exception has occurred or the URL is not a brain
*/ */
public static NeeoSystemInfo getSystemInfo(String ipAddress, ClientBuilder clientBuilder) throws IOException { public static NeeoSystemInfo getSystemInfo(String ipAddress, HttpClient httpClient) throws IOException {
NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty"); NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
final String sysInfo = NeeoConstants.PROTOCOL + (ipAddress.startsWith("/") ? ipAddress.substring(1) : ipAddress) final String sysInfo = NeeoConstants.PROTOCOL + (ipAddress.startsWith("/") ? ipAddress.substring(1) : ipAddress)
+ ":" + NeeoConstants.DEFAULT_BRAIN_PORT + NeeoConstants.SYSTEMINFO; + ":" + NeeoConstants.DEFAULT_BRAIN_PORT + NeeoConstants.SYSTEMINFO;
try (HttpRequest req = new HttpRequest(clientBuilder)) { try (HttpRequest req = new HttpRequest(httpClient)) {
final HttpResponse res = req.sendGetCommand(sysInfo); final HttpResponse res = req.sendGetCommand(sysInfo);
if (res.getHttpCode() == HttpStatus.OK_200) { if (res.getHttpCode() == HttpStatus.OK_200) {
return Objects.requireNonNull(GSON.fromJson(res.getContent(), NeeoSystemInfo.class)); return Objects.requireNonNull(GSON.fromJson(res.getContent(), NeeoSystemInfo.class));
@ -392,7 +400,7 @@ public class NeeoApi implements AutoCloseable {
try { try {
setConnected(false); setConnected(false);
NeeoUtil.close(request.getAndSet(new HttpRequest(clientBuilder))); NeeoUtil.close(request.getAndSet(new HttpRequest(httpClient)));
NeeoUtil.checkInterrupt(); NeeoUtil.checkInterrupt();
registerApi(); registerApi();
@ -461,6 +469,41 @@ public class NeeoApi implements AutoCloseable {
return deviceKeys; return deviceKeys;
} }
private synchronized HttpClient getHttpClient() {
int stackSize = httpClientStack.size();
if (stackSize == 0) {
int httpClientId = this.httpClientId + 1;
this.httpClientId = httpClientId;
String httpClientIdString = "neeo-" + brainId + "-" + httpClientId;
logger.debug("getHttpClient created new client {} for brain {}", httpClientIdString, brainId);
HttpClient httpClient = httpClientFactory.createHttpClient(httpClientIdString);
try {
httpClient.start();
} catch (Exception e) {
logger.debug("Exception while starting HttpClient: {}", e.getMessage(), e);
}
return httpClient;
} else {
logger.debug("getHttpClient popped a client from the stack for brain {} depth {}", brainId, stackSize);
return (HttpClient) httpClientStack.pop();
}
}
private synchronized void returnHttpClient(HttpClient httpClient) {
int stackSize = httpClientStack.size();
if (stackSize <= NeeoConstants.HTTPCLIENT_POOL_SIZE) {
logger.debug("getHttpClient returned a client for brain {} depth {}", brainId, stackSize);
httpClientStack.push(httpClient);
} else {
try {
logger.debug("getHttpClient destroyed a client for brain {}", brainId);
httpClient.stop();
} catch (Exception e) {
logger.debug("Exception while stopping HttpClient: {}", e.getMessage(), e);
}
}
}
/** /**
* Send a notification to the brain * Send a notification to the brain
* *
@ -469,12 +512,17 @@ public class NeeoApi implements AutoCloseable {
*/ */
public void notify(String msg) throws IOException { public void notify(String msg) throws IOException {
if (isConnected()) { if (isConnected()) {
final HttpRequest rqst = request.get(); HttpClient httpClient = getHttpClient();
final HttpRequest rqst = new HttpRequest(httpClient);
logger.debug("Sending Notification to brain ({}): {}", brainId, msg); logger.debug("Sending Notification to brain ({}): {}", brainId, msg);
final HttpResponse resp = rqst.sendPostJsonCommand(brainUrl + NeeoConstants.NOTIFICATION, msg); final HttpResponse resp = rqst.sendPostJsonCommand(brainUrl + NeeoConstants.NOTIFICATION, msg);
if (resp.getHttpCode() != HttpStatus.OK_200) { if (resp.getHttpCode() != HttpStatus.OK_200) {
returnHttpClient(httpClient);
throw resp.createException(); throw resp.createException();
} else {
logger.debug("Response from brain ({}): {} - {}", brainId, resp.getHttpCode(), resp.getContent());
} }
returnHttpClient(httpClient);
} else { } else {
logger.debug("Notification ignored - brain not connected"); logger.debug("Notification ignored - brain not connected");
} }
@ -553,8 +601,15 @@ public class NeeoApi implements AutoCloseable {
NeeoUtil.cancel(connect.getAndSet(null)); NeeoUtil.cancel(connect.getAndSet(null));
try { try {
int stackSize = httpClientStack.size();
while (stackSize > 0) {
HttpClient httpClient = (HttpClient) httpClientStack.pop();
httpClient.stop();
httpClient = null;
stackSize--;
}
deregisterApi(); deregisterApi();
} catch (IOException e) { } catch (Exception e) {
logger.debug("Exception while deregistring api during close - ignoring: {}", e.getMessage(), e); logger.debug("Exception while deregistring api during close - ignoring: {}", e.getMessage(), e);
} finally { } finally {
// Do this regardless if a runtime exception was thrown // Do this regardless if a runtime exception was thrown

View File

@ -16,9 +16,9 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Objects; import java.util.Objects;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.io.neeo.internal.models.BrainStatus; import org.openhab.io.neeo.internal.models.BrainStatus;
import org.openhab.io.neeo.internal.servletservices.NeeoBrainSearchService; import org.openhab.io.neeo.internal.servletservices.NeeoBrainSearchService;
import org.openhab.io.neeo.internal.servletservices.NeeoBrainService; import org.openhab.io.neeo.internal.servletservices.NeeoBrainService;
@ -46,9 +46,9 @@ public class NeeoBrainServlet extends AbstractServlet {
* @param servletUrl the non-null, non-empty servlet URL * @param servletUrl the non-null, non-empty servlet URL
* @param api the non-null API * @param api the non-null API
*/ */
private NeeoBrainServlet(ServiceContext context, String servletUrl, NeeoApi api, ClientBuilder clientBuilder) { private NeeoBrainServlet(ServiceContext context, String servletUrl, NeeoApi api, HttpClient httpClient,
super(context, servletUrl, new NeeoBrainSearchService(context), HttpClientFactory httpClientFactory) {
new NeeoBrainService(api, context, clientBuilder)); super(context, servletUrl, new NeeoBrainSearchService(context), new NeeoBrainService(api, context, httpClient));
Objects.requireNonNull(context, "context cannot be null"); Objects.requireNonNull(context, "context cannot be null");
NeeoUtil.requireNotEmpty(servletUrl, "servletUrl cannot be empty"); NeeoUtil.requireNotEmpty(servletUrl, "servletUrl cannot be empty");
@ -68,16 +68,16 @@ public class NeeoBrainServlet extends AbstractServlet {
* @throws IOException when an exception occurs contacting the brain * @throws IOException when an exception occurs contacting the brain
*/ */
public static NeeoBrainServlet create(ServiceContext context, String servletUrl, String brainId, public static NeeoBrainServlet create(ServiceContext context, String servletUrl, String brainId,
InetAddress ipAddress, ClientBuilder clientBuilder) throws IOException { InetAddress ipAddress, HttpClient httpClient, HttpClientFactory httpClientFactory) throws IOException {
Objects.requireNonNull(context, "context cannot be null"); Objects.requireNonNull(context, "context cannot be null");
NeeoUtil.requireNotEmpty(servletUrl, "servletUrl cannot be empty"); NeeoUtil.requireNotEmpty(servletUrl, "servletUrl cannot be empty");
NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty"); NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty");
Objects.requireNonNull(ipAddress, "ipAddress cannot be null"); Objects.requireNonNull(ipAddress, "ipAddress cannot be null");
final NeeoApi api = new NeeoApi(ipAddress.getHostAddress(), brainId, context, clientBuilder); final NeeoApi api = new NeeoApi(ipAddress.getHostAddress(), brainId, context, httpClient, httpClientFactory);
api.start(); api.start();
return new NeeoBrainServlet(context, servletUrl, api, clientBuilder); return new NeeoBrainServlet(context, servletUrl, api, httpClient, httpClientFactory);
} }
/** /**

View File

@ -49,6 +49,7 @@ public class NeeoConstants {
/** Constant used to identify thread pool name */ /** Constant used to identify thread pool name */
public static final String THREAD_POOL_NAME = "neeoio"; public static final String THREAD_POOL_NAME = "neeoio";
public static final int HTTPCLIENT_POOL_SIZE = 5;
/** Constants used for the Web APP */ /** Constants used for the Web APP */
public static final String WEBAPP_PREFIX = "/neeo"; public static final String WEBAPP_PREFIX = "/neeo";

View File

@ -22,9 +22,8 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.io.neeo.internal.models.NeeoThingUID; import org.openhab.io.neeo.internal.models.NeeoThingUID;
@ -54,18 +53,18 @@ public class NeeoDeviceKeys {
/** The brain's url */ /** The brain's url */
private final String brainUrl; private final String brainUrl;
private final ClientBuilder clientBuilder; private final HttpClient httpClient;
/** /**
* Creates the object from the context and brainUrl * Creates the object from the context and brainUrl
* *
* @param brainUrl the non-empty brain url * @param brainUrl the non-empty brain url
*/ */
NeeoDeviceKeys(String brainUrl, ClientBuilder clientBuilder) { NeeoDeviceKeys(String brainUrl, HttpClient httpClient) {
NeeoUtil.requireNotEmpty(brainUrl, "brainUrl cannot be empty"); NeeoUtil.requireNotEmpty(brainUrl, "brainUrl cannot be empty");
this.brainUrl = brainUrl; this.brainUrl = brainUrl;
this.clientBuilder = clientBuilder; this.httpClient = httpClient;
} }
/** /**
@ -74,7 +73,7 @@ public class NeeoDeviceKeys {
* @throws IOException Signals that an I/O exception has occurred. * @throws IOException Signals that an I/O exception has occurred.
*/ */
void refresh() throws IOException { void refresh() throws IOException {
try (HttpRequest request = new HttpRequest(clientBuilder)) { try (HttpRequest request = new HttpRequest(httpClient)) {
logger.debug("Getting existing device mappings from {}{}", brainUrl, NeeoConstants.PROJECTS_HOME); logger.debug("Getting existing device mappings from {}{}", brainUrl, NeeoConstants.PROJECTS_HOME);
final HttpResponse resp = request.sendGetCommand(brainUrl + NeeoConstants.PROJECTS_HOME); final HttpResponse resp = request.sendGetCommand(brainUrl + NeeoConstants.PROJECTS_HOME);
if (resp.getHttpCode() != HttpStatus.OK_200) { if (resp.getHttpCode() != HttpStatus.OK_200) {

View File

@ -34,10 +34,10 @@ import java.util.stream.Collectors;
import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener; import javax.jmdns.ServiceListener;
import javax.ws.rs.client.ClientBuilder;
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.core.common.ThreadPoolManager; import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.io.transport.mdns.MDNSClient; import org.openhab.core.io.transport.mdns.MDNSClient;
import org.openhab.io.neeo.internal.NeeoApi; import org.openhab.io.neeo.internal.NeeoApi;
@ -106,17 +106,17 @@ public class MdnsBrainDiscovery extends AbstractBrainDiscovery {
/** The file we store definitions in */ /** The file we store definitions in */
private final File file = new File(NeeoConstants.FILENAME_DISCOVEREDBRAINS); private final File file = new File(NeeoConstants.FILENAME_DISCOVEREDBRAINS);
private final ClientBuilder clientBuilder; private final HttpClient httpClient;
/** /**
* Creates the MDNS brain discovery from the given {@link ServiceContext} * Creates the MDNS brain discovery from the given {@link ServiceContext}
* *
* @param context the non-null service context * @param context the non-null service context
*/ */
public MdnsBrainDiscovery(ServiceContext context, ClientBuilder clientBuilder) { public MdnsBrainDiscovery(ServiceContext context, HttpClient httpClient) {
Objects.requireNonNull(context, "context cannot be null"); Objects.requireNonNull(context, "context cannot be null");
this.context = context; this.context = context;
this.clientBuilder = clientBuilder; this.httpClient = httpClient;
} }
/** /**
@ -253,7 +253,7 @@ public class MdnsBrainDiscovery extends AbstractBrainDiscovery {
NeeoSystemInfo sysInfo; NeeoSystemInfo sysInfo;
try { try {
sysInfo = NeeoApi.getSystemInfo(brainInfo.getValue().toString(), clientBuilder); sysInfo = NeeoApi.getSystemInfo(brainInfo.getValue().toString(), httpClient);
} catch (IOException e) { } catch (IOException e) {
// We can get an MDNS notification BEFORE the brain is ready to process. // We can get an MDNS notification BEFORE the brain is ready to process.
// if that happens, we'll get an IOException (usually bad gateway message), schedule another attempt to get // if that happens, we'll get an IOException (usually bad gateway message), schedule another attempt to get
@ -302,7 +302,7 @@ public class MdnsBrainDiscovery extends AbstractBrainDiscovery {
try { try {
final InetAddress addr = InetAddress.getByName(ipAddress); final InetAddress addr = InetAddress.getByName(ipAddress);
final NeeoSystemInfo sysInfo = NeeoApi.getSystemInfo(ipAddress, clientBuilder); final NeeoSystemInfo sysInfo = NeeoApi.getSystemInfo(ipAddress, httpClient);
logger.debug("Manually adding brain ({}) with system information: {}", ipAddress, sysInfo); logger.debug("Manually adding brain ({}) with system information: {}", ipAddress, sysInfo);
systemsLock.lock(); systemsLock.lock();

View File

@ -14,24 +14,25 @@ package org.openhab.io.neeo.internal.net;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; 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.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.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.eclipse.jetty.http.HttpStatus;
import org.glassfish.jersey.filter.LoggingFilter;
import org.openhab.io.neeo.internal.NeeoUtil; import org.openhab.io.neeo.internal.NeeoUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* This class represents an HTTP session with a client * This class represents an HTTP session with a httpClient
* *
* @author Tim Roberts - Initial Contribution * @author Tim Roberts - Initial Contribution
*/ */
@ -41,18 +42,14 @@ public class HttpRequest implements AutoCloseable {
/** the logger */ /** the logger */
private final Logger logger = LoggerFactory.getLogger(HttpRequest.class); private final Logger logger = LoggerFactory.getLogger(HttpRequest.class);
/** The client to use */ /** The httpClient to use */
private final Client client; private final HttpClient httpClient;
/** /**
* Instantiates a new request * Instantiates a new request
*/ */
public HttpRequest(ClientBuilder clientBuilder) { public HttpRequest(HttpClient httpClient) {
client = clientBuilder.build(); this.httpClient = httpClient;
if (logger.isDebugEnabled()) {
client.register(new LoggingFilter(new Slf4LoggingAdapter(logger), true));
}
} }
/** /**
@ -64,18 +61,18 @@ public class HttpRequest implements AutoCloseable {
public HttpResponse sendGetCommand(String uri) { public HttpResponse sendGetCommand(String uri) {
NeeoUtil.requireNotEmpty(uri, "uri cannot be empty"); NeeoUtil.requireNotEmpty(uri, "uri cannot be empty");
try { try {
final Builder request = client.target(uri).request(); final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(uri);
request.method(HttpMethod.GET);
final Response content = request.get(); request.timeout(10, TimeUnit.SECONDS);
ContentResponse refreshResponse = request.send();
try { return new HttpResponse(refreshResponse);
return new HttpResponse(content);
} finally {
content.close();
}
} catch (IOException | IllegalStateException | ProcessingException e) { } catch (IOException | IllegalStateException | ProcessingException e) {
String message = e.getMessage(); String message = e.getMessage();
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : "");
} 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 : "");
} }
} }
@ -91,23 +88,24 @@ public class HttpRequest implements AutoCloseable {
Objects.requireNonNull(body, "body cannot be null"); Objects.requireNonNull(body, "body cannot be null");
try { try {
final Builder request = client.target(uri).request(MediaType.APPLICATION_JSON); final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(uri);
request.content(new StringContentProvider(body));
final Response content = request.post(Entity.entity(body, MediaType.APPLICATION_JSON)); request.header(HttpHeader.CONTENT_TYPE, "application/json");
request.method(HttpMethod.POST);
try { request.timeout(10, TimeUnit.SECONDS);
return new HttpResponse(content); ContentResponse refreshResponse = request.send();
} finally { return new HttpResponse(refreshResponse);
content.close();
}
} catch (IOException | IllegalStateException | ProcessingException e) { } catch (IOException | IllegalStateException | ProcessingException e) {
String message = e.getMessage(); String message = e.getMessage();
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : "");
} 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 @Override
public void close() { public void close() {
client.close();
} }
} }

View File

@ -13,7 +13,6 @@
package org.openhab.io.neeo.internal.net; package org.openhab.io.neeo.internal.net;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.ContentResponse;
/** /**
* This class represents an {@link HttpRequest} response * This class represents an {@link HttpRequest} response
@ -50,20 +50,15 @@ public class HttpResponse {
* @param response the non-null response * @param response the non-null response
* @throws IOException Signals that an I/O exception has occurred. * @throws IOException Signals that an I/O exception has occurred.
*/ */
HttpResponse(Response response) throws IOException { HttpResponse(ContentResponse response) throws IOException {
Objects.requireNonNull(response, "response cannot be null"); Objects.requireNonNull(response, "response cannot be null");
httpStatus = response.getStatus(); httpStatus = response.getStatus();
httpReason = response.getStatusInfo().getReasonPhrase(); httpReason = response.getReason();
contents = response.getContent();
if (response.hasEntity()) { for (String key : response.getHeaders().getFieldNamesCollection()) {
contents = response.readEntity(InputStream.class).readAllBytes(); headers.put(key, response.getHeaders().getField(key).toString());
} else {
contents = null;
}
for (String key : response.getHeaders().keySet()) {
headers.put(key, response.getHeaderString(key));
} }
} }

View File

@ -21,11 +21,11 @@ import java.util.concurrent.ScheduledExecutorService;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.ClientBuilder;
import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNull;
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.core.common.ThreadPoolManager; import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.events.Event; import org.openhab.core.events.Event;
import org.openhab.core.events.EventFilter; import org.openhab.core.events.EventFilter;
@ -114,7 +114,7 @@ public class NeeoBrainService extends DefaultServletService {
* @param api the non-null api * @param api the non-null api
* @param context the non-null context * @param context the non-null context
*/ */
public NeeoBrainService(NeeoApi api, ServiceContext context, ClientBuilder clientBuilder) { public NeeoBrainService(NeeoApi api, ServiceContext context, HttpClient httpClient) {
Objects.requireNonNull(api, "api cannot be null"); Objects.requireNonNull(api, "api cannot be null");
Objects.requireNonNull(context, "context cannot be null"); Objects.requireNonNull(context, "context cannot be null");
@ -125,7 +125,7 @@ public class NeeoBrainService extends DefaultServletService {
scheduler.execute(() -> { scheduler.execute(() -> {
resendState(); resendState();
}); });
request = new HttpRequest(clientBuilder); request = new HttpRequest(httpClient);
} }
/** /**