Stufe 6Werkzeuge und Oberflächen · Lektion 32 von 34
Play Framework
Play ist das bekannteste Webframework der Scala-Welt. Es verbindet eine klare Struktur aus Routen, Controllern und Vorlagen mit einem asynchronen Kern, der viele gleichzeitige Anfragen bewältigt – und bei Änderungen am Code einfach neu lädt.
Was Play ausmacht
- Zustandslos: Play hält zwischen zwei Anfragen keinen Sitzungszustand auf dem Server. Das macht es leicht, eine Anwendung auf mehrere Server zu verteilen.
- Asynchron: Eine Anfrage belegt keinen Thread, während sie auf eine Datenbank oder einen anderen Dienst wartet. Controller können ein Future zurückgeben.
- Entwicklerfreundlich: Im Entwicklungsmodus übersetzt Play geänderten Code beim nächsten Seitenaufruf automatisch neu. Fehler erscheinen mit Quelltextausschnitt direkt im Browser.
Unter der Haube baut Play 3 auf Apache Pekko auf, die ältere Reihe 2.9 noch auf Akka. Seit einigen Jahren wird Play von der Community weiterentwickelt und über Sponsoring finanziert.
Ein Projekt anlegen
$ sbt new playframework/play-scala-seed.g8
$ cd mein-projekt
$ sbt runNach dem Start ist die Anwendung unter http://localhost:9000 erreichbar. Die wichtigsten Ordner:
| Ordner / Datei | Inhalt |
|---|---|
app/controllers | Controller mit den Actions |
app/views | Twirl-Vorlagen (HTML mit Scala) |
conf/routes | Zuordnung von URLs zu Actions |
conf/application.conf | Konfiguration |
public | statische Dateien wie CSS und Bilder |
test | Tests |
Der Weg einer Anfrage
Die Datei conf/routes ordnet HTTP-Methode und Pfad einer Action zu:
GET / controllers.StartController.index()
GET /hallo/:name controllers.StartController.hallo(name: String)
GET /api/stufen controllers.StufenController.liste()
POST /api/stufen controllers.StufenController.anlegen()
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)Play übersetzt diese Datei beim Build in Scala-Code. Tippfehler in Controller- oder Methodennamen fallen deshalb schon beim Übersetzen auf, nicht erst im Betrieb.
Controller und Actions
package controllers
import javax.inject.*
import play.api.mvc.*
@Singleton
class StartController @Inject() (cc: ControllerComponents) extends AbstractController(cc):
def index(): Action[AnyContent] = Action {
Ok(views.html.index("Willkommen"))
}
def hallo(name: String): Action[AnyContent] = Action {
Ok(s"Hallo, $name!")
}Eine Action ist eine Funktion von einer Anfrage zu einer Antwort. Ok(…) erzeugt eine Antwort mit Status 200; daneben gibt es NotFound, BadRequest, Redirect, Created und so weiter. Controller werden per Dependency Injection erzeugt – daher die Annotationen @Inject und @Singleton.
Twirl-Vorlagen
HTML entsteht aus Twirl-Vorlagen: HTML-Dateien mit eingebettetem Scala, die ebenfalls übersetzt und typgeprüft werden.
@(titel: String)
<!doctype html>
<html lang="de">
<head><title>@titel</title></head>
<body>
<h1>@titel</h1>
<ul>
@for(i <- 1 to 3) {
<li>Stufe @i</li>
}
</ul>
</body>
</html>Die erste Zeile legt die Parameter fest. Aus der Vorlage wird die Funktion views.html.index(titel: String), die der Controller oben aufruft. Ausgaben mit @ werden automatisch HTML-maskiert – ein wirksamer Schutz gegen Cross-Site-Scripting.
JSON-APIs
Für Schnittstellen bringt Play eine JSON-Bibliothek mit. Formate für case classes lassen sich automatisch ableiten:
import play.api.libs.json.*
case class Stufe(nr: Int, titel: String)
object Stufe:
given Format[Stufe] = Json.format[Stufe]
class StufenController @Inject() (cc: ControllerComponents) extends AbstractController(cc):
private val stufen = List(Stufe(1, "Einstieg"), Stufe(2, "Sprachkern"))
def liste() = Action {
Ok(Json.toJson(stufen))
}
def anlegen() = Action(parse.json) { anfrage =>
anfrage.body.validate[Stufe].fold(
fehler => BadRequest(JsError.toJson(fehler)),
stufe => Created(Json.toJson(stufe))
)
}validate liefert entweder Fehler oder ein gültiges Objekt – dasselbe Prinzip wie bei Either.
Asynchrone Actions
Muss eine Action auf etwas warten, gibt sie ein Future zurück. Play schickt die Antwort, sobald es erfüllt ist, und blockiert dabei keinen Thread:
def kunde(id: Long) = Action.async {
kundenDienst.finde(id).map {
case Some(k) => Ok(Json.toJson(k))
case None => NotFound
}
}Play und Suchmaschinen
Weil Play HTML auf dem Server erzeugt, sind die Inhalte für Suchmaschinen sofort lesbar – anders als bei Anwendungen, die ihre Seiten erst im Browser per JavaScript aufbauen. Für eine gut auffindbare Website sollten Sie trotzdem auf sprechende Pfade in der routes-Datei, korrekte Statuscodes (404 statt einer Fehlerseite mit Status 200), Weiterleitungen mit MovedPermanently bei geänderten Adressen sowie auf <title> und Beschreibung in jeder Vorlage achten.