Bessere Web-Apps mit HTML5-APIs

Pimp my Browser

Phillip Ghadir, Simon Kölsch

Der Begriff HTML5 fasst eine bunte Sammlung aus verfügbaren und geplanten APIs zusammen, auf die im Browser von JavaScript aus zugegriffen werden kann. Einige dieser APIs ermöglichen heute, Anwendungen für den Browser zu realisieren, die vor wenigen Jahren noch Plugins erfordert hätten. Die prominentesten Vertreter sind sicherlich Video oder WebRTC. Zum Beispiel ist heute die Public-Key-Verschlüsselung bereits im Web-Client integrierbar oder das Reagieren auf Sensordaten der Betriebs-Hardware des Browsers möglich. Es folgt eine persönliche Auswahl an etablierten APIs und ein Ausblick auf einige noch recht frische.

Die vernetzte Welt funktioniert heute dank einer breiten Akzeptanz von im World Wide Web üblichen Standards. Und obwohl deren Verbreitung quasi universell sein könnte, werden proprietäre Frameworks von Einzelnen - dank der Marktmacht und strahlenden Kraft der Marke ihres Arbeitgebers - gehyped.

Solange die Frameworks aus der Anwendungsentwicklung entstehen und für ihren Kontext Probleme erfolgreich adressieren, gibt es daran nichts auszusetzen. Wenn sie aber zur Abhängigkeit von proprietären Lösungen verleiten, für die es eigentlich eine allgemein verbreitete Lösung gibt oder geben sollte, ist die Verbreitung solcher Frameworks nicht nur gut.

In diesem Artikel stellen wir gesammelt APIs im Web-Umfeld vor, die heute - oder hoffentlich in Kürze - für alle gängigen Browser-Plattformen verfügbar sind, auch wenn je nach API, Browser und Versionen eigene Namensprefixe verwendet werden.

Wir meinen, dass die Kenntnis dieser APIs hilft, den Einsatz irgendwelcher JavaScript-Frameworks besser zu bewerten.

Was geht?

Bevor wir uns also mit den APIs selbst beschäftigen, wären Instrumente zum Auswählen von APIs hilfreich. Einen guten Startpunkt bilden da:

  • die API-Übersicht von Eric Wilde
  • caniuse.com
  • quirksmode.com
  • microjs.com

Sind die ersten Schritte getan, helfen dann auch Testsuiten, die Anwendungen in verschiedenen Client-Umgebungen (von IE, Firefox, Chrome, iOS, Android) unterschiedlicher Versionen und mehreren Bildschirmauflösungen testen können. Darauf gehen wir in diesem Artikel aber nicht weiter ein.

Eric Wildes HTML5 Übersicht

Eingaben und Sensoren

Tastatur und Maus sind nicht alles. Wer eine Web-Site oder Web-Applikation für diverse Mobile-Geräte fitt machen will, kann mit Hilfe einer Reihe von Device-spezifischen APIs auf Funktionen des Geräts wie zum Beispiel die Akku-Laufzeit, der Beschleunigungssensoren oder die geographische Ortsangabe auslesen.

Battery Status API

Mit der Battery Status API lässt sich beispielsweise auf den aktuellen Batterie-Status eines Geräts zugreifen, zumindest unter Firefox, Chrome und auf neusten Android-Geräten. Damit lässt sich erfragen, ob ein Akku installiert ist, wie viele Leistung der Akku noch hat und ob er gerade aufgeladen wird.

Man kann Handler für die Events “wird geladen/oder auch nicht” oder “Energiestand aktualisiert” registrieren.

Page Visibilty API

Möchte man im Client darauf reagieren, dass der Benutzer den Tab wechselt oder aber das Fenster minimiert, bietet sich dafür die Page Visibility API an. Sie fügt im Wesentlichen zwei nur lesbare Attribute zum document Objekt hinzu:

    document.hidden

Wenn die Seite für den Benutzer nicht sichtbar ist, liefert document.hidden true, sonst false.

Zudem:

    document.visibilityState

Das Attribut enthält als String einen der möglichen 4 möglichen Werte – wovon nur der Wert “visible” document.hidden == false entspricht:

  • visible
  • hidden
  • prerender
  • unloaded

Auf die Änderung der Sichtbarkeit kann man über einen Event-Handler für das visibilityChanged-Ereignis reagieren.

Auch wenn die API von sehr vielen Browsern schon seit langem unterstützt wird, verwenden einige für die genannten Attribute spezifische Prefixe, so dass das Attribut hidden dann “mozHidden”, “msHidden” oder “webkitHidden” und das visibilityChange-Event dann “mozvisibilitychange”, “msvisibilitychange” oder “webkitvisibilitychange” heißen kann. Man beachte die konsistente Groß-/Kleinschreibung.

Geolocation API

Über die seit vielen Jahren verbreitete Geolocation-API lässt sich bei entsprechender Zustimmung durch den Nutzer die Geo-Koordinaten von dem Gerät erfragen.

Wird die API unterstützt, ist navigator.geolocation definiert. Dann kann – sofern der Nutzer die Berechtigung dafür erteilt – die Position des Geräts entweder einmalig über

navigator.geolocation.getCurrentPosition( eineCallBackFunktion );

oder kontinuierlich über

navigator.geolocation.watchPosition( eineCallBackFunktion );

ermittelt werden. Mit navigator.geolocation.clearWatch() lässt sich so ein Handler dann auch wieder deregistrieren.

Die Callback-Funktion erhält als Argument die Position, die neben dem Längen- und Breitengrad auch noch die Höhenangabe sowie die Information über die Genauigkeit enthält. Eine Genauigkeit bezieht sich auf die Längen- und Breitengrade und eine auf die Höhenangabe. Weitere Informationen sind die Richtung, Geschwindigkeit und der Zeitpunkt der Antwort.

Damit lässt sich Einiges anfangen. Leider zeigen die Beispiele, der üblichen Verdächtigen einfach nur, wie die abgefragten Daten ohne Umschweife direkt zu Google geschickt werden können. Als wüsste die größte Datenkrake der Welt nicht eh schon alles.

Nebenläufigkeit im Browser

Je komplexer wird, was wir im Browser ausführen möchten, desto schmerzlicher wird die ausschließlich sequenzielle Verarbeitung von JavaScript. Hier schafft die breit unterstützte Web Worker API Abhilfe: Sie ermöglicht die Ausführung von Scripten im Hintergrund parallel zu dem, was auf der aktuellen Seite ausgeführt wird.

In einer separaten JavaScript-Datei verpackt, können Web Worker per

    var meinWorker = new Worker( "meineSpezielleWebWorker.js" );

erzeugt werden.

Dabei muss das Worker-JavaScript vom selben Schema und URI- Pfad geladen werden, wie die HTML-Seite, die den Worker per Script-Tag einbindet. Hat das Instanziieren fehlerfrei geklappt, wird der Web Worker in einem eigenen Thread/Prozess - in dem Kontext der aufrufenden Seite - gestartet und ausgeführt, ohne die Ausführung auf der Hauptseite zu beeinträchtigen.

An den Web Worker kann man nun über dessen Methode postMessage() Nachrichten schicken:

    meinWorker.postMessage( irgendwelcheJSONDaten );

Web Worker dürfen andere Scripte (via importScripts()-Methode ) importieren sowie auf die WindowsTimers, den Navigator und XMLHttpRequest zugreifen. Zugriffe auf den DOM (und damit auf die Objekte window, document bzw. parent) sind nicht gestattet.

Die umgebende Seite kann bei dem Web Worker einen OnMessage-Handler registrieren, über den sie der Worker benachrichtigen kann.

Die umgebende Seite kann einen Worker mit terminate() beenden. Innerhalb eines Workers kann er sich selbst per close() beenden.

Online / Offline Mode

Es gibt eine API zum Erkennen, ob der Browser im Offline-Modus ist. Leider verspricht der Name mehr, als durch die API tatsächlich geliefert wird. Ist man beispielsweise im Firefox im Online-Modus, obwohl man selbst keinerlei Internet-Verbindung hat, liefert die API immer noch ein “Alles gut, Du bist online.”

In anderen Browsern ist das Verhalten etwas anders, aber auch da erkennt die API nicht den Zustand “Kein Internet”, sofern irgendeine Netzverbindung besteht.

Wer hier programmatisch auf ein fehlendes Netz reagieren möchte, könnte sich mit einer geeigneten Monitoring-API behelfen. Darauf gehen wir hier nicht weiter ein.

Alternativ gibt noch deutlich mehr APIs, mit denen auf die Umgebung reagiert werden kann. Als Beispiele werfen wir mal die noch sehr jungen Ambient Light API und Proximity API in die Runde. Belassen wir es dabei und greifen einen etablierten Vertreter für das Client-seitige Persistieren von Daten heraus.

Daten Client-seitig persistieren

Über die quasi universell verfügbare Web Storage API stehen jeder Web Anwendung im Browser zwei Key-/Value-Maps zur Verfügung, in der Paare von Strings abgelegt werden können. Der so genannte LocalStorage ist als Attribut des window-Objekts zugreifbar und speichert Daten permanent – über die Lebensdauer der Prozessinstanz des Browsers hinweg.

Dahingegen ermöglicht der sogenannte SessionStorage – ebenfalls zugreifbar als Attribut des window-Objekts – das Persistieren von Schlüssel-/Wert-Paaren für die Dauer einer Browser-Session. Die hier gespeicherten Daten werden beim Schließen des Browser-Tabs bzw. des Browser-Tabs entfernt. Selbst, wenn die selbe Seite mehrfach parallel in verschiedenen Tabs geöffnet wird, steht jedem dieser Tab ein eigener Session-Storage zur Verfügung.

Alles, was von einer Domain geladen wird, kann allerdings auf die gleichen Daten in dem LocalStorage zugreifen.

Ist LocalStorage definiert, kann man über

    localStorage.setItem( "schlüssel", "wert" );

einen Wert setzen und per

    localStorage.getItem( "schlüssel" );

auslesen. Es ist sichergestellt, dass der Zugriff nur auf Werte ermöglicht wird, die von Skripten der gleichen Domain gespeichert wurden.

Analog funktioniert sessionStorage.

Die Web Storage API sieht sowohl für die im Session- als auch für die im LocalStorage gespeicherten Daten keine Weitergabe an den Server vor. Das heisst, die über die API gespeicherten Daten verbleiben auf dem Client, sofern die Anwendung nicht explizit diese ausliest und an das Backend überträgt.

Sicher?

Je mehr Anwendungslogik im Browser ausgeführt wird, desto höher sind die Anforderungen an die Sicherheit der Browser als Plattform. Dieser Situation wird mit unterschiedlichen APIs Rechnung getragen.

CORS

Am weitesten fortgeschritten, verbreitet und damit schon fast ein alter Hut ist ohne Frage “Cross-Origin Resource Sharing”, besser bekannt als “CORS”.

Aktuelle Sicherheitseinstellungen in den Browsern erzwingen normalerweise, dass JavaScript von der selben Domain geladen muss, von der auch die Seite geladen wurde, die das JavaScript einbindet. Dies erschwert allerdings die Integration von Services, die sich auf anderen Domains befinden.

Mit den Sicherheitseinstellungen wäre der Browser als Applikationsplattform nur für Systeme zu gebrauchen, die im Backend integriert werden.

CORS schafft hier Abhilfe durch die Definition allgemeingültiger, spezieller HTTP-Response-Header, die dem Browser mitteilen, zu welchen anderen Domains zusätzlich welche HTTP-Requests abgesetzt werden können. CORS wird von allen Browsern unterstützt und ist älteren Alternativen, wie beispielsweise JSONP auf jedenfall vorzuziehen.

Web Crypto API

Die Web Crypto API erlaubt ohne grossen Aufwand, die Funktionalität zu nutzen, kryptographische Schlüssel zu generieren, exportieren, importieren und anzuwenden. Diese Funktionalität ist ohnehin schon in der TLS-Implementierung des Browsers vorhanden.

Das Problem der Authentizität beim Übermitteln von JavaScript Dateien löst die Web Crypto API nicht.

Länger laufende Requests werden asynchron über das Promises-Pattern behandelt. Dabei erhält man von der API ein “Promises”-Objekt, und übergibt eine eigene Funktion, die im Erfolgsfall ausgeführt werden soll. Ein SHA–512 Hash kann zum Beispiel so gebildet werden:

    var encoder = new TextEncoder("utf-8");
    window.crypto.subtle.digest(
            {name:'SHA-1'},
            encoder.encode("Text"))
        .then(function(resultHash) {
            // Code im Erfolgsfall });

Dazu kommt ein kryptographisch sicherer Zufallszahlengenerator, der ein übergebenes Array befüllt:

    window.crypto.getRandomValues(new Uint32Array(5));

In aktuellen Browsern ist die API bereits vollständig implementiert und für gängige Algorithmen stabil und benutzbar.

Übrigends: An einem Modell zum sicheren Übertragen von JavaScript wird unter dem W3C Draft-Titel “Privileged Contexts” gearbeitet, konkrete Umsetzungen sind aber im Moment noch nicht abzusehen.

Content Security Policy 2 - Level Up

Der Server kann durch Setzen des “Content-Security-Policy”-Headers im HTTP-Response einen zusätzlichen Sicherheitsmodus im Browser aktivieren.

Zum Beispiel kann das Auslagern von JavaScript in externe Dateien erzwungen und dadurch das Einschleusen von Code über script-Tags verhindert werden.

Damit wird eine der am weitest verbreiteten Angriffstechniken auf Web- Applikationen eingeschränkt: das Cross-Site-Scripting oder “XSS”. Diese Methode schleust ein bösartiges Script in den Kontext einer sonst vertrauenswürdigen Seite ein und kann dann Aktionen mit den Berechtigungen des Users ausführen.

Level 2 der Content Security Policy ist seit Februar eine W3C Canidate Recommendation, wird also von den Browserherstellern implementiert.

Dabei gab es einige Änderungen, die hier den Rahmen sprengen würden.

Level 2 der Content Security Policy führt neue Sicherheitsregeln ein:

  • Zum Beispiel sind Redirects nicht mehr pauschal möglich, sondern müssen über den Ausdruck ‘unsafe-redirect’ erlaubt werden.
  • Zudem bezieht die Spezifikation jetzt auch explizit Web Worker ein und
  • ermöglicht das Abschicken von Formularen zu kontrollieren.
  • Der Aufruf beliebiger Plugins ist nun nicht mehr ohne Weiteres möglich.
  • Inline-Elemente wie JavaScript und Stylesheets sind nun zwar möglich, müssen aber über Hashwerte und Nonces abgesichert werden.

Schleust ein Angreifer hier Code in einem Inline-Script ein, verändert er zwangsläufig den Hash und der Browser wird diesen Code nicht ausführen. Verletzungen dieser Regeln konnten schon bei CSP Level 1 über eine ‘report-uri’ automatisiert zurückgemeldet werden. Dieser Mechanismus wurde nun durch DOM-Events mit mehr Informationen erweitert.

Und jetzt HTTPS

Alle Anforderungen für eine Anwendung zu kennen, ist leider eher die Ausnahme. Bis die Entscheidung getroffen wird, eine Domain über HTTPS erreichbar zu machen, können mehere Content Management Systeme im Einsatz gewesen sein und das eigentliche Markup wir von einem ganzen dutzend an Services aggregiert und produziert. Teile davon sind vielleicht sogar eine Sammlung von älterem Content mit hartkodierten Links. Ist das Linkziel nicht relativ, sondern das Protokoll mit angegeben, kann es fast unmöglich sein, alte Links auf HTTPS “umzubiegen”. Auch Redirects sind nicht immer die passende Lösung.

Abhilfe könnte hier der Draft “Upgrade Insecure Requests” schaffen. Damit wird eine zusätzliche Content-Security-Policy geschaffen, welche den Browser einfach anweist alle Resourcen über HTTPS und nicht über HTTP zu laden. Resourcen von anderen Domains sind davon natürlich nicht betroffen.

Durch den

    Response-Header: Content-Security-Policy: upgrade-insecure-requests

wird ein Link danach als behandelt. wird weiterhin gleich behandelt.

Google Chrome unterstützt dieses Feature seit Version 43 und Firefox arbeitet an der Implementierung.

Credential Management

Dieser Entwurf nimmt sich dem Problem der Zugangsdaten im Browser an. Passwörter sollten nicht nur häufig geändert werden, sondern können schon fast früher oder später als offen angesehen werden. Daher sollte man am besten für jeden Dienst ein anderes Passwort verwenden.

Nicht jeder greift dabei auf die Hilfe eines extra Passwort-Managers zurück, sondern nutzt zum Beispiel für den Zugang zu Webforen die Passwort-Speichern-Funktion des Browsers selbst.

Dabei kann der Browser eine gespeicherte Kombination von Benutzername und Passwort nur dadurch an die Web-Applikation weitergeben, in dem er die Input-Felder im Anmelde-Formular ausfüllt.

Der Browser erkennt ein mit autocomplete markiertes User/Passwort-Feld und füllt dieses mit den entsprechenden Daten. Eher unübliche Mechanismen, wie Login über einen von JavaScript aus verschickten XMLHttpRequest oder OpenID/OAuth fallen hier unter den Tisch.

“Credential Management Level 1” hat zum Ziel, eine möglichst einfache API zur Verfügung zu stellen, bei der Applikationen nach Zugängen unterschiedlicher Art im Browser fragen können.

Einverständnis des Benutzers vorausgesetzt, wäre es so möglich, nicht nur Passwörter einfacher zu übermitteln, sondern auch komplexere Daten wie zum Beispiel “Access-Tokens”.

Im Moment befindet sich die API zwar noch in der Entwurfsphase, wird aber als Hauptautor von Google getragen und damit hoffentlich bald in den Browsern zu finden sein.

Wie geht es weiter?

Der Schwerpunkt dieser Ausgabe ist ja die Frage nach der Zukunft von Java und JavaScript. Wir haben in diesem Artikel ein grobes Bild dessen gezeichnet, was heute problemlos mit aktuellen Browsern möglich ist. Die Entwicklung mit JavaScript wird auch weiterhin erfordern, manche Plattformunterschiede in der Namensgebung bzw. etwas anderen Schnittstellenspezifikationen explizit zu kompensieren.

Viele Unterschiede der Browser-Plattformen können durch mehr oder minder umfangreiche Frameworks ausgeglichen werden. Einige Frameworks können bereits heute manche fehlende Unterstützung von APIs kompensieren. Für Vieles gibt es gleich mehrere Framework-Alternativen. Auf Frameworks selbst konnten wir im Rahmen dieses Artikels leider gar nicht eingehen.

In diesem Artikel hätten eigentlich noch viel mehr APIs vorgestellt werden müssen. Und dabei entwickelt sich die Liste der unterstützten Features nicht nur in Bezug auf Sicherheit, Ausführungsgeschwindigkeit und Kommunikation immer weiter, sondern auch in Richtung der Laufzeitumgebung der Plattform, näher an die Hardware und damit näher an die Peripherie. Dank der wachsenden Sensorik in den Geräten und der Möglichkeit, auch für einen Browser eher exotische Peripherie ansprechen zu können (von Vibrations-Features über Abstandsmesser und Lichtsensoren bis hin zu Midi-Geräten) – die Palette des W3C bietet noch eine Menge an Standards an, auf deren breite Unterstützung wir uns noch freuen können.

Thumb dsc08236

Phillip Ghadir, member of innoQ’s CxO team, is a consultant and trainer with a focus on software architecture.

More content

Thumb simon

Simon Kölsch works as a senior consultant at innoQ Deutschland GmbH with a focus at web-architecture and security.

More content

Java spektrum
Dieser Artikel ist ursprünglich in Ausgabe 04/2015 der Zeitschrift JavaSPEKTRUM erschienen. Die Veröffentlichung auf innoq.com erfolgt mit freundlicher Genehmigung des SIGS-Datacom-Verlags.

Comments

Please accept our cookie agreement to see full comments functionality. Read more