Im Grunde kann sich der gemeine Zeitgenosse die URLs selber zusammenbasteln. Doch es gibt auch eine Web-Seite, die einem die Schreibarbeit abnimmt:
Beispiel: Gernot Hassknechts Tirade über die EU (bei 21 Minuten und 30 Sekunden)
Donnerstag, 20. Februar 2014
Im Grunde kann sich der gemeine Zeitgenosse die URLs selber zusammenbasteln. Doch es gibt auch eine Web-Seite, die einem die Schreibarbeit abnimmt:
Beispiel: Gernot Hassknechts Tirade über die EU (bei 21 Minuten und 30 Sekunden)
Tags: Link, time, Timecode, Tool, Youtube, Zeit, Zeitmarke
Labels: Web
Donnerstag, 19. Dezember 2013
Heuer war es wieder mal soweit — Bekannte, welche im September 2013 geheiratet und an deren Hochzeit Stephanie und ich teilgenommen haben, haben die Fotos über ichwuensche.ch zugänglich gemacht. Da es mir zu blöd war, mich durch die 12 Web-Seiten voller Hochzeitsbilder zu klicken, habe ich mir ein kleines PHP-Script geschrieben, welches mir die URLs der Fotos generiert. Dafür benötige ich die fortlaufende Nummer des ersten sowie letzten Bildes der Serie; das Script generiert anschliessend automatisiert alle URLs.
<?php $base = 'http://www.ichwuensche.ch/images/gallery/'; $start = 1269245; $end = 1269774; $suffix = '_l.jpg'; $counter = 0; for($i = $start; $i <= $end; $i++) { $counter++; // $middle = 4/4/2/9/6/ if JPEG basename is 1269245 $middle = strrev($i); $middle = substr($middle,0,5); $middle = implode('/',str_split($middle)) . '/'; $url = $base . $middle. $i . $suffix; echo $url . "\n"; } echo "\n" . $counter . "\n"; ?>
Anschliessend führt man das Script aus, pipet die Ausgabe in eine Textdatei und lässt dann wget seine Arbeit verrichten:
$ php generate-urls.php > urls.txt $ wget -i urls.txt --2013-12-19 16:06:37-- http://www.ichwuensche.ch/images/gallery/4/7/7/9/6/1269774_l.jpg Verbindungsaufbau zu www.ichwuensche.ch (www.ichwuensche.ch)|85.158.232.45|:80... verbunden. HTTP-Anforderung gesendet, warte auf Antwort... 200 OK Länge: 150027 (147K) [image/jpeg] In »»1269774_l.jpg«« speichern. ...
ACHTUNG: Leider haben die Entwickler der Web-Site nicht viel überlegt: Unter den Total 530 Fotos findet sich auch die Hochzeit eines unbekannten Paares aus dem Thurgau, eine Amerika-Reise eines anderen, ebenfalls unbekannten Paares sowie die wohl fast schon obligatorischen Katzenfotos.
Umgekehrt bedeutet dies, dass ein Script-Kiddie in einer Nacht alle Fotos der Web-Site abräumen könnte. Na dann.
Tags: Batch, Download, ichwuensche.ch, Photo Gallery, Photogalerie, PHP, wget
Labels: Web
Mittwoch, 18. Dezember 2013
Obwohl ich primär als IT-Auditor unterwegs bin, haben wir uns vor einigen Tagen mit einer Tomcat-basierenden Audit-Applikation herumgeschlagen. Kurz ging es darum, die Kommunikation im Intranet zwingend mit HTTPS zu verschlüsseln. Da der Server mit verschiedenen Domainnamen (teilweise nicht FQDNs) angesprochen werden kann, mussten wir Tomcat zuerst einmal so konfigurieren, dass er alle HTTP-Anfragen auf eine bestimmte Domain umleitete, falls die Anfrage nicht bereits an den korrekten Host gerichtet war (wir haben uns den UrlRewriteFilter) zu Nutze gemacht — und trauerte dabei leise dem (noch) eleganteren mod_rewrite in .htaccess Dateien unter Apache nach …
Item. Da unsere Web-Browser nicht wirklich hilfreich waren, um die Redirects zu analysieren und auch noch ein Enterprise Proxy-Server dazwischenstand, behalf ich mich mit der Win32-Version von cURL, curl.exe mit den Optionen --verbose, um den gesamten Ablauf der Verbindungsaufnahme auf der Kommandozeile auszugeben, sowie mit --noproxy *, um sicherzugehen, dass wir direkt mit dem Server sprachen und den Enterprise Proxy so umgingen. Das Resultat sah folgendermassen aus:
C:\Temp\cURL\> curl.exe --verbose --noproxy * http://software.domain.tld:8080/r2d2/asdf * Adding handle: conn: 0x1c3def0 * Adding handle: send: 0 * Adding handle: recv: 0 * Curl_addHandleToPipeline: length: 1 * - Conn 0 (0x1c3def0) send_pipe: 1, recv_pipe: 0 * About to connect() to software.domain.tld port 8080 (#0) * Trying 10.0.0.111... * Connected to software.domain.tld (10.0.0.111) port 8080 (#0) > GET /r2d2/asdf HTTP/1.1 > User-Agent: curl/7.32.0 > Host: software.domain.tld:8080 > Accept: */* > < HTTP/1.1 301 Moved Permanently * Server Apache-Coyote/1.1 is not blacklisted < Server: Apache-Coyote/1.1 < Location: https://protected.domain.tld:8443/r2d2/asdf < Content-Length: 0 < Date: Wed, 11 Dec 2013 10:35:41 GMT < * Connection #0 to host software.domain.tld left intact
Alles Bestens: Anfragen auf http://software.domain.tld:8080/r2d2/asdf werden auf https://protected.domain.tld:8443/r2d2/asdf umgeleitet. Und das Auditorenherz schlägt höher.
Tags: curl, curl.exe, HTTP, HTTPS, Proxy, Redirect, Tomcat, Verbose, Win32
Labels: Web
Montag, 7. Oktober 2013
Ungefähr einmal im Jahr konfiguriere ich unter einer Mac OS X-Installation den lokalen Web-Server, um auf dem Gerät Web-Applikationen zu entwickeln. Und bei jedem Mal vergesse ich in der Regel, dass unter Mac OS X das Apache-Modul mod_negotiation installiert ist, welches spätestens bei der Verwendung von mod_rewrite zu komischem Verhalten führt.
Beispiel: Ich rufe im Browser die URL http://localhost/article/132 auf, welche eigentlich mittels mod_rewrite auf index.php umgeleitet werden sollte. Habe ich im DocumentRoot des Web-Servers aber dummerweise ein Script namens article.php rumliegen, wird Apache die Rewrite-Regeln nicht beachten und stattdessen article.php aufrufen.
Unter Mac OS X könnte man nun rabiaterweise einfach das Laden von mod_negotiation verhindern. Leider führt das zu neuen Problemen und Fehlermeldungen, welche ich hier aus Zeitgründen nicht ausführen möchte, weshalb man stattdessen die mod_negotiation-Funktionalität in der VirtualHost-Konfiguration für das DocumentRoot und alle darunterliegenden Verzeichnisse deaktiviert:
... Options -Multiviews ...
Und gut is …
Tags: Apache, mod_negotiation, mod_rewrite, Negotiation, Rewrite
Labels: Web
Mittwoch, 18. September 2013
Irgendwann ab den frühen 2000ern habe ich alle meine Web-Projekte über das Shared Hosting von Hostpoint AG im Internet bereitgestellt, und dort sind sie in den meisten Fällen noch heute.
Spätestens seit ich diese mit eMeidi.smt laufenden Web-Sites jede Minute mit Pingdom überwache, realisiere ich, wie instabil Hostpoints Hosting ist. Verglichen mit einem an der Uni Bern gehosteten virtualisierten Root-Server (Debian und VMware ESX) und eMeidi.com, welches beim Hoster meines Vertrauens Cyon GmbH in Basel läuft, haben wir es bei Hostpoint mit einem hostingmässigen Drittwelt-Land zu tun.
Dank den vom eMeidi.smt im Dateisystem abgelegten Fehlermeldungen lässt sich rückwirkend jeweils rasch eruieren, wo denn das Problem des Pingdom-Alarms lag. Als Beispiel sei hier der Ausfall vom 13. September 2013 zwischen 8:47 und 9:23 erwähnt:
#2003: Can't connect to MySQL server on 'sekneueg.mysql.db.internal' (13) in /home/sekneueg/public_html/irgendwo/mysql.class.php:240 for URI </> with referer <unknown>
Tjach, dumm gelaufen, ne? In den letzten 30 Tagen betrug die Uptime für diese spezifische Web-Site nur 99.68%, was Ausfällen von insgesamt 2 Stunden und 21 Minuten entspricht. Die längsten Ausfälle fanden am 26. August (57 Minuten), 13. September (36 Minuten) und am 22. August (11 Minuten) statt. Erbärmlich, zumal es sich dabei nicht etwa um Wartungsarbeiten mitten in der Nacht gehandelt hat, sondern um Ausfälle während Bürozeiten.
Der Fall ist klar: Die Profis von heute hosten bei der Cyon GmbH. Da hilft es auch nichts, wenn der Ausfall-anfällige Konkurrenzhoster aus Rapperswil-Jona den bloggenden Tom anheuert – dessen Lohn (sorry, Tom!) würde man lieber in bessere und stabilere Hard- und Software stecken als unnütze Gutfühl-PR.
Freitag, 30. August 2013
Hierzu haben die Google-Entwickler folgende URI erdacht:
Über diese Benutzeroberfläche lassen sich Daten auch im JSON-Format exportieren. Damit diese JSON-Daten auch für Menschen (einigermassen) lesbar werden, nimmt man Python zu Hilfe. Im nachfolgenden Beispiel gehen wir davon aus, dass der JSON-Dump in der Datei dump.json abgelegt ist:
python -mjson.tool < dump.json > dump-pretty.json
Tags: Beautify, Chrome, Debug, Decode, Google, JSON, Prettify, Python
Labels: Web
Donnerstag, 22. August 2013
Mein Raspberry Pi, welcher ein 24″ Dashboard speist, hat sich in den letzten Tagen vermehrt aufgehängt. Dies mag mit einer Aktualisierung der Dashboard-Software zusammenhängen, insbesondere wohl mit Anpassungen am JavaScript-Code.
Ich setze jQuery als JavaScript-Framework ein und steure damit einige visuelle Gimmicks; beispielsweise schwarze Eselsohren an aktualisierten Tiles, welche innert 30 Sekunden nach der Aktualisierung verblassen und dann ganz verschwinden. Auch wird das Doppelpunkt der Zeitanzeige (HH:MM) so animiert, dass es jede zweite Sekunde ausgeblendet wird.
Dies scheint dazu geführt zu haben, dass die schwachbrüstige CPU des Raspberry Pis voll ausgelastet war. Der Browser Midori, welcher als Vollbild läuft, beanspruchte teilweise bis zu 50% CPU-Last und auch das X-Window-System wieso ähnlich hohe Werte auf. Die Load Average des Raspberry Pis war über 1.
Ich entschied mich deshalb, in den JavaScript-Code eine Weiche einzubauen, welche die grafischen Animationen für schwachbrünstige Browser/Systeme auf ein Minimum beschränkte.
Leider hatte die jQuery-eigenen Konfigurationseinstellung Kollateralschäden zur Folge, weshalb ich den Code selber optimieren musste:
jQuery.fx.off = true;
… funktionierte nicht zufriedenstellend.
Nachfolgend einige Konstrukte, die seit gestern Abend in der Produktion laufen:
var browserIsPerformant = true; if(navigator.userAgent.match(/(Midori)/)) { browserIsPerformant = false; }
An den zwei Orten, wo ich schnelle (250ms – eine Viertelsekunde) und langsame (30000ms – 30 Sekunden) FadeOuts implementiert hatte, baute ich mit der Variable browserIsPerformant eine Weiche ein. jQuery kennt die .delay()-Funktion, mit welcher Aktionen für eine benutzerdefinierte Zeit (Millisekunden) hinausgezögert werden können. Damit konnte ich sicherstellen, dass die Effekte sowohl in der abgespeckten als auch in der Vollversion des Dashboards zur selben Zeit endeten.
Leider kann .delay() nicht auf .toggle(), .show() und .hide() angewendet werden.
if(browserIsPerformant) { $(obj).children('.updated').fadeToggle(30000); } else { $(obj).children('.updated').delay(30000).fadeOut(1); }
Indem ich im else-Abschnitt den Delay auf 30 Sekunden setzte und den fadeOut auf 1 Millisekunde, ergab sich unter Midori neu keine Performance-Einbusse mehr.
if(browserIsPerformant) { $('.separator').fadeTo(250,clockSeparatorMap[opacity]); } else { $('.separator').delay(250).fadeTo(1,clockSeparatorMap[opacity]); }
Auch hier arbeite ich mit der .delay()-Funktion, setze sie hier aber „nur“ auf 250 Millisekunden. Anschliessen wird der FadeOut innert 1 Millisekunde gemacht.
Obwohl Midori nach diesen Anpassungen vorerst stabil lief, fror der Browser nach ca. 10 Stunden erneut ein.
Ich entschied mich deshalb, statt Midori auf Chromium zu setzen:
# apt-get install chromium
… und den Google-Browser folgendermassen zu starten (/etc/xdg/lxsession/LXDE/autostart, via Raspberry PI kiosk mode with Chromium.):
@xset s off @xset -dpms @xset s noblank @chromium --kiosk --incognito http://domain.tld/dash
Auch Chromium (Version 22) hat Probleme mit den opulenten jQuery-Animationen, weshalb ich den JavaScript-Code noch ein wenig anpassen musste:
var browserIsPerformant = true; if(navigator.userAgent.match(/(Midori)/) || navigator.userAgent.match(/(armv6l)/)) { browserIsPerformant = false; }
Chromium trägt aktuell auch die Prozessorplattform im User Agent-String, im Falle von Raspberry Pi ist das ARM.
Tags: Dashboard, JavaScript, Midori, Performance, Raspberry Pi
Labels: Web
Samstag, 22. Juni 2013
Am 11. Juni wurden die in meinem Dashboard eingebundenen Twitter-Feeds plötzlich still. Der Grund: Twitter hat an diesem Tag API 1.0 deaktiviert und macht es seither Entwicklern und Script-Kiddies schwer, mit wenigen Zeilen Code auf öffentliche Twitter-Feeds zuzugreifen.
Wer vor dem selben Problem wie ich steht, sei hier eine kurze Anleitung präsentiert, um die Grundfunktionalität wieder online zu schalten:
Ich habe mich für die PHP-Klasse Codebird entschieden, da ich keine Lust hatte, OAuth-Requests und sonstigen Firlefanz auf eigene Faust zu programmieren.
Zuerst eröffnet man über den offiziellen Entwicklungsbereich mit dem persönlichen Twitter-Konto eine neue Twitter-Applikation. Wichtig ist, dass man sich den Consumer Key und das Consumer Secret merkt, denn diese beiden Variablen werden von der Klasse zur Kommunikation mit Twitter benötigt — authentifizieren sie doch die Applikation gegenüber dem Kurznachrichtendienst.
Nachdem man codebird.php sowie — ganz wichtig — cacert.pem in einem Unterverzeichnis des Web-Roots abgelegt hat, integriert man die Klasse in die bestehen Infrastrukutur:
function __construct() { parent::__construct(); require_once(dirname(__FILE__) . '/twitter/codebird.php'); \Codebird\Codebird::setConsumerKey('AAAAAAAAAAAAAAAAAAAAA', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); $this->cb = \Codebird\Codebird::getInstance(); }
Als nächstes benötigt man einen sog. Bearer Token. Einmal generiert kann dieser für jeden weiteren Request an die Twitter API wiederverwendet werden — sofern man den Token nicht manuell deaktiviert. Alternativ kann man diesen Token auch bei jedem Aufruf des PHP-Scripts neu von Twitters Server abholen, was aber nicht einer guten Entwicklungspraxis (Cacheing!) entspricht.
Den Bearer Token erhält man in etwa folgendermassen:
$reply = $this->cb->oauth2_token(); $bearerToken = $reply->access_token; \Codebird\Codebird::setBearerToken($bearerToken);
Jetzt also ist man wie mit API 1.0 in der Lage, eine simple Abfrage abzusetzen — im vorliegenden Fall rufe ich die letzten Tweets von John Gruber ab:
$this->cb->setReturnFormat(CODEBIRD_RETURNFORMAT_JSON); $jsonData = $this->cb->statuses_userTimeline(array('screen_name' => 'daringfireball','count' => 25),true); // 'true' is needed for app only auth
Damit die Kompatibilität von bestehendem Code mit der neuen Klasse gewahrt wird, nenne ich JSON explizit als Antwortformat. Ganz wichtig ist, dass die Codebird-Methode als zweiten Paramenter true übergeben erhält — dies weist die Klasse an, die Anfrage im Kontext einer Applikation und nicht eines bestimmten Benutzers abzusetzen.
Tags: API, API 1.0, API 1.1, Codebird, Dashboard, PHP, Twitter
Labels: Web