Moderne Webanwendungen mit Haskell und Javascript: clientseitige Implementierung
Im ersten Teil des Artikels haben wir in Haskell einen einfachen Server mit Rest-API für eine Blogging-Software geschrieben. Nun möchten wir noch ein einfaches Frontend dafür bauen. Wie bereits erwähnt wollen wir hier unter anderem die Soy-Templates-Sprache von Google und deren Kompiler verwenden, um über Templates mit JavaScript HTML zu erzeugen. Außerdem werden wir einen einfachen Controller in JavaScript schreiben, der die entsprechenden Funktionen zusammenfügt und mit Daten versorgt.
Zunächst möchte ich mich nocheinmal kurz auf den ersten Teil beziehen: Mit Hilfe von TemplateHaskell und der JSON-Biliothek aeson haben für unsere Datentypen Persistenz und JSON-Serialisierung erzeugt. Dann haben wir mit scotty auf einfache Art und Weise HTTP-Routen erzeugt um auf unsere persistent serialisierten Objekte zugreifen zu können. Zum Schluss hatten wir einen fertigen Server mit REST-API, den man wie folgt ansteuern konnte:
Beiträge hinzufügen:
Liste aller Beiträge anzeigen:
Beginnen wir nun mit den HTML-Frontend für unsere Anwendung. Ich denke, das statische HTML und CSS Grundgerüst muss an dieser Stelle nicht weiter erläutert werden, es ist im GitHub Repository des Mini-Blogs zu finden.
Nun kommen die bereits erwähnten SOY-Templates ins Spiel. Die SOY-Template Sprache ist eine Mischung aus so genannten „SOY-Commands“ und einfachem HTML. Daraus kompiliert der SOY-Compiler dann eine JavaScript-Datei, in der alle Templates eine JavaScript-Funktion darstellen. Die Funktion nimmt als Parameter unter Anderem die Template-Parameter (s.u.) und gibt dann das gerenderte Template als String zurück. Der Kompiliervorgang geschieht wärend dem Deployment, sodass man in seinem JavaScript-Controller dann ganz einfach auf die Templates zugreifen kann.
Schreiben wir nun also unser erstes Template:
Der Namespace (in Wirklichkeit einfach ein JavaScript-Objekt), in dem später alle Templates als JavaScript-Funktionen definiert sind, wird BlogUI
genannt. Mit autoescape="true"
aktivieren wir die sehr nützlichen autoescape Features von SOY-Templates. Wie diese genau funktioniert, kann man hier nachlesen. Weiter geht‘s mit dem ersten Template:
Unser erstes Template erzeugt eine Seite mit Blogbeiträgen. Zunächst ist vor jedem Template ein Kommentar notwendig. Dieser enthält eine optionale Beschreibung des Templates, und die Liste aller Parameter. Mit {template .news}
beginnen wir nun ein neues Template im aktuellem Namespace
und nennen es news
. Die foreach-Syntax ist an die von JavaScript angelehnt, in obiger Form kann man die Schleife als ein Haskell map
verstehen: Auf alle Elemente in der Liste $news
wird das Template newsBit
angewendet. Zum Schluss prüfen wir noch, ob es überhaupt Beiträge gibt, und wenn, nicht geben wir die Meldung „Keine Beiträge vorhanden“ aus - damit der Blog nicht komplett leer ist. Zur Erinnerung: die JSON-Struktur eines Beitrags sieht wie folgt aus:
Das werden wir nun zum Rendern von Beiträgen im nächsten Template ausnutzen:
Das Template für einen einzelnden Blogbeitrag zeigt weitere Funktionen der SOY-Templates: Mit {$variable}
liest man den Inhalt einer
Variable und zeigt ihn an - hier kommt dann auch unser autoescape ins Spiel! Enthält die Variable $title
etwa den Wert <script>alert('alert');</script>
, wird die Ausgabe automatisch „escaped“. Außerdem kann man der Anzeige von Variablen noch so genannte Print Directives mitgeben, wie zum Beispiel bei {$content|changeNewlineToBr}
. In diesem Fall wird aus einem \n
Zeilenumbruch ein HTML <br>
. Alles weitere ist einfach HTML und JavaScript, bis auf den {literal}
-Blocks. Diese sorgen dafür, dass der SOY-Kompiler dessen Inhalt ignoriert, und es keine Probleme mit den geschweiften Klammern gibt. Mit der jQuery Notation $('...')
kann man den DOM-Baum traversieren und ein Element wählen. Mehr zu der Syntax findet man hier. Das Blog
-Objekt ist unser Controller, welcher weiter unten erklärt wird.
Die Templates für das Anzeigen von Kommentaren unter Blogbeiträgen funktionieren analog zu denen der Blogbeiträge.
Mit dem Soy-Compiler können wir nun daraus JavaScript machen:
Jetzt fehlt nur noch die Logik, die die REST-API mit unseren Templates/Views verbindet. Das geht mit Hilfe von jQuery auch relativ einfach:
Wir erzeugen mithilfe von einem Objekt einen „Namespace“ Blog
für unsere Funktionen.
Mit $.get("url", function () {})
laden wir unsere JSON-Newsliste vom Server und generieren mit unseren Templates
dann dessen HTML-Repräsentation. Diese wird dann in unser <div id="main">
geladen:
Folgende weitere Funktionen unterstützt unserer Controller. Da die Implementierung relativ einfach ist haben wir sie hier aus Gründen der Übersichtlichkeit weggelassen. Sie finde aber den kompletten Code im GitHub Repository des Mini-Blogs.
Zum Schluss registrieren wir einen onload
Handler, also eine Funktion die nach dem Laden der Seite aufgerufen wird, die alle Blogbeiträge lädt und mit .submit()
abfängt wenn das „Neuen Beitrag anlegen“ Formular abgeschickt wird. Wir lesen dann die Felder aus und schicken sie mit unserer Hilfsfunktion an den Server.
Nun wäre das Grundgerüst für den Blog fertig! Wir haben also mit rund 200 Zeilen Haskell, etwa 100 Zeilen JavaScript und 150 Zeilen HTML einen kleinen Blog implementiert, der gut skaliert und sicher gegen XSS und SQL-Injections ist. Versuchen wir etwa HTML-Code in einen Kommentar zu schmuggeln, sorgt das AutoEscaping der SOY-Templates dafür, dass aus beispielsweise <script>alert("HALLO!");</script>
<script>alert("HALLO!");&lgt;</script>
wird. Wenn wir einen Kommentar schreiben, mit dem wir versuchen das eigentliche SQL-Query was dahinter steckt zu manipulieren (zB: ', NULL, NULL, 'ASDF') --
, schlägt das ebenfalls fehl: Das persistent Framework kennt nämlich die Typen unserer SQL-Felder und escaped die Eingaben entsprechend.
Den gesammten Code für den Blog findet man im bereits erwähten GitHub Repository mit entsprechender Cabal-Datei. Den Blog kann man also einfach mit cabal configure && cabal build
bauen und dann starten.
Natürlich kann man einen Blog mit noch weniger Haskell-Code schreiben. Wie das geht und wie man dem Blog noch Benutzerauthentifizierung und Sessions spendiert, werde ich in einem weiteren Beitrag erklären.