#

Jetty for all

Jetty ist eine kleine aber feine Implementierung eines 100% pure Java Webservers, der u. a. WAR, JSPs und Servlets unterstützt. Leider ist Jetty korrekt zu konfigurieren, um eine Standalone Applikation zu erstellen gar nicht so trivial, wie es scheint. Selbst die Informationen auf dem Wiki ist sehr verteilt und unklar strukturiert. Ein einfaches Step-By-Step Tutorial fehlt. Ein Grund, die gemachten Erfahrungen zu notieren.

Jetty kommt eigentlich mit einem Out-Of-The Box ausführbaren Archiv (start.jar), der jedoch alle angeführten Erweiterungen von Jetty und damit alle Dependencies (etwa 20 MB) benötigt. Für die meisten kleinen Stand-Alone Applikationen reicht jedoch ein Servlet-Handler oder ein JSP-Handler vollkommen aus. Um eine solche minimale Variante zum Laufen zu bringen, muss man sich verschiedenste Informationen aus dem Wiki raus fischen.

Prerequisites
Zu aller erst sei erwähnt, dass Jetty die gängige WAR (Web-Archive) Ordnerstruktur für Webanwendungen benutzt:

+myapp
   +WEB-INF
      +lib
      web.xml

Um Jetty korrekt auszuführen werden zuerst die Basislibraries benötigt:
jetty-6-1-10.jar
jetty-util-6-1-10.jar

Für den Servlet-Support:
serlvet-api-2.5-6-1-10.jar

Für den JSP (2.0)-Support:
serlvet-api-2.5-6-1-10.jar
ant-1.6-5.jar
xmlParserAPIs-2.6.2.jar
xercesImpl-2.6.2.jar
slf4j-api-1.3.1.jar
jsp-api-2.0.jar
jasper-runtime-5.5.15.jar
jasper-compiler-jdt-5.5.15.jar
jasper-compiler-5.5.15.jar
commons-el-1.0.jar
jcl104-over-slf4j-1.3.1.jar

Und wenn Log4J benutzt werden soll:
slf4j-log4j12-1.3.1jar
log4j-1.2.13.jar

Zu beachten ist hierbei die Version von slf4j und den zugehörigen Libraries. Es muss sich um die Version 1.3.1 handeln. Benutzt man eine andere (die neuere 1.4), so gibt es sehr unschöne Exceptions, wie z. B. java.lang.IllegalStateException: Level number 20 is not recognized.

Configuration
Jetty wird am einfachsten über eine XML-Konfigurationsdatei konfiguriert. Diese sieht wie folgt aus:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
 
<Configure id="Server" class="org.mortbay.jetty.Server">
  <Set name="Connectors">
    <Array type="org.mortbay.jetty.Connector">
      <Item>
        <New class="org.mortbay.jetty.nio.SelectChannelConnector">
          <Set name="port">
            <SystemProperty name="jetty.port" default="8080" />
          </Set>
        </New>
      </Item>
    </Array>
  </Set>
 
  <New id="webapp" class="org.mortbay.jetty.webapp.WebAppContext">
    <Set name="contextPath">/</Set>
    <Set name="war">./</Set>
    <Set name="defaultsDescriptor">./WEB-INF/webdefault.xml</Set>
    <Set name="overrideDescriptor">./WEB-INF/web.xml</Set>
  </New>
 
  <Set name="handler"><Ref id="webapp"/></Set>
 
</Configure>

Mit der Zeile „<Set name=“war“>./</Set>“ wird der Root der Webapplikation festgelegt, mit defaultsDescriptor wird die Server-Standard Webkonfiguration und mit overrideDescriptordie anwendungsspezifische Webkonfiguration festgelegt. Warum man zwei Benutzt, wird später diskutiert.
Diese Konfigurationsdatei kann man unter myapp/WEB-INF/jetty.xml abspeichern.

Den Server selber startet man in einer Main-Klasse wie folgt:

public class Main {
  private static Log LOG = LogFactory.getLog(Main.class);
 
  public static void main(String argv[]){
    try {
      Server server = new Server();
      XmlConfiguration configuration = new XmlConfiguration(
          new FileInputStream("WEB-INF/jetty.xml")); 
      // or use new XmlConfiguration(new FileInputStream("myJetty.xml"));
      configuration.configure(server);
      server.start();
    }
    catch (Exception e) {
      LOG.fatal("Error on startup of server", e);
    }
  }
}

defaultsDescriptor
Jettys Server behandelt in „nackter“ Form nichts, weder JSPs noch einfachen Dateianfragen. Um all dies zu können, gibt es in den Jetty-Packeten extra Servlets, die einzelne Features aktivieren. Um diese Jetty-Spezifischen Webapplikationskonfigurationen aus der web.xml rauszuhalten, gibt es den defaultsDescriptor, in dem man diese eintragen kann. Die folgende Datei myapp/WEB-INF/webdefault.xml enthält die Konfiguration für JSPs (JspServlet) und standard Dateianfragen (html, css, Bilder etc). Die ganzen Konfigurationsparameter können im Wiki nachgeschlagen werden.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
  <servlet>
    <servlet-name>JspServlet</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
      <servlet-name>JspServlet</servlet-name>
      <url-pattern>*.jsp</url-pattern>
    </servlet-mapping>
 
    <servlet>
      <servlet-name>default</servlet-name>
      <servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
      <init-param>
        <param-name>acceptRanges</param-name>
        <param-value>true</param-value>
      </init-param>
      <init-param>
        <param-name>dirAllowed</param-name>
        <param-value>true</param-value>
      </init-param>
      <init-param>
        <param-name>redirectWelcome</param-name>
        <param-value>false</param-value>
      </init-param>
      <init-param>
        <param-name>maxCacheSize</param-name>
        <param-value>2000000</param-value>
      </init-param>
      <init-param>
        <param-name>maxCachedFileSize</param-name>
        <param-value>254000</param-value>
      </init-param>
      <init-param>
        <param-name>maxCachedFiles</param-name>
        <param-value>1000</param-value>
      </init-param>
      <init-param>
        <param-name>useFileMappedBuffer</param-name>
        <param-value>true</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>/</url-pattern>
    </servlet-mapping>
 
    <welcome-file-list>
      <welcome-file>index.html</welcome-file>
     </welcome-file-list>
</web-app>

Nun kann man seine normale Anwendung schreiben und in der Date myapp/WEB-INF/web.xml konfigurieren:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
  <display-name>myapp</display-name>
 
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>
 
  <filter>
    <display-name>Spring open session in view filter</display-name>
    <filter-name>SpringSessionFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
  </filter>
 
  <filter-mapping>
    <filter-name>SpringSessionFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>
  </filter-mapping>
 
  <servlet>
    <description>Spring Context Loader Servlet</description>
    <servlet-name>ContextLoaderServlet</servlet-name>
    <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
  </servlet>
 
  ...
</web-app>
Tags:, , ,

2 Responses to “Jetty for all” »»

  1. Comment by Toni | 14:32 20.06.08|X

    …oder man nutzt http://mojo.codehaus.org/jetty-maven-plugin/ und tippt einfach:
    mvn jetty:run :)

  2. Comment by Secco | 13:24 15.01.09|X

    In einigen Fällen, wenn man z. B. den Spring ContextLoaderListener in der web.xml benutzt, erhält man eine sehr bescheidene Exception: Cannot initialize context because there is already a root application context present -
    check whether you have multiple ContextLoader* definitions in your web.xml!

    Für diese Fälle habe ich einen eigenen ContextLoader geschrieben, der eine modifizierte Version aus dem 2.0.8 er Spring darstellt.
    Er sorgt lediglich durch eine statische Instanz dafür, dass der Webcontext ein Singleton ist und bleibt.

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
     
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.web.context.ContextLoader;
    import org.springframework.web.context.ContextLoaderServlet;
    import org.springframework.web.context.WebApplicationContext;
     
    public class ContextLoaderListener implements ServletContextListener {
     
      private static final Log LOG = LogFactory.getLog(ContextLoaderListener.class);
     
      private static ContextLoader contextLoader;
     
      /**
       * Initialize the root web application context.
       */
      public synchronized void contextInitialized(ServletContextEvent event) {
        if (contextLoader==null){
          if (LOG.isDebugEnabled()){
            LOG.debug("initializing: event source: "+event.getSource()+
              ", context: "+event.getServletContext());
          }
          contextLoader = createContextLoader();
          contextLoader.initWebApplicationContext(event.getServletContext());
        }
      }
     
      /**
       * Create the ContextLoader to use. Can be overridden in subclasses.
       * @return the new ContextLoader
       */
      protected synchronized ContextLoader createContextLoader() {
        return new ContextLoader();
      }
     
      /**
       * Return the ContextLoader used by this listener.
       * @return the current ContextLoader
       */
      public synchronized ContextLoader getContextLoader() {
        return contextLoader;
      }
     
     
      /**
       * Close the root web application context.
       */
      public synchronized void contextDestroyed(ServletContextEvent event) {
        if (LOG.isDebugEnabled()){
          LOG.debug("destroying: event source: "+event.getSource()+
            ", context: "+event.getServletContext());
        }
     
        if (contextLoader != null) {
          contextLoader.closeWebApplicationContext(event.getServletContext());
        }
        contextLoader = null;
      }
    }

Leave a Reply »»

Note: All comments are manually approved to avoid spam. So if your comment doesn't appear immediately, that's ok. Have patience, it can take some days until I have the time to approve my comments.