Der Jetty Web Server lässt sich sehr einfach in eigene Programme einbetten. Wir benutzen diesen Server innerhalb von Eclipse um Webapplikationen mit Maven zu entwickeln. Die Webapplikationen lassen sich mit dem eingebetteten Jetty wie normale Javaprogramme starten und debuggen.
Für einen ersten Test benötigen wir die folgenden Abhängigkeiten. Der Jetty 8.0.x setzt Java 6 voraus. Wenn noch Java 5 eingesetzt wird, muss stattdessen der Jetty 7.5.x benutzt werden.
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>8.0.4.v20111024</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>8.0.4.v20111024</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<version>8.0.4.v20111024</version>
<scope>provided</scope>
</dependency>
Die Libraries werden im Scope “provided” hinzugefügt, damit sie nicht in das fertige WAR-File eingepackt werden.
Das Projektlayout entspricht einem normalen Mavenprojekt. Der Java Sourcecode befindet sich im src/main/java Directory. Die HTML/JSP Seiten sowie der WEB-INF Ordner sind unter src/main/webapp abgelegt.
src/main/webapp/index.html
<!DOCTYPE HTML>
<meta charset="utf-8">
<title>Simple HTML Page</title>
the body
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" />
Der eingebettete Jetty Server wird wie folgt gestartet.
public class JettySimple {
public static void main(String[] args) throws Exception {
WebAppContext context = new WebAppContext("./src/main/webapp", "/");
Server server = new Server(8080);
server.setHandler(context);
server.start();
}
}
Der zweite Parameter der WebAppContext Klasse definiert den Context. Für dieses Beispiel benutzen wir den Root Context (/). Im Browser muss deshalb die Applikation mit http://localhost:8080/ aufgerufen werden.
Wenn das Projekt auch JSP Seiten beinhaltet müssen weitere Libraries dem Projekt hinzugefügt werden.
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jsp-impl</artifactId>
<version>2.2.2.b05.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>el-impl</artifactId>
<version>2.2.1-b05</version>
<scope>provided</scope>
</dependency>
Um auch die Tags der JSTL (JavaServer Pages Standard Tag Library) zu benutzen (c:if, c:test, …) wird folgende Library benötigt.
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jstl-impl</artifactId>
<version>1.2</version>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
Diese Library wird im normalen Scope eingefügt, damit sie im WAR-File landet. Dies ist dann nötig wenn der Zielserver die Library nicht bereits mitliefert (wie z.B. Tomcat). Wenn der Zielserver die JSTL bereits beinhaltet kann hier der Scope auf provided geändert werden.
Als Testseite verwenden wir folgende JSP Seite.
src/main/webapp/test.jsp
<!doctype html>
<%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html><head><title>Simple JSP</title></head><body>
<p>
<c:if test="${params.test}">
request contains test parameter
</c:if>
<c:if test="${not params.test}">
no request parameter
</c:if>
</p>
<p>TEST=${params.test}</p>
</body>
</html>
Der Jetty Server wird mit dem JettySimple Programm gestartet und die JSP Seite im Browser mit http://localhost:8080/test.jsp?test=1 geöffnet.
Lock unter Windows
Unter Windows wird man eventuell ein Problem haben wenn versucht wird HTML/JSP Seiten zu editieren während der Jetty Server läuft. Jetty benutzt Memory-Mapped Files und diese Files werden unter Windows gelockt, so das diese nicht editiert werden können. Man müsste jedesmal den Server anhalten um eine Datei zu ändern. Man kann aber Jetty so konfigurieren das keine Memory-Mapped Files benutzt werden.
Dazu lädt man sich eine Jetty Distribution herunter und extrahiert das File webdefault.xml. Die Datei wird in das Projekt in einen beliebigen Ordner kopiert. Wir benutzen normalerweise den Ordner src/main/config. In der Datei webdefault.xml wird nun der Parameter useFileMappedBuffer auf false gesetzt.
<init-param>
<param-name>useFileMappedBuffer</param-name>
<param-value>false</param-value>
</init-param>
Das Startprogramm wird erweitert damit das geänderte webdefault.xml eingelesen wird.
WebAppContext context = new WebAppContext("./src/main/webapp", "/");
context.setDefaultsDescriptor("./src/main/config/webdefault.xml");
Server server = new Server(8080);
server.setHandler(context);
server.start();
Fehlermeldung bei bereits belegtem Port
Eine weitere kleine Unschönheit ist, das standardmässig keine Fehlermeldung, wegen des bereits belegten Ports, ausgegeben wird wenn der Server zweimal gestartet wird.
Damit eine Exception geworfen wird wenn der Port bereits belegt ist, muss im Connector reuseAddress auf false gesetzt.
Server server = new Server(8080);
((SelectChannelConnector) server.getConnectors()[0]).setReuseAddress(false);
server.setHandler(context);
server.start();
Alternativ kann man sich auch seinen eigenen Port Check einbauen. Das folgende Programm schreibt eine Meldung in die Console wenn der Port bereits belegt ist und beendet sich danach.
int port = 8080;
try {
ServerSocket srv = new ServerSocket(port);
srv.close();
} catch (IOException e) {
System.out.println("PORT " + port + " ALREADY IN USE");
return;
}
WebAppContext context = new WebAppContext("./src/main/webapp", "/");
context.setDefaultsDescriptor("./src/main/config/webdefault.xml");
Server server = new Server(port);
server.setHandler(context);
server.start();
JNDI
Auch JNDI lässt sich in einem eingebetteten Jetty benutzen. Dazu wird die jetty-plus LIbrary benötigt.
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-plus</artifactId>
<version>8.0.4.v20111024</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>activation</artifactId>
<groupId>javax.activation</groupId>
</exclusion>
</exclusions>
</dependency>
Die Java Activation Library wird hier ausgeschlossen, da sich diese bereits im Java 6 Runtime befindet.
Im Start Programm muss die Configuration des Contextes angepasst werden damit JNDI benutzt werden kann. Wichtig sind die beiden Configuration Klassen EnvConfiguration und PlusConfiguration.
WebAppContext context = new WebAppContext("./src/main/webapp", "/");
context.setDefaultsDescriptor("./src/main/config/webdefault.xml");
context.setConfigurations(new Configuration[] {
new org.eclipse.jetty.webapp.WebInfConfiguration(),
new org.eclipse.jetty.webapp.WebXmlConfiguration(),
new org.eclipse.jetty.webapp.MetaInfConfiguration(),
new org.eclipse.jetty.webapp.FragmentConfiguration(),
new org.eclipse.jetty.plus.webapp.EnvConfiguration(),
new org.eclipse.jetty.plus.webapp.PlusConfiguration(),
new org.eclipse.jetty.webapp.JettyWebXmlConfiguration() });
Einen einfachen String Eintrag fügt man mit addBean in den Context.
context.addBean(new EnvEntry("aSimpleEntry", "theValue"));
Innerhalb der Webapplikation lässt sich dieser Wert wie folgt auslesen:
InitialContext ic = new InitialContext();
String value = (String) ic.lookup("java:comp/env/aSimpleEntry")
Auch Datasourcen lassen sich einfügen. Hier ein Beispiel mit der JdbcDataSource von H2.
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setUser("sa");
dataSource.setURL("jdbc:h2:~/starter");
context.addBean(new org.eclipse.jetty.plus.jndi.Resource("jdbc/ds", dataSource));
Für dieses Beispel wird zusätzlich ein resource-ref Eintrag im web.xml benötigt um dann in der Applikation mit ic.lookup(“java:comp/env/jdbc/ds”) die DataSource zu lesen.
<resource-ref>
<description>The DataSource Reference</description>
<res-ref-name>jdbc/ds</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Der Sourcecode zu all diesen Beispielen findet man in meinem GitHub Account unter: https://github.com/ralscha/jettyembed