Sonntag, 27. Dezember 2009

Scala-Servlets

Ein schönes Beispiel dafür, wie sich gewohnte Java-Programmierung in Scala "anfühlt", sind Servlets:
package com.muchsoft.scala.servlets

import javax.servlet.http.HttpServlet
import javax.servlet.http.{HttpServletRequest => Request,
HttpServletResponse => Response}

class MeinErstesScalaServlet extends HttpServlet {

override def doGet(request: Request, response: Response) {

val ausgabe =
<html>
<head><title>Hallo Scala</title></head>
<body>
<h1>Hallo Scala-Welt!</h1>
</body>
</html>

response.getWriter().print(ausgabe)
}
}
Das sieht doch fast wie Java aus, oder? Nicht ganz: Beim Import können wir Umdefinitionen vornehmen, das override vor der Methodendefinition ist zwingend notwendig, zudem ein Schlüsselwort und keine Annotation. Der Rückgabetyp der Methode doGet() ist nicht angegeben und entspricht dadurch dem Java-void. Für Scala hätten wir vor die öffnende geschweifte Methodenklammer :Unit= schreiben können.

Innerhalb von doGet() legen wir das XML-Literal ausgabe an (als nicht änderbaren Wert mit val; mit var angelegte Variablen versucht man in funktionalen Sprachen zu vermeiden). Dieses XML-Literal schreiben wir ganz normal in den Ausgabestrom des Response-Objekts. Der Typ von ausgabe ist scala.xml.Elem.

Die Struktur der Web-Applikation entspricht dem JavaEE-Standard:


Der vom Scala-Compiler erzeugte Java-Bytecode landet in WEB-INF/classes. Die Scala-Standardbibliothek scala-library.jar (aus dem Scala-Installationsverzeichnis) muss in WEB-INF/lib mit ausgeliefert werden. In WEB-INF/web.xml mappen wir die Klasse com.muchsoft.scala.servlets.MeinErstesScalaServlet auf die URL "hallo.scala". Nach dem Bereitstellen (Deployment) z.B. in Tomcat oder GlassFish kann das Servlet im Web-Browser dann mit der URL http://localhost:8080/halloscala/hallo.scala aufgerufen werden.

Zum Übersetzen der Servlet-Klasse muss sich noch die Servlet-API auf dem Build-Path (Classpath) befinden. Dazu kann man z.B. aus dem Tomcat-Installationsverzeichnis servlet-api.jar oder aus dem GlassFish-Verzeichnis javaee.jar als externe Bibliothek einbinden.

Nun verbessern wir das erste Scala-Servlet und trennen Darstellung und Steuerung etwas besser voneinander:
class MeinZweitesScalaServlet extends HttpServlet {

def ausgabe =
<html>
<head><title>Hallo Scala</title></head>
<body>
<h1>Hallo Scala-Welt!</h1>
<p>Zeit auf dem Server: {jetzt} </p>
</body>
</html>

def jetzt = new java.util.Date

override def doGet(request: Request, response: Response) {
response.getWriter().print(ausgabe)
}
}
ausgabe ist nun eine Funktion außerhalb von doGet(), die auf die Funktion jetzt zurückgreift, um einen aktuellen Zeitstempel auszugeben. Beide Funktionen sind ohne Parameterklammern definiert und müssen daher ohne Klammern aufgerufen werden. Hätten wir die Funktionen mit leeren Parameterklammern definiert, hätten wir es uns beim Aufruf aussuchen können, ob wir die Klammern hinschreiben oder nicht. Es hat sich die Konvention herausgebildet, die Klammern bei der Definition wegzulassen, wenn eine Funktion keine Nebenwirkungen (Seiteneffekte) hat.

Für eine "vollständige" Web-Applikation fehlt noch ein Formular. Bitteschön:
class MeinDrittesScalaServlet extends HttpServlet {

def ausgabe(benutzername: String) =
<html>
<head><title>Hallo Scala-Welt</title></head>
<body>
<h1>Hallo {benutzername}</h1>
<p>Zeit auf dem Server: {jetzt}</p>
<form method="GET" action="hallo3.scala">
<p>Wie heißt Du?
<input name="benutzer" />
<input type="submit" value="Abschicken" />
</p>
</form>
</body>
</html>

def jetzt = new java.util.Date

override def doGet(request: Request, response: Response) {

val param = request getParameter "benutzer"

val benutzername =
if ((param == null) || (param.isEmpty))
"Scala"
else
param capitalize

val antwort = ausgabe(benutzername)

response.getWriter().print(antwort)
}
}
ausgabe() bekommt nun einen String-Parameter übergeben, der in doGet() anhand des Request-Parameters "benutzer" ermittelt wird. Statt des ?:-Operators von Java, den es in Scala nicht gibt, verwenden wir ein besser lesbares if-else, das in Scala ein Ausdruck ist und entsprechend ein Ergebnis liefert.

Wenn diese dritte Servlet-Klasse in web.xml auf die URL "hallo3.scala" gemappt wird, kann der Aufruf im lokalen Applikations-Server mit http://localhost:8080/halloscala/hallo3.scala?benutzer=Thomas erfolgen.

In der Praxis wird man Servlets wohl eher nicht direkt programmieren, sondern ein Framework wie Struts, JSF, Wicket o.ä. einsetzen. Man sieht hier aber gut, wie problemlos man Scala in bestehende Java-(Web-)Anwendungen integrieren kann. Und wenn man Web-Applikationen so richtig funktional entwickeln möchte, kann man das Scala-Lift-Framework nutzen.