blob: abe63e2852bc3cdc9580894a89773a883ea05f4a [file] [log] [blame]
/*
* 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;
import java.util.Properties;
import org.mortbay.component.LifeCycle;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.security.HashUserRealm;
import org.mortbay.jetty.security.SslSocketConnector;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.OsgiServletHandler;
import org.mortbay.jetty.servlet.SessionHandler;
import org.mortbay.log.Log;
import org.mortbay.log.StdErrLog;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.http.HttpService;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
/**
* Basic implementation of OSGi HTTP service 1.1.
*
* TODO:
*
* - fuller suite of testing and compatibility tests
*
* - only exposed params are those defined in the OSGi spec. Jetty is
* very tunable via params, some of which it may be useful to expose
*
* - no cacheing is performed on delivered resources. Although not part
* of the OSGi spec, it also isn't precluded and would enhance
* performance in a high usage environment. Jetty's ResourceHandler
* class could be a model for this.
*
* - scanning the Jetty ResourceHandler class it's clear that there are
* many other sophisticated areas to do with resource handling such
* as checking date and range fields in the http headers. It's not clear
* whether any of these play a part in the OSGi service - the spec
* just describes "returning the contents of the URL to the client" which
* doesn't state what other HTTP handling might be compliant or desirable
*/
public class Activator implements BundleActivator
{
public static final boolean DEFAULT_HTTP_ENABLE = true;
public static final boolean DEFAULT_HTTPS_ENABLE = false;
public static final boolean DEFAULT_USE_NIO = true;
public static final int DEFAULT_HTTPS_PORT = 443;
public static final int DEFAULT_HTTP_PORT = 80;
public static final String DEFAULT_SSL_PROVIDER = "org.mortbay.http.SunJsseListener";
/** Felix specific property to override the SSL provider. */
public static final String FELIX_SSL_PROVIDER = "org.apache.felix.https.provider";
public static final String OSCAR_SSL_PROVIDER = "org.ungoverned.osgi.bundle.https.provider";
/** Felix specific property to override the keystore key password. */
public static final String FELIX_KEYSTORE_KEY_PASSWORD = "org.apache.felix.https.keystore.key.password";
public static final String OSCAR_KEYSTORE_KEY_PASSWORD = "org.ungoverned.osgi.bundle.https.key.password";
/** Felix specific property to override the keystore file location. */
public static final String FELIX_KEYSTORE = "org.apache.felix.https.keystore";
public static final String OSCAR_KEYSTORE = "org.ungoverned.osgi.bundle.https.keystore";
/** Felix specific property to override the keystore password. */
public static final String FELIX_KEYSTORE_PASSWORD = "org.apache.felix.https.keystore.password";
public static final String OSCAR_KEYSTORE_PASSWORD = "org.ungoverned.osgi.bundle.https.password";
/** Standard OSGi port property for HTTP service */
public static final String HTTP_PORT = "org.osgi.service.http.port";
/** Standard OSGi port property for HTTPS service */
public static final String HTTPS_PORT = "org.osgi.service.http.port.secure";
/** Felix specific property to enable debug messages */
public static final String FELIX_HTTP_DEBUG = "org.apache.felix.http.debug";
public static final String HTTP_DEBUG = "org.apache.felix.http.jetty.debug";
/** Felix specific property to determine the
name of the service property to set with the http port used. If not supplied
then the HTTP_PORT property name will be used for the service property */
public static final String HTTP_SVCPROP_PORT = "org.apache.felix.http.svcprop.port";
/** Felix specific property to determine the
name of the service property to set with the https port used. If not supplied
then the HTTPS_PORT property name will be used for the service property */
public static final String HTTPS_SVCPROP_PORT = "org.apache.felix.http.svcprop.port.secure";
/** Felix specific property to control whether NIO will be used. If not supplied
then will default to true. */
public static final String HTTP_NIO = "org.apache.felix.http.nio";
/** Felix specific property to control whether to enable HTTPS. */
public static final String FELIX_HTTPS_ENABLE = "org.apache.felix.https.enable";
public static final String OSCAR_HTTPS_ENABLE = "org.ungoverned.osgi.bundle.https.enable";
/** Felix specific property to control whether to enable HTTP. */
public static final String FELIX_HTTP_ENABLE = "org.apache.felix.http.enable";
protected static boolean debug = false;
private static ServiceTracker m_logTracker = null;
private BundleContext m_bundleContext = null;
private ServiceRegistration m_svcReg = null;
private HttpServiceFactory m_httpServ = null;
private Server m_server = null;
private OsgiServletHandler m_hdlr = null;
private int m_httpPort;
private int m_httpsPort;
private boolean m_useNIO;
private String m_sslProvider;
private String m_httpsPortProperty;
private String m_keystore;
private String m_passwd;
private String m_keyPasswd;
private boolean m_useHttps;
private String m_httpPortProperty;
private Properties m_svcProperties = new Properties();
private boolean m_useHttp;
//
// Main class instance code
//
public void start( BundleContext bundleContext ) throws BundleException
{
m_bundleContext = bundleContext;
setConfiguration();
m_logTracker = new ServiceTracker( bundleContext, LogService.class.getName(), null );
m_logTracker.open();
// org.mortbay.util.Loader needs this (used for JDK 1.4 log classes)
Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
// set the Jetty logger to be LogService based
initializeJettyLogger();
try
{
initializeJetty();
}
catch ( Exception ex )
{
//TODO: maybe throw a bundle exception in here?
log( LogService.LOG_INFO, "Http2", ex );
return;
}
m_httpServ = new HttpServiceFactory();
m_svcReg = m_bundleContext.registerService( HttpService.class.getName(), m_httpServ, m_svcProperties );
// OSGi spec states the properties should not be changed after registration,
// so create new copy for later clone for updates
m_svcProperties = new Properties(m_svcProperties);
}
private void setConfiguration() {
debug = getBooleanProperty(FELIX_HTTP_DEBUG, getBooleanProperty(HTTP_DEBUG, false));
// get default HTTP and HTTPS ports as per the OSGi spec
m_httpPort = getIntProperty(HTTP_PORT, DEFAULT_HTTP_PORT);
m_httpsPort = getIntProperty(HTTPS_PORT, DEFAULT_HTTPS_PORT);
// collect other properties, default to legacy names only if new ones are not available
m_useNIO = getBooleanProperty(HTTP_NIO, DEFAULT_USE_NIO);
m_sslProvider = getStringProperty(FELIX_SSL_PROVIDER, getStringProperty(OSCAR_SSL_PROVIDER, DEFAULT_SSL_PROVIDER));
m_httpsPortProperty = getStringProperty(HTTPS_SVCPROP_PORT, HTTPS_PORT);
m_keystore = getStringProperty(FELIX_KEYSTORE, m_bundleContext.getProperty(OSCAR_KEYSTORE));
m_passwd = getStringProperty(FELIX_KEYSTORE_PASSWORD, m_bundleContext.getProperty(OSCAR_KEYSTORE_PASSWORD));
m_keyPasswd = getStringProperty(FELIX_KEYSTORE_KEY_PASSWORD, m_bundleContext.getProperty(OSCAR_KEYSTORE_KEY_PASSWORD));
m_useHttps = getBooleanProperty(FELIX_HTTPS_ENABLE, getBooleanProperty(OSCAR_HTTPS_ENABLE, DEFAULT_HTTPS_ENABLE));
m_httpPortProperty = getStringProperty(HTTP_SVCPROP_PORT, HTTP_PORT);
m_useHttp = getBooleanProperty(FELIX_HTTP_ENABLE, DEFAULT_HTTP_ENABLE);
}
public void stop( BundleContext bundleContext ) throws BundleException
{
//TODO: wonder if we need to closedown service factory ???
if ( m_svcReg != null )
{
m_svcReg.unregister();
}
try
{
m_server.stop();
}
catch ( Exception e )
{
//TODO: log some form of error
}
// replace non-LogService logger for jetty
Log.setLog( new StdErrLog() );
m_logTracker.close();
}
public int getIntProperty(String name, int dflt_val)
{
int retval = dflt_val;
try
{
retval = Integer.parseInt( m_bundleContext.getProperty( name ) );
}
catch ( Exception e )
{
// maybe log a message saying using default?
retval = dflt_val;
}
return retval;
}
public boolean getBooleanProperty(String name, boolean dflt_val)
{
boolean retval = dflt_val;
String strval = m_bundleContext.getProperty( name );
if ( strval != null)
{
if (strval.toLowerCase().equals( "true" ) ||
strval.toLowerCase().equals( "yes" ))
{
retval = true;
}
else
{
// poss should raise error/warn here
retval = false;
}
}
return retval;
}
public String getStringProperty(String name, String dflt_val)
{
String retval = dflt_val;
String strval = m_bundleContext.getProperty( name );
if ( strval != null)
{
retval = strval;
}
return retval;
}
protected void initializeJettyLogger() {
String oldProperty = System.getProperty( "org.mortbay.log.class" );
System.setProperty( "org.mortbay.log.class", LogServiceLog.class.getName() );
if (!(Log.getLog() instanceof LogServiceLog)) {
Log.setLog( new LogServiceLog() );
}
Log.getLog().setDebugEnabled( debug );
if (oldProperty != null) {
System.setProperty( "org.mortbay.log.class", oldProperty );
}
}
protected void initializeJetty() throws Exception
{
//TODO: Maybe create a separate "JettyServer" object here?
// Realm
HashUserRealm realm = new HashUserRealm( "OSGi HTTP Service Realm" );
// Create server
m_server = new Server();
m_server.addUserRealm( realm );
if (m_useHttp)
{
initializeHTTP();
}
if (m_useHttps)
{
initializeHTTPS();
}
// setup the Jetty web application context shared by all Http services
m_hdlr = new OsgiServletHandler();
Context hdlrContext = new Context( m_server, new SessionHandler(), null, m_hdlr, null );
hdlrContext.setClassLoader( getClass().getClassLoader() );
hdlrContext.setContextPath( "/" );
debug( " adding handler context : " + hdlrContext );
try
{
hdlrContext.start();
}
catch ( Exception e )
{
// make sure we unwind the adding process
log( LogService.LOG_ERROR, "Exception Starting Jetty Handler Context", e );
}
m_server.start();
}
private void initializeHTTP() {
Connector connector = m_useNIO ?
(Connector) new SelectChannelConnector() : (Connector) new SocketConnector();
connector.addLifeCycleListener(
new ConnectorListener(m_httpPortProperty)
);
connector.setPort( m_httpPort );
connector.setMaxIdleTime( 60000 );
m_server.addConnector( connector );
}
//TODO: Just a basic implementation to give us a working HTTPS port. A better
// long-term solution may be to separate out the SSL provider handling,
// keystore, passwords etc. into it's own pluggable service
protected void initializeHTTPS() throws Exception
{
if (m_useNIO) {
// we do not want to create a compile time dependency on Java 5 classes
// so we use a bit of reflection here
SelectChannelConnector s_listener = (SelectChannelConnector) Class.forName("org.mortbay.jetty.security.SslSelectChannelConnector").newInstance();
s_listener.addLifeCycleListener(new ConnectorListener(m_httpsPortProperty));
s_listener.setPort(m_httpsPort);
s_listener.setMaxIdleTime(60000);
if (m_keystore != null) {
s_listener.getClass().getMethod("setKeystore", new Class[] {String.class}).invoke(s_listener, new Object[] { m_keystore });
}
if (m_passwd != null) {
System.setProperty("jetty.ssl.password" /* SslSelectChannelConnector.PASSWORD_PROPERTY */ , m_passwd);
s_listener.getClass().getMethod("setPassword", new Class[] {String.class}).invoke(s_listener, new Object[] { m_passwd });
}
if (m_keyPasswd != null) {
System.setProperty("jetty.ssl.keypassword" /* SslSelectChannelConnector.KEYPASSWORD_PROPERTY */, m_keyPasswd);
s_listener.getClass().getMethod("setKeyPassword", new Class[] {String.class}).invoke(s_listener, new Object[] { m_keyPasswd });
}
m_server.addConnector(s_listener);
}
else {
SslSocketConnector s_listener = new SslSocketConnector();
s_listener.addLifeCycleListener(new ConnectorListener(m_httpsPortProperty));
s_listener.setPort(m_httpsPort);
s_listener.setMaxIdleTime(60000);
if (m_keystore != null) {
s_listener.setKeystore(m_keystore);
}
if (m_passwd != null) {
System.setProperty(SslSocketConnector.PASSWORD_PROPERTY, m_passwd);
s_listener.setPassword(m_passwd);
}
if (m_keyPasswd != null) {
System.setProperty(SslSocketConnector.KEYPASSWORD_PROPERTY, m_keyPasswd);
s_listener.setKeyPassword(m_keyPasswd);
}
m_server.addConnector(s_listener);
}
}
public static void debug( String txt )
{
if ( debug )
{
log( LogService.LOG_DEBUG, ">>Felix HTTP: " + txt, null );
}
}
public static void log( int level, String message, Throwable throwable )
{
LogService log = ( LogService ) m_logTracker.getService();
if ( log != null )
{
log.log( level, message, throwable );
}
else
{
System.out.println( message );
if ( throwable != null )
{
throwable.printStackTrace( System.out );
}
}
}
// Inner class to provide basic service factory functionality
public class HttpServiceFactory implements ServiceFactory
{
public HttpServiceFactory()
{
// Initialize the statics for the service implementation.
HttpServiceImpl.initializeStatics();
}
public Object getService( Bundle bundle, ServiceRegistration registration )
{
Object srv = new HttpServiceImpl( bundle, m_server, m_hdlr );
debug( "** http service get:" + bundle + ", service: " + srv );
return srv;
}
public void ungetService( Bundle bundle, ServiceRegistration registration, Object service )
{
debug( "** http service unget:" + bundle + ", service: " + service );
( ( HttpServiceImpl ) service ).unregisterAll();
}
}
// Innner class to listen for connector startup and register service
// properties for actual ports used. Possible connections may have deferred
// startup, so this should ensure "port" is retrieved once available
public class ConnectorListener implements LifeCycle.Listener
{
String m_svcPropName;
public ConnectorListener(String svcPropName)
{
m_svcPropName = svcPropName;
}
public void lifeCycleFailure(LifeCycle event, Throwable cause) {}
public void lifeCycleStarted(LifeCycle event)
{
Connector conn = (Connector) event;
int actualPort = conn.getLocalPort();
debug( "** http set service prop:" + m_svcPropName + ", value: " + actualPort );
m_svcProperties.setProperty(m_svcPropName, String.valueOf(actualPort));
if (m_svcReg != null)
{
m_svcReg.setProperties(m_svcProperties);
m_svcProperties = new Properties(m_svcProperties);
}
}
public void lifeCycleStarting(LifeCycle event) {}
public void lifeCycleStopped(LifeCycle event) {}
public void lifeCycleStopping(LifeCycle event) {}
}
}