There’s a .̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̨̨̨̨̨̨̨̨̨̨̨̨.̸̸̨̨ crack in your Twitter

Vor zwei Tagen begann auf Twitter ein Mem:

Dylann (dailydylann) on Twitter.jpg
2011-04-27 von @dailydylann

Ich wurde gebeten, dieses lustige Symbol kurz zu erklären, und da das nicht auf Twitter passt, hier der Blogpost.

Schauen wir uns den interessanten Teil des Tweets mal genau an:

00000000  2e cc b8 cc b8 cc a8 cc  b8 cc b8 cc a8 cc b8 cc  |................|
00000010  b8 cc b8 cc a8 cc b8 cc  b8 cc a8 cc b8 cc b8 cc  |................|
00000020  a8 cc b8 cc a8 cc b8 cc  b8 cc a8 cc b8 cc b8 cc  |................|
*
00000040  a8 cc b8 cc a8 cc b8 cc  b8 2e cc a8 cc b8 cc b8  |................|

Das 2e am Anfang ist ein ganz normaler Punkt – “.”. Danach wird’s spannend: Es folgen Kombinationen aus zwei UTF-8-Zeichen: Hexadezimal 0xCCB8: Unicode-Zeichen U+0338 “COMBINING LONG SOLIDUS OVERLAY” (Solidus = Schrägstrich); und hexadezimal 0xCCA8: Unicode-Zeichen U+0328 “COMBINING OGONEK” (Ogonek = Schwänzchen). Dann folgt nochmal ein Punkt und nochmal ein paar dieser “COMBINING”-Zeichen. Das “COMBINING” bedeutet, dass die Zeichen nicht für sich allein stehen, sondern zu dem Zeichen gehören, das vor ihnen steht, und sich mit diesem, nun ja, kombinieren.

Ich habe diese beiden Zeichen hier nochmal “allein” dargestellt, also über ein Leerzeichen gelegt:

U+0338  ̸ COMBINING LONG SOLIDUS OVERLAY
U+0328  ̨ COMBINING OGONEK

Und hier nochmal als Beispiel für die “Kombination” über den Buchstaben “a” gelegt:

a̸ = a +  ̸
ą = a +  ̨
a̸̸̸̸ = a +  ̸ +  ̸
ą̸̸̸ = a +  ̸ +  ̸ +  ̨ +  ̸

Und mit der Kombination aus dem Original-Tweet:

ą̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̸̨̨̨̨̨̨̨̨̨̨̨ = a +  ̸ ̸ ̨ ̸ ̸ ̨ ̸ ̸ ̸ ̨ ̸ ̸ ̨ ̸ ̸ ̨ ̸ ̨ ̸ ̸ ̨ ̸ ̸ ̨ ̸ ̨ ̸ ̸ ̨ ̸ ̸ ̨ ̸ ̨ ̸ ̸

Die Kombination aus mehrfachen “COMBINING”-Zeichen akkumuliert also – an ein Zeichen mit Häkchen unten wird ein weiteres Häkchen angehängt. Damit wächst das Zeichen natürlich in der Höhe. Durch die Slashes wird die Fläche “ausgefüllt”, durch die Häkchen wird es nach unten erweitert.

Für mehr Spaß mit Unicode empfehle ich als Einstieg den Wikipedia-Artikel zu UTF-8. Viel Spaß beim Durchlesen sämtlicher Unicode-Planes – Unicode kodiert momentan ja nur 1.112.064 Zeichen.

PS: UTF-8 ist leider immer noch nicht perfekt umgesetzt. Schaut man sich z.B. den Original-Tweet über den obigen Link an, sieht man die einzelnen Zeichen statt der Kombination, weil Twitter offenbar eine Dekombination in ihrer Pipeline haben. In der User-Timeline und in Twitter-Clients sieht es aber richtig aus.

Managing multiple mod_passenger standalone configurations

If you’re deploying multiple Ruby/Rails apps with passenger, you might end up with different ruby version requirements. Or maybe you want to keep gemsets separate. For whatever reason, you end up running one or more of your apps in a standalone passenger server and proxying them through Apache or nginx.

Or maybe you’re doing mass webhosting with only passenger standalone instances. Whatever floats your boat.

This is the script I hacked together for Toptranslation. Instructions below.

#!/bin/bash

### BEGIN INIT INFO
# Provides:          passenger-standalone
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Easily start or stop standalone passenger instances
### END INIT INFO

# default config
USER=www-data
APP_DIR=
RVM_RUBY=1.9.2
ADDR=127.0.0.1
PORT=3000
ENVIRONMENT=production

if [ -n "$2" -a -r "/etc/passenger-standalone.d/$2" ]; then
  CONFIG="$2"
elif [ -r "/etc/passenger-standalone.d/$(basename $0)" ]; then
  CONFIG="$(basename $0)"
fi

if [ -n "$CONFIG" ]; then
  if [ ! -r "/etc/passenger-standalone.d/$CONFIG" ]; then
    echo "Error: Configuration '${CONFIG}' not found in /etc/passenger-standalone.d/" >&2
    exit 4
  fi
  source "/etc/passenger-standalone.d/$CONFIG"
  if [ -z "$APP_DIR" ]; then
    echo "Error: APP_DIR is not defined."
    exit 5
  fi
  CMD_START="cd $APP_DIR; rvm use $RVM_RUBY; passenger start -a $ADDR -p $PORT -e $ENVIRONMENT -d"
  CMD_STOP="cd $APP_DIR; rvm use $RVM_RUBY; passenger stop -p $PORT"
fi

. /lib/lsb/init-functions
case "$1" in
  start)
    echo "Starting passenger for ${CONFIG}"
    echo "[$USER] $CMD_START"
    su - $USER -c "$CMD_START"
    ;;
  stop)
    echo "Stopping passenger for ${CONFIG}"
    echo "[$USER] $CMD_STOP"
    su - $USER -c "$CMD_STOP"
    ;;
  *)
    echo "Usage: $0 start|stop [config]" >&2
    exit 3
    ;;
esac

To use, first put it into /etc/init.d/passenger-standalone. Then, for each standalone instance you want to start, create a configuration file in /etc/passenger-standalone.d/ with a sensible name. For example, here’s a redmine configuration:

# /etc/passenger-standalone.d/redmine 
USER=www-data
APP_DIR=/opt/redmine
RVM_RUBY=1.8.7-p330
ADDR=127.0.0.1
PORT=3081
ENVIRONMENT=production

And here is the corresponding VirtualHost for Apache:

<VirtualHost *:80>
  ServerName redmine.example.com
  DocumentRoot /opt/redmine/public

  PassengerEnabled Off
  ProxyRequests Off
  <Proxy *>
    Order deny,allow
    Allow from all,
  </Proxy>
  ProxyPass / http://127.0.0.1:3081/
  ProxyPassReverse / http://127.0.0.1:3081/
</VirtualHost>

Now, when you reload apache with that config and try to access redmine.example.com, you will get an Error 500 because the passenger instance is not running yet. To start it, simply run /etc/init.d/passenger-standalone start redmine, OR symlink /etc/init.d/passenger-standalone to /etc/init.d/redmine and run /etc/init.d/redmine start.

To automatically run the script in the appropriate runlevels, symlink it as described, then run update-rc.d redmine defaults. This will set up the appropriate symlinks.

Update: Fixed a backtick that wasn’t supposed to be there.

On computers

The most amazing achievement of the computer software industry is its continuing cancellation of the steady and staggering gains made by the computer hardware industry.
      — Henry Petroski

Android Developer Labs Berlin 2010

Am Dienstag, dem 2. November 2010, fand in Berlin das Google Android Developers Lab in den Räumen der Beuth-Hochschule statt. Geladen wurde kaum mehr als eine Woche vor dem Event, mit teils großen Ankündigungen wie “Android 3.0 Dev Lab” auf XING. Natürlich meldete ich mich als Entwickler mobiler Webseiten und Applikationen sofort an, wie auch viele meiner Freunde.

Zu nachtschlafender Zeit ging es also mit dem ICE nach Berlin, um rechtzeitig um 9 Uhr in Berlin-Wedding anzukommen. Leider landete ich durch eine Kombination aus Zugverspätungen und Verwirrung erst im Zug nach Stuttgart, so dass ich dann – nach Umsteigen in Hannover – doch erst gegen Mittag in Berlin ankam. Aber das sollte nur passende Einstimmung für den Tag sein.

An der Hochschule angekommen war der Wegweiser schnell erspäht, Gebäude und Raum schnell gefunden. Der Zugang zum WLAN dauerte dafür dann um so länger – offenbar war die Authentifizierungs-Infrastruktur überlastet, und auch ein hinzugerufener Techniker der TU konnte nichts weiter tun, als allen zu versichern, dass es wohl funktionieren solle. In der Mittagspause fing sich dann auch die Authentifizierung.

Gerade rechtzeitig, denn ohne Internet wäre es vermutlich zu Ausschreitungen oder Einschlafen gekommen: An Stelle des mehr oder minder offen angekündigten Android 3.0 “Gingerbread” wurden ältere Themen aufgewärmt, teilweise ältere Talks recyclet. Auf Nachfragen des Publikums stritten die Googler jede Kenntnis von “Gingerbread” ab – zumindest auf der Bühne. Im kleineren Kreis konnte man hören, dass man intern bis zuletzt hoffte, Gingerbread endlich öffentlich vorstellen zu können, das System aber nicht rechtzeitig fertig geworden sei. Statt dessen wurde über Visual Design bei Android-Entwicklung (wichtig, aber meiner Meinung nach an der Zielgruppe vorbei) geredet, es wurde vom NDK erzählt (nichts neues, ein paar kleine Ankündigungen), aber der Großteil der Leute, die ich gesehen habe, nutzten die Zeit statt dessen, um z.B. an ihren Android-Apps zu schrauben, Blinkendroid-Matrixen zu bauen, oder Twitter zu lesen. Auch nicht das Schlimmste, aber ich hatte mir von dem Tag mehr versprochen.

In other news, vor dem Google Headquarter steht seit einigen Tagen der Gingerbread Man. Vielleicht klappt’s also zum Google Developer Day am Dienstag.

Using BIGINT columns in rails migrations

Sometimes, you need to use BIGINT columns in your databases, e.g. when working with Twitter whose IDs are over 32 bits long now, and will be 64 bits long soon.

After some googling which didn’t bring up any good results, a quick look into the adapter code helps us:


# activerecord-3.0.0/lib/active_record/connection_adapters/mysql_adapter.rb
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
  return super unless type.to_s == 'integer'

  case limit
  when 1; 'tinyint'
  when 2; 'smallint'
  when 3; 'mediumint'
  when nil, 4, 11; 'int(11)'  # compatibility with MySQL default
  when 5..8; 'bigint'
  else raise(ActiveRecordError, "No integer type has byte size #{limit}")
  end
end

So, use integer with :limit => 8 to get a bigint column. If you need a primary key of type bigint, you need to pass :id => false to your create_table call, then define the id column manually:


class CreateDemo  false do |t|
      t.integer :id, :limit => 8
    end
  end
end

Fighting frameworks

Rant time!

So, back when I was still doing PHP, the story went like this: “We have this kinda okay framework which is documented okay and there are almost no plugins or contributions, so you’re on your own, and if things don’t work, well, you’re screwed, enjoy digging in the depths of the ‘magic’ code.” As soon as you try to do something the framework creators haven’t thought of, it’s like clubbing wild bears to death with only your bare hands and small sticks, in the mud, while torrential rain is falling on you and the air reeks of sulphur. Boy, am I glad I’m done with PHP.

With Rails, it’s different: “We have this super awesome framework which is pretty well documented [ed: in blog posts], oh, except, the new version [ed: rails 3] isn’t, and we have a gazillion of plugins and stuff, but they’re mostly not documented at all, so you’re best off reading the source, and the plugins might or might not work with your version of rails, or they might eat your soul.” You still end up fighting the framework and the plugins, but at least you’re doing it with rapiers. It’s a very civilized form of combat.

(A vaguely related side note, it’s a damn shame how much Yahoo! sucks. They had this great search engine, owned the market, and then they got the idea that they were a ‘media company’ with ‘producers’ managing ‘properties’ and they turned the suck up to eleven. Nowadays, they have a great bunch of very bright people, yet still manage to turn out mostly products that suck. It is quite astonishing. Also, what is it with them launching products and then just discontinuing them a few months later? Are they trying to be like Google?)

Motorola Milestone / Droid and 802.11n Access Points

At UX Camp Europe 2010, my Motorola Milestone could not connect to the barcamp wifi, a WPA2/PSK WLAN running on a mesh of Enterasys APs. The phone would not even manage to associate to the AP, the debug log (adb logcat) showing the following:

E/wpa_supplicant( 3892): ERROR - \
    wpa_driver_tista_private_send - error sending \
    Wext private IOCTL to STA driver \
    (ioctl_cmd = 8021501,  res = -1, errno = 95)
E/wpa_supplicant( 3892): ERROR - Failed to do tista scan!
W/wpa_supplicant( 3892): Failed to initiate AP scan.

So there was never a visible attempt at connecting in the UI.

After a lot of searching, reading source, asking around and debugging, I managed to solve my problem by modifying the configuration of the WLAN driver module (tiwlan.ini) as follows (requires the Android SDK / adb and root access on your phone, or a adb-root recovery partition):

No warranty if you brick your phone, if you don’t feel confident doing this, have someone with Android expertise do it for you or wait for a fix from Motorola. Or try disabling 802.11n (draft-n) in your AP if you can control it.

  1. Connect your phone via USB, make sure you enable USB debugging in Settings > Applications > Development.
  2. Get the configuration file from your phone:

    adb pull /system/etc/wifi/tiwlan.ini tiwlan.ini

  3. Change HT_Enable=1 to HT_Enable=0 in the file.

    sed -i -e s/HT_Enable=1/HT_Enable=0/ tiwlan.ini

  4. Push the changed file back onto your phone (you can’t directly overwrite the file in /system/etc/wifi/ because adb is not running as root on the phone):

    adb push tiwlan.ini /tmp/tiwlan.ini

  5. Start an adb shell on your phone, get root, make /system writable and move the file to the right location:

    adb shell
    su
    mount -o remount,rw -t yaffs2 /dev/block/mtdblock6 /system
    cp -f /tmp/tiwlan.ini /system/etc/wifi/

  6. Reboot your phone

    reboot

Your phone should now be able to associate with the AP.

People have reported similar problems with APs by other manufacturers (Enterasys, D-Link, some old Linksys WRT), so this might work for you.

Rewriting Favcharts – Part 1

Um in Form zu bleiben, widme ich möglichst jeden Tag 30-90 Minuten Nebenprojekten.

Diese Woche baue ich die favcharts neu. Die momentante Implementation der favcharts ist ein Haufen von “Hacks upon hacks”, weil die Charts aus einem Satz hastig programmierter Scripte und viel Zeitdruck entstanden sind. Es funktioniert irgendwie, aber so richtig super ist es nicht. Ein Rewrite ist schon seit einiger Zeit überfällig, jetzt ist es soweit.

Kurz zu den aktuellen favcharts: Der Code ist in PHP geschrieben, die Daten liegen in MySQL, klassischer LAMP-Stack also. Ein Crawler durchforstet regelmäßig die Favorites aller bekannten Nutzer und packt diese Daten in die Datenbank. Das Webfrontend liest aus der DB, macht Auswertungen, und speichert die Resultate dann zwischen, je nach Ansicht (täglich, monatlich, User-spezifisch, etc.) für ein paar Stunden bis mehrere Monate. Beim nächsten Zugriff wird dann nur der Cache gelesen, die Datenbank muss also nicht nochmal ran.

Soweit die Theorie. In der Praxis treten jedoch gehäuft Probleme auf: Viele Requests treffen den Cache gar nicht, weil z.B. jeder seine eigenen Charts abruft, und der User Cache dann irgendwann voll läuft und verdrängt wird. Oder es kommen Suchmaschinencrawler und fragen uralte Charts ab. Außerdem bieten die favcharts relativ viele Ansichten (verschiedene Zeiträume, verschiedene Gewichtungen, etc.), so dass die Anzahl der Cache-Einträge schnell hoch wird. Die Requests, die nicht vom Cache abgehandelt werden können, tun dann richtig weh: Je nach Scoring-Methode muss über riesige Datenmengen berechnet werden. Das kostet Zeit und I/O. Für die gewichtete Bewertung muss sogar das ganze Resultset zweimal durchlaufen werden. Dank mod_php wird dann für geraume Zeit ein Apache-Prozess blockiert und schluckt mehr und mehr Speicher und CPU, und der Rest der Apaches wird dadurch auch nicht schneller. In Stoßzeiten brauchen Requests so manchmal über zehn Sekunden. Der Crawlingprozess funktioniert dafür hervorragend – neue Favs sind oft schon nach wenigen Minuten erfasst. Das Datenbankzugriffsmuster ist allerdings eher ungesund: Gleichzeitig finden viele Writes (von den Crawlern) und viele lange Reads (vom Webfrontend) statt. Das führt zu Congestion, schlechten Indexen, und generell sehr unpraktischen Zugriffsmustern.

All das soll mit dem Rewrite besser werden. In den folgenden Posts beschreibe ich den Prozess des Rewrites, und mit welchen Ansätzen diese Probleme gelöst und in Zukunft verhindert werden sollen.

Und weil ich schon lange nichts mehr mit Ruby gemacht habe, und Rails nach wie vor “in” ist, wird die neue Version auf Ruby on Rails gebaut. Yee-haw.

Twitter-Status mit XING synchronisieren via Xwitter

Mit Xwitter kann man seinen Twitter-Status selektiv mit XING synchronisieren. Dazu muss man nur auf xwitter.de die Schritte durchlaufen und danach Tweets, die nach XING laufen sollen, mit ‘#xwitter’ taggen. Man benötigt dazu aber noch einen XING Premium-Account.

Interessant daran finde ich, dass sich bei XING endlich was zu bewegen scheint. Vor dem Hintergrund sinkender Neuanmeldungen und sinkender Premium-Mitgliedszahlen, außerdem dem sehr großen Momentum, das LinkedIn hat, ist XING nach Ansicht vieler meiner Bekannten jetzt im Zugzwang. Ein erster, zögerlicher Schritt war damals die Übernahme von SocialMedian durch XING und damit einhergehend die Öffnung für interne OpenSocial-Anwendungen. Den großen Schritt einer offenen API hat man sich aber noch nicht getraut, und mit SocialMedian/OpenSocial ist auch nicht all zu viel passiert – es geht schleppend langsam voran, gerade, wenn man das Innovationstempo im “umliegenden” Web sieht.

Um so erstaunlicher dann, dass Xwitter nur wenige Tage nach der Einführung eines ähnlichen Features beim großen Konkurrenten LinkedIn erscheint, noch viel erstaunlicher aber: bei Xwitter handelt es sich um ein halb-offizielles Projekt des VP Engineering Rails von XING, Andreas Gehret. Und er nutzt dazu eine inoffizielle API namens XWS, und bezeichnet es auf Xwitter öffentlich als ‘unfortunate’, dass diese inoffiziell sei.

Ich bin gespannt, was da noch kommt. Ich hoffe, dass XING nicht in einer knee-jerk reaction Xwitter offline nimmt und Andreas Gehret Ärger bekommt. Statt dessen sollte XING die Gelegenheit nutzen und die internen ‘Change Agents’ mal machen lassen: Einen Teil der API öffnen, OpenSocial für alle zulassen, und einige Features von Premium auf Basic-Mitgliedschaften übertragen. Ansonsten wird der mächtige Konkurrent LinkedIn (oder vielleicht sogar Facebook) in gar nicht allzuferner Zukunft XING verdrängen.

Verbesserungsvorschläge habe ich auch schon: 1. Scheint Xwitter momentan regelmäßig zu pollen, also alle registrierten Nutzer abzuklopfen. Das bedeutet, dass es immer eine Weile dauert, bis der Status übernommen wird, und dass man irgendwann in die Twitter-Limits reinläuft. Ich würde daher auf die Twitter Realtime-API umstellen, und nur bei Protected Accounts pollen; 2. würde ich den Hashtag kürzer machen; und 3. würde ich den Hashtag auf XING-Seite dann aus dem Tweet entfernen.

Update: Xwitter ist jetzt Teil der neuen XING Labs, womit die Datenschutzbedenken ausgeräumt sein sollten. Außerdem ist der Hashtag auf #xng verkürzt worden.

Aufgeräumt: History-Leaks (nebenbei)

Seit geraumer Zeit geistern immer mal wieder Links zu Seiten herum, die die History der besuchten Webseiten auslesen. Mal mittels JavaScript, mal ohne. Momentan ist making-the-web recht frisch. Daher, damit nicht immer wieder selbe Panik aufkommt und ich immer wieder die selbe Argumentation führen muss:

  • Ja, die History kann in gewissem Maße ausgelesen werden.
  • JavaScript ist dafür nicht nötig.
  • CSS schon.
  • Es kann nur ausgelesen werden, was auch vorhanden ist. Wer seine History auf 7 Tage begrenzt, bei dem sind auch maximal 7 Tage ‘auslesbar’.
  • Es können nur exakte Treffer ausgelesen werden.

Das Verfahren sieht so aus: Die Seite fragt den Browser, ‘wurde die URL bereits besucht?’ – volle URLs, wohlgemerkt! Wenn man also nur eine Unterseite von Slashdot, Heise, oder meinetwegen YouPorn aufgerufen hat, und die Ausleseseite nur nach ‘http://slashdot.org/’, ‘http://www.heise.de/’ oder eben ‘http://www.youporn.com/’ fragt, dann wird der Besuch nicht erkannt. Das bedeutet außerdem: Um das Surfverhalten breit genug zu erfassen, braucht man eine sehr, sehr lange Liste mit Adressen und ausreichend Zeit.

Also – seid euch bewusst, welche Daten gespeichert sind und wie sie genutzt (oder missbraucht) werden können. Panik jedoch ist unangebracht.