Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

improve "get port" mechanism #2331

Merged
merged 4 commits into from
Oct 25, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +29,8 @@
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.service.http.HttpContext;
import org.osgi.service.http.HttpService;
Expand Down Expand Up @@ -195,13 +195,21 @@ 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.
String port = bundleContext.getProperty("org.osgi.service.http.port"); // 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* 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");
}

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get that comment...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for "org.osgi.service.http.port" and "org.osgi.service.http.port.secure" is the same only the property name differs. So a private method getHttpServicePortProperty has been used that takes the property as argument.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay, got it. Then please change class to method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, didn't see that type. Thanks.

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, this in theory cannot happen. However, if it does for whatever crazy circumstances, it's hard do find. So IMHO it can't hurt to log it anyway....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, a log is normally okay.
But using a log in the static method involves the creation of a static logger -- but okay, this could be done, too.

If you have a look at the API documentation the InvalidSyntaxException is thrown "If the specified filter contains an invalid filter expression that cannot be parsed."
filter is null and so not used 😉
Perhaps a line comment in the code would be nice to explain why the exception is ignored silently.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. Nevertheless, having spent way to much time analyzing weird bugs searching in the dark, I would prefer the ad-hoc logger creation in that case. As it's a will-never-happen-if-we-can-rely-on-laws-of-physics-Exception this will not hurt here.

}
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) {
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: it's a property set by the user - if this property is set with bogus, somebody will be pretty disappointed if it gets ignored silently

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would like to parse the port number used by the HTTP servlet.
IMHO If the port is configured in the wrong way, the HTTP servlet has to care about and should report a warning.
As we only consume what the servlet is using -- and if the servlet cannot consume the property, I think it is okay, to ignore it, too.

Copy link
Contributor

@sjsf sjsf Oct 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the port is configured in the wrong way, the HTTP servlet has to care about and should report a warning.

good point

} else if (value instanceof Integer) {
return (Integer) value;
}
}

return -1;
}

}
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the convention in ESH to use non-static loggers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, definitely.
This method has been introduced by Kai.
I moved the method an utility class and make this utility function static.
So, how to use a non-static logger in a static context?

We agreed that in static context we can use a static logger.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay


private NetUtil() {
}

/**
* Get the first candidate for a local IPv4 host address (non loopback, non localhost).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running the stuff on a server with several network interfaces, such automatisms tend to always pick the wrong one. Shouldn't we also provide some configuration options (i.e. ConfigAdmin + system property) to override this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As written above and comment in the PR itself, this method (and its whole logic) has been migrated from #2323

*/
public static String getLocalIpv4HostAddress() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somehow this looks weird to me, IMHO getLocalIPv4HostAddress is more brain-friendly, but that could just be personal preference...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't care much. There are different meanings about correct camel case usage.
I can change this.

try {
String hostAddress = null;
final Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
final NetworkInterface current = interfaces.nextElement();
if (!current.isUp() || current.isLoopback() || current.isVirtual()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure that ignoring virtual interfaces is correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, just realized I'm asking the wrong one... #2323 ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, just realized I'm asking the wrong one... #2323 ;-)

😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are still answering the wrong one, because I only took the inspiration from @gerrieg from here... So I cannot answer either, whether this is a good idea or not...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged it nonetheless; if we want to include virtual interfaces, we can change this logic in a new PR.

@gerrieg: You might want to update the homematic binding to use the new NetUtil class (which now holds your method :-))

continue;
}
final Enumeration<InetAddress> 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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,6 +21,7 @@
* discover it.
*
* @author Kai Kreuzer - Initial contribution and API
* @author Markus Rathgeb - Use HTTP service utility functions
*/
public class MDNSAnnouncer {

Expand Down Expand Up @@ -47,13 +49,17 @@ public void activate(BundleContext bundleContext, Map<String, Object> 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) {
}
}
Expand Down