m2w2tech

Anything Java, Perl, ExtJS, JavaScript, JSON and tech stuff in general

4.9.09

Auswirkungen von Snow Leopard

Gerade eben habe ich Mac OS X 10.6 (Snow Leopard) bei mir installiert. Bisher habe ich mir nicht viel Zeit genommen, die Auswirkungen genau unter die Lupe zu nehmen. Beobachten konnte ich in den ersten 10 Minuten nach der erfolgreichen Installation, die übrigens 56 Minuten gedauert hat (iMac 2007, 2,4GHz, 4 GB RAM), folgendes:
  • Der freie Platz auf der Festplatte ist um stolze 10,5 GB gewachsen.
  • Vom von Apple beworbenen schnelleren Starten von Anwendungen merke schon etwas, richtig auffällig ist es aber vor allem bei Apple's hauseigener Anwendung Aperture.
  • Aperture gibt nach dem Beenden anscheindend endlich allen zuvor allokierten Speicher wieder frei. Schätzungsweise und hoffentlich ist das auch bei anderen Anwendungen so.
  • iCal versteht sich jetzt von Haus aus mit Accounts aus Google Calendar.
  • OpenOffice fühlt sich nicht schneller an als unter 10.5 Leopard.
  • Front Row funktioniert wieder! Unter Leopard hat es bei mir nur anfangs funktioniert, irgendwann konnte ich es dann aber nur noch manuell durch Öffnen der .app-Datei starten.
  • Nach dem ersten Anmelden lief für etwa eine Minute im Hintergrund ein fensterloses Ruby-Script mit etwa 8% Prozessorlast. Warum, weiß ich nicht.
  • Der Prozess mdworker, der nach meiner Google-Recherche wohl etwas mit zu indizierenden Dateien und Spotlight zu tun haben könnte, lief nach dem ersten Anmelden für etwa 5 Minuten mit einer Prozessorlast zwischen 10% und 70%.
  • The GIMP wollte mit der seit Leopard-Zeiten installierten Version 2.4.3 nicht mehr starten, erst mit Version 2.6.7 klappte es wieder.
  • In Textfeldern innerhalb von Java-Applets funktioniert der Accent Circonflêxe ^ nicht mehr, möglicherweise liegt das aber auch am Update auf Java 1.6.0_15, das kurz vorher anstand.
Soweit dazu, jetzt installiere ich iLife'09 und hoffe, dass es damit auch so gut klappt wie mit dem Schneeleoparden.

Labels: , , , ,

9.7.09

MySQL to Perl to JSON

Das nette CPAN stellt für die Umwandlung von Perl-Datenstrukturen in JSON das eigentlich geeignete JSON-Modul zur Verfügung. Will man jedoch Daten, die man gerade aus einer MySQL-Datenbank geholt hat, in mit dem CPAN-JSON-Modul so in JSON konvertieren, dass gängige JavaScript-Frameworks das JSON anstandslos verarbeiten können, gibt es Probleme:

Angenommen, das Holen der Daten in Perl erfolgt so
my $statementHandle = $dbh->prepare($statement);
my $queryResult = $statementHandle->execute();
my $resultHashRef = $statementHandle->fetchall_arrayref({});
und die Konvertierung nach JSON so
my $jsonText = JSON->new->encode($resultHashRef);
so werden numerische Werte aus Spalten, die beispielsweise mit SMALLINT(5) angelegt wurden, mit Hochkommata umschlossen, obwohl dies nicht sein dürfte (JavaScript-Frameworks im Frontend erwarten Typsicherheit).

Die eigentliche Information, von welchem Typ eine Spalte ist, ist am Statement-Handle und am DBH jedoch vorhanden. Mit
my $typesOfFields  = $statementHandle->{TYPE};
lassen sich die Datentypen aller in der letzten Abfrage gestellten Spalten holen.

Ein anschließendes Iterieren über die Typen und Ausgabe von deren type_info offenbart die internen Metainformationen über den Datentyp der jeweiligen Spalte (und weil mir JSON gut gefällt, lasse ich mir das auch gleich als JSON ausgeben):
foreach my $typeElement (@$typesOfFields) {
  my $typeInfo = $dbh->type_info($typeElement);
  my $jsonType = JSON->new->encode($typeInfo);
  print "jsonType = ".$jsonType."\n";
}
Die Ausgabe dieses Codes sieht dann für eine Tabelle mit einer einzigen Spalte, die als SMALLINT(5) angelegt wurde, so aus:
jsonType = {
 "UNSIGNED_ATTRIBUTE" : 0,
 "MAXIMUM_SCALE" : 0,
 "INTERVAL_PRECISION" : 0,
 "CREATE_PARAMS" : null,
 "NUM_PREC_RADIX" : 10,
 "SEARCHABLE" : 3,
 "LOCAL_TYPE_NAME" : "Short integer",
 "LITERAL_PREFIX" : null,
 "COLUMN_SIZE" : 5,
 "MYSQL_NATIVE_TYPE" : 2,
 "MINIMUM_SCALE" : 0,
 "TYPE_NAME" : "smallint",
 "AUTO_UNIQUE_VALUE" : 0,
 "NULLABLE" : 1,
 "SQL_DATATYPE" : 5,
 "DATA_TYPE" : 5,
 "LITERAL_SUFFIX" : null,
 "CASE_SENSITIVE" : 0,
 "FIXED_PREC_SCALE" : 0,
 "MYSQL_IS_NUM" : 1,
 "SQL_DATETIME_SUB" : 0
}
Für eine Tabelle mit einer Spalte, die mit VARCHAR(255) angelegt wurde, sieht sie so aus:
jsonType = {
 "UNSIGNED_ATTRIBUTE" : 0,
 "MAXIMUM_SCALE" : 0,
 "INTERVAL_PRECISION" : 0,
 "CREATE_PARAMS" : "max length",
 "NUM_PREC_RADIX" : null,
 "SEARCHABLE" : 3,
 "LOCAL_TYPE_NAME" : "variable length string",
 "LITERAL_PREFIX" : "'",
 "COLUMN_SIZE" : 255,
 "MYSQL_NATIVE_TYPE" : 253,
 "MINIMUM_SCALE" : 0,
 "TYPE_NAME" : "varchar",
 "AUTO_UNIQUE_VALUE" : 0,
 "NULLABLE" : 1,
 "SQL_DATATYPE" : 12,
 "DATA_TYPE" : 12,
 "LITERAL_SUFFIX" : "'",
 "CASE_SENSITIVE" : 0,
 "FIXED_PREC_SCALE" : 0,
 "MYSQL_IS_NUM" : 0,
 "SQL_DATETIME_SUB" : 0
}
Das Feld, anhand dessen man numerische von nicht-numerischen Spalten unterscheiden kann, ist MYSQL_IS_NUM. Ist sein Wert 0, ist es kein numerisches Feld, bei 1 schon. Jedoch verursacht speziell die Kombination von Anforderungen, MySQL plus Perl plus JSON, noch ein weiteres Problem: Weder MySQL noch Perl kennen atomare Bool-Werte. In MySQL behilft man sich im CREATE TABLE-Statment in den allermeisten Fällen mit enum('true','false'). Jedoch kennt auch Perl keinen eigens dafür gedachten Boolean-Typ, so dass bei der JSON-Konvertierung der Bool-Wert auch hier mit Hochkommata umschlossen wird.

Das JSON-Modul aus dem CPAN sieht für Boolsche Werte hier die eigenen Typen JSON::true oder JSON::false vor. Damit man diese in einer Perl-Datenstruktur jedoch setzen kann, muss man erst herausfinden, ob eine Spalte überhaupt ein enum-Typ war. enum unterscheidet sich vom VARCHAR weder beim MYSQL_IS_NUM noch beim LITERAL_SUFFIX, das aussagt, ob der Value bei der Ausgabe in Quotes gesetzt werden soll oder nicht. Glücklicherweise gibt es hier noch das Feld MYSQL_NATIVE_TYPE, das für den enum den Wert 254, für den VARCHAR jedoch den Wert 253 trägt (für SMALLINT(5) hat es den Wert 2).

Mit diesem Wissen lässt sich nun mit ein wenig Aufwand ein korrekter JSON-String bauen, der von JavaScript-Frameworks wie beispielsweise ExtJS anstandslos geschluckt wird.

Geholfen haben mir:
http://www.mathematik.uni-ulm.de/help/perl5/doc/DBD/mysql.html
http://www.freeopenbook.com/mysqlcookbook/mysqlckbk-CHP-9-SECT-3.html
http://cpansearch.perl.org/src/CAPTTOFU/DBD-mysql-3.0007/dbdimp.c

Labels: , , , , , , , , , , , , ,

6.2.09

Erfahrungen mit Strato Mail-Hosting

Das Unternehmen, in dem und für das ich arbeite, lässt E-Mail-Dienste bei Strato hosten. Jedem Mitarbeiter stehen 2 GB an Speicherplatz zur Verfügung. Nicht mehr ganz zeitgemäß, aber momentan noch ausreichend. Nun ist es aber so, dass einem Teil der Mitarbeiter täglich schätzungsweise durchschnittlich 100 Mails zugestellt werden. Gelegentlich sind es jedoch auch mehrere Tausend (fragen Sie nicht warum, denn ja: es IST wirklich erforderlich ;-)). Dann jedoch verhalten sich die Strato-Mailserver sehr sehr sonderbar.
  • Bei manchen Leuten, die mit IMAP arbeiten, reagiert der Server beim Versuch, in einen anderen Ordner zu wechseln, gar nicht oder sehr langsam.
  • Beim Versuch, einen Ordner mit dem IMAP-Kommando EXPUNGE von bereits gelöschten Mails endgültig zu befreien, kommt keine Antwort vom Server.
  • Beim Versuch, mich in den Webmail-Dienst von Strato einzuloggen, bekomme ich trotz garantiert korrektem Passwort nun die Meldung "Benuterdaten ungültig!". Kommentar von einem Kollegen: Das kommt alle paar Monate mal vor.
  • Beim Versuch, mit dem Mail-Client Thunderbird nach Mails auf dem Server zu suchen, bekomme ich die Meldung "Command Systax Error". Bei meinem privaten Mail-Account (anderer Provider) geht das problemlos mit demselben Mail-Client.
  • Downloads von größeren Attachments auf den lokalen Rechner starten meist erst nach mehreren Minuten.
Privat habe ich bei einem anderen Anbieter einen Mail-Account, der momentan über 8000 Mails enthält und den ich ebenfalls über IMAP over SSL nutze, ohne auch nur eines der oben genannten Probleme, und egal mit welchem Mail-Client. Ich werde bei längerer Weigerung der Strato-Server, mich meine Geschäfte ordentlich abwickeln zu lassen (und einen Auto-Responder einrichten gehört für mich dazu) den Verantwortlichen in unserem Unternehmen empfehlen, den Mail-Dienst zu wechseln.

Labels: , , , ,

18.9.08

WebKit hui

Das Unternehmen Net Applications veröffentlicht, wie einige anderen auch, Browser- und Betriebssystemstatistiken. Diese beruhen darauf, mit welcher Kennung sich ein Browser beim Server einer besuchten Website zu erkennen gibt. Aus dieser Kennung lässt sich fast immer das Betriebssystem und der Browsertyp herauslesen.

Verfolgt man die bei Net Applications verfügbare Statistik über verwendete Betriebssysteme vom September 2006 bis August 2008, sieht man, dass der Marktanteil von Microsoft Windows in diesem Zeitram von 94,8% auf 90,66% gesunken ist. Das sind also 4,14 Prozentpunkte in 23 Monaten, das macht 0,18 Prozentpunkte pro Monat. Im selben Zeitraum ist der Anteil der Benutzer, die mit Apples Betriebssystem Mac OS X arbeiten, von 4,72% auf 7,86%, also um 3,14 Prozentpunkte (oder anders ausgedrückt um 66,53% oder um 0,14 Prozentpunkte im Monat) gestiegen. Würde man diese Wachstumskurven ganz naiv monoton weiterzeichenen, wäre Microsoft in 503,7 Monaten, also knapp 42(!) Jahren, weg vom Fenster und Apple hätte im selben Zeitraum 68,76% des Marktes erobert.

Ganz so einfach ist es zum Glück nicht. Die mathematischen Ableitungen der Kurven aus der Statistik sind kaum ermittelbar und wären auch kein verlässlicher Indikator für die zu erwartenden Änderungen. Hier spielt viel Marketing mit rein, Sympathien der Anwender können sich von heute auf morgen ändern. Der Trend wird jedoch klar: Apples Betriebssystem kommt, Microsoft verliert Anteile. Gleichzeitig hat inzwischen Google seinen Browser Chrome veröffentlicht, der mit "WebKit" auf der selben technischen Basis wie Apples Browser Safari steht. Auch das iPhone und dessen Verwandter, der iPod touch, nutzen für ihren integrierten Safari-Browser die WebKit-Basis. WebKit wird wegen seiner hohen Geschwindigkeit geschätzt, und die Browser, die WebKit als Basis verwenden, glänzen, viel mehr als Microsofts Internet Explorer in der aktuellen Version 7, mit Konformität zu festgelegten Standards.

Entwickler von Web-Anwendungen tun also gut daran, künftig noch mehr damit zu rechnen, dass ihre Kunden WebKit-basierte Browser wie Safari und Chrome verwenden. Und erst recht ist angesagt, bei der Entwicklung von Web-Anwendungen Frameworks zu verwenden, die von der Komplexität bei der Entwicklung für unterschiedliche Browser abstrahieren und eine API oberhalb dieser Ebene anbieten.

Labels: ,

10.9.08

ExtJS, ISO-8859-1 und UTF-8 verheiratet

Aktuelle Web-Anwendungen nutzen als Encoding-Schema ihrer angezeigten Seiten als auch ihrer in Datenbanken gespeicherten Daten in den allermeisten Fällen UTF-8. Die Vor- und Nachteile sind anderenorts ausreichend dokumentiert.

Jedoch gibt es auch Anwendungen, die bereits vor der "UTF-8-Zeit" entstanden sind. Solche Anwendungen nutzen beispielsweise ISO 8859-1 oder andere Encoding-Schemata und lassen sich aufgrund des Aufwands nicht ohne weiteres umstellen. Sie arbeiten nicht "besser" oder "schlechter" als die, die UTF-8 nutzen. Interessant wird es jedoch, wenn solche Systeme plötzlich mit Daten zurecht kommen müssen, die in anderen Kodierungen vorliegen.

Situationen, in denen ein System plötzlich mit anderen Encodings umgehen können muss, sind beispielsweise die Anbindung an ein Fremdsystem, das nicht das eigene Encoding unterstützt oder auch die Nutzung eines Frameworks, das ein bestimmtes Encoding erzwingt.

Ajax-Bibliotheken, die auch Mittel zur Komposition der Web-Oberfläche anbieten, sind solche Frameworks. Vor dem Absenden von Formulardaten per XMLHttpRequest nutzt beispielsweise das Ajax-Framework ExtJS (aktuell: Version 2.2) die JavaScript-Funktion encodeURIComponent. Die Daten, die im dann abgesendeten Request am Server ankommen sind dann erstens URI-kodiert und zweitens UTF-8-kodiert. Dies tut ExtJS völlig unabhängig vom Encoding der Seite, von der aus der XMLHttpRequest abgesendet wurde, also auch dann, wenn das Encoding der Seite zufällig schon ISO-8859-1 lautet. Auch der Versuch, durch das künstliche Setzen von Request-Parametern das jeweilige Framework dazu zu bewegen, die URI-kodierten Daten mit ISO-8859-1-Kodierung auf die Leitung zu pusten, schlagen dann fehl. Im Fall von ExtJS wird in dessen Entwicklerforum immer wieder empfohlen, mittels
Ajax.defaultHeaders = {
    "Content-Type": "text/javascript; charset=ISO-8859-1"
}
zu erzwingen, dass die Formulardaten ISO-8859-1-kodiert versendet werden. Dies ist so jedoch nicht möglich. Es ist irrelevant, welches Encoding der Browser vorgibt(!), wenn die im Request enthaltenen Daten dann doch nicht so kodiert sind wie angegeben. Vielmehr ist es nötig, serverseitig richtig mit den Daten umzugehen.

Dazu macht man zunächst serverseitig eine URI-Dekodierung, anschließend eine UTF-8-Dekodierung. Sollen die Daten dann in Systemen verarbeitet werden, die eine ISO-8859-1-Kodierung nutzen, werden die Daten also auch noch ISO-8859-1-kodiert.

Symptome, an denen sich fehlende oder falsche Kodierungen erkennen lassen:

  • Wurde die URI-Dekodierung vergessen, sieht man die Zeichenkette "%20" statt eines Leerzeichens und statt eines "@" sieht man beispielsweise "%40".
  • Wurden Daten URI-dekodiert aber nicht UTF-8-dekodiert, macht sich das bei späterer Anzeige auf einer Website durch die zwei(!) Zeichen "ä" statt einem ä bemerkbar.
  • Hat man sowohl die URI-Dekodierung als auch die UTF-8-Dekodierung vorgenommen, aber die ISO-8859-1-Kodierung vergessen, erscheint an der Weboberfläche statt eines "ä" dann eine nicht eindeutig bestimmbares Zeichen statt eines "ä".
  • Wurde weder die UTF-8- noch die URI-Dekodierung vorgenommen und werden diese Daten dann ISO-8859-1-kodiert zur Anzeige gebracht, erscheint "ä" statt eines "ä". Noch übler wird es, wenn diese Daten von der Web-Oberfläche wiederum innerhalb eines Formulars abgesendet und serverseitig wiederum falsch verarbeitet werden. Die vier Sonderzeichen "ä" würden wiederum falsch verarbeitet und später im Fall einer erneuten Anzeige am UI dann mit acht(!) Sonderzeichen angezeigt...

Übertragen auf das gute alte EVA-Prinzip von Eingabe, Verarbeitung und Ausgabe heißt das: Kann man also nicht die Encoding-Einstellungen der miteinander kommunizierenden Systeme ändern, gilt: Alles, was rein kommt, wird dekodiert, dann verarbeitet und bei der Ausgabe wieder so kodiert, dass das empfangende System damit umgehen kann.

Systemweit UTF-8 zu verwenden ist also ein guter und häufiger Rat. Ein schönes Plädoyer für UTF-8 ist hier zu finden.

Labels: , , , , , , , , , , , , , , ,

2.9.08

Firefox 3.0.x kann keine synchronen Ajax-Calls

Zumindest nicht immer (siehe Update weiter unten): Wie ich gerade schmerzlich feststellen musste, beherrscht Firefox 3.0.1 keine synchronen Ajax-Requests. Das Problem ist bei Mozilla bekannt. Die entsprechenden Bugs sind in der Bugzilla-Datenbank von Firefox gepflegt:
Erster Bug
Zweiter Bug
Dritter Bug

Mit Firefox Version 3.1b1 (zum jetzigen Zeitpunkt hier erhältlich) ist das Problem laut meinen Tests behoben.

(Es gibt jetzt sicher Stimmen, die sagen, dass man Ajax-Requests ohnehin nicht synchron machen sollte und die behaupten, dass es synchrone Aufrufe niemals wirklich erforderlich sind. Ich brauche es definitiv und ich behaupte im Gegenzug, dass diejenigen, die das sagen, einfach noch nicht in der Situation waren, dass synchronen Aufrufe wirklich nötig waren. Warum wohl ist es überhaupt in der XMLHttpRequest-API vorgesehen, dass man den Aufruf synchron machen kann?)

Update: Mit einem Workaround ist es mir gelungen, das Problem für alle wichtigen Browser zu umgehen. Vorher sah der Code so aus:

function determineXyz() {
  var xhr = getXmlHttpRequest();
  var postParams = "isXyz=1";
  xhr.open("POST", "../someBackend.do", false);
  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
      if (xhr.status == 200 || xhr.status == 304) {
        alert(xhr.responseText);
      }
    }
  }
  xhr.send(postParams);
}

Hier wird der onreadystatechange-Handler in Firefox nicht aufgerufen. Seltsamerweise wird er in Firefox 2 dann aufgerufen, wenn Firebug installiert und aktiviert ist. In Firefox 3 wird er auch dann nicht aufgerufen. Ausnahmsweise arbeiten hier sowohl IE6 als auch IE7 fehlerfrei.

Wenn man sich nun nach der inzwischen allzu gewohnten Denke, dass Requests doch so schön asynchron sind jedoch endlich mal wieder klar macht, dass das Senden eines synchronen Requests wie im obigen Code das Ablaufen des Scripts nach dem Aufruf von xhr.send(postParams) blockiert und dass somit nach dieser Zeile das xhr.responseText bereits gefüllt sein muss, ist die Abhilfe klar:

function determineXyz() {
   var xhr = getXmlHttpRequest();
   var postParams = "isXyz=1";
   xhr.open("POST", "../someBackend.do", false);
   xhr.send(postParams);
   // Obige Zeile blockiert solange, bis die Response vom Server
   // vorliegt. Anschließend kann auf xhr.responseText zugegriffen
   // werden:
  alert(xhr.responseText);
}

Es ist also wichtig, zu wissen, dass ein alleiniges Ändern des dritten Übergabeparameters (isAsynchronous) an die Methode open des XMLHttpRequest-Objekts von true nach false ein Script nicht über alle Browser hinweg verlässlich synchron statt asynchron arbeiten lässt.

Ich habe diese Möglichkeit im Mozilla Support Forum gepostet und werde dort das Feedback der anderen hier kommentieren, wenn es noch andere Lösungen gibt.

Labels: , , , , , , , , , ,