Seite wählen

NETWAYS Blog

Große Dateien mit der JavaScript File-Api verarbeiten

Die JavaScript File-Api ist für mich (zusammen mir der Drag&Drop Api) etwas, das gemischte Gefühle erzeugt. Es ist unheimlich praktisch, Dateien clientseitig Einlesen und Verarbeiten zu können. Trotzdem  hat sowohl die Schnittstelle, als auch deren Implementierung ein paar Strickfallen, vor allem wenn die Dateigröße keine Rolle spielen soll.
Ein kleines Beispiel:
Ein einfaches Beispiel der File-API habe ich mal in jsfiddle zum ausprobieren angelegt. Hier sieht man schnell, wie man Dateien in der File-Api einliest, um sie später verarbeiten zu können
Folgende Funktion wird aufgerufen, wenn eine Datei ausgewählt wird:
var uploadHdl = function(ev) {
   var input = ev.target;
   var fileToRead = input.files[0];
    //neuen Reader erzeugen
    var reader = new FileReader();
reader.addEventListener("load",function(loadEv) {
      // hier liegt ein ArrayBuffer mit dem Dateiinhalt und
      // kann weiterverarbeitet werden
      alert("Datei eingelesen: "+loadEv.target.result.byteLength+" bytes");
})
reader.readAsArrayBuffer(fileToRead);
}

Und bis hier gibt es auch noch nicht viel an der Schnittstelle zu meckern, ausser vielleicht dass der FileReader keine Information in seinem onload event mitgibt, welche Datei er gerade gelesen hat. Daran geht die Welt aber nicht unter.
Was mich persönlich allerdings stört, ist dass das obige Beispiel nicht skaliert: Verwendet man eine Datei mit mehreren 100 mb oder sogar ein paar GB, sollte man in Deckung springen: Der Browser wird es nicht überleben.
mehr lesen…

Weekly Snap: MySQL Backup, Teltonika & HTML/JavaScript Tips

6 – 10 February offered a nice mix of developer and sys admin tips, hardware and consulting news as well as general software food for thought.
Along these lines, Julian discovered the software complexity behind iPhones’ ‘home’ button, while Sebastian shared his tip for backing up MySQL with LVM snapshots.
Jannis then offered four JavaScript/HTML features for developers to impress their friends with – cross origin resource sharing, manifest files, deferred scripts and error objects.
Birger briefly reported on an Icinga project at Pegasus, transforming RDD files into SQL-ready performance data.
Lastly, Martin reassured shoppers at our hardware store, that there are alternatives to the much loved, but discontinued Teltonika ModemUSB/G10 GSM.

HTML/JavaScript Features, die auf jeder Party gut kommen

Heute werde ich Ausnahmsweise keine Benchmarks präsentieren (ok, einen halben), sondern eine kleine Ansammlung von JavaScript/HTML Features mit denen man gut bei seinen Freunden angeben kann:
1. CORS – Cross Origin Resource Sharing
Browser unterliegen der Geheimhaltungspflicht, d.h. Asynchrone Anfragen dürfen nur an die gleiche Domain gehen, auf der das Script liegt. Das ganze heißt ’same-origin-policy‘ und ist ein wichtiger Sicherheitsaspekt.
Führe ich auf meinem lokalen Webserver z.b. folgenden Code aus:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://www.netways.de', true);
xhr.onreadystatechange = function () {
/* ... hier wird die Antwort des Servers bearbeitet ... */
};
xhr.send(null)

Macht mir mein Browser gleich einen Strich durch die Rechnung:
„XMLHttpRequest cannot load http://www.netways.de/. Origin http://localhost is not allowed by Access-Control-Allow-Origin.“
Was aber wenn ich z.B. eine Api habe (wie z.B. Icinga-Web) und möchte diese von einer externen Webanwendung ansprechen (wie es z.B. icinga-mobile macht)?
Hier ist die Situation nicht ganz Ausweglos:  Der W3C hat hierfür CORS definiert. Bei obiger Anfrage macht der Browser nämlich mehr, als nur festzustellen ob origin == requestOrigin und wenn nicht einen Fehler zu werfen. In einem Netzwerksniffer würde man sehen, dass zuerst ein OPTIONS Request an den Server geschickt wird (der sogenannte Preflight).
Will der Server erlauben, dass externe Webanwendungen darauf zugreifen, kann er darauf Access-Control Header zurückschicken, z.b:

Access-Control-Allow-Origin: http://localhost
Access-Control-Max-Age: 2520
Access-Control-Allow-Methods: GET, PUT, DELETE, XMODIFY

Dies würde Seiten, die auf der Domain localhost laufen erlauben, den Server (der auf einer anderen Domain liegt) via GET, PUT, DELETE und XMODIFY anzusprechen. Der Max-Age Header gibt an, dass erst nach 2520 Sekunden ein neuer Preflight gestartet werden muss (ansonsten würde der Browser vor jeder anfrage einmal UPDATE anfragen und auf Antwort warten). Will man hier Cookies verwenden, muss man den allow-credentials Header auf true setzen – dann ist es jedoch nicht mehr möglich, eine Wildcard für den Origin zu setzen.
2. Seiten offline verfügbar machen – Manifest Files
HTML5 hat das html tag um ein Attribut bereichert: manifest.  Frech wie ich bin, klau ich mir jetzt einfach das Beispiel vom W3C:
Man hat z.B. eine einfache Webapplikation mit drei Komponenten: clock.html, clock.js, clock.css, die eine Uhr anzeigen (Ohne ntp, sondern nur lokal).
Will jemand ohne Netzverbindung die Seite aufrufen (auch wenn er Sie schonmal gesehen hat), bekommt er höchstwahrscheinlich eine Fehlermeldung, bzw. eine 404 Exception. Mit HTML5 kann man da ganz einfach sagen, dass bestimmte Teile nach dem Besuch der Seite auf dem Client gespeichert werden sollen, und später auch ohne Netzverbindung lokal verwendet werden können. Dafür definiert man eine einfache Datei mit beliebigen namen, hier clock.appcache, und legt sie neben der clock.html ab.
Der Inhalt besteht aus allen Dateien, die lokal gespeicher werden sollen (so ein Manifest kann eigentlich noch viel mehr, aber das würde den Rahmen hier sprengen)

CACHE MANIFEST
clock.html
clock.css
clock.js

Jetzt erweitern wir einfach das HTML Tag unserer clock.html seite mit
<html manifest=“clock.appcache“>
..und fertig – bei einem HTML5 kompatiblen Browser werden beim ersten Aufruf der Seite alle benötigten Dateien beim Client zwischengespeichert und können so auch ohne Netzverbindung verwendet werden.
3. Deferred scripts
(Ok, das ist jetzt nicht wirklich neu)
Der einzig wahre Ort für externe Skripte sollte im Head Bereich des HTML liegen. Doch was wenn das parsen eines Skriptes viel Zeit benötigt und deswegen den Aufbau der kompletten Seite bremst (obwohl es eventuell erst zu einem späteren Zeitpunkt benötigt wird)?
Für solche Fälle kann man dem Browser mit dem Attribut ‚defer‘ einen Hinweis geben, dass ein Skript erst nach vollständigem Laden der Seite geparsed werden soll:
<script src=“slowscript.js“ defer> </script>
Zugegebenermaßen hält sich der praktische Nutzen dessen oft in Grenzen (vor allem da defer nur für Skripte gilt, die nicht auf den DOM Baum zugreifen), im ein oder anderen Extremfall kann es dennoch sehr nützlich sein.
Der Standard definiert zusätzlich noch ein async Attribut, das ein Skript nebenläufig ausführt. Die Implementierung war bei meinen Tests anscheinend noch nicht vollständig, zumindest gab es keinen Unterschied zu defer.
4. Error Objekte:
JavaScript besitzt laut dem ECMA-262 Standard ein Error Objekt, das in den meisten Browsern auch verwendet wird. Anstatt wild irgendwelche Strings zu throwen, kann man so einfach eine der Error Klassen feuern (oder erweitern).
Leider besitzt JavaScript aber keine Möglichkeit, nur spezielle Error-Typen in einem catch() statement zu fangen, das feststellen ‚welcher‘ Error gefeuert wurde wird einem damit also nicht auf Sprachebene abgenommen.
Ich habe jetzt zwar nicht wirklich Geheimnisse verraten, vielleicht ist für den ein oder anderen aber ja was neues dabei.

Weekly Snap: Introducing Gunnar & Java Script Surprises

9 – 13 January our development team took the lead, with a staff introduction and surprising JavaScript lessons learned.
Jannis discovered why native browser methods don’t necessarily guarantee better JavaScript performance. While testing his new Java array features to benchmark against native methods in V8 (Chrome) and Spider Monkey (Firefox) he was met with a surprise. In V8 his code was just a little slower or sometimes a little faster, but in Spider Monkey it was significantly better. He put this down to Mozilla’s tracing engine, and noted the best performance came with methods that take arguments as functions eg. Array.map(), Array.filter(), Array.forEach(). Jannis shared his simulation for others to test against, and in spite of his surprising find – he cautioned web developers to consider the compatibility of new language features before implementing them in a productive environment.
Following on, our Application Developer Gunnar, introduced himself and his work at NETWAYS. This has included work on a robust, distributed “single sign on” application for a customer, the inGraph addon for Icinga/Nagios, as well as on IcingaMQ. Away from the office, Gunnar plays with new technologies – most recently SIP and video-streaming via IP multicast.

JavaScript Performance: Warum 'nativ' nicht immer besser ist.

Eigentlich wollte ich den heutigen Artikel neuen JavaScript Array-Features widmen, die am kommen oder schon da sind (forEach(), indexOf(), Iteratoren, Generatoren, etc.). Garnieren wollte ich das ganze mit ein paar Benchmarks, um bestenfalls zu zeigen dass die ’nativen‘ Arraymethoden der Browser meine selbstgeschriebenen Equivalente vor Neid erblassen lassen und was die Zukunft bringt. Doch das wäre wohl zu einfach gewesen…
Das Ergebnis war überraschend: Während meine Benchmarks im v8 (d.h. die JS-Engine von Chrome) zwar zeigten, dass manche meiner Methoden nur einen kleinen Tick langsamer waren (manche aber auch schneller), war es bei Spidermonkey (die Firefox engine) genau umgekehrt: Sobald dort die Tracing Engine aktiviert war (was Standard seit FF > 3.5 ist), lief mein eigener Code mit einer viel besseren Performance (z.T um den Faktor x6) als die ’nativen‘ SpiderMonkey-Methoden, die in C++ geschrieben waren.

Was komisch klingt liegt an der Art und Weise wie Fuchs optimiert: Mein eigener Code wird zur Laufzeit ge’traced‘ (grob gesagt: Es wird verfolgt, ob die Typen der Variablen gleichbleibend sind und dementsprechend wenn möglich auf Typkonvertierungen und -überprüfungen verzichtet – wer Details will findet eine gute Einführung im Mozilla Blog ). Dementsprechend werden bei meinem selbstgeschriebenen Code weniger Operationen pro Durchgang benötigt. Bei den nativen Methoden ist dass anscheinend anders: Eine kleine Wanderung in den C++ Code von Spidermonkey zeigte, dass z.B. die Array.prototype.every() Methode Typen immer überprüft und konvertiert und ich schätze, dass hier derzeit kein Tracing stattfindet.
Die größten Performancesprünge lagen dabei interessanterweise bei Methoden, die Funktionen als Argumente nehmen, wie z.b. Array.map(), Array.filter(), Array.forEach().
Hier noch mein Benchmark zum selber Ausprobieren. Natürlich ist das immer ein Szenario und deckt damit nicht jeden Fall ab – ein anderer Benchmark kann ein völlig anderes Ergebnis liefern, ein neuerer Browser andere Werte, etc. Ich habe die Funktionen was Argumente und Verhalten angeht nachgeschrieben, allerdings ist ein 1:1 Vergleich immer schwer und das Szenario (wie bei einem Benchmark üblich) natürlich extrem (10000 Elemente im Array und 1000 Durchgänge).
Das eigentliche Fazit: Gerade bei neuen Methoden wie den JavaScript 1.6+ Array Funktionen, die nicht bei jedem Browser vorhanden sind sollte immer erst geprüft ob sich der Aufwand lohnt, oder ob man auf ‚konservative‘ Methoden zurückgreift. Cross-Browser-Kompatibilität erfordert immer Mehraufwand, und dieser wird nur gerechtfertigt wenn unterm Strich ein Mehrwert herauskommt. Und darauf will ich hinaus: Wir Webentwickler werden zwar einige neue Features wie Iteratoren und Generatoren bekommen (Firefox unterstützt diese auch schon bereits) – trotzdem ergibt es oft Sinn, lieber auf Helferfunktionen von Core-Toolset wie Prototype, JQuery oder ExtJS zu setzen, die browserübergreifend die selben Funktionalitäten anbieten. Die ’neueren‘ Methoden sind  a) nicht in jedem Browser vorhanden, und b) evtl. sogar langsamer, oder nur minimal schneller – womit sie derzeit für den Produktiveinsatz ausscheiden.
Ich will hier natürlich keinen Fortschritt aufhalten: Es ist gut, dass sinnvolle Hilfsmethoden wie filter() oder map() Einzug in den Standard finden und ich werde sie sicher auch selbst nutzen wenn Sie verbreitet genug sind. Allerdings sollte man für den Produktiveinsatz eher mit geschärften Bewusstsein an neue (Sprach-)Features wagen.