FELIX-2786 - allow random port to be used:
- Applied patch supplied by Carl Hall, after applying some minor cleanups.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1540843 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
index 7da5871..049c8df 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
@@ -16,6 +16,8 @@
*/
package org.apache.felix.http.jetty.internal;
+import java.io.IOException;
+import java.net.ServerSocket;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Collection;
@@ -24,6 +26,7 @@
import java.util.Iterator;
import java.util.Properties;
+import org.apache.felix.http.base.internal.logger.SystemLogger;
import org.osgi.framework.BundleContext;
public final class JettyConfig
@@ -177,12 +180,12 @@
public int getHttpPort()
{
- return getIntProperty(HTTP_PORT, 8080);
+ return determinePort(String.valueOf(getProperty(HTTP_PORT)), 8080);
}
public int getHttpsPort()
{
- return getIntProperty(HTTPS_PORT, 8443);
+ return determinePort(String.valueOf(getProperty(HTTPS_PORT)), 8443);
}
public int getHttpTimeout()
@@ -197,14 +200,7 @@
*/
public int getIntProperty(String name, int defValue)
{
- try
- {
- return Integer.parseInt(getProperty(name, null));
- }
- catch (Exception e)
- {
- return defValue;
- }
+ return parseInt(getProperty(name, null), defValue);
}
public String getKeyPassword()
@@ -212,16 +208,16 @@
return getProperty(FELIX_KEYSTORE_KEY_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_KEY_PASSWORD));
}
- public String getKeystoreType()
- {
- return getProperty(FELIX_KEYSTORE_TYPE, KeyStore.getDefaultType());
- }
-
public String getKeystore()
{
return getProperty(FELIX_KEYSTORE, this.context.getProperty(OSCAR_KEYSTORE));
}
+ public String getKeystoreType()
+ {
+ return getProperty(FELIX_KEYSTORE_TYPE, KeyStore.getDefaultType());
+ }
+
public String getPassword()
{
return getProperty(FELIX_KEYSTORE_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_PASSWORD));
@@ -239,13 +235,7 @@
*/
public String getProperty(String name, String defValue)
{
- Dictionary conf = this.config;
- Object value = (conf != null) ? conf.get(name) : null;
- if (value == null)
- {
- value = this.context.getProperty(name);
- }
-
+ Object value = getProperty(name);
return value != null ? String.valueOf(value) : defValue;
}
@@ -363,7 +353,126 @@
return false;
}
- private String[] getStringArrayProperty(String name, String[] defValue)
+ private void closeSilently(ServerSocket resource)
+ {
+ if (resource != null)
+ {
+ try
+ {
+ resource.close();
+ }
+ catch (IOException e)
+ {
+ // Ignore...
+ }
+ }
+ }
+
+ /**
+ * Determine the appropriate port to use. <code>portProp</code> is based
+ * "version range" as described in OSGi Core Spec v4.2 3.2.6. It can use the
+ * following forms:
+ * <dl>
+ * <dd>8000 | 8000</dd>
+ * <dd>[8000,9000] | 8000 <= port <= 9000</dd>
+ * <dd>[8000,9000) | 8000 <= port < 9000</dd>
+ * <dd>(8000,9000] | 8000 < port <= 9000</dd>
+ * <dd>(8000,9000) | 8000 < port < 9000</dd>
+ * <dd>[,9000) | 1 < port < 9000</dd>
+ * <dd>[8000,) | 8000 <= port < 65534</dd>
+ * </dl>
+ *
+ * @param portProp
+ * The port property value to parse.
+ * @return The port determined to be usable. -1 if failed to find a port.
+ */
+ private int determinePort(String portProp, int dflt)
+ {
+ // Default cases include null/empty range pattern or pattern == *.
+ if (portProp == null || "".equals(portProp.trim()))
+ {
+ return dflt;
+ }
+
+ // asking for random port, so let ServerSocket handle it and return the answer
+ portProp = portProp.trim();
+ if ("*".equals(portProp))
+ {
+ ServerSocket ss = null;
+ try
+ {
+ ss = new ServerSocket(0);
+ return ss.getLocalPort();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ finally
+ {
+ closeSilently(ss);
+ }
+ }
+ else
+ {
+ // check that the port property is a version range as described in
+ // OSGi Core Spec v4.2 3.2.6.
+ // deviations from the spec are limited to:
+ // * start, end of interval defaults to 1, 65535, respectively, if missing.
+ char startsWith = portProp.charAt(0);
+ char endsWith = portProp.charAt(portProp.length() - 1);
+ String interval = portProp.substring(1, portProp.length() - 1);
+
+ int minPort = 1;
+ int maxPort = 65535;
+
+ int comma = interval.indexOf(',');
+ if (comma >= 0 && (startsWith == '[' || startsWith == '(') && (endsWith == ']' || endsWith == ')'))
+ {
+ // check if the comma is first (start port in range is missing)
+ int start = (comma == 0) ? minPort : parseInt(interval.substring(0, comma), minPort);
+ // check if the comma is last (end port in range is missing)
+ int end = (comma == interval.length() - 1) ? maxPort : parseInt(interval.substring(comma + 1), maxPort);
+ // check for exclusive notation
+ if (startsWith == '(')
+ {
+ start++;
+ }
+ if (endsWith == ')')
+ {
+ end--;
+ }
+ // find a port in the requested range
+ int port = start - 1;
+ for (int i = start; port < start && i <= end; i++)
+ {
+ ServerSocket ss = null;
+ try
+ {
+ ss = new ServerSocket(i);
+ port = ss.getLocalPort();
+ }
+ catch (IOException e)
+ {
+ SystemLogger.debug("Unable to bind to port: " + port + " | " + portProp);
+ }
+ finally
+ {
+ closeSilently(ss);
+ }
+ }
+
+ return (port < start) ? dflt : port;
+ }
+ else
+ {
+ // We don't recognize the pattern as special, so try to parse it to an int
+ return parseInt(portProp, dflt);
+ }
+ }
+ }
+
+ private Object getProperty(String name)
{
Dictionary conf = this.config;
Object value = (conf != null) ? conf.get(name) : null;
@@ -371,6 +480,12 @@
{
value = this.context.getProperty(name);
}
+ return value;
+ }
+
+ private String[] getStringArrayProperty(String name, String[] defValue)
+ {
+ Object value = getProperty(name);
if (value instanceof String)
{
return new String[] { (String) value };
@@ -397,4 +512,16 @@
return defValue;
}
}
+
+ private int parseInt(String value, int dflt)
+ {
+ try
+ {
+ return Integer.parseInt(value);
+ }
+ catch (NumberFormatException e)
+ {
+ return dflt;
+ }
+ }
}
diff --git a/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/JettyConfigTest.java b/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/JettyConfigTest.java
new file mode 100644
index 0000000..9d4e7cc
--- /dev/null
+++ b/http/jetty/src/test/java/org/apache/felix/http/jetty/internal/JettyConfigTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.http.jetty.internal;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.replay;
+
+import java.net.ServerSocket;
+import java.util.Hashtable;
+
+import junit.framework.TestCase;
+
+import org.osgi.framework.BundleContext;
+
+/**
+ * Unit test for JettyConfig
+ */
+public class JettyConfigTest extends TestCase
+{
+ JettyConfig config;
+ BundleContext context;
+
+ public void testGetDefaultPort()
+ {
+ assertEquals("HTTP port", 8080, this.config.getHttpPort());
+ assertEquals("HTTPS port", 8443, this.config.getHttpsPort());
+ }
+
+ public void testGetPortInRange()
+ {
+ Hashtable<String, Object> props = new Hashtable<String, Object>();
+ props.put("org.osgi.service.http.port", "[8000,9000]");
+ props.put("org.osgi.service.http.port.secure", "[10000,11000)");
+ this.config.update(props);
+
+ assertTrue(this.config.getHttpPort() >= 8000 && this.config.getHttpPort() <= 9000);
+ assertTrue(this.config.getHttpsPort() >= 10000 && this.config.getHttpsPort() < 11000);
+
+ props.put("org.osgi.service.http.port", "(12000,13000]");
+ props.put("org.osgi.service.http.port.secure", "(14000,15000)");
+ this.config.update(props);
+
+ assertTrue(this.config.getHttpPort() > 12000 && this.config.getHttpPort() <= 13000);
+ assertTrue(this.config.getHttpsPort() > 14000 && this.config.getHttpsPort() < 15000);
+
+ props.put("org.osgi.service.http.port", "[,9000]");
+ props.put("org.osgi.service.http.port.secure", "[9000,)");
+ this.config.update(props);
+
+ assertTrue(this.config.getHttpPort() >= 1 && this.config.getHttpPort() <= 9000);
+ assertTrue(this.config.getHttpsPort() >= 9000 && this.config.getHttpsPort() < 65535);
+ }
+
+ public void testGetPortInvalidRange()
+ {
+ Hashtable<String, Object> props = new Hashtable<String, Object>();
+ props.put("org.osgi.service.http.port", "+12000,13000*");
+ props.put("org.osgi.service.http.port.secure", "%14000,15000");
+ this.config.update(props);
+
+ assertEquals(8080, this.config.getHttpPort());
+ assertEquals(8443, this.config.getHttpsPort());
+ }
+
+ public void testGetRandomPort()
+ {
+ Hashtable<String, Object> props = new Hashtable<String, Object>();
+ props.put("org.osgi.service.http.port", "*");
+ props.put("org.osgi.service.http.port.secure", "*");
+ this.config.update(props);
+ assertTrue(this.config.getHttpPort() != 8080);
+ assertTrue(this.config.getHttpsPort() != 433);
+ }
+
+ public void testGetSpecificPort() throws Exception
+ {
+ ServerSocket ss = new ServerSocket(0);
+ int port = ss.getLocalPort();
+ ss.close();
+ Hashtable<String, Object> props = new Hashtable<String, Object>();
+ props.put("org.osgi.service.http.port", port);
+ props.put("org.osgi.service.http.port.secure", port);
+ this.config.update(props);
+ assertTrue(this.config.getHttpPort() == port);
+ assertTrue(this.config.getHttpsPort() == port);
+ }
+
+ @Override
+ protected void setUp()
+ {
+ this.context = createNiceMock(BundleContext.class);
+ replay(this.context);
+ this.config = new JettyConfig(this.context);
+ }
+}
\ No newline at end of file