Groovy: Web-Anwendungen

Einer der häufigsten Anwendungsfälle der Java-basierten Technologien sind Anwendungen, auf die man mit dem Web-Browser zugreift. Die unübersehbare Vielfalt von Web-Entwicklungsplattformen, die es für Java gibt, unterstreicht dies. Eine von ihnen, Grails, basiert auf Groovy und macht sich insbesondere dessen dynamischen Eigenschaften zunutze, um eine extrem effiziente Basis für Web-Entwicklungen zu bieten.

Für einfache Web-Anwendungen brauchen Sie aber nicht unbedingt auf Grails zurückgreifen, denn auch Groovy allein hat schon einiges zu bieten, wenn auch auf einer eher elementaren Ebene. Sie haben hier eine interessante Alternativen zur üblichen Java-Servlet-Programmierung, bei der die verschiedenen speziellen Stärken dieser Programmiersprache gut zum Tragen kommen.

Webseiten generieren Bearbeiten

Wenn es einfach nur darum geht, HTML-Seiten programmgesteuert zusammenzustellen, hat Groovy zwei Möglichkeiten zur Verfügung, die Sie bereits in anderen Zusammenhängen kennen gelernt haben.

Mit dem MarkupBuilder Bearbeiten

Die Klasse groovy.util.MarkupBuilder haben wir schon im Zusammenhang mit dem Generieren von XML-Dokumenten vorgestellt. Dieselbe Klasse eignet sich natürlich auch für den Aufbau von Webseiten, denn den MarkupBuilder interessiert in keiner Weise, was für Tags er in seinen Writer schreibt. Die einzige Schwierigkeit könnte darin bestehen, dass er immer wohlgeformtes XML erzeugt, bei dem alle Tags geschlossen werden. Dies kann bei älteren Browsern zu Problemen führen, die beispielsweise bei einem Tag wie
den Schrägstrich nicht verdauen können. Zur Sicherheit sollte den mit dem MarkupBuilder erzeugten Dokumenten auch immer ein XHTML-Header vorangestellt werden, der deutlich macht, dass hier wohlgeformtes XML kommt.

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE html
  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">

Freilich muss man sich dann auch an die XHTML-Syntax halten, die in einigen Punkten weniger liberal als das normale HTML ist.

Davon einmal abgesehen, ist das Generieren einer HTML-Seite mit dem MarkupBuilder recht übersichtlich:

def builder = new groovy.xml.MarkupBuilder()
builder.html (xmlns:"http://www.w3.org/1999/xhtml",lang:"de",'xml:lang':"de") {
  head { title "Hello-World" }
  body {
    h1 "Die Hello-World-Seite"
    p "Die aktuelle Zeit ist: ${new Date()}"
  }
}

Wenn wir den MarkupBuilder nicht mit einem Reader instanziieren, schreibt er seine Ausgaben einfach in die Konsole, und dort erscheint Folgendes:

<html xml:lang='de' xmlns='http://www.w3.org/1999/xhtml' lang='de'>
  <head>
    <title>Hello-World</title>
  </head>
  <body>
    <h1>Die Hello-World-Seite</h1>
    <p>Die aktuelle Zeit ist: Mon May 28 15:45:04 CEST 2007</p>
  </body>
</html>

Sie sehen: alle Tags sitzen schon einmal richtig. Richtig interessant wird es aber erst, wenn Sie HTML und Daten mischen. Angenommen, wir hätten noch die Datenbank mit den Staaten aus dem ersten Abschnitt dieses Kapitels, die wir so abgefragt haben:

sql.eachRow('SELECT * FROM staat') {
  println "$it.name, $it.hauptstadt, $it.vorwahl, $it.feiertag"
}

Mit nicht allzu viel Mühe können wir daraus auch eine Webseite zaubern. Allerdings nutzen wir jetzt die rows()-Methode des SQL-Objekts, mit dem wir eine Liste von Ergebnisdaten erhalten; damit wir nicht innerhalb des Builder-Codes eine Closure aufmachen müssen.

// Daten einlesen (das SQL-Objekt ist bereits offen)
staaten = sql.rows('SELECT * FROM staat')
// Webseite generieren.
new File('staaten.html').withPrintWriter { writer ->
  writer.println '''<?xml version="1.0" encoding="iso-8859-1" ?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd<nowiki>"><nowiki>'''
  builder = new groovy.xml.MarkupBuilder(writer)
  builder.html (xmlns:"http://www.w3.org/1999/xhtml",lang:"de",'xml:lang':"de") {
    head {Vtitle "Staaten" }
    body {
      h1 "Staaten der Erde"
      table (border:'1') {
        tr {
          th "Land"
          th "Hauptstadt"
          th "Vorwahl"
          th "Feiertag"
        }
        for (staat in staaten) {
          tr {
            td staat.name
            td staat.hauptstadt
            td staat.vorwahl
            td (staat.feiertag?staat.feiertag:'-')
} } } } } }

Das Ergebnis ist nicht unbedingt preiswürdig, aber korrekter HTML-Code, wie folgende Abbildung zeigt. Mit einem geeigneten Stylesheet ließe es sich leicht verschönern.

 
Ergebnis des MarkupBuilder-Einsatzes

Mit der Template-Engine Bearbeiten

Auch die Template-Engine von Groovy lässt sich hervorragend zum Generieren von Webseiten verwenden. Sie eignet sich insbesondere dann, wenn die Gestaltung vom Inhalt getrennt sein soll. Im Templates haben Sie bereits gesehen, wie Templates prinzipiell in Groovy funktionieren. Nun wollen wir sehen, wie man mit ihrer Hilfe das gleiche Ergebnis wie mit dem MarkupBuilder erzielen kann.

Die Textvorlage, die wir benötigen, sieht folgendermaßen aus. Wir speichern sie unter dem Namen staaten-template.html ab.

<?xml version="1.0" encoding="iso-8859-1" ?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xml:lang='de' xmlns='http://www.w3.org/1999/xhtml' lang='de'>
   <head>
     <title>Staaten</title>
   </head>
   <body>
     <h1>Staaten der Erde</h1>
     <table border='1'>
       <tr>
         <th>Land</th>
         <th>Hauptstadt</th>
         <th>Vorwahl</th>
         <th>Feiertag</th>
       </tr>
       <% for (staat in staaten) { %>
         <tr>
           <td>$staat.name</td>
           <td>$staat.hauptstadt</td>
           <td>$staat.vorwahl</td>
           <td>${staat.feiertag?staat.feiertag:'-'}</td>
         </tr>
       <% } // Ende def For-Schleife %>
     </table>
   </body>
 </html>

Der interessante Bereich liegt in der for-Schleife, die am Anfang und am Ende durch die Tags <%...%> gekennzeichnet ist. Dazwischen durchläuft das Template eine Liste namens staaten und legt für jede Datenbankzeile eine HTML-Tabellenzeile an.

Nun müssen wir nur noch dafür sorgen, dass die Vorlage mit den Daten zusammengemischt wird. Das geschieht in folgendem Skript-Abschnitt. Auch hier gehen wir wieder davon aus, dass das SQL-Objekt für die Datenbankverbindung bereits vorhanden ist.

// Template-Engine instanziieren.
engine = new groovy.text.SimpleTemplateEngine()
// Template aus Textvorlage erzeugen.
template = engine.createTemplate(new File('staaten-template.html'))
// Binding für die Daten anlegen.
daten = [staaten:sql.rows('SELECT * FROM staat')]
// Template und Daten mischen.
ergebnis = template.make(daten)
// Ergebnis in Datei schreiben.
new File('staaten2.html').withWriter { writer ->
  writer.write(ergebnis)
}

Die frisch generierte HTML-Seite staaten2.html unterscheidet sich in keiner Weise von der mit dem MarkupBuilder erzeugten Seite, wie sie in der Abbildung oben dargestellt ist.

Groovy-Servlets Bearbeiten

Bis dahin haben wir nur davon gesprochen, wie man mit Groovy HTML-Seiten erzeugen kann. Das ist ein wenig wie Trockenschwimmen, denn die Seiten sollen in Wirklichkeit natürlich nicht mit Hilfe von Skripten auf dem PC entstehen, sondern in einem Web-Server produziert werden. Nun steht ja mit dem Servlet-Container ein bewährter Standard für den Betrieb von Web-Servern unter Java zur Verfügung. Und da Groovy auf Java basiert, kann man diese natürlich nutzen.

Vorbereitungen Bearbeiten

Um die folgenden Experimente verfolgen zu können, benötigen Sie irgendeinen Java-basierten Web-Server, der standardkonform mit Servlets umgehen kann. Es gibt eine ganze Reihe von solchen Programmen, die frei verfügbar sind, sehr populär ist beispielsweise der Apache-Tomcat-Server. Ebenfalls sehr beliebt ist ein Server namens Jetty. Er ist nicht ganz so schwergewichtig wie Tomcat und reicht für unsere Zwecke völlig aus.

Wenn Sie also noch keinen Servlet-fähigen Webserver haben, laden Sie sich einfach die neueste Jetty-Version von http://jetty.mortbay.org/jetty/index.html herunter und entpacken Sie sie in ein beliebiges Verzeichnis. Unterhalb dieses Verzeichnisses finden Sie dann unter anderem ein Verzeichnis namens webapps, in dem die Web-Anwendungen installiert werden. Wenn Sie einen anderen Servlet-Container haben, wird es sicher auch ein analoges Verzeichnis geben. Führen Sie nun folgende Schritte durch.

  1. Legen Sie unterhalb von webapps ein neues Verzeichnis groovy an. In diesem Verzeichnis- oder darunter liegenden Verzeichnissen, werden Sie gleich Skripte und Templates ablegen können, die von Groovy verarbeitet werden sollen.
  2. Legen Sie unterhalb von groovy ein Verzeichnis WEB-INF an. Die Dateien in diesem Verzeichnis werden nicht angezeigt sondern enthalten interne Informationen.
  3. Legen Sie unterhalb von WEB-INF ein weiteres Verzeichnis lib an. Dieses ist für Java-Bibliotheken vorgesehen.
  4. Legen Sie in lib eine Kopie der Datei groovy-all-1.1.jar (oder eine andere Version) aus Ihrer Groovy-Distribution ab.
  5. Legen Sie in WEB-INF eine einfache Textdatei namens web.xml mit dem in Beispiel ## angezeigten Inhalt an. Dies ist der Deployment-Descriptor, der dem Webserver mitteilt, wie mit den Groovy-Servlets umzugehen ist.
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd" >
<web-app>
  <servlet>
    <servlet-name>GroovyServlet</servlet-name>
    <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
  </servlet>
  <servlet>
  <servlet-name>TemplateServlet</servlet-name>
    <servlet-class>groovy.servlet.TemplateServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>GroovyServlet</servlet-name>
    <url-pattern>*.groovy</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>TemplateServlet</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
</web-app>

Grob gesagt, enthält die Datei web.xml die Information, dass alle Dateien mit der Endung .groovy von dem Servlet groovy.servlet.GroovyServlet und alle Dateien mit der Endung .html von dem Servlet groovy.servlet.TemplateServlet behandelt werden sollen. Was das konkret heißt, werden Sie in den folgenden Abschnitten gleich sehen.

Prüfen Sie aber vorher noch einmal, ob die Struktur Ihres Verzeichnisses so aussieht wie hier dargestellt:

  • webapps ‒ Wurzelverzeichnis für alle Web-Anwendungen
    • groovy ‒ Verzeichnis für Ihre Versuche mit den Groovy-Servlets
      • WEB-INF ‒ Internes Verzeichnis der Web-Anwendung
        • lib ‒ Verzeichnis für Java-Bibliotheken
          • groovy-all…jar ‒ Groovy-Bibliothek
        • web.xml ‒ Deployment-Deskriptor

Starten Sie nun den Webserver ‒ bei Jetty geschieht dies mit einem Skript namens jetty oder jetty.bat im Wurzelverzeichnis des Webservers ‒ oder starten Sie ihn neu, falls er schon laufen sollte. Legen Sie eben noch folgende rudimentäre XML-Datei unter dem Namen index.html in das Verzeichnis groovy, damit wir ausprobieren können, ob alles funktioniert.

<html><body>Es funktioniert. ${new Date()}</body></html>

Wenn Sie jetzt Ihren Browser auf die Adresse http://localhost:8080/groovy richten (eventuell müssen Sie auch eine andere oder gar keine Portnummer angeben, im Allgemeinen meldet der Web-Server beim Start, auf welchem Port er arbeitet), sollte genau dieser Text im Browser-Fenster erscheinen: »Es funktioniert«, und die aktuelle Uhrzeit ‒ so wie es folgende Abbildung 8-2 zeigt.

 
Webserver mit Groovy erfolgreich gestartet

Groovlets und das GroovyServlet Bearbeiten

Der Rest ist fast ein Kinderspiel. Sie können nun ein normales Groovy-Skript bauen, es in das Wurzelverzeichnis Ihrer Web-Anwendung legen, und es kann unter diesem Namen über das Netzwerk ausgeführt werden; alle Programmausgaben werden automatisch an den Browser geleitet. Achten Sie nur darauf, dass der Dateiname auf .groovy endet. Wenn Sie ein neues Skript hinzufügen, eines verändern oder eines löschen, brauchen Sie den Server nicht neu starten (oder ein hot deployment einzuleiten) das GroovyServlet bemerkt es auch so und reagiert sofort.

Solche Skripte, die vom GroovyServlet ausgeführt werden, bezeichnet man auch als Groovlets. Jedes Groovlet bekommt im Binding alle Informationen mitgeliefert, die auch ein Servlet zur Verfügung hat. Welche es sind, sehen wir uns einfach mit einem Groovlet, das in folgendem Beispiel dargestellt ist.

html.html (xmlns:"http://www.w3.org/1999/xhtml",lang:"de",'xml:lang':"de") {
  head {
    title "Groovlet-Binding"
  }
  body {
    h1 "Groovlet-Binding"
    table (border:1) {
      th "Name"
      th "Typ"
      th "Inhalt"
      for (var in binding.variables) {
        tr {
          td var.key
          td var.value.getClass().simpleName
          td var.value.toString()
        }
      }
    }
  }
}

Das ganze Skript besteht aus einem einzigen Aufruf eines MarkupBuilder-Objekts, das in jedem Groovlet unter dem Namen html verfügbar ist. Das ist sehr praktisch, muss aber nicht so sein. Das Skript kann aus ganz normalem Groovy-Code bestehen. Sie brauchen auch nicht unbedingt den MarkupBuilder verwenden, sondern können die darzustellende Webseite auch aus normalen print()- und println()-Ausgaben zusammensetzen. Dieses Skript macht nichts weiter, als alle Binding-Variablen, die über die Property binding.variables in Form einer Map abgefragt werden können, in einer Tabelle darzustellen.

Speichern Sie das Skript im Wurzelverzeichnis ihrer Web-Anwendung (also in groovy) unter dem Namen GroovletBinding.groovy. Rufen Sie dann im Browser die URL http://localhost:8080/groovy/GroovletBinding.groovy auf. Das Ergebnis sieht ungefähr so aus:

 
Das Skript GroovletBinding im Browser

In der folgenden Liste wollen wir Ihnen die Bedeutung der Binding-Variablen im Einzelnen erläutern.

javax.servlet.ServletContext context
Der Servlet-Kontext, in dem das aktuelle Servlet läuft. Auf die Kontextveriablen können wie auf Properties zugegriffen werden, z.B. servername=context.serverInfo. Eine Map mit allen Kontextvariablen erhält man mit context.properties. Der Servlet-Kontext kann auch unter dem Variablennamen application angesprochen werden.
java.util.Map<String,String> headers
Map mit den Namen und Werten aller Header aus dem HTTP-Request.
java.util.Map<String,Object> params
Map mit den Namen und Werten der Request-Parameter. Die Werte können Strings oder Arrays von Strings sein.
javax.servlet.http.HttpServletRequest request
Request-Objekt des Servlet-Aufrufs. Auf die Request-Variablen kann wie auf Properties zugegriffen werden, z.B. aufrufzeit=request.timeStampStr. Eine Map mit allen Request-Variablen erhält man mit request.properties.
javax.servlet.http.HttpServletResponse response
Response-Objekt des Servlet-Aufrufs.
javax.servlet.http.HttpSession session
Aktuelles Session-Objekt. Wenn es keine Session gibt, ist der Wert null.Eine neue Session anlegen können Sie mit der Anweisung session=request.session.

Auf die Session-Variablen kann wie auf Properties zugegriffen werden, z.B. letzterZugriff=session.lastAccessTime. Eine Map mit allen Request-Variablen kann erhält man mit request.properties.

Es stehen im Groovlet noch einige weitere Variablen zur Verfügung stehen, obwohl sie nicht im Binding enthalten sind. Diese sind:

groovy.xml.MarkupBuilder html
Vordefinierter Builder für die bequeme Erzeugung von HTML-Text. Das Ergebnis wird über out ausgegeben.
java.io.PrintWriter out
Writer für die Ausgabe von Text. Die Variable wird erst bei ihrer ersten Verwendung erzeugt.
javax.Servlet.ServletOutputStream sout
Binärer Ausgabe-Stream; die Variable wird erst bei ihrer ersten Verwendung erzeugt,

Der Writer und der Ausgabestrom werden in besonderer Weise behandelt, damit beispielsweise im Response-Objekt Header-Werte gesetzt werden können, dies ist nicht mehr möglich, wenn schon Ausgaben erfolgt sind.

Tipp: Wenn Sie etwas mit Groovlets experimentieren, werden Sie festellen, dass Sie im Fall eines Fehlers in Ihrem Browser unter Umständen nicht viel sehen und kaum eine Chance haben, die Ursache zu ergründen, außer das Fehlerprotokoll des Servlet-Containers zu untersuchen. Laufzeitfehler können Sie allerdings besser sichtbar machen, indem Sie das gesamte Servlet in einen try-Befehl einkleiden:
try { //Hier den gesamten Groovlet-Code einfügen
} catch (Throwable th) { th.printStackTrace(out)}

Dann wird der Stacktrace in die Webseite geschrieben. Es kann allerdings sein, dass Sie im Browser »Seitenquelltext Anzeigen« anwählen müssen, um diese Information lesbar zu machen.

Das Template Servlet Bearbeiten

Wenn Sie Ihre Seiten nicht aus einem Skript heraus generieren möchten, sondern lieber Templates einsetzen wollen, um Daten und Präsentation zu trennen, bietet sich das TemplateServlet von Groovy an. Es funktioniert im Prinzip genau so wie das GroovyServlet, nur stellen Sie hier kein Skript, sondern ein Template ein. Das Template in folgendem Beispiel soll dasselbe bewirken wie das obige Skript GroovletBinding.groovy. Es zeigt, dass im TemplateServlet die gleichen Binding-Variablen definiert sind wie im GroovyServlet.

<html xmlns="http://www.w3.org/1999/xhtml",lang="de",xml:lang="de">
 <% session = request.session %>
 <head>
   <title>Template-Binding"</title>
 </head>
 <body>
   <h1>Template-Binding</h1>
   <table border="1">
     <tr><th>Name</th><th>Typ</th><th>Inhalt</th></tr>
 <% for (var in binding.variables) { %>
     <tr>
       <td>$var.key</td>
       <td>${var.value.getClass().simpleName}</td>
       <td>${var.value.toString()}</td>
     </tr>
 <% } %>
   </table>
 </body>
 </html>

Speichern Sie jetzt die Datei unter dem Namen TemplateBinding.html im groovy-Verzeichnis der Servlet-Engine ab, denn wir haben ja die Dateiendung .html mit dem TemplateServlet verknüpft. Wenn Sie nun im Adressfeld des Browsers http://localhost:8080/groovy/TemplateBinding.html eintragen, erscheint eine Seite, die genau so aussieht wie in der obigen Abbildung dargestellt, nur lautet der Titel jetzt »Template-Binding«.