HTTP-Proxy simuliert Modem-Leitungen
Urlaub in der Steinzeit
Ein kleiner Proxyserver erlaubt es, auch auf superschnellen InternetVerbindungen die Welt durch die Brille armer Modem-Benutzer zu sehen.
Ein kleiner Proxyserver erlaubt es, auch auf superschnellen InternetVerbindungen die Welt durch die Brille armer Modem-Benutzer zu sehen.
Neulich war ich mal für eine Woche in Deutschland und schaute durch Zufall meine Website durch eine traditionelle Modemverbindung an. Zu meinem nicht geringen Erstaunen dauerte es ungefähr 30 Sekunden, bis der Browser die Amerika-Rundbriefe auf Perlmeister.com anzeigte. Grund dafür war das HTML-Design der Seiten, das aus einer riesigen zweispaltigen Tabelle bestand, die der Browser erst dann anfing darzustellen, als die ganze 50 KByte große Seite durch die enge Leitung gepumpt war. Unter DSL war mir das nie aufgefallen, da dort 50 KByte in einem Bruchteil der Zeit durchrauschen.
Wieder daheim in den USA angekommen, nahm ich mir deshalb vor, meine Seiten vor der Veröffentlichung künftig auch auf meinem flinken DSL-Anschluss mittels eines kleinen Tricks unter Schneckenmodem-Geschwindigkeit zu testen. Hierzu wird einfach der heute vorgestellte Proxyserver gestartet, dann ein handelsüblicher Browser darauf konfiguriert - und schon drosselt der Proxy die verfügbare Bandbreite auf beliebig einstellbare Werte herunter.
Wie in [2] schon einmal vorgestellt, ist es ein Leichtes, unter Perl einen Proxyserver zu schreiben, der zwischen dem Browser und dem kontaktierten Webserver steht und allerlei lustige Streiche treibt. Das Modul HTTP::Daemon von Gisle Aas erledigt die Feinheiten, wir müssen nur die Logik hinzufügen, die den Durchsatz verlangsamt.
Listing 1 zeigt die Implementierung. Zeile 5 definiert den Port, auf dem der Proxyserver lauscht, und Zeile 6 den maximalen Durchsatz in Bytes pro Sekunde. Die Zeilen 8 und 9 ziehen die benötigten Zusatzmodule herein, die wir im Kasten "Installation" vom CPAN holen werden.
Zeile 12 setzt einen Signal-Handler auf, der das SIGPIPE-Signal ignoriert. Es kann auftreten, wenn ein Browser unvermittelt die Verbindung abbricht. Der zweite Signal-Handler in Zeile 14 erlöst beendete Prozesskinder aus ihrem Zombiestatus - weiter unten werden wir Parallelprozesse abfeuern.
Zeile 17 erzeugt den neuen HTTP-Dämon, den eigentlichen Proxy. Er lauscht auf dem eingestellten Port auf Anfragen, holt die angeforderten Seiten anschließend vom Web und liefert sie schließlich wieder an den anfragenden Rechner zurück. Der Reuse-Parameter lässt den Server auch dann starten, wenn der Socket einer kurz zuvor rüde unterbrochenen Instanz von slowie.pl noch etwas unschlüssig auf dem Port herumhängt.
Zeile 21 beendet das Programm sofort, falls der Dämon nicht starten kann. Andernfalls schreibt Zeile 24 eine Meldung auf die Standardausgabe, die angibt, unter welchem Port der Proxy zu erreichen ist.
Zeile 26 erzeugt ein Objekt vom Typ LWP::UserAgent, das später beim Einholen von Webpages behilflich sein wird. Die im Anschluss aufgerufene agent()-Methode bestimmt, wie der Proxy den UserAgent-Header bei Anfragen an den Webserver setzt - slowie/1.0 wird sicher zur Erheiterung des einen oder anderen Webmasters beitragen.
Die accept()-Methode in Zeile 29 blockt so lange, bis ein Request vom Browser ankommt, und besetzt dann $conn mit einer Referenz auf das Verbindungsobjekt. Geht dabei etwas schief, bricht das Programm ab.
Da der Browser Requests unter Umständen schnell hintereinander abfeuert und der Proxy mehrere Anfragen quasi gleichzeitig bearbeiten soll, ist es wichtig, dass er - noch während der Request bearbeitet und die Daten vom Web geholt werden - schnellstens wieder zur accept()-Methode in Zeile 29 zurückkehrt, um gleich die nächste Anfrage des Browsers entgegenzunehmen. Das löst slowie.pl durch parallele Prozesse, die der fork()-Befehl in Zeile 32 kreiert.
Zeile 34 schickt den Vaterprozess sofort wieder zur accept()-Methode am Anfang des Blocks zurück, während der neue Kindprozess in Zeile 37 damit fortfährt, die Request-Daten vom Browser entgegenzunehmen. Zeile 39 nutzt das Objekt vom Typ LWP::UserAgent, um die gewünschten Daten vom Web zu holen. Dabei verwendet es bewusst simple_ request() und nicht request(), da wir dem Browser keinerlei Arbeit abnehmen wollen und er demgemäß den Redirects auch gefälligst selber folgen muss.
Zeile 47 ruft daraufhin die Methode send_response() der Browser-Verbindung auf, um die Antwort zurückzuschicken; send_response() versteht zwei verschiedene Parameterarten: Einen String sendet sie sofort zurück zum Browser, und eine Referenz auf ein Objekt vom Typ HTTP::Response nutzt sie, um dessen content()-Methode immer und immer wieder aufzurufen und das jeweils zurückgelieferte Ergebnis als String stückweise weiter an den Browser weiterzuleiten.
Genau diesen Mechanismus nutzt slowie.pl dazu, den Datendurchsatz zum Browser künstlich zu begrenzen. Für den Fall, dass der Webserver auf die gestellte Anfrage hin tatsächlich Daten liefert, ruft Zeile 43 die Funktion get_slowsub() auf, die eine Referenz auf eine Funktion zurückliefert, die jene mit $resp->content() ursprünglich übergebenen Webserver-Daten speichert und bei jedem anschließenden Aufruf einen kleinen Bissen davon so lange zurückgibt, bis schließlich alle Daten geliefert wurden. Die in $subref gespeicherte Funktionsreferenz schmuggelt Zeile 44 dem Response-Objekt $resp als Inhalt unter und ersetzt damit die ursprünglich dort gespeicherten Antwortdaten des Webservers.
Wenn alles gut ging, steckt also in Zeile 47 in dem an send_response() übergebenen HTTP::Response-Objekt der Wolf im Schafspelz: send_ response() wird feststellen, dass $resp->content() keine Daten, sondern eine Funktionsreferenz liefert, und deswegen die dahinter steckende Funktion wieder und wieder aufrufen, bis sie einen leeren String oder einen undefinierten Wert liefert. Alle bis dato von der Funktion gelieferten Daten schickt sie stückweise an den Browser. Danach hat der Kindprozess seine Aufgabe erledigt.
Zeigt die while-Schleife in Zeile 37 an, dass der Browser keinen neuen Request mehr in dieser Session hat, wird die Verbindung zu ihm in Zeile 49 gekappt und der Kindprozess mit exit(0) beendet. Der schon erwähnte Signalhandler in Zeile 14 sorgt dafür, dass aus ihm kein Zombie wird.
Nun zur trickreichen Funktion get_ slowsub(), die eine Referenz auf eine Funktion liefert, die die von der Webseite schon vollständig empfangenen Daten nur sehr zögerlich, der eingestellten Bandbreitenbegrenzung entsprechend herausgibt. get_slowsub() nimmt einen String entgegen, der dem Inhalt der angeforderten Website entspricht, und definiert eine so genannte Closure, um eine Funktion mit Gedächtnis zu verwirklichen, deren Referenz sie anschließend zurückgibt.
Alle Rezensionen aus dem Linux-Magazin
Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...