scalatutorial.de

Stufe 6Werkzeuge und Oberflächen · Lektion 32 von 34

Play Framework

Aktualisiert am 24. September 20263 Min. LesezeitSchritt 4/6 dieser Stufe

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

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

 Terminal
$ sbt new playframework/play-scala-seed.g8
$ cd mein-projekt
$ sbt run

Nach dem Start ist die Anwendung unter http://localhost:9000 erreichbar. Die wichtigsten Ordner:

Ordner / DateiInhalt
app/controllersController mit den Actions
app/viewsTwirl-Vorlagen (HTML mit Scala)
conf/routesZuordnung von URLs zu Actions
conf/application.confKonfiguration
publicstatische Dateien wie CSS und Bilder
testTests

Der Weg einer Anfrage

Weg einer Anfrage durch eine Play-Anwendung: Routes, Controller, ViewBrowserGET /halloroutesDatei conf/routesControllerAction { … }ViewTwirl-VorlageHTTP-Antwort (HTML oder JSON)
Eine Anfrage geht über die Routen zum Controller, der eine Vorlage oder JSON zurückgibt.

Die Datei conf/routes ordnet HTTP-Methode und Pfad einer Action zu:

conf/routesAusgabe
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

app/controllers/StartController.scalaScala 3
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.

app/views/index.scala.htmlAusgabe
@(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:

app/controllers/StufenController.scalaScala 3
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:

 Scala 3
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.

Passt dazu