Das Problem:
Für ein kleines Projekt habe ich kürzlich eine Website abrufen und analysieren wollen. In Perl wahrlich kein Problem, die LWP-Module nehmen einem fast alle Arbeit ab. Allerdings hatte die einfache Aufgabe zwei kleine Haken, die sich in Summe zu einem Nachmittag „Recherchieren & Ausprobieren“ ergänzten. Welche?

  • Die Kommunikation muss durch einen Proxy gehen
  • Die Website ist zwar per http zu erreichen, leitet aber direkt per Refresh auf die https-Version der Site weiter.

Grundsätzlich muss LWP erstmal mit SSL (dem „s“ von „https“) umgehen können. Dazu muss das Modul Crypt::SSLeay installiert sein. Liest man nun die Dokumentation zu LWP, sollte die folgende Vorgehensweise funktioneren:

Entweder die Umgebungsvariablen

http_proxy
ftp_proxy
https_proxy

(kleingeschrieben!) setzen und (wenn $ua unser LWP-Useragent ist)

$ua->env_proxy;

aufrufen. Oder direkt innerhalb von Perl

$ua->proxy( [ ‚http‘, ‚ftp‘, ‚https‘ ], ‚http://mein.proxy.de/3128‘);

aufrufen. Danach sollte jeder Request durch den Proxy geleitet werden. Das funktioniert sogar – aber leider nur mit Proxies, die wie mod_proxy des Apache Webservers funktionieren.

Andere Proxies wie der weit verbreitete Squid oder auch der MS-ISA verstehen die Methode von LWP aber nicht. Dummerweise gibt es keine Fehlermeldung, sondern ein „200 OK“ für die Verbindung, aber mit leerem Body. Gemeint ist damit das Ergebnis für’s GET zum Proxy, welches aber im Falle einer HTTPS-Verbindung ein CONNECT sein müsste…

Die Lösung:
Damit LWP::UserAgent für HTTPS-Verbindungen nicht seine fehlerhafte bzw. unpassende Art des Verbindungsaufbaus verwendet, darf für LWP kein Proxy für https gesetzt werden. Für http/ftp-Verbindungen jedoch muss der Proxy wie gewohnt gesetzt sein! Das Proxying für die HTTPS-Verbindungen übernimmt statt dessen Crypt::SSLeay, was für LWP::UserAgent vollkommen transparent ist.

Wie stellt man nun das Proxying für Crypt::SSLeay ein? Hier hilft die Dokumentation zum Modul weiter. Crypt::SSLeay beachtet einige Umgebungsvariablen, wobei hier HTTPS_PROXY (*Großschreibung beachten*) zum Zuge kommt. Auch hier eine kleine Falle, die es zu umgehen gilt: Die Schreibweise

HTTPS_PROXY=http://mein.proxy.de:3128/

funktioniert nicht, da Crypt::SSLeay vom abschließenden „/“ irritiert wird. Die korrekte Schreibweise lautet also

HTTPS_PROXY=http://mein.proxy.de:3128

In der Praxis würde man entweder komplett auf Umgebungsvariablen zurückgreifen oder direkt im Perlskript alle notwendigen Werte setzen. Daher skizziere ich hier mal beide Herangehensweisen.

Proxying per Umgebungsvariablen:
In der Shell

PROXY=http://mein.proxy.de:3128 # Kein / am Ende!
http_proxy=$PROXY # Fuer LWP, kleingeschrieben
ftp_proxy=$PROXY # Fuer LWP, kleingeschrieben
HTTPS_PROXY=$PROXY # Fuer Crypt:SSLeay, großgeschrieben
export http_proxy ftp_proxy HTTPS_PROXY

und dann im Skript

use LWP::UserAgent;
my $ua = LWP::UserAgent->new();
$ua->env_proxy;

Proxying komplett im Skript:

use LWP::UserAgent;
my $ua = LWP::UserAgent->new();
my $proxy = ‚http://mein.proxy.de:3128‘; # Kein / am Ende!
$ua->proxy( [ ‚http‘, ‚ftp‘ ], $proxy); # Fuer LWP
$ENV{‚HTTPS_PROXY‘} = $proxy; # Fuer Crypt::SSLeay

In einigen Forumthreads im Web fand ich Diskussionen über den Zeitpunkt, an dem die Umgebungsvariable HTTPS_PROXY im Perlskript zu setzen sei. Die oben aufgeführte Variante funktioniert hier bei mir unter Perl 5.10.1 (Kubuntu 11.04), andere hatten scheinbar mehr Erfolg, wenn HTTPS_PROXY vor dem Laden der Module gesetzt wurde:

BEGIN { $ENV{‚HTTPS_PROXY‘} = $proxy; }
use LWP::UserAgent;

Damit sollte das Problem endgültig erschlagen sein. Die gleiche Vorgehensweise ist übrigens auch bei allen anderen Modulen nötig, die auf LWP zum Herunterladen von Daten per HTTP/HTTPS zurückgreifen, wie zum Beispiel WWW::Mechanize oder WWW::Mechanize::Shell.

Ich hoffe, der Tipp spart irgendjemand da draußen ein paar Stunden Herumbasteln…