From f4d8b27cfe7e11bbf53570f817a5b04350533ba8 Mon Sep 17 00:00:00 2001 From: Markus Rathgeb Date: Sun, 16 Oct 2016 20:48:27 +0200 Subject: [PATCH 1/4] improve "get port" mechanism It is not mandatory that the system property is used for setting the service port. So we should also respect the service property. The configuration admin is e.g. used by Pax Web. See also: http://felix.apache.org/documentation/subprojects/apache-felix-http-service.html The service can both be configured using OSGi environment properties and using Configuration Admin. ... If you use both methods, Configuration Admin takes precedence. Signed-off-by: Markus Rathgeb --- .../core/audio/internal/AudioServlet.java | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java index 92cea62d5bf..a1886912016 100644 --- a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java +++ b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java @@ -32,6 +32,9 @@ import org.eclipse.smarthome.core.audio.AudioStream; import org.eclipse.smarthome.core.audio.FixedLengthAudioStream; import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; import org.osgi.service.http.HttpContext; import org.osgi.service.http.HttpService; import org.osgi.service.http.NamespaceException; @@ -198,8 +201,11 @@ private URL getURL(String streamId) { String ipAddress = InetAddress.getLocalHost().getHostAddress(); // we use the primary interface; if a client // knows it any better, he can himself // change the url according to his needs. - String port = bundleContext.getProperty("org.osgi.service.http.port"); // we do not use SSL as it can cause - // certificate validation issues. + final int port = getHttpServicePort(bundleContext); // we do not use SSL as it can cause certificate + // validation issues. + if (port == -1) { + return null; + } return new URL("http://" + ipAddress + ":" + port + SERVLET_NAME + "/" + streamId); } catch (UnknownHostException | MalformedURLException e) { logger.error("Failed to construct audio stream URL: {}", e.getMessage(), e); @@ -207,4 +213,57 @@ private URL getURL(String streamId) { } } + private final int getHttpServicePort(final BundleContext bc) { + Object value; + int port = -1; + + // Try to find the port by using the service property (respect service ranking). + try { + int candidate = Integer.MIN_VALUE; + final ServiceReference[] refs = bc.getAllServiceReferences("org.osgi.service.http.HttpService", null); + for (final ServiceReference ref : refs) { + value = ref.getProperty("org.osgi.service.http.port"); + if (value == null) { + continue; + } + final int servicePort; + try { + servicePort = Integer.parseInt(value.toString()); + } catch (final NumberFormatException ex) { + continue; + } + value = ref.getProperty(Constants.SERVICE_RANKING); + final int serviceRanking; + if (value == null || !(value instanceof Integer)) { + serviceRanking = 0; + } else { + serviceRanking = (Integer) value; + } + if (serviceRanking >= candidate) { + candidate = serviceRanking; + port = servicePort; + } + } + } catch (final InvalidSyntaxException ex) { + } + if (port > 0) { + return port; + } + + // If the service does not provide the port, try to use the system property. + value = bc.getProperty("org.osgi.service.http.port"); + if (value != null) { + if (value instanceof String) { + try { + return Integer.parseInt(value.toString()); + } catch (final NumberFormatException ex) { + } + } else if (value instanceof Integer) { + return (Integer) value; + } + } + + return -1; + } + } \ No newline at end of file From fa567d95852e988d3c967eaab11bdb0d5a27c079 Mon Sep 17 00:00:00 2001 From: Markus Rathgeb Date: Mon, 17 Oct 2016 15:48:14 +0200 Subject: [PATCH 2/4] refactor network stuff * create network utility class * add logic from https://github.com/eclipse/smarthome/pull/2323 "improved logic to retrieve local ip address" * create http service utility class Signed-off-by: Markus Rathgeb --- .../META-INF/MANIFEST.MF | 1 + .../core/audio/internal/AudioServlet.java | 77 +++------------- .../META-INF/MANIFEST.MF | 1 + .../smarthome/core/net/HttpServiceUtil.java | 89 +++++++++++++++++++ .../eclipse/smarthome/core/net/NetUtil.java | 63 +++++++++++++ 5 files changed, 167 insertions(+), 64 deletions(-) create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java create mode 100644 bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/NetUtil.java diff --git a/bundles/core/org.eclipse.smarthome.core.audio/META-INF/MANIFEST.MF b/bundles/core/org.eclipse.smarthome.core.audio/META-INF/MANIFEST.MF index 2eeaae4cdfd..3190e295888 100644 --- a/bundles/core/org.eclipse.smarthome.core.audio/META-INF/MANIFEST.MF +++ b/bundles/core/org.eclipse.smarthome.core.audio/META-INF/MANIFEST.MF @@ -18,6 +18,7 @@ Import-Package: javax.servlet, org.eclipse.smarthome.core.items, org.eclipse.smarthome.core.items.events, org.eclipse.smarthome.core.library.types, + org.eclipse.smarthome.core.net, org.eclipse.smarthome.core.types, org.eclipse.smarthome.io.console, org.eclipse.smarthome.io.console.extensions, diff --git a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java index a1886912016..5116f1ae056 100644 --- a/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java +++ b/bundles/core/org.eclipse.smarthome.core.audio/src/main/java/org/eclipse/smarthome/core/audio/internal/AudioServlet.java @@ -9,10 +9,8 @@ import java.io.IOException; import java.io.InputStream; -import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; -import java.net.UnknownHostException; import java.util.Hashtable; import java.util.Map; import java.util.UUID; @@ -31,10 +29,9 @@ import org.eclipse.smarthome.core.audio.AudioHTTPServer; import org.eclipse.smarthome.core.audio.AudioStream; import org.eclipse.smarthome.core.audio.FixedLengthAudioStream; +import org.eclipse.smarthome.core.net.HttpServiceUtil; +import org.eclipse.smarthome.core.net.NetUtil; import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; import org.osgi.service.http.HttpContext; import org.osgi.service.http.HttpService; import org.osgi.service.http.NamespaceException; @@ -198,72 +195,24 @@ public URL serve(FixedLengthAudioStream stream, int seconds) { private URL getURL(String streamId) { try { - String ipAddress = InetAddress.getLocalHost().getHostAddress(); // we use the primary interface; if a client - // knows it any better, he can himself - // change the url according to his needs. - final int port = getHttpServicePort(bundleContext); // we do not use SSL as it can cause certificate - // validation issues. + final String ipAddress = NetUtil.getLocalIpv4HostAddress(); + if (ipAddress == null) { + logger.warn("No network interface could be found."); + return null; + } + + // we do not use SSL as it can cause certificate validation issues. + final int port = HttpServiceUtil.getHttpServicePort(bundleContext); if (port == -1) { + logger.warn("Cannot find port of the http service."); return null; } + return new URL("http://" + ipAddress + ":" + port + SERVLET_NAME + "/" + streamId); - } catch (UnknownHostException | MalformedURLException e) { + } catch (final MalformedURLException e) { logger.error("Failed to construct audio stream URL: {}", e.getMessage(), e); return null; } } - private final int getHttpServicePort(final BundleContext bc) { - Object value; - int port = -1; - - // Try to find the port by using the service property (respect service ranking). - try { - int candidate = Integer.MIN_VALUE; - final ServiceReference[] refs = bc.getAllServiceReferences("org.osgi.service.http.HttpService", null); - for (final ServiceReference ref : refs) { - value = ref.getProperty("org.osgi.service.http.port"); - if (value == null) { - continue; - } - final int servicePort; - try { - servicePort = Integer.parseInt(value.toString()); - } catch (final NumberFormatException ex) { - continue; - } - value = ref.getProperty(Constants.SERVICE_RANKING); - final int serviceRanking; - if (value == null || !(value instanceof Integer)) { - serviceRanking = 0; - } else { - serviceRanking = (Integer) value; - } - if (serviceRanking >= candidate) { - candidate = serviceRanking; - port = servicePort; - } - } - } catch (final InvalidSyntaxException ex) { - } - if (port > 0) { - return port; - } - - // If the service does not provide the port, try to use the system property. - value = bc.getProperty("org.osgi.service.http.port"); - if (value != null) { - if (value instanceof String) { - try { - return Integer.parseInt(value.toString()); - } catch (final NumberFormatException ex) { - } - } else if (value instanceof Integer) { - return (Integer) value; - } - } - - return -1; - } - } \ No newline at end of file diff --git a/bundles/core/org.eclipse.smarthome.core/META-INF/MANIFEST.MF b/bundles/core/org.eclipse.smarthome.core/META-INF/MANIFEST.MF index 693e1f6a0a1..85040d25ad8 100644 --- a/bundles/core/org.eclipse.smarthome.core/META-INF/MANIFEST.MF +++ b/bundles/core/org.eclipse.smarthome.core/META-INF/MANIFEST.MF @@ -12,6 +12,7 @@ Export-Package: org.eclipse.smarthome.core.binding, org.eclipse.smarthome.core.items.events, org.eclipse.smarthome.core.library.items, org.eclipse.smarthome.core.library.types, + org.eclipse.smarthome.core.net, org.eclipse.smarthome.core.scheduler, org.eclipse.smarthome.core.service, org.eclipse.smarthome.core.storage, diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java new file mode 100644 index 00000000000..67a4c2ac484 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.net; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +/** + * Some utility functions related to the http service + * + * @author Markus Rathgeb - Initial contribution and API + */ +public class HttpServiceUtil { + + private HttpServiceUtil() { + } + + /** + * Get the port that is used by the HTTP service. + * + * @param bc the bundle context used for lookup + * @return the port if used, -1 if no port could be found + */ + public static int getHttpServicePort(final BundleContext bc) { + return getHttpServicePortProperty(bc, "org.osgi.service.http.port"); + } + + // Utility class that could be used for non-secure and secure port. + private static int getHttpServicePortProperty(final BundleContext bc, final String propertyName) { + Object value; + int port = -1; + + // Try to find the port by using the service property (respect service ranking). + try { + int candidate = Integer.MIN_VALUE; + final ServiceReference[] refs = bc.getAllServiceReferences("org.osgi.service.http.HttpService", null); + for (final ServiceReference ref : refs) { + value = ref.getProperty(propertyName); + if (value == null) { + continue; + } + final int servicePort; + try { + servicePort = Integer.parseInt(value.toString()); + } catch (final NumberFormatException ex) { + continue; + } + value = ref.getProperty(Constants.SERVICE_RANKING); + final int serviceRanking; + if (value == null || !(value instanceof Integer)) { + serviceRanking = 0; + } else { + serviceRanking = (Integer) value; + } + if (serviceRanking >= candidate) { + candidate = serviceRanking; + port = servicePort; + } + } + } catch (final InvalidSyntaxException ex) { + } + if (port > 0) { + return port; + } + + // If the service does not provide the port, try to use the system property. + value = bc.getProperty(propertyName); + if (value != null) { + if (value instanceof String) { + try { + return Integer.parseInt(value.toString()); + } catch (final NumberFormatException ex) { + } + } else if (value instanceof Integer) { + return (Integer) value; + } + } + + return -1; + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/NetUtil.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/NetUtil.java new file mode 100644 index 00000000000..362ae5590e9 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/NetUtil.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.net; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Some utility functions related to network interfaces etc. + * + * @author Markus Rathgeb - Initial contribution and API + */ +public class NetUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(NetUtil.class); + + private NetUtil() { + } + + /** + * Get the first candidate for a local IPv4 host address (non loopback, non localhost). + */ + public static String getLocalIpv4HostAddress() { + try { + String hostAddress = null; + final Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + final NetworkInterface current = interfaces.nextElement(); + if (!current.isUp() || current.isLoopback() || current.isVirtual()) { + continue; + } + final Enumeration addresses = current.getInetAddresses(); + while (addresses.hasMoreElements()) { + final InetAddress current_addr = addresses.nextElement(); + if (current_addr.isLoopbackAddress() || (current_addr instanceof Inet6Address)) { + continue; + } + if (hostAddress != null) { + LOGGER.warn("Found multiple local interfaces - ignoring {}", current_addr.getHostAddress()); + } else { + hostAddress = current_addr.getHostAddress(); + } + } + } + return hostAddress; + } catch (SocketException ex) { + LOGGER.error("Could not retrieve network interface: " + ex.getMessage(), ex); + return null; + } + } + +} From a5c1b3b27a95674097afaf5664969b142142fe5a Mon Sep 17 00:00:00 2001 From: Markus Rathgeb Date: Mon, 17 Oct 2016 16:24:45 +0200 Subject: [PATCH 3/4] use http service utilities for mdns rest announcer Signed-off-by: Markus Rathgeb --- .../smarthome/core/net/HttpServiceUtil.java | 4 ++++ .../META-INF/MANIFEST.MF | 3 ++- .../io/rest/mdns/internal/MDNSAnnouncer.java | 14 ++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java index 67a4c2ac484..d86c2db1dee 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java @@ -32,6 +32,10 @@ public static int getHttpServicePort(final BundleContext bc) { return getHttpServicePortProperty(bc, "org.osgi.service.http.port"); } + public static int getHttpServicePortSecure(final BundleContext bc) { + return getHttpServicePortProperty(bc, "org.osgi.service.http.port.secure"); + } + // Utility class that could be used for non-secure and secure port. private static int getHttpServicePortProperty(final BundleContext bc, final String propertyName) { Object value; diff --git a/bundles/io/org.eclipse.smarthome.io.rest.mdns/META-INF/MANIFEST.MF b/bundles/io/org.eclipse.smarthome.io.rest.mdns/META-INF/MANIFEST.MF index 3beab914726..9e69fef1829 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.mdns/META-INF/MANIFEST.MF +++ b/bundles/io/org.eclipse.smarthome.io.rest.mdns/META-INF/MANIFEST.MF @@ -5,7 +5,8 @@ Bundle-SymbolicName: org.eclipse.smarthome.io.rest.mdns Bundle-Version: 0.9.0.qualifier Bundle-Vendor: Eclipse.org/SmartHome Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Import-Package: org.eclipse.smarthome.io.rest, +Import-Package: org.eclipse.smarthome.core.net, + org.eclipse.smarthome.io.rest, org.eclipse.smarthome.io.transport.mdns, org.osgi.framework, org.slf4j diff --git a/bundles/io/org.eclipse.smarthome.io.rest.mdns/src/main/java/org/eclipse/smarthome/io/rest/mdns/internal/MDNSAnnouncer.java b/bundles/io/org.eclipse.smarthome.io.rest.mdns/src/main/java/org/eclipse/smarthome/io/rest/mdns/internal/MDNSAnnouncer.java index d07f4149f68..a5e64f56b14 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.mdns/src/main/java/org/eclipse/smarthome/io/rest/mdns/internal/MDNSAnnouncer.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.mdns/src/main/java/org/eclipse/smarthome/io/rest/mdns/internal/MDNSAnnouncer.java @@ -10,6 +10,7 @@ import java.util.Hashtable; import java.util.Map; +import org.eclipse.smarthome.core.net.HttpServiceUtil; import org.eclipse.smarthome.io.rest.RESTConstants; import org.eclipse.smarthome.io.transport.mdns.MDNSService; import org.eclipse.smarthome.io.transport.mdns.ServiceDescription; @@ -20,6 +21,7 @@ * discover it. * * @author Kai Kreuzer - Initial contribution and API + * @author Markus Rathgeb - Use HTTP service utility functions */ public class MDNSAnnouncer { @@ -47,13 +49,17 @@ public void activate(BundleContext bundleContext, Map properties mdnsName = "smarthome"; } try { - httpPort = Integer.parseInt(bundleContext.getProperty("org.osgi.service.http.port")); - mdnsService.registerService(getDefaultServiceDescription()); + httpPort = HttpServiceUtil.getHttpServicePort(bundleContext); + if (httpPort != -1) { + mdnsService.registerService(getDefaultServiceDescription()); + } } catch (NumberFormatException e) { } try { - httpSSLPort = Integer.parseInt(bundleContext.getProperty("org.osgi.service.http.port.secure")); - mdnsService.registerService(getSSLServiceDescription()); + httpSSLPort = HttpServiceUtil.getHttpServicePortSecure(bundleContext); + if (httpSSLPort != -1) { + mdnsService.registerService(getSSLServiceDescription()); + } } catch (NumberFormatException e) { } } From 818eb3930a17c892d7939606e3a8ded55d522f7d Mon Sep 17 00:00:00 2001 From: Markus Rathgeb Date: Tue, 18 Oct 2016 10:31:50 +0200 Subject: [PATCH 4/4] fixed review comments Signed-off-by: Markus Rathgeb --- .../org/eclipse/smarthome/core/net/HttpServiceUtil.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java index d86c2db1dee..9f3c91449d1 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/net/HttpServiceUtil.java @@ -11,6 +11,8 @@ import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Some utility functions related to the http service @@ -36,7 +38,7 @@ public static int getHttpServicePortSecure(final BundleContext bc) { return getHttpServicePortProperty(bc, "org.osgi.service.http.port.secure"); } - // Utility class that could be used for non-secure and secure port. + // Utility method that could be used for non-secure and secure port. private static int getHttpServicePortProperty(final BundleContext bc, final String propertyName) { Object value; int port = -1; @@ -69,6 +71,9 @@ private static int getHttpServicePortProperty(final BundleContext bc, final Stri } } } catch (final InvalidSyntaxException ex) { + // This point of code should never be reached. + final Logger logger = LoggerFactory.getLogger(HttpServiceUtil.class); + logger.warn("This error should only be thrown if a filter could not be parsed. We don't use a filter..."); } if (port > 0) { return port; @@ -81,6 +86,7 @@ private static int getHttpServicePortProperty(final BundleContext bc, final Stri try { return Integer.parseInt(value.toString()); } catch (final NumberFormatException ex) { + // If the property could not be parsed, the HTTP servlet itself has to care and warn about. } } else if (value instanceof Integer) { return (Integer) value;