The Yesod Framework

Ein Webrahmenwerk in Haskell

Routing & Handlers

Die Yesod-Typklasse

Bereits in den Grundlagen wurde die so genannte Foundation angesprochen, auf der das Framework basiert. Diese wird durch eine Instanz der Yesod-Typklasse definiert. Die Yesod-Typklasse bildet eine zentrale Anlaufstelle für globale Einstellungen für die Webanwendung. Weiterhin stellt sie hilfreiche Funktionen zur Verfügung, deren Überschreibung teilweise von Nutzen ist. Die einzige Funktion, die nicht vordefiniert, sondern vom Nutzer selbst anzugeben ist, ist approot, welche bereits im einführenden Beispiel aufgeführt wurde.[1]

1instance Yesod MyApplication where
2    approot _ = ""

Diese Funktion bestimmt die Application-Root, welche von Yesod benötigt wird, um aus typischeren URLs String-Repräsentationen in absoluter Form zu generieren. Sämtliche für den Frontcontroller definierten relativen Routen beziehen sich demnach auf diese Application-Root. Der irrelevant erscheinende Parameter für die approot-Funktion rührt daher, dass die Angabe der Application-Root beispielsweise aus einer Konfigurationsdatei geladen werden könnte. In diesem Fall könnte der Wert in der Foundation gespeichert werden und die approot-Funktion würde diesen Wert von dort beziehen.[2]

Ein gutes Beispiel für eine im Rahmen der Yesod-Typklasse vordefinierte Funktion ist defaultLayout, welche bereits im Kapitel 3.1. Templates zur Sprache gekommen ist. Die folgende Definition von defaultLayout soll den Mechanismus zur Gestaltung eines standardmäßigen Layouts für das Frontend verdeutlichen.

1defaultLayout contents = do
2        PageContent title headTags bodyTags <- widgetToPageContent $ do
3            addCassius [cassius|
4#body
5    font-family: sans-serif
6#wrapper
7    width: 760px
8    margin: 0 auto
9|]
10            addWidget contents
11        hamletToRepHtml [hamlet|
12!!!
13
14<html>
15    <head>
16        <title>#{title}
17        ^{headTags}
18    <body>
19        <div id="wrapper">
20            ^{bodyTags}
21|]

Zwischen den Zeilen 3 und 10 werden zu den an die Funktion übergebenen Inhalten im Rahmen der Zugriffskomponenten von Widgets zunächst generelle CSS-Angaben beigefügt. Das resultierende Widget wird in der Folge in Zeile 2 in die Komponenten «Titel», «Tags im <head>-Kontext» und «Tags im <body>-Kontext» entpackt. Schließlich erfolgt in Zeile 11 bis 21 eine weitere Nutzung dieser Komponenten, indem sie als Inhalte in den HTML-Dokumentbaum eingepflegt werden. Die Ausrufezeichen in Zeile 12 haben die Einfügung eines HTML-Doctypes zur Folge. Standardmäßig handelt es sich dabei um die Definition <!DOCTYPE html> zur Kennzeichnung von HTML 5.[3]

Routing bei Yesod

Im Rahmen der Betrachtung des einleitenden Beispiels aus den Grundlagen wurde bereits der so genannte Front Controller beim Routing in Yesod vorgestellt. Er definiert sich zum einen aus den beschriebenen Routen und zum anderen aus den entsprechend zuständigen Handlern. Es gibt im Kontast dazu allgemein noch zwei andere Herangehensweisen zum Routen von Anfragen. Dies ist einerseits die Möglichkeit der Weiterleitung je nach Name der Datei, welche angesprochen wurde. Diese Methode ist beispielsweise bei PHP und ASP.NET gängig. Die zweite Methode zum Routen von Anfrage wird u. a. von Django und Rails verfolgt. Hierbei erfolgt die Weiterleitung über eine zentrale Funktion, welche anhand von regulären Ausdrücken die verschiedenen Routen definiert.

Der Ansatz von Yesod ist der zweiten angesprochenen Herangehensweise zum Routen von Anfragen ähnlicher. Während jedoch dort reguläre Ausdrücke zum Einsatz kommen, werden bei Yesod lediglich die Einzelteile einer Route betrachtet, welche vom Entwickler bei der Definition von Routen anzugeben sind. Der entscheidende Vorteil von Yesod gegenüber dem Ansatz, bei dem reguläre Ausdrücke verwendet werden, ist jedoch ein anderer. Im Kontext der meisten herkömmlichen Frameworks besteht nämlich nur eine One-Way-Verbindung vom Router zum zugehörigen Handler. Durch die Definition eines Route-Datentyps in Haskell ist es Yesod jedoch möglich, eine beidseitige Verbindung zwischen den Routen und der weiteren Applikationslogik herzustellen, welche sich üblicherweise im Rahmen der entsprechenden Handler befindet. An dieser Stelle zeigen wieder die typsicheren URLs ihre Stärke. Dies impliziert, dass jede Route anhand ihres entsprechenden Bezeichners eindeutig identifiziert werden kann. Die technische Implementierung des Routingsystems in Yesod wird automatisch generiert. Auch hier hilft Template Haskell bzw. dessen Quasi-Quotations, welche es Entwicklern ermöglicht, nur die gültigen Routen der Webanwendung anzugeben. Durch Anwendung des Quoters «parseRoutes» erfolgt während der Kompilierung die automatische Generierung des zum Aufbau des Routingsystems benötigten Haskellcodes.[4]

Argumente innerhalb von Routes

Bei der Definition von Routen können insgesamt drei Angabemöglichkeiten unterschieden werden.[5]

Der folgende Code enthält jeweils ein Beispiel für jede dieser Möglichkeiten zur Angabe von Routes.

1mkYesod "RouteExample" [parseRoutes|
2/home HomeR GET
3/book/#Author/#Title BookR GET
4/uploads/*Files FileR GET
5|]

Zeile 3 enthält die Angabe zweier Argumente #Author und #Title. Diese Bezeichnungen kennzeichnen nicht etwa die Argumentnamen, sondern die Datentypen der Argumente. Dies bedeutet, dass nur Anfragen ausgewertet werden, welche gültige Argumente enthalten. Über die Definition eigener Datentypen mitsamt entsprechender Funktionen zur Konvertierung von und zu Text kann auf diese Weise sichergestellt werden, dass dem Handler nur gültige Argumente übermittelt werden.

Die GHandler-Monade

GHandler-Monaden bilden den Kontext, in welchem sich die Webapplikation bei der Bearbeitung in Handlern befindet und entsprechen somit dem eigentlichen Controller. Sie beinhalten einen Reader zur Auslesung von Daten aus der Anfrage an den Server wie beispielsweise Headerinformationen. Außerdem steht ein Writer bereit, welcher in der Lage ist, Headerinformation der Antwort an den Client hinzuzufügen. Eine Zustandskomponente sorgt zudem für die Verwaltung von Sitzungsvariablen. Durch Instanzierung von monadIO ist es möglich, über liftIO sämtliche IO-Aktionen durchzuführen.[6] Die GHandler-Monade ist anhand der Signatur

1data GHandler sub master a

beschrieben, wobei sub für den Foundation-Datentyp einer Subsite und master für den Foundation-Datentyp derer Mastersite steht. Das Resultat eines jeden Handlers besteht aus der Antwort, welche an den Client gesendet wird. Dies ist üblicherweise RepHtml, kann aber für einfachen Klartext ebenso RepPlain sein. Bislang wurden ausnahmslos Anwendungsfälle dargestellt, bei denen keine gesonderte Subsite existiert hat. Die Subsite entsprach dabei ihrer Mastersite. Bei einer beispielhaften Foundation-Bezeichnung MyApp wäre die entsprechende GHandler-Signatur zwecks Ausgabe von HTML GHandler MyApp MyApp RepHtml gewesen. Für diese Konstellation empfiehlt sich die Nutzung eines Alias type Handler = GHandler MyApp MyApp, um eine resultierende übersichtlichere Signatur Handler RepHtml zu ermöglichen. Die Typsignatur eines Handlers für die in Zeile 3 des gezeigten Routensystembeispiels definierte Route könnte beispielsweise so aussehen.

1getBookR :: Author -> Title -> Handler RepHtml

Subsites sind sinnvoll, um die unterschiedlichen Aufgabenbereiche einer Webanwendung zu kapseln. Beispielsweise könnten eigene Subsites für den Zweck der Authentifizierung oder für den Zugriff auf statische Inhalte wie Grafiken und Downloads definiert werden. Subsites werden anhand eines eigenen Routingsystems definiert.[7] Nähere Informationen zu diesem Thema finden sich im Yesod Web in einem eigenen Kapitel zum Aufbau von Subsites.