Unable to locate Spring NamespaceHandler for XML schema namespace
Die Ursache folgender Fehlermeldung:
Offending resource: class path resource [applicationContext.xml]
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.3.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
Posted at 11:04PM Jul 29, 2010 by Randy Gupta in Java | Kommentare[27]
JSF mit Facelets
Auf der Suche nach einem schnellen Einstieg in das Thema Facelets bin ich auf ein sehr gutes englischsprachiges Tutorial von Richard Hightower gestoßen.
Mein Urteil: sehr zu empfehlen!
Posted at 02:50PM Apr 07, 2010 by Randy Gupta in Java | Kommentare[68]
ICEFaces Dependencies (JAR's) mit Maven konfigurieren
Wer der JSF-Framework ICEfaces verwendet wird am Anfang möglicherweise darüber stolpern, dass man zunächst nicht weiß, welche JAR's man überhaupt benötigt. ICEfaces hat hier eine Übersicht ins Netz gestellt. Für das Timezone-Tutorial habe ich mir für Tomcat 6 folgende POM (Maven) erstellt:
<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>
<groupId>de.neurox.tutorials</groupId>
<artifactId>timezone2</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>timezone2 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>sun-jaxws</groupId>
<artifactId>FastInfoset</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.icefaces</groupId>
<artifactId>icefaces-comps</artifactId>
<version>1.8.2</version>
<exclusions>
<exclusion>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.icefaces</groupId>
<artifactId>icefaces-facelets</artifactId>
<version>1.8.2</version>
<exclusions>
<exclusion>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-api</artifactId>
<version>1.2.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
<version>1.2.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-el</groupId>
<artifactId>commons-el</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<!--
Only required when using Excel format export with ice:dataExporter
Component
-->
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6</version>
</dependency>
<!-- Only required when using the ice:outputChart component -->
<dependency>
<groupId>net.sf.jcharts</groupId>
<artifactId>krysalis-jCharts</artifactId>
<version>1.0.0-alpha-1</version>
</dependency>
</dependencies>
<build>
<finalName>timezone1</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Leider habe ich auch eine Weile gebraucht um die passende web.xml zusammen zu stellen. Hier eine funktionierede Version in Kombination mit myfaces:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>ICEfaces Tutorial: Timezone Part 2</display-name>
<description>
ICEfaces Tutorial: Timezone Part 2
Show how simple it is to
integrate ICEfaces technology into an existing
JavaServer Faces
environment.
</description>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.jspx</param-value>
</context-param>
<!--
Specifies to the ICEfaces framework that synchronous update mode is to
be used. By default, ICEfaces uses asynchronous update mode to support
server-initiated updates (AJAX push). Setting to true will enable
synchronous update mode and disable AJAX push features.
-->
<context-param>
<param-name>com.icesoft.faces.synchronousUpdate</param-name>
<param-value>true</param-value>
</context-param>
<!--
ConfigureListener is not generally required. Due to an apparent bug in
Tomcat users have reported seeing the following error "SEVERE:
ICEfaces could not initialize JavaServer Faces. Please check that the
JSF .jar files are installed correctly.". Specifying the
ConfigureListener resolves the issue.
-->
<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
<!-- Faces Servlet -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<!--
<servlet-class>org.apache.myfaces.webapp.MyFacesServlet</servlet-class>
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Persistent Faces Servlet -->
<servlet>
<servlet-name>Persistent Faces Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Blocking Servlet -->
<servlet>
<servlet-name>Blocking Servlet</servlet-name>
<servlet-class>com.icesoft.faces.webapp.xmlhttp.BlockingServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Persistent Faces Servlet Mappings -->
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>/xmlhttp/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Persistent Faces Servlet</servlet-name>
<url-pattern>*.iface</url-pattern>
</servlet-mapping>
<!-- Blocking Servlet Mapping -->
<servlet-mapping>
<servlet-name>Blocking Servlet</servlet-name>
<url-pattern>/block/*</url-pattern>
</servlet-mapping>
<!-- Faces Servlet Mapping -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!-- Welcome File List -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Posted at 02:09PM Mrz 11, 2010 by Randy Gupta in Java | Kommentare[14]
Flash clickTAG Tutorial
Wenn man sich als Programmierer, der eine Entwicklungsumgebung wie Eclipse gewöhnt ist, in die Verlegenheit gerät mit Flash zu arbeiten, dann kann man dabei schon mal schnell Zahnschmerzen bekommen. Heute ist wieder einer dieser Tage. Glücklicherweise habe ich hier ein Tutorial gefunden, das genau auf mein Problem eingeht: einen ClickTag in einem Flash Banner einzubaue.
Mein tiefster Dank gilt den Verfassern!
Posted at 02:15PM Feb 15, 2010 by Randy Gupta in Allgemeines | Kommentare[14]
java.lang.Exception: Socket bind failed: [22] Invalid argument (Apache Tomcat unter Ubuntu Karmic Koala)
Wer unter Ubuntu Karmic Koala beim Start von Apache Tomcat folgende Fehlermeldung erhält:
java.lang.Exception: Socket bind failed: [22] Invalid argument
at org.apache.tomcat.util.net.AprEndpoint.init(AprEndpoint.java:612)
at org.apache.coyote.http11.Http11AprProtocol.init(Http11AprProtocol.java:107)
at org.apache.catalina.connector.Connector.initialize(Connector.java:1058)
at org.apache.catalina.core.StandardService.initialize(StandardService.java:677)
at org.apache.catalina.core.StandardServer.initialize(StandardServer.java:795)
at org.apache.catalina.startup.Catalina.load(Catalina.java:530)
at org.apache.catalina.startup.Catalina.load(Catalina.java:550)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:616)
at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:260)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:412)
Der ist sehr wahrscheinlich Opfer eines Bugs in dem Paket libtcnative-1 geworden. Das Problem läßt sich umgehen, wenn man IPv6 ausschaltet. Bei den meisten Leuten kommt es im Moment (leider) noch nicht zu Einsatz. Insofern wäre das alleine schon ein Grund, das Protokoll abzuschalten.
Das Protokoll läßt sich deaktivieren, in dem man dem Kernel beim Booten einen entsprechenden Parameter mit gibt. Hierzu muss die Grub-Konfiguration editiert werden. Ich gehe hierbei von dem Standard unter Karmic Koala aus. Hier ist Grub2 Stand der Technik.
Man editiere folgende Datei:
Dort such man nach der Zeile:
und ändere sie nach:
Anschließend führt man noch folgenden Befehl aus, um die Änderungen tatsächlich anzuwenden:
Danach das System neustarten und die Katze rennt. Wenn nicht, dann lag es an was anderem 
Posted at 07:41PM Feb 10, 2010 by Randy Gupta in Java | Kommentare[24]
Apache Tomcat und Apache2 HTTP Server unter Debian Linux mit mehreren Instanzen installieren (Tutorial/Howto)
Wer unter mit Java Webanwendungen programmieren möchte, der kommt in den wenigsten Fällen um Apache Tomcat herum. Selbst wenn man nicht unbedingt Programmierer ist, kommt man schnell in die Verlegenheit Tomcat installieren zu müssen um eine javabasierende Webanwendung zu verwenden.
Tomcat ist mit seinen 6 MB ausgesprochen schlank und da es sich bei Tomcat selbst auch um eine Java-Anwendung handelt, ist er obendrein weitestgehend plattformunabhänig. Dadurch braucht man Tomcat eigentlich nicht wirklich zu installieren. Man muss sich lediglich die Distribution herunterladen, entpacken und starten. Das Tomcat-Verzeichnis läßt sich hierbei auch beliebig samt installierter Applikationen jederzeit verschieben.
Wenn man aber mit Tomcat etwas intensiver arbeiten möchte oder ihn sogar im Produktivbetrieb einsetzen möchte, gilt es einige Dinge zu beachten.
In diesem Beitrag möchte ich meinen Focus im wesentlich auf zwei Themen legen:
Sicherheit
Es liegt in der Natur von Tomcat, Dienste im Internet bereit zustellen. Somit kann jeder darauf zugreifen und er kann auch von jedem angegriffen werden. Tomcat selbst ist relativ sicher, aber das alleine reicht nicht aus. Wenn die Webanwendung, die Tomcat ausführt nicht wirklich sicher ist, dann wird Tomcat zum Einfallstor für ungebetene Gäste.
Genau aus diesem Grunde sollten sich gerade Anfänger und auch Administratoren, die mal eben was ausprobieren möchten, gut überlegen wie Tomcat mit dem Netz verbunden wird. Ich werde im Folgenden beschreiben, wie man Tomcat unter Debian Linux mit reduzierten Benutzerrechten startet. Sollte hier ein Einbruch gelingen, ist nicht sofort der Superuser kompromittiert und man kann noch etwas Schadensbegrenzung betreiben.
Multiple Instanzen (Clustering)
Da Tomcat relativ leichtgewichtig ist, eigent er sich auch hervorragend dazu auch in mehreren Instanzen gestartet zu werden. Auf diese Art und Weise kann man eine Applikation zum einen etwas ausfallsicherer betreiben und zum anderen kann man auch die Applikationen selbst von einander etwas besser trennen. Wer jetzt sag: „Klar, kenne ich! Man muss nur die Installation kopieren, die Ports anpassen und die Instanzen starten ...“ der sollte vielleicht doch noch ein bisschen weiter lesen. Ich werde im Weiteren beschreiben, wie sich auch diese Schritte stark vereinfachen lassen.
Jeder Tomcat-Instanz schalte ich im produktiven Betrieb immer einen Apache2 HTTP Server vor. Tomcat selbst kommt zwar auch ohne den HTTP Server aus, denn er ist ja im Grunde genommen selber einer, aber in der Praxis müssen auch sehr viele statische Inhalte (Bilder etc.) ausgeliefert werden, was durch den Apache2 HTTP Server wesentlich schneller erfolgt. Auch wäre ein Loadbalancing mit mehreren Tomcat Instanzen ohne den Apache2 HTTP Server nicht ohne weiteres möglich.
… und los geht’s
Ich gehe davon aus, dass Debian Linux installiert und eine aktuelle JVM ebenfalls bereit steht und wir als root lokal auf der Maschine arbeiten. In der Praxis wird man vermutlich per SSH auf den Server gehen. Ich mute dem Leser also die Transferleistung zu, die Anleitung auf diesen Fall zu übertragen.
Wir müssen zunächst hier eine aktuelle Version von Apache Tomcat besorgen.
Ihr könnt natürlich auch eine andere Version und einen anderen Mirror verwenden.
Mit
entpacken wir das Ganze, und erhalten dann das eigentlich Verzeichnis von Tomcat. Nun ist ein guter Zeitpunkt für einen kleinen Rundgang gekommen.
In dem frisch entpackten Tomcat-Verzeichnis finden wir folgende Verzeichnisse:
conf
lib
LICENSE
logs
NOTICE
RELEASE-NOTES
RUNNING.txt
temp
webapps
work
Ich werde sie einfach der Reihe nach durch gehen:
In bin befinden sich im wesentlichen die Startscripte für Tomcat. Um die Startskripte verwenden zu können müssen wir sie zuvor noch mit
ausführbar machen. Anschließend starten wir Tomcat das erste mal mit
was nur dann Probleme bereitet, wenn einer oder mehrere der von Tomcat vernwendeten Standardports bereits in Verwendung sind.
Wenn wir also in unserem Browser http://localhost:8080 eingeben, sollten wir auf die Startseite von Tomcat kommen.
Falls das nicht funktioniert, dann sollten wir zunächst prüfen, ob die Ports 8005, 8009 und 8080 schon durch irgendeine andere Applikation verwendet werden. Das ist gar nicht so unwahrscheinlich, denn Tomcat wird in viele Applikationen eingebettet und dort oftmals auch mit diesen Ports verwendet.
Die Startseite von Tomcat ist selbst nur eine Webapplikation, die wir später beliebig ersetzen können. Der Voreilige Leser wird sicherlich schon unter dem Punkt Administration auf Status geklickt haben und an der Sicherheitsabfrage gescheitert sein. Ich werde an passender Stelle erklären, wie man diesem Problem begegnet.
Wir wenden uns wieder der Konsole zu und stoppen Tomcat mit
Die Skripte bin/startup.sh und bin/shutdown.sh haben noch ihre Pendants für die Windowswelt und funktionieren analog. Sie sind eigentlich nur Wrapperscripte für bin/catalina.sh.
Wer ist eigentlich Catalina? Nun, dass ist der eigentliche Servlet-Container, der zusammen mit dem Coyote Connector Tomcat bilden. Zu dem Connector werde ich später kommen, den brauchen wir um Tomcat hinter den Apache2 HTTP Server zu hängen.
Im Verzeichnis lib befinden sich eine Reihe von Bibliotheken, die Tomcat selbst nutzt, die aber auch den Webapplikationen zur Verfügung stehen. Es befinde sich nicht besonders viel in diesem Verzeichnis, was auch so gewollt und auch gut so ist. Tomcat ist eben „nur“ ein Servlet-Container, den wir bei Bedarf erweitern können. Das bedeutet, dass wir selbst für eine stinknormale Datenbankverbindung die Entsprechenden Treiber unserer Webapplikation mitgeben müssen, oder diese auch in das lib-Verzeichnis von Tomcat legen können.
Allerdings würde ich von letzterem aus Gründen der Portabilität abraten. Irgendwann muss man dann doch mal die Applikation auf einen anderen Server schieben und wundert sich, weshalb einem eine ClassLoader Exception um die Ohren fliegt. Schöner ist es dann ja doch eine Applikation zu haben, in der schon alles drin ist. In der Regel ist der Festplattenplatz für unsere Anwendungen nicht unbedingt der Flaschenhals. Zeit, die bei der Fehlersuche drauf geht, ist hingegen kostbar.
Das Verzeichnis conf beeinhaltet, wie der Name schon vermuten lässt, die Konfiguration von Tomcat und auch teilweise von unseren Webapplikationen. Die Datei server.xml enthält wohl die wichtigsten Konfigurationen für den Betrieb von Tomcat. Ich empfehle von dieser Datei ein Backup zu machen
und anschließend in der Datei alle Kommentare raus zuschmeißen. Am besten macht man dass mit einen XML-Editor. Was dann übrig bleibt, ist für uns am Anfang relevant. Der Rest ist im Augenblick als Dokumentation zu betrachten. In dieser Datei werden wir später auch die Ports (shutdown 8005, AJP 8009, HTTP 8080) anpassen, denn wie ich Eingangs schon erwähnt hatte, verhindert eine Mehrfachbelegung der Ports den Start von Tomcat. Wenn wir mehrere Tomcat-Instanzen gleichzeitig starten möchten, dann müssen wir zwangsläufig die Ports der einzelnen Instanzen aufeinander abstimmen, damit sie sich nicht ins Gehege kommen.
Hier finden wir auch die Datei tomcat-users.xml. Die nichtvorhandenen Einträge in dieser Datei haben uns zuvor davon abgehalten alle Bereiche unserer Manager-Application zu verwenden.
Das Wurzelement der XML-Datei ist <tomcat-users />. In der Minimalkonfiguration muss es für den Zugriff auf die Manager-Applikation mindestens zwei Kindelemente besitzen: <role /> und <user/> .
Mit <role /> wird zunächst eine Benutzergruppe definiert. Die Manager-Applikation interessiert sich z.B. gar nicht für den Benutzer, sondern nur für die Gruppe. D.h. nur Mitglieder der Gruppe manager erhalten Zugriff auf die Applikation.
<user/> Definiert den Benutzer, sein Passwort und die Gruppe(n), in denen der Benutzer Mitglied ist. Mehere Gruppen werden innerhalb des Parameters „roles“ per Kommata voneinander getrennt.
Eine funktionstüchtige tomcat-users.xml sieht wie folgt aus:
<tomcat-users>
<role rolename="manager"/>
<user username="admin" password="geheimesPasswort" roles="manager"/>
</tomcat-users>
Ich kann es gar nicht oft genug sagen, wohl wissend, dass viele mich nicht erhören werden:
Verwendet bitte einzigartige und komplexe Passwörter auch in der Testumgebung! Zu schnell werden besonders diese Konfigurationsdateien mit in die Produktivumgebung kopiert. Selbst wenn sie nur im Testbetrieb laufen und trotzdem im Internet exponiert werden, so stellen schwache Passwörter ein sehr hohes Risiko für das gesamte System dar!
Im Produktivbetrieb hat sich der Einsatz von JNDI/LDAP bewährt. Eine Erklärung würde hier aber zu weit führen.
Wir können Tomcat nun mit bin/startup.sh (ggfls. vorher mit bin/shutdown.sh runterfahren) starten und unter http://localhost:8080/ auf Tomcat Manager klicken. Nachdem wir in der Passwortabfrage unsere zuvor hinterlegten Zugangsdaten eingegeben haben, gelangen wir auf die Manager-Applikation. Hier können wir nun durch einfaches hochladen einer WAR-Datei eine Applikation auf dem Tomcat starten.
Wir haben unseren Tomcat nun also einigermaßen im Griff. Was im Augenblick stört, ist dass Tomcat noch als root ausgeführt und nicht automatisch beim Booten mit gestartet wird. Hierzu müssen wir nun einige notwendige Schritte unternehmen:
Zunächst legen wir wir
Um bei zuküntigen Updates von Tomcat nicht immer gleich alle Pfade in den Skripten ändern zu müssen, legen wir noch einen Softlink an, der auf das aktuelle Tomcat-Verzeichnis verweist:
ln -s apache-tomcat-6.0.24/ tomcat6
Wenn wir eine neue Tomcat-Version installieren, brauchen wir nur den Link zu ändern.
Das Verzeichnis
ist also nun unser endgültiges Installationsverzeichnis. Wenn in der Dokumentation von Tomcat von CATALINA_HOME die Rede ist, dann ist genau dieses Verzeichnis gemeint.
Die Dateien in CATALINA_HOME sind bei jeder Tomcat-Instanz die selben und es wird nur lesend darauf zugegriffen. Aus diesem Grunde brauchen wir dem User tomcat an dieser Stelle noch keine besonderen Rechte auf das Verzeichnis einzuräumen.
Wir benötigen nun die Verzeichnisse, in denen wir die einzelnen Tomcat-Instanzen unter dem User tomcat ausführen möchten. Bei diesem Verzeichnis handelt es sich um CATALINA_BASE. Das erste Verzeichniss erstellen wir mit
und wechseln mit
in selbiges.
mit
kopieren wir uns die Dateien, die wir für jede einzelne Tomcat-Instanz exklusiv benötigen.
Wir legen noch mit
ein Unterverzeichnis an, in dem Später die PID der Instanz abgelegt wird.
Was nun folgt, ist wohl die komplexeste Änderung, die wir an der Standardinstallation von Tomcat durchführen werden. Die Datei bin/catalina.sh erweitern wir am Anfang der Datei um folgende Zeilen:
MY_PATH="`( cd \"$MY_PATH\" && pwd )`"
if [ -z "$MY_PATH" ] ; then
exit 1
fi
VAR=`echo ${MY_PATH} | awk -F / '{ print $(NF-1) }'`
test -z "${VAR}" -o -n "`echo \"$VAR\" | tr -d '[0-9]'`" && IS_NUMBER=FALSE
if [ IS_NUMBER != "FALSE" ] ; then
typeset -i VAR
CATALINA_HOME=/usr/lib/tomcat6
CATALINA_BASE=${MY_PATH%????}
let PORT_PREFIX=80+$VAR
CATALINA_OPTS="-DPORT_PREFIX=${PORT_PREFIX} -DWORKER_NO=${VAR}"
CATALINA_PID=${CATALINA_BASE}/run/tomcat.pid
JRE_HOME=/usr/lib/jvm/java-6-openjdk
fi
Bitte achtet darauf, dass ihr die Variable JRE_HOME entsprechend eurer Umgebung setzt.
Was hier passiert bedarf sicherlich einiger Erklärung:
Wie ich am Anfang des Tutorials bereits angedeutet habe, war es mir irgendwann zu lästig, mit jeder neuen Tomcat-Instanz immer die Ports hochzuzählen. So etwas wie einen PortOffset wie der Applikationsserver Apache Geronimo kennt Tomcat leider nicht. Mein Dank gilt an dieser Stelle Peter Roßbach, der mir seines Zeichens als Tomcat Entwickler den Tipp gab per -DPORT_PREFIX einen PortPrefix als Parameter an die JVM zu übergeben und diesen in der server.xml (und ggfls. context.xml und web.xml) per
den Wert wieder auszulesen und Tomcat mit entsprechenden Werten zu starten. Den Prefix selbst errechne ich einfach aus dem Pfad also aus CATALINA_BASE. Das erste Verzeichnis trägt in diesem Tutorial die Nr. 1 (/svr/tomcat/1/). Wenn wir alle Änderungen abgeschlossen haben, brauchen wir einach nur noch das Verzeichnis /svr/tomcat/1/ nach /svr/tomcat/2/ und /svr/tomcat/3/ etc. zu kopieren und die Ports werden automatisch angepasst.
Noch haben wir aber ein paar Handgriffe zu tägigen. Unsere server.xml ändern wir wie folgt:
<Server port="${PORT_PREFIX}05" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="${PORT_PREFIX}80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="${PORT_PREFIX}09" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat${WORKER_NO}">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
</Host>
</Engine>
</Service>
</Server>
Es werden nun die Variablen für die bestimmung der Ports verwendet und ich bin hier auch schon einen Schritt weiter gegangen und habe die jvmRoute dynamisch gesetzt. Jede Tomcat-Instanz muss einen eindeutigen Namen tragen, damit sie bei einer Anbindung an den Apache2 HTTP Server erkannt werden kann. Dieser Name ist die jvmRoute. Auch diese Name errechne ich aus dem Verzeichnisnamen von CATALINA_BASE.
Macht den User tomcat zum neuen Besitzer des Verzeichnisses und gibt der Gruppe www-data ebenfalls Zugriff.
Nun möchten wir unser Debian noch dazu überreden Tomcat beim Systemstart mit den Rechten des Users tomcat auszuführen:
und schreiben folgenden Inhalt:
# Tomcat Init-Script
SU=/bin/su
TOMCAT_USER=tomcat
WORKDIR=/svr/tomcat
start() {
for var in `ls -1 /svr/tomcat/`
do
$SU $TOMCAT_USER -c "${WORKDIR}/${var}/bin/catalina.sh start"
done
}
stop() {
for var in `ls -1 /svr/tomcat/`
do
$SU $TOMCAT_USER -c "${WORKDIR}/${var}/bin/catalina.sh stop -force"
done
}
case $1 in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
esac
exit 0
Anschließend machen wir die Datei mit:
noch ausführbar.
Ein erster Start mit
sollte erfolgreich sein, so dass wir den die neue Tomcat Instanz unter http://localhost:8180 erreichen können. Wenn wir schon weitere Kopien von CATALINA_BASE erstellt haben, werden auch diese automatisch gestartet. Die HTTP-Ports sind dann 8280, 8380, 8480 usw.
Mit
werden alle Tomcat Instanzen wieder abgeschossen.
Damit die Tomcat Instanzen nun automatisch mit dem Systemstart starten, legen wir nun noch die notwendigen Startdateien an:
Was unseren Tomcat angeht, so ist unsere Arbeit damit eigentlich auch schon getan. Allerdings möchten wir eigentlich nicht den Tomcat direkt als Webserver ins Internet stellen, denn so wäre ja auch gar kein Loadbalancing möglich.
Aus diesem Grunde werden wir Tomcat nun noch mit dem Apache2 HTTP Server verbinden.
Auf meinem System ist er schon installiert und ich denke die Meisten sind auch in der Lage ihn per aptitude oder apt-get zu installieren, deshalb werde ich mich jetzt auch den Teil beschränken, der sich um die Tomcat-Anbindung dreht.
Mit
installieren wir den Connector.
Anschließen sollten wir folgende Datei im Konfigurationsverzeichnis von Apache2 finden:
Dieses Modul müssen wir nun noch aktivieren, in dem wir einen Softlink setzen:
Dann legen wir eine Datei namens /etc/apache2/workers.properties mit folgendem Inhalt an:
worker.template.port=8009
worker.template.host=localhost
worker.template.type=ajp13
worker.template.lbfactor=1
worker.template.cachesize=10
worker.template.cache_timeout=600
worker.template.socket_keepalive=1
worker.template.socket_timeout=300
worker.tomcat1.reference=worker.template
worker.tomcat2.reference=worker.template
worker.tomcat3.reference=worker.template
worker.tomcat4.reference=worker.template
worker.tomcat5.reference=worker.template
worker.tomcat1.port=8109
worker.tomcat2.port=8209
worker.tomcat3.port=8309
worker.tomcat4.port=8409
worker.tomcat5.port=8509
worker.helloWorld.type=lb
worker.helloWorld.sticky_session=True
worker.helloWorld.balanced_workers=tomcat1,tomcat2,tomcat3,tomcat4,tomcat5
In dieser Datei haben wir zunächt ein Template von einem Worker angelegt. Anschließ referenzieren wir in den einzelnen Instanzen auf das Template und ändern nur noch das ab, worin sich die Worker voneinander unterscheiden. In diesem Fall ist das nur der Port. Diese Worker fassen wir dann wieder zu einem virtuellen Worker für Apache2 zusammen zusammen. Fällt ein Worker aus, oder ist einfach überlastet, so bemerkt der Besucher der Website nichts, da ja noch vier andere Worker vorhanden sind. Auf diese Weise kann ich die Last auch auf eine ganze Serverfarm verteilen. In dem Fall ist dann die Adresse des Workers eben nicht localhost, sondern ein entferntes System. In größeren Systemen (wie z.B. mobile.de mit ca. 500 Servern) sind mehrere hundert Worker (natürlich auf jeweils eigener Serverhardware verteilt) nichts Ungewöhnliches.
Die Properties-Datei müssen wir dem Apache2 noch bekann machen indem wir die Datei /etc/apache2/conf.d/jk.conf mit folgendem Inhalt anlegen:
JkWorkersFile /etc/apache2/workers.properties
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel error
</IfModule>
Damit uns für unser Beispiel auch genügend Tomcat Instanzen zur Verfügung stehen, legen wir einfach noch ein paar Kopien davon an:
for i in 2 3 4 5 ; do cp -ax 1 ${i} ; done
/etc/init.d/tomcat restart
Nun sollten fünf Tomcat Instanzen gestartet sein. Exemplarisch werden wir nun noch die mitgelieferten Beispielanwedungen hinter den Apache2 hängen. Wir editieren hierzu die Datei /etc/apache2/sites-available/default und fügen vor dem Tag </VirtualHost> folgendes ein:
<IfModule mod_jk.c>
JkMount /examples/* helloWorld
JkLogLevel debug
</IfModule>
### Tomcat ###
Mit diesem Eintrag weisen wir Apache2 an, alles was unterhalb von examples kommt an den Tomcat Worker „helloWorld“ zu deligieren. Wenn wir nun http://localhost/examples/ eingeben, werden uns die Tomcat-Beispielanwendungen angezeigt. Unschwer können wir erkennen, dass wir hier keine exotischen Ports mehr eingeben müssen, sondern dass die Seiten, wie eine ganz normale Website über den Apache2 auf Port 80 ausgeliefert werden.
Wir sind nun in der Lage beliebig viele Tomcat Instanzen durch einfache kopieren der Verzeichnisse zu erstellen und haben diese auch erfolgreich mit Apache2 verbunden.
Wenn wir nun in unserer Webanwendung ein z.B. ein Verzeichnis mit Bildern haben, dann ist es sinnvoll, diese statischen Inhalte durch den Apache2 ausliefern zu lassen. Das Bewerkstelligen wir, in dem wir das die Dateien auf dem Apache2 verfügbar machen und per
aus dem Seitenbaum wieder herausnehmen. So kann sich Tomcat um das generieren von Seiteninhalten kümmern und Apache2 macht das, was er am besten kann: performant statische Inhalte ausliefern!
An dieser Stelle möchte ich einen Schönheitsfehler bei der ganzen Sache nicht verschweigen: Beim herunterfahren von Tomcat wird die Datei server.xml noch ein mal geparst. Dabei werden leider nicht die Variablen für den Shutdown Port 8x05 richtig erkannt. Tomcat kann somit nicht vernünftig herunter gefahren werden. In dem Init-Skript habe ich daher eine Option eingebaut, die das Herunterfahren von Tomcat durch einen Kill-Befehl erzwingt. Das ist sicherlich nicht optimal und wen das stört, der muss dann doch in der server.xml den Port manuel eingeben. Dann funktioniert das herunterfahren wieder über die vorgesehene Methode.
Trotz allem denke ich, dass man mit dieser Vorgehensweise sehr schnell eine robuste Test- und Produktivumgebung mit Apache Tomcat und Apache2 HTTP Server aufbauen kann.
Für Kritik und Anregung habe ich natürlich immer ein offenes Ohr.