Docker image for building ModPagespeed with patches applied

I published my first Docker image for building and experimenting with ModPagespeed. It provides an environment for building the latest stable version v1.14.36.1 for Apache, with some patches applied (including the fix that prevents ModPagespeed from deleting width and height attributes of inlined image elements). The image is based on Ubuntu 12.04.5 and uses GCC 4.8.4 (the same version that the latest stable release was built with).

The image comes with the patched ModPagespeed already built. The modules are found at out/Release/libmod_pagespeed.so and out/Release/libmod_pagespeed_ap24.so. You can modify the source code, build it with ./build.sh and run tests with ./run_tests.sh.

Fix for ModPagespeed: Keep width and height attributes of inlined image elements

I really like ModPagespeed, a module for Apache and nginx that rewrites HTML and resources on the fly to reduce page loading time, layout shifts, etc. Sadly, its development has been discontinued (there’s a branch by one of the main developers, but it doesn’t seem to be alive, either). Even though it may not support every modern HTML or CSS feature, it still works quite well and can be very useful.

One annoying issue I came across is that inlining an image element has an unwanted side-effect. For example, take this image:

<img src="image.png" width="100" height="100">

When ModPagespeed inlines it, this element becomes:

<img src="data:image/png;base64,[...]">

ModPagespeed deletes the width and height attributes if their values match the image’s actual dimensions. The reasoning behind this is that the image’s dimensions are readily available in the data URI. While that’s true, the browser still has to decode the image before knowing the dimensions. It must convert Base64 to binary and then decode the actual image, and modern browsers do this asynchronously. So if the width and height attributes are not specified (or, in this case, have been deleted), the browser doesn’t immediately know the image’s dimensions and cannot allocate space for it in the layout. Consequently, the layout may shift once the decoding is done and the image dimensions are available. In fact, PageSpeed Insights complains about the missing dimensions.

As mentioned in the issue on GitHub, the culprit is the call to the DeleteMatchingImageDimsAfterInline function, which does exactly what its name suggests. Since ModPagespeed is no longer maintained, I decided to fix this myself, thinking that it should be quite easy to remove that call. Unfortunately, building ModPagespeed from source seems to be a complex task, which I didn’t even try (Edit: I did it, see this post!), so my next best option was to apply the fix directly to the binary file. I disassembled the .so file using Binary Ninja. Finding the correct address was difficult using the binary I had on my system, because all symbols (function names) had been stripped. To my luck, ModPagespeed provides unstripped binaries as well in its release archive. In particular, the fix I’m describing here applies to version 1.13.35.1 for Apache. While I haven’t checked the few newer binaries or the binaries for nginx, I’m quite sure that the fix will work there, too.

The C++ code

// Never strip width= or height= attributes from non-img elements.
if (element->keyword() != HtmlName::kImg) {
    return;
}

// (The code that follows ultimately removes the width and height attributes.)

at the beginning of the DeleteMatchingImageDimsAfterInline function, which has been inlined by the compiler, translates to the following x86 instructions:

498b4610           mov     rax, qword [r14+0x10]
8378086c           cmp     dword [rax+0x8], 0x6c
0f84ff030000       je      0x5143e0

The cmp instruction checks whether the element is an <img>, and if so, jumps (je) to the part of the code that ultimately deletes the width and height attributes. So if we can prevent this jump, the attributes will stay.

All that’s needed is to „delete“ the je instruction. But we can’t just delete the 6 bytes that make up the instruction, as this would offset all following code, and other jumps would end up jumping to the wrong address. The easy way to effectively delete the instruction is to overwrite it with nop instructions. On x86, nop is just a single byte, 0x90. So we replace the bytes 49 8b 46 10 83 78 08 6c 0f 84 ff 03 00 00 with the bytes 49 8b 46 10 83 78 90 90 90 90 90 90.

I made sure that ModPagespeed’s .so file only contains a single occurrence of this byte pattern. This is how I performed the actual replacement:

MODULE_PATH=(insert the path to your ModPagespeed .so file here)

# Make a backup copy of the original .so file.
cp -p "$MODULE_PATH" "$MODULE_PATH".original

# Patch the module.
xxd -p -c 1 "$MODULE_PATH" | tr -d '\n' | sed 's/498b46108378086c0f84ff030000/498b46108378086c909090909090/g' | fold -w 1 | xxd -r -p -c 1 - "$MODULE_PATH".patched
chown --reference="$MODULE_PATH" "$MODULE_PATH".patched
chmod --reference="$MODULE_PATH" "$MODULE_PATH".patched

# Print the changed bytes.
diff --changed-group-format='%<' --unchanged-group-format='' <(xxd -p -c 1 "$MODULE_PATH") <(xxd -p -c 1 "$MODULE_PATH".patched)

Now make sure that the last output shows exactly 6 bytes, they should be:

0f
84
ff
03
00
00

If that's the case, we can stop Apache (or nginx), replace the module and start the web server again:

apache2ctl stop
sleep 10
mv "$MODULE_PATH".patched "$MODULE_PATH"
apache2ctl start

When testing, remember that ModPagespeed can take some time until resources are optimized. If everything works, you should now see inlined images like this:

<img src="data:image/png;base64,[...]" width="100" height="100">

Enjoy!

In the future, I might invest some more time into trying to build ModPagespeed from source - there's at least one more thing I'd like to fix ...

Edit: I did that, see this post.

Lösung für langsamen Zugriff auf Dateien in Dropbox-Ordnern

Ich nutze Dropbox intensiv und habe auch einige Python-Skripte in meiner Dropbox liegen, die auf Dateien zugreifen, die ebenfalls in der Dropbox liegen. Vor einiger Zeit fiel mir auf, dass diese Zugriffe extrem langsam wurden. Selbst das Starten eines leeren Python-Skripts konnte im Extremfall mal locker 8 Sekunden dauern. Auch mein Texteditor brauchte Ewigkeiten, um zu starten, wenn ich Dateien aus der Dropbox geöffnet hatte und diese Tabs beim Starten wiederhergestellt wurden.

Nun habe ich den Grund für dieses Problem gefunden: Scheinbar war eine wichtige Änderung bei Dropbox an mir vorübergegangen – und zwar wurde irgendwann eine Option eingeführt, neu angelegte Dateien nur noch online (also in der Cloud) zu speichern und nicht mehr lokal (bzw. nur wenn man sie einmal öffnet, aber nicht unbedingt permanent). Zu meinem Erstaunen war diese Option bei mir aktiviert, möglicherweise ist das sogar die Standardeinstellung. Das bedeutet natürlich, dass man keinen Zugriff auf seine Dateien hat, wenn man offline ist, und erklärt auch, warum der Dateizugriff so langsam ist. Eine weitere Option zum automatischen Sparen von Festplattenspeicher, die ebenfalls aktiviert war, macht lokale „selten genutzte Dateien“ nur noch online verfügbar, löscht sie also lokal.

Nach dem Ausschalten der Option war das Problem gelöst. Ich empfehle jedem, der nicht unter akutem lokalem Speicherplatzmangel leidet, folgende Einstellungen zu nutzen:

Der Zugriff auf Dateien in der Dropbox ist dann wieder so schnell wie gewohnt, und man hat kein Problem, wenn man mal offline ist.

Nachtrag: Dropbox unterscheidet zwischen drei Verfügbarkeits-Modi: „nur online verfügbar“, „offline verfügbar“ und „immer offline verfügbar“. Im Windows Explorer wird ersteres mit einem Wolken-Icon dargestellt, und die beiden anderen Modi mit einem Häkchen. Weißer Hintergrund ist „offline verfügbar“, grüner Hintergrund ist „immer offline verfügbar“. Der Unterschied ist wohl, dass im ersten Fall nicht garantiert ist, dass die Datei offline verfügbar bleibt. Dropbox könnte sie lokal löschen. Leider kann man nicht zwischen diesen beiden Modi hin und her schalten. Um Dateien/Ordner „immer offline verfügbar“ zu machen, muss man sie zuerst „nur online verfügbar“ und anschließend wieder „offline verfügbar“ machen. Das geht im Windows Explorer über das Kontextmenü. Die Änderung des Modus kann sehr lange dauern. Ich habe das jetzt für meine komplette Dropbox gemacht, indem ich den Dropbox-Ordner selbst umgestellt habe, was locker 2 Stunden gedauert hat.

Einfache Lösung für IKEA-Bug „Die Geschenkkarte konnte nicht hinzugefügt werden“

Gerade hatten wir eine sehr frustrierende Erfahrung mit dem IKEA-Onlineshop. Wir hatten eine größere Menge von Geschenkkarten, die wir einlösen wollten. Immer exakt nach dem Einlösen von 6 Geschenkkarten erschien die Fehlermeldung „Die Geschenkkarte konnte nicht hinzugefügt werden“. Nach langem Herumprobieren (eine bestimmte Zeit warten, Cookies löschen, neue IP-Adresse holen, …) fanden wir dann die sehr einfache Lösung: Es reicht aus, in einem neuen Browser-Tab einmal die IKEA-Website neu aufzurufen (konkret habe ich die Seite „FAQ & Kontakt“ geladen, aber das spielt wahrscheinlich keine Rolle). Danach ist das Einlösen von Geschenkkarten sofort wieder möglich!

WordPress-Update auf Server hinter Proxy

Kürzlich musste ich feststellen, dass das Aktualisieren von WordPress auf einem Server, der nur über einen Proxy nach draußen kommunizieren kann, ziemlich problematisch sein kann. Eigentlich liest man überall, dass es ausreicht, in der Datei wp-config.php die beiden Optionen WP_PROXY_HOST und WP_PROXY_PORT korrekt zu setzen. In meinem Fall war das nicht genug. WordPress konnte dann zwar Informationen über verfügbare Aktualisierungen und Plug-ins abrufen, diese jedoch nicht herunterladen („Download fehlgeschlagen“). Nach schier endlosem Herumprobieren und Suchen fand ich schließlich den entscheidenden Hinweis: Man muss das Paket php-curl installieren. Danach klappte alles prima.

DSGVO/GDPR: Speichern von IP-Adressen durch PHP-Anwendungen und in Server-Logdateien verhindern

Ein wichtiger Hinweis im Voraus: Allein durch die hier dargestellten Methoden hat man nicht automatisch alle Vorgaben der DSGVO erfüllt – dazu gehört deutlich mehr! Ich möchte hier nur ein paar Tipps geben, wie gewisse Teilprobleme ohne viel Aufwand gelöst werden können.

Da in knapp einem Monat die neue Datenschutz-Grundverordnung (DSGVO) bzw. General Data Protection Regulation (GDPR) zur Anwendung kommt, muss sich so manch ein Webmaster Gedanken darüber machen (idealerweise bereits gemacht haben), wie er seinen PHP-Anwendungen (WordPress, CMS, Forum, Wiki, …) beibringt, nicht mehr die IP-Adressen der Besucher zu speichern. Die DSGVO definiert IP-Adressen nämlich explizit als personenbezogen. Sofern man sich nicht glaubhaft auf ein berechtigtes Interesse berufen kann, sollte man diese Adressen somit lieber nicht unnötigerweise speichern. Je weniger man speichert, desto weniger Angst muss man vor eventuellen Bußgeldern haben und desto weniger Schaden kann bei einem Datendiebstahl entstehen.

Nun kann man entweder für jede seiner Anwendungen ein Plug-in suchen oder selber schreiben, oder man versucht es gleich mit einem radikaleren Ansatz. Zumindest für PHP-Anwendungen gibt es eine sehr einfache und „idiotensichere“ Lösung, und zwar die php.ini-Variable auto_prepend_file. Wenn man hier eine PHP-Datei einträgt, dann wird diese am Anfang jedes ausgeführten PHP-Skripts geladen, so als ob sie mit require() eingebunden worden wäre. Und genau das können wir nutzen, um die Variable $_SERVER["REMOTE_ADDR"], in der PHP die IP-Adresse des Besuchers ablegt, zu verändern, bevor die PHP-Anwendung sie zu Gesicht bekommt. Also schreiben wir in unsere php.ini oder eine spezielle Datei im conf.d-Verzeichnis:

auto_prepend_file = /pfad/zur/anonymize_ip.php

Die Datei anonymize_ip.php enthält den Code, der die IP-Adresse anonymisiert, indem das letzte Oktett auf 0 gesetzt wird (mein Server unterstützt noch kein IPv6, also behandle ich erst einmal nur IPv4-Adressen – wer IPv6 unterstützt, der sollte den Code anpassen und einen deutlich größeren Teil der Adresse auf 0 setzen):

<?php

if (isset($_SERVER["REMOTE_ADDR"]))
{
    $ip = $_SERVER["REMOTE_ADDR"];
    $_SERVER["ORIGINAL_REMOTE_ADDR"] = $ip;
    $octets = explode(".", $ip);
    if (count($octets) == 4)
    {
        $octets[3] = "0";
        $_SERVER["REMOTE_ADDR"] = implode(".", $octets);
    }
}

?>

Auf diese Weise muss man die PHP-Anwendung nicht anpassen. Sie bekommt von vornherein nur eine gekürzte IP-Adresse zu Gesicht, die dann nicht mehr personenbezogen ist. Man muss also nicht mühsam alle Stellen suchen, an denen die Anwendung die IP-Adresse benutzt. Den Trick mit dem Kürzen der IP-Adresse benutzt übrigens auch Google Analytics, um die Datenschützer zufriedenzustellen. Probleme mit den PHP-Anwendungen sind dabei eigentlich nicht zu erwarten, denn die gekürzten IP-Adressen sind immer noch gültig, und es ist sowieso nicht unüblich, dass mehrere Besucher unter derselben IP-Adresse unterwegs sind (Universitäten, Firmen, …). Und falls man in gewissen Fällen doch mal die komplette IP-Adresse benötigt, dann kann man immer noch über $_SERVER["ORIGINAL_REMOTE_ADDR"] auf sie zugreifen.

Doch was ist mit den Server-Logdateien? In der Standardkonfiguration loggen Webserver wie Apache jede Anfrage in einer Logdatei. Das sieht dann beispielsweise so aus:

89.245.114.71 - - [27/Apr/2018:16:49:53 +0200] "GET /impressum-datenschutz/ HTTP/1.1" 200 10408 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"

Auch hier wird die IP-Adresse gespeichert (im Beispiel meine eigene, also darf ich das), und das ist möglicherweise problematisch. Hier gibt es mehrere Lösungen:

  • Die meisten Webserver (wenigstens Apache und nginx) erlauben die Änderung des Logformats. Möchte man keine IP-Adressen loggen, dann ist das kein Problem – man lässt das entsprechende Feld einfach weg.
  • Möchte man nicht komplett auf IP-Adressen verzichten, sondern sie in gekürzter Form (so wie oben beschrieben) loggen, dann geht das auch – zumindest mit Apache. Man kann nämlich Piped Logs benutzen. Anstatt in eine Datei zu loggen, wird ein separater Prozess gestartet, der die Logzeilen über seine Standardeingabe erhält. Nun muss man nur ein kleines Tool schreiben, das Zeile für Zeile liest, die IP-Adressen kürzt und das Ergebnis in eine Logdatei schreibt.
  • Bei den Server-Logdateien stehen die Chancen gut, dass man sich auf ein berechtigtes Interesse berufen kann, um die IP-Adressen zumindest für eine begrenzte Zeit in ungekürzter Form zu speichern, denn sie bieten mehr oder weniger die einzige Möglichkeit, Angriffe nachvollziehen und gegen sie vorgehen zu können. Man könnte sich also ein kleines Programm schreiben, das über einen Cronjob jeden Tag über die Logdateien läuft und bei allen Einträgen, die älter sind als z. B. eine Woche, die IP-Adressen kürzt. In Verbindung mit Logrotation ist das besonders einfach.

Ich hoffe, dieser Artikel konnte bei dem einen oder anderen die DSGVO-Kopfschmerzen zumindest ein bisschen lindern.

Maximale Vermasselung bei der Netbank — Finger weg!

Seit einigen Monaten habe ich ein Geschäftskonto bei der Netbank. Es fing schon merkwürdig an: Das Datum, an dem ich mein Gewerbe angemeldet habe, wurde als Geburtsdatum ins System eingetragen. Darum war ich für das System noch minderjährig und konnte mit meinem neuen Konto erst einmal nicht viel anfangen. Erst nach längerem Nachfragen wurde mir mitgeteilt, dass ich andere Zugangsdaten benutzen muss als die, die mir gegeben wurden. Mit den neuen Zugangsdaten funktionierte das Banking dann.

Ein Grund, warum ich mich für die Netbank entschieden hatte, war die Möglichkeit der Einlösung von Schecks (bei manch anderer reiner Online-Bank geht das nicht). Doch als ich von dieser Möglichkeit erstmals Gebrauch machte, wurde ich gleich böse überrascht: Bei einem US-Scheck über umgerechnet knapp 430 € fielen saftige Gebühren von 62 € an, die sich anhand des Preisverzeichnisses nicht nachvollziehen ließen. Bei meiner Raiffeisenbank ist das wesentlich günstiger, da bezahle ich für die gleichen Schecks 12,50 €. Also war dieses „Feature“ schon mal für die Tonne.

Apropos „für die Tonne“: Die Online-Banking-Oberfläche der Netbank war bis vor Kurzem noch unglaublich hässlich. Sie schien (ohne Witz) für eine feste Bildschirmauflösung von 800×600 Pixel ausgelegt zu sein, und dementsprechend erinnerte auch das Bedienkonzept an eine Website aus den Neunzigern. Der Funktionsumfang ließ ebenfalls zu wünschen übrig, z. B. konnte man keine angekündigten SEPA-Lastschriften anzeigen (das geht auch jetzt noch nicht). Das Online-Banking-Interface war zwar schlecht — richtig schlecht — aber immerhin funktionierte es, und es gab auch sonst keine Probleme.

Vor ungefähr einem Monat bekam ich per Post eine protzige Hochglanz-A4-Mappe von der Netbank geschickt, in der eine neue Online-Banking-Oberfläche angekündigt wurde. Die abgedruckten Screenshots der neuen Oberfläche weckten Vorfreude in mir. Würde nun alles besser werden? Davon ging ich aus. Die neuen Zugangsdaten bekam ich etwas später ebenfalls per Post geschickt (da hatte ich Glück, wie sich später herausstellen sollte).

Am 9. Oktober loggte ich mich erstmals in die neue Online-Banking-Oberfläche ein. Mit den neuen Zugangsdaten war das kein Problem. Das neue Design ist modern und responsive — so, wie man es im Jahr 2017 erwarten würde. Es fehlten zwar noch einige Funktionen, aber die sollten später freigeschaltet werden. OK, damit hätte ich leben können.

Als ich jedoch zum ersten Mal nach der Umstellung in meiner Buchhaltungssoftware meine Kontoumsätze via HBCI/FinTS abrufen wollte, kam eine böse Überraschung: Die HBCI/FinTS-Implementierung wurde scheinbar ebenfalls ersetzt, und zwar durch eine maximal Unbrauchbare. Es fehlen wichtige Felder wie Sender/Empfänger der Zahlung, Teile des Verwendungszwecks sowie eine eindeutige ID für jede Transkation. Insbesondere Letztere ist nötig, um Duplikate zu erkennen. Ohne diese eindeutige ID werden dieselben Transaktionen bei jedem Abruf immer wieder als neue Transaktionen erkannt und erscheinen doppelt, dreifach, vierfach, …, in der Banking-Software.

Schnell fand ich im Internet weitere Kunden, die von diesem und ähnlichen Problemen berichteten. Noch weitaus schlimmer als die nicht funktionierende HBCI/FinTS-Implementierung ist jedoch, dass seit der Umstellung keine SEPA-Lastschriften mehr funktionieren (kann ich selbst bezeugen). Wenn also beispielsweise das Finanzamt die Umsatzsteuer vom Konto abbuchen möchte, dann schlägt das fehl, und man hat Ärger am Hals. Auch eingehende Zahlungen funktionieren nicht mehr. Viele Kunden dürfen sich darum mit fehlgeschlagenen Transkationen und eventuell anfallenden Gebühren herumärgern. Besonders hart trifft es Online-Händler, die mit PayPal arbeiten, denn der Netbank-Server gibt dem PayPal-Server die Auskunft, dass das Konto geschlossen wurde, woraufhin es aus dem PayPal-Konto entfernt wird. Dabei gehöre ich wohl noch zu den Glücklicheren, denn ich habe zumindest meine neuen Zugangsdaten per Post erhalten. Viele Kunden berichten, dass sie ihre Zugangsdaten nie erhalten haben. Die Service-Hotline ist wohl völlig überlastet — wenn man es überhaupt in die Warteschleife schafft, dann darf man dort über eine Stunde verharren.

Was die Netbank verschwiegen hatte: Scheinbar wurde nicht nur die Online-Banking-Oberfläche ausgetauscht, sondern ein Großteil der IT-Infrastruktur der Bank. Zu allem Überfluss wurde auch noch die BIC geändert. Letzteres wurde aber kaum irgendwo erwähnt, und die neue BIC scheint sich noch nicht „herumgesprochen“ zu haben, denn in vielen Fällen wird sie zurückgewiesen.

Neben den massiven technischen Problemen finde ich eines aber noch viel schlimmer: Bis jetzt (11. Oktober, 09:30 Uhr) gibt es weder auf der Netbank-Website noch auf Facebook eine offizielle Stellungnahme — und das, obwohl die Bank seit mehreren Tagen von den Problemen weiß. Schlechter geht es wohl kaum! Eine einfache Meldung würde doch schon ausreichen, um den Kunden die Gewissheit zu geben, dass an der Lösung der Probleme gearbeitet wird. Das würde auch den Druck auf die Hotline reduzieren. Lustigerweise klettert der „Gefällt mir“-Zähler der Netbank auf Facebook stetig weiter — alles klar!

Zusammenfassung des Netbank-Desasters:

  • Online-Banking-Oberfläche umgestellt, aber vielen Kunden ihre neuen Zugangsdaten nicht mitgeteilt
  • HBCI/FinTS funktioniert nicht mehr richtig, was es unmöglich macht, das Konto aus einer Software heraus zu verwenden
  • BIC geändert, ohne ordentlich darauf hinzuweisen und sie auf korrekte Weise publik zu machen, wodurch sie von anderen Systemen nicht akzeptiert wird
  • SEPA-Lastschriften werden zurückgebucht, wodurch (Mahn-)Gebühren und Zinsforderungen entstehen
  • Eingehende Zahlungen funktionieren nicht mehr
  • Service-Hotline völlig überlastet
  • Seit Tagen keine einzige Stellungnahme der Bank

Die Umstellung bei der Netbank wurde jedenfalls massiv vermasselt. Es muss ein absoluter Albtraum für diejenigen sein, die das zu verantworten haben, und auch für diejenigen, die es ausbaden dürfen (Kunden und Kundendienst). Der Schaden für die Bank dürfte groß sein, denn nun werden viele Kunden abwandern. Ich bin einer davon.

Fazit: Finger weg von der Netbank! Wer so laienhaft arbeitet und sich so wenig um seine Kunden schert, der hat absolut kein Vertrauen verdient.

Nachtrag (13. Oktober 2017): Mittlerweile gibt es zumindest eine (wenn auch gut versteckte) Stellungnahme der Netbank. Dort werden die Probleme aber heruntergespielt, und vom nicht mehr brauchbaren HBCI/FinTS ist keine Rede. Die Netbank will jedem Kunden pauschal 5 € pro fehlgeschlagener Lastschrift gutschreiben. Das reicht aber nicht, um gewöhnliche Strafgebühren abzudecken. Der Kundendienst ist weiterhin nicht erreichbar. Mittlerweile habe ich ein Konto bei der Fidor Bank eröffnet. Leider musste ich feststellen, dass dort gar kein HBCI/FinTS angeboten wird, sondern lediglich eine API, die aber von kaum einer Software unterstützt wird. Mein Fehler, dass ich davon ausging, dass eine Bank, die ein Geschäftskonto anbietet, auch HBCI/FinTS anbietet.

Nachtrag (10. Mai 2019): Tja, ich bin immer noch bei der Netbank, und ich muss sagen, dass ich mittlerweile gar keinen Grund mehr habe mich zu beschweren. Es gab nie mehr Probleme mit Zahlungen, die Gebühren bei eingehenden Zahlungen in Fremdwährungen sind akzeptabel, und das Interface funktioniert so, wie es soll.

Echtzeitanalyse von Apache-Logs ohne Umweg über das Dateisystem

Ich wollte mir schon seit langer Zeit ein Tool schreiben, mit dem ich meine Apache-Logs mehr oder weniger in Echtzeit analysieren kann (z. B. um Angriffe zu erkennen). Mein erster Ansatz wäre zunächst gewesen, alle paar Minuten ein Skript laufen zu lassen, das alles, was seit dem letzten Lauf in die Log-Dateien geschrieben wurde, verarbeitet. Aber so richtig schön wäre das nicht gewesen, ginge das doch unnötigerweise über das Dateisystem. Das Rotieren der Log-Dateien hätte ebenfalls speziell behandelt werden müssen.

Dann sah ich, dass mit der CustomLog-Direktive von Apache nicht nur normale Dateien als Log-Ziel angegeben werden können, sondern es auch möglich ist, die Log-Ausgaben über eine Pipe in einen anderen Prozess zu leiten („Piped Logs“). Dazu gibt man den Pfad zur ausführbaren Datei eines Programms an, das von Apache gestartet wird und dann die Log-Ausgaben über seine Standardeingabe erhält, zum Beispiel so:

CustomLog "||/pfad/zum/programm" "<%h|%D>"

Der zweite Teil der Direktive definiert das Log-Format. Im Beispiel werden nur die IP-Adresse des Aufrufers und die zur Verarbeitung benötigte Zeit geloggt. Das ist noch ein Vorteil gegenüber der Analyse von Log-Dateien: Man kann genau auswählen, welche Daten man zur Echtzeitverarbeitung benötigt. Warum ich die Zeichen < und > am Anfang und am Ende benutze, erläutere ich weiter unten.

Nun könnte man die Log-Ausgaben direkt in diesem einen Programm verarbeiten. Jedoch wird für jeden Apache-Elternprozess eine separate Instanz dieses Programms gestartet, also laufen normalerweise viele davon parallel. Das würde es schwierig machen, die Daten zusammenzuführen.

Um eine zentrale Verarbeitung zu ermöglichen, erstelle ich ein FIFO (mkfifo) und starte ein separates Auswertungsprogramm, das stets im Hintergrund läuft. Das Programm, das von Apache gestartet wird, wird nur zum Sammeln der Daten benutzt und heißt daher von nun an „Sammelprogramm“. Alle Instanzen des Sammelprogramms schreiben ins FIFO, und das Auswertungsprogramm liest daraus und analysiert die Daten.

Ein paar Hinweise, falls das jemand nachbauen möchte:

  • Da viele Instanzen des Sammelprogramms parallel laufen werden, sollte man es möglichst speichersparend implementieren. Ich habe dafür C++ genommen statt wie üblich Python.
  • Wenn das Auswertungsprogramm nicht läuft (z. B. weil es abgestürzt ist), werden die Sammelprogramme irgendwann hängen bleiben. Das ist nicht gut, weil dann auch der jeweilige Apache-Prozess beim Loggen hängen bleibt. Zum Glück kann man die Situation erkennen, indem man das FIFO nicht-blockierend öffnet (in C/C++ mit dem Flag O_NONBLOCK). Das Öffnen schlägt dann einfach fehl statt zu blockieren.
  • Das Auswertungsprogramm sollte das FIFO zuerst zum Lesen öffnen und danach zusätzlich auch zum Schreiben. Somit verhindert man, dass es irgendwann keinen Prozess mehr gibt, der das FIFO zum Schreiben geöffnet hat (dann passieren seltsame Dinge).
  • Wenn mehrere Prozesse parallel in ein FIFO schreiben (so wie es hier der Fall ist), ist es nur unter gewissen Umständen garantiert, dass die Daten jeweils am Stück geschrieben werden. Theoretisch kann es passieren, dass das Auswertungsprogramm zuerst einen Teil der von Prozess A geschriebenen Daten liest und dann einen Teil der von Prozess B geschriebenen Daten („interleaved“). Um solche Fälle zu erkennen, verwende ich die Zeichen < und > am Anfang und am Ende jeder ins FIFO geschriebenen Zeile. Somit kann man erkennen, ob man eine unvollständige Zeile gelesen hat. Solange man nur Daten am Stück schreibt, die höchstens PIPE_BUF Bytes groß sind (Linux: 4096), sollte dies jedoch nie passieren.

Ich bin bisher sehr zufrieden mit dieser Vorgehensweise und kann sie nur weiterempfehlen.

Lösung für Facebook-Fehlermeldung „This Page isn’t eligible to have a username.“

Wer hin und wieder Facebook-Seiten erstellt, kennt vielleicht das Problem, dass man manchmal einfach keinen Benutzernamen für die Seite vergeben kann. Das Problem tritt wohl willkürlich auf. Ich selbst hatte zwei fast identische Seiten kurz hintereinander erstellt. Bei der einen klappte die Vergabe des Benutzernamens problemlos, bei der anderen wurde ich stets mit der Fehlermeldung „This Page isn’t eligible to have a username.“ abgespeist. Wenn man im Netz danach sucht, findet man viele verzweifelte Leute und auch Lösungsansätze, die aber nicht immer vollständig sind. Mit der folgenden Methode hat es bei mir auf Anhieb geklappt:

  1. Bitte einen Freund um Hilfe, oder lege dir kurzerhand einen neuen Account auf Facebook an.
  2. Wichtig ist, dass der Hilfsaccount (Account des Freundes oder neuer Account) eine Telefonnummer angegeben hat, die per SMS bestätigt ist! Ich konnte ohne Probleme dieselbe Telefonnummer verwenden, die ich auch für meinen echten Account benutze. Man bekommt dann lediglich per E-Mail eine Warnung, dass ein anderer Account dieselbe Nummer verwendet hat, und wenn man die Zwei-Faktor-Authentifizierung mittels SMS aktiviert hat, wird sie sicherheitshalber deaktiviert, lässt sich danach aber wieder aktivieren.
  3. Trage in den Einstellungen der Seite, für die du den Benutzernamen vergeben möchtest, unter „Rollen für die Seiten“ den Hilfsaccount als zusätzlichen Administrator der Seite ein. Der Hilfsaccount erhält daraufhin eine Einladung, die er akzeptieren muss.
  4. Der Hilfsaccount sollte nun in der Lage sein, der Seite einen Benutzernamen zu geben. Bei meinem ersten Versuch bekam ich nun die leicht anderslautende Fehlermeldung „You’re not eligible to create a username.“, da ich den Account noch nicht mittels SMS bestätigt hatte. Nachdem ich das nachgeholt hatte, lief alles reibungslos.
  5. Zum Abschluss sollte der Hilfsaccount seine Administratorrolle wieder aufgeben.

Ich hoffe, dass dies dem einen oder anderen hilft!