FELIX-1456: Added improved httpservice implementation
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@814549 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/jetty/pom.xml b/http/jetty/pom.xml
new file mode 100644
index 0000000..4458d19
--- /dev/null
+++ b/http/jetty/pom.xml
@@ -0,0 +1,107 @@
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.http</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <name>Apache Felix Http Jetty</name>
+ <artifactId>org.apache.felix.http.jetty</artifactId>
+ <packaging>bundle</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>
+ org.apache.felix.http.jetty.internal.JettyActivator
+ </Bundle-Activator>
+ <Export-Package>
+ org.apache.felix.http.api;version=${pom.version},
+ org.osgi.service.http,
+ javax.servlet.*;version=2.5
+ </Export-Package>
+ <Private-Package>
+ org.apache.felix.http.base.*,
+ org.apache.felix.http.jetty.*,
+ org.mortbay.*;-split-package:=merge-first
+ </Private-Package>
+ <Import-Package>
+ javax.net.ssl; javax.security.cert;
+ javax.xml.parsers; org.xml.sax;
+ org.xml.sax.helpers;
+ org.slf4j;resolution:=optional,
+ *;resolution:=optional
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>servlet-api-2.5</artifactId>
+ <version>6.1.14</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty</artifactId>
+ <version>6.1.14</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>6.1.14</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>jetty-sslengine</artifactId>
+ <version>6.1.14</version>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.http.api</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.http.base</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyActivator.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyActivator.java
new file mode 100644
index 0000000..9549274
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyActivator.java
@@ -0,0 +1,38 @@
+/*
+ * 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 org.apache.felix.http.base.internal.AbstractActivator;
+
+public final class JettyActivator
+ extends AbstractActivator
+{
+ private JettyService jetty;
+
+ protected void doStart()
+ throws Exception
+ {
+ this.jetty = new JettyService(getBundleContext(), getDispatcherServlet());
+ this.jetty.start();
+ }
+
+ protected void doStop()
+ throws Exception
+ {
+ this.jetty.stop();
+ }
+}
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
new file mode 100644
index 0000000..3251331
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java
@@ -0,0 +1,189 @@
+/*
+ * 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 org.osgi.framework.BundleContext;
+import java.util.Dictionary;
+import java.util.Properties;
+
+public final class JettyConfig
+{
+ /** Standard OSGi port property for HTTP service */
+ private static final String HTTP_PORT = "org.osgi.service.http.port";
+
+ /** Standard OSGi port property for HTTPS service */
+ private static final String HTTPS_PORT = "org.osgi.service.http.port.secure";
+
+ /** Felix specific property to enable debug messages */
+ private static final String FELIX_HTTP_DEBUG = "org.apache.felix.http.debug";
+ private static final String HTTP_DEBUG = "org.apache.felix.http.jetty.debug";
+
+ /** Felix specific property to override the keystore file location. */
+ private static final String FELIX_KEYSTORE = "org.apache.felix.https.keystore";
+ private static final String OSCAR_KEYSTORE = "org.ungoverned.osgi.bundle.https.keystore";
+
+ /** Felix specific property to override the keystore password. */
+ private static final String FELIX_KEYSTORE_PASSWORD = "org.apache.felix.https.keystore.password";
+ private static final String OSCAR_KEYSTORE_PASSWORD = "org.ungoverned.osgi.bundle.https.password";
+
+ /** Felix specific property to override the keystore key password. */
+ private static final String FELIX_KEYSTORE_KEY_PASSWORD = "org.apache.felix.https.keystore.key.password";
+ private static final String OSCAR_KEYSTORE_KEY_PASSWORD = "org.ungoverned.osgi.bundle.https.key.password";
+
+ /** Felix specific property to control whether to enable HTTPS. */
+ private static final String FELIX_HTTPS_ENABLE = "org.apache.felix.https.enable";
+ private static final String OSCAR_HTTPS_ENABLE = "org.ungoverned.osgi.bundle.https.enable";
+
+ /** Felix specific property to control whether to enable HTTP. */
+ private static final String FELIX_HTTP_ENABLE = "org.apache.felix.http.enable";
+
+ /** Felix specific property to override the truststore file location. */
+ private static final String FELIX_TRUSTSTORE = "org.apache.felix.https.truststore";
+
+ /** Felix specific property to override the truststore password. */
+ private static final String FELIX_TRUSTSTORE_PASSWORD = "org.apache.felix.https.truststore.password";
+
+ /** Felix specific property to control whether to want or require HTTPS client certificates. Valid values are "none", "wants", "needs". Default is "none". */
+ private static final String FELIX_HTTPS_CLIENT_CERT = "org.apache.felix.https.clientcertificate";
+
+ private final BundleContext context;
+ private boolean debug;
+ private int httpPort;
+ private int httpsPort;
+ private String keystore;
+ private String password;
+ private String keyPassword;
+ private boolean useHttps;
+ private String truststore;
+ private String trustPassword;
+ private boolean useHttp;
+ private String clientcert;
+
+ public JettyConfig(BundleContext context)
+ {
+ this.context = context;
+ reset();
+ }
+
+ public boolean isDebug()
+ {
+ return this.debug;
+ }
+
+ public boolean isUseHttp()
+ {
+ return this.useHttp;
+ }
+
+ public boolean isUseHttps()
+ {
+ return this.useHttps;
+ }
+
+ public int getHttpPort()
+ {
+ return this.httpPort;
+ }
+
+ public int getHttpsPort()
+ {
+ return this.httpsPort;
+ }
+
+ public String getKeystore()
+ {
+ return this.keystore;
+ }
+
+ public String getPassword()
+ {
+ return this.password;
+ }
+
+ public String getTruststore()
+ {
+ return this.truststore;
+ }
+
+ public String getTrustPassword()
+ {
+ return this.trustPassword;
+ }
+
+ public String getKeyPassword()
+ {
+ return this.keyPassword;
+ }
+
+ public String getClientcert()
+ {
+ return this.clientcert;
+ }
+
+ public void reset()
+ {
+ update(null);
+ }
+
+ public void update(Dictionary props)
+ {
+ if (props == null) {
+ props = new Properties();
+ }
+
+ this.debug = getBooleanProperty(props, FELIX_HTTP_DEBUG, getBooleanProperty(props, HTTP_DEBUG, false));
+ this.httpPort = getIntProperty(props, HTTP_PORT, 8080);
+ this.httpsPort = getIntProperty(props, HTTPS_PORT, 433);
+ this.keystore = getProperty(props, FELIX_KEYSTORE, this.context.getProperty(OSCAR_KEYSTORE));
+ this.password = getProperty(props, FELIX_KEYSTORE_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_PASSWORD));
+ this.keyPassword = getProperty(props, FELIX_KEYSTORE_KEY_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_KEY_PASSWORD));
+ this.useHttps = getBooleanProperty(props, FELIX_HTTPS_ENABLE, getBooleanProperty(props, OSCAR_HTTPS_ENABLE, false));
+ this.useHttp = getBooleanProperty(props, FELIX_HTTP_ENABLE, true);
+ this.truststore = getProperty(props, FELIX_TRUSTSTORE, null);
+ this.trustPassword = getProperty(props, FELIX_TRUSTSTORE_PASSWORD, null);
+ this.clientcert = getProperty(props, FELIX_HTTPS_CLIENT_CERT, "none");
+ }
+
+ private String getProperty(Dictionary props, String name, String defValue)
+ {
+ String value = (String)props.get(name);
+ if (value == null) {
+ value = this.context.getProperty(name);
+ }
+
+ return value != null ? value : defValue;
+ }
+
+ private boolean getBooleanProperty(Dictionary props, String name, boolean defValue)
+ {
+ String value = getProperty(props, name, null);
+ if (value != null) {
+ return (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"));
+ }
+
+ return defValue;
+ }
+
+ private int getIntProperty(Dictionary props, String name, int defValue)
+ {
+ try {
+ return Integer.parseInt(getProperty(props, name, null));
+ } catch (Exception e) {
+ return defValue;
+ }
+ }
+}
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyLogger.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyLogger.java
new file mode 100644
index 0000000..13f025c
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyLogger.java
@@ -0,0 +1,111 @@
+/*
+ * 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 org.mortbay.log.Logger;
+import org.apache.felix.http.base.internal.util.SystemLogger;
+
+import java.util.Map;
+import java.util.HashMap;
+
+public final class JettyLogger
+ implements Logger
+{
+ private final static Map<String, Logger> LOGGERS =
+ new HashMap<String, Logger>();
+
+ private final String name;
+ private boolean debugEnabled;
+
+ public JettyLogger()
+ {
+ this("org.mortbay.log");
+ }
+
+ public JettyLogger(String name)
+ {
+ this.name = name;
+ }
+
+ public org.mortbay.log.Logger getLogger(String name)
+ {
+ Logger logger = LOGGERS.get(name);
+ if (logger == null) {
+ logger = new JettyLogger(name);
+ logger.setDebugEnabled(isDebugEnabled());
+ LOGGERS.put(name, logger);
+ }
+
+ return logger;
+ }
+
+ public boolean isDebugEnabled()
+ {
+ return this.debugEnabled;
+ }
+
+ public void setDebugEnabled(boolean enabled)
+ {
+ this.debugEnabled = enabled;
+ }
+
+ public void debug(String msg, Throwable cause)
+ {
+ SystemLogger.get().debug(msg);
+ }
+
+ public void debug(String msg, Object arg0, Object arg1)
+ {
+ SystemLogger.get().debug(format(msg, arg0, arg1));
+ }
+
+ public void info(String msg, Object arg0, Object arg1)
+ {
+ SystemLogger.get().info(format(msg, arg0, arg1));
+ }
+
+ public void warn(String msg, Throwable cause)
+ {
+ SystemLogger.get().warning(msg, cause);
+ }
+
+ public void warn( String msg, Object arg0, Object arg1 )
+ {
+ SystemLogger.get().warning(format(msg, arg0, arg1), null);
+ }
+
+ public String toString()
+ {
+ return this.name;
+ }
+
+ private String format(String msg, Object arg0, Object arg1)
+ {
+ int i0 = msg.indexOf("{}");
+ int i1 = i0 < 0 ? -1 : msg.indexOf("{}", i0 + 2);
+
+ if (arg1 != null && i1 >= 0) {
+ msg = msg.substring(0, i1) + arg1 + msg.substring(i1 + 2);
+ }
+
+ if (arg0 != null && i0 >= 0) {
+ msg = msg.substring(0, i0) + arg0 + msg.substring(i0 + 2);
+ }
+
+ return msg;
+ }
+}
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
new file mode 100644
index 0000000..7cfffe9
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
@@ -0,0 +1,213 @@
+/*
+ * 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 org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.mortbay.jetty.security.HashUserRealm;
+import org.mortbay.jetty.security.SslSelectChannelConnector;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.Connector;
+import org.mortbay.jetty.nio.SelectChannelConnector;
+import org.mortbay.jetty.servlet.*;
+import org.mortbay.log.Log;
+import org.mortbay.log.StdErrLog;
+import org.apache.felix.http.base.internal.util.SystemLogger;
+import org.apache.felix.http.base.internal.DispatcherServlet;
+import java.util.Properties;
+import java.util.Dictionary;
+
+public final class JettyService
+ implements ManagedService, Runnable
+{
+ /** PID for configuration of the HTTP service. */
+ private static final String PID = "org.apache.felix.http";
+
+ private final JettyConfig config;
+ private final BundleContext context;
+ private boolean running;
+ private Thread thread;
+ private ServiceRegistration configServiceReg;
+ private Server server;
+ private DispatcherServlet dispatcher;
+
+ public JettyService(BundleContext context, DispatcherServlet dispatcher)
+ {
+ this.context = context;
+ this.config = new JettyConfig(this.context);
+ this.dispatcher = dispatcher;
+ }
+
+ public void start()
+ throws Exception
+ {
+ this.running = true;
+ this.thread = new Thread(this, "Jetty HTTP Service");
+ this.thread.start();
+
+ Properties props = new Properties();
+ props.put(Constants.SERVICE_PID, PID);
+
+ this.configServiceReg = this.context.registerService(ManagedService.class.getName(), this, props);
+ }
+
+ public void stop()
+ throws Exception
+ {
+ if (this.configServiceReg != null) {
+ this.configServiceReg.unregister();
+ }
+
+ this.running = false;
+ this.thread.interrupt();
+
+ try {
+ this.thread.join(3000);
+ } catch (InterruptedException e) {
+ // Do nothing
+ }
+ }
+
+ public void updated(Dictionary props)
+ throws ConfigurationException
+ {
+ this.config.update(props);
+ if (this.thread != null) {
+ this.thread.interrupt();
+ }
+ }
+
+ private void startJetty()
+ {
+ try {
+ initializeJetty();
+ } catch (Exception e) {
+ SystemLogger.get().error("Exception while initializing Jetty.", e);
+ }
+ }
+
+ private void stopJetty()
+ {
+ try {
+ this.server.stop();
+ } catch (Exception e) {
+ SystemLogger.get().error("Exception while stopping Jetty.", e);
+ }
+ }
+
+ protected void initializeJettyLogger()
+ {
+ Log.setLog(new JettyLogger());
+ }
+
+ private void destroyJettyLogger()
+ {
+ Log.setLog(new StdErrLog());
+ }
+
+ private void initializeJetty()
+ throws Exception
+ {
+ HashUserRealm realm = new HashUserRealm("OSGi HTTP Service Realm");
+ this.server = new Server();
+ this.server.addUserRealm(realm);
+
+ if (this.config.isUseHttp()) {
+ initializeHttp();
+ }
+
+ if (this.config.isUseHttps()) {
+ initializeHttps();
+ }
+
+ Context context = new Context(this.server, "/", Context.SESSIONS);
+ context.addServlet(new ServletHolder(this.dispatcher), "/*");
+
+ this.server.start();
+ }
+
+ private void initializeHttp()
+ throws Exception
+ {
+ Connector connector = new SelectChannelConnector();
+ connector.setPort(this.config.getHttpPort());
+ connector.setMaxIdleTime(60000);
+ this.server.addConnector(connector);
+ }
+
+ private void initializeHttps()
+ throws Exception
+ {
+ SslSelectChannelConnector connector = new SslSelectChannelConnector();
+ connector.setPort(this.config.getHttpsPort());
+ connector.setMaxIdleTime(60000);
+
+ if (this.config.getKeystore() != null) {
+ connector.setKeystore(this.config.getKeystore());
+ }
+
+ if (this.config.getPassword() != null) {
+ System.setProperty(SslSelectChannelConnector.PASSWORD_PROPERTY, this.config.getPassword());
+ connector.setPassword(this.config.getPassword());
+ }
+
+ if (this.config.getKeyPassword() != null) {
+ System.setProperty(SslSelectChannelConnector.KEYPASSWORD_PROPERTY, this.config.getKeyPassword());
+ connector.setKeyPassword(this.config.getKeyPassword());
+ }
+
+ if (this.config.getTruststore() != null) {
+ connector.setTruststore(this.config.getTruststore());
+ }
+
+ if (this.config.getTrustPassword() != null) {
+ connector.setTrustPassword(this.config.getTrustPassword());
+ }
+
+ if ("wants".equals(this.config.getClientcert())) {
+ connector.setWantClientAuth(true);
+ } else if ("needs".equals(this.config.getClientcert())) {
+ connector.setNeedClientAuth(true);
+ }
+
+ this.server.addConnector(connector);
+ }
+
+ public void run()
+ {
+ Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+
+ while (this.running) {
+ initializeJettyLogger();
+ startJetty();
+
+ synchronized (this) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // we will definitely be interrupted
+ }
+ }
+
+ stopJetty();
+ destroyJettyLogger();
+ }
+ }
+}