Farid Hajji: Perl - Einführung, Anwendungen, Referenz
2., aktualisierte und erweiterte Auflage
Addison-Wesley Longman, ISBN 3-8273-1535-2
Oft stellt sich die Frage, wie User Dateien zu einem Webserver übertragen können. Bei einer Anwendung geht es z.B. darum, Winword-Dokumente der Allgemeinheit oder einem eingeschränkten Kreis zur Verfügung zu stellen. Eine andere Anwendung sieht z.B. vor, GIF- oder JPEG-Dateien in eine Kontaktdatenbank zu speichern etc.
In all diesen Fällen soll eine Datei auf dem lokalen Rechner des Anwenders über ihren Namen ausgewählt und anschließend zum Webserver übertragen werden. Dies ist sozusagen der umgekehrte Weg, den man beim Surfen sonst geht.
Es gibt diverse File-Upload-Techniken:
In diesem Abschnitt werden wir diese Techniken anhand einfacher Beispielprogramme erläutern.
CGI.pmMit Hilfe des File-Upload-Features von Netscape kann innerhalb eines Formulars ein Dateiauswahl-Button bzw. Eingabewidget eingeblendet werden. Damit kann der User eine Datei auf seinem lokalen Rechner auswählen, die er zum Webserver übertragen möchte. Sobald dann das Formular mittels submit abgeschickt werden soll, wird der Inhalt dieser selektierten Datei zusammen mit den restlichen Daten des Formulars zum Webserver gesendet.
Der Webserver ruft in diesem Fall wie gewohnt ein CGI-Programm auf, und übergibt ihm die empfangenen Daten, also auch den Inhalt der so übermittelten Datei. Die Aufrufmethode wird sicherlich POST sein. Es liegt nun in der Verantwortung des CGI-Programms, die empfangenen Formulardaten auszulesen und im Falle des Dateiinhaltes, etwas damit zu tun (z.B. diesen Inhalt serverseitig irgendwo abzuspeichern).
Das CGI-Programm cgi-upload.pl verwendet hierfür das CGI.pm-Modul. Um dieses Programm auch zu verstehen, benötigen Sie noch folgende Informationen:
CGI.pm wie gewohnt verwendet.$query->filefield(-name => 'uploaded_file'),
<INPUT TYPE="file" NAME="uploaded_file">
Dieser Fileselektor sieht bei der Windoze-Version von Netscape natürlich anders aus. Nun kann der User mit Hilfe dieses Fileselektors eine beliebige Datei auf seinem lokalen Rechner auswählen. Alternativ zum Fileselektor kann auch das Eingabefenster verwendet werden. In diesem Fall sollte dort der Dateiname (Pfadname) der lokalen, zu übertragenden Datei angegeben werden.
start_form() die Funktion start_multipart_form(). Diese erzeugt einen speziellen Typ für das Formular:
<FORM METHOD="POST" ENCTYPE="multipart/form-data">
<FORM METHOD="POST" ENCTYPE="application/x-www-form-urlencoded">
CGI.pm richtig verstanden. Wenn Sie manuell die CGI-Daten parsen (definitiv nicht empfohlen), werden Sie hier große Schwierigkeiten bekommen! Glücklicherweise kommt CGI.pm mit dieser neueren Encoding-Methode gut zurecht.
uploaded_file finden wir den Inhalt der uploadeten Datei. Statt jedoch die CGI.pm-Funktion param() zu verwenden, benutzen wir die eingebaute Methode upload(). Diese liefert uns einen Filehandle zurück, den wir wie jeden anderen Filehandle auch benutzen können, um den Dateiinhalt auszulesen. Meta-Informationen, also Informationen über die übertragene Datei erhalten wir mit der Methode uploadInfo():
# Es sind Daten angekommen; auswerten.
my $filefh = $query->upload('uploaded_file');
my $finfo = $query->uploadInfo($filefh);
$filefh machen, sollten wir überprüfen, ob die Datei auch tatsächlich komplett uploaded wurde. Vielleicht hatte der User ja keine Geduld mehr und die STOP-Taste seines Browsers angeklickt. In diesem Fall, wäre das Upload abgebrochen worden. Dies erkennen wir wie folgt und geben eine passende Fehlermeldung aus:
# Wir pruefen aber zuerst, ob der File-Transfer auch
# richtig abgeschlossen werden konnte (User hat nicht
# mit STOP die Uebertragung geschlossen):
if (!$filefh && $query->cgi_error) {
my $errmsg = $query->header(-status => $query->cgi_error);
print
$query->header(),
$query->start_html("Fehler"),
"Fehler beim Upload: $errmsg",
$query->end_html();
exit 0;
}
while (<$filefh>) {
# Tue etwas mit der aktuellen Zeile in $_
}
read()-Methode ein und speichern diese in eine serverseitige Datei:
# Der Filehandle ist okay, wir lesen nun alle Daten ein
# und speichern sie irgendwohin. Wir gehen davon aus,
# dass es sich um eine binaere Datei handelt:
open(OUTFILE, "> /tmp/fromupload.$$") or die "can't create file: $!\n";
while (read($filefh, $buffer, 1024)) {
print OUTFILE $buffer;
}
close(OUTFILE);
$$ die Prozeß-ID des aktuellen CGI-Prozesses ist.mod_perl der Fall sein!uploadInfo() generiert. Es wird eine Referenz auf einen Hash zurückgegeben. Dieser Hash enthält nun nützliche Informationen, wie beispielsweise den Dateinamen, den der User bei sich selektiert hat usw. Zur Illustration, geben wir als Antwort auf das Upload diese Meta-Informationen aus. Um dies einfacher zu gestalten, rufen wir die Methode Dumper des Standardmoduls Data::Dumper auf, wie wir es aus Kapitel 13 und Kapitel 18 gelernt haben:
# Okay, alles klar!
print
$query->header(),
$query->start_html('File uploaded'),
"Erhalten: ", $query->p(),
$query->pre(Dumper($finfo)),
$query->end_html();

# Wir speichern auch die diversen Informationen zu dieser Datei # in einem Logfile (das sollte lieber mittels flock() # atomar geschehen (NYI)). open(LOGFILE, ">> /tmp/upload.log") or die "can't open log: $!\n"; print LOGFILE "/tmp/fromupload.$$:\n", Dumper($finfo), "\n"; close(LOGFILE);
CGI.pm durch Zuweisung an die Variable $CGI::POST_MAX angegeben werden. Dies geschieht auch direkt am Anfang unseres Programms, noch bevor wir das $query-Objekt erzeugt haben:
use CGI; use CGI::Carp 'fatalsToBrowser'; use Data::Dumper; $CGI::POST_MAX = 1024 * 1024; # Hoechstens 1 MByte akzeptieren! my $query = new CGI;
open(OUTFILE, "> /tmp/fromupload.$$") or die "can't create file: $!\n";
while (read($filefh, $buffer, 1024)) {
print OUTFILE $buffer;
}
close(OUTFILE);
$CGI::POST_MAX-Grenze liegen, werden sie dennoch nach und nach unsere Server-Platte füllen.cgi-s-roaming.pl-Beispiel aus dem Buch gezeigt benutzen, oder durch die Angabe eines Paßwortes auf dem Eingabeformular und anschließender Überprüfung sicherstellen, nur registrierten Usern das Upload zu erlauben. Pro User sollten dann bestimmte Quoten eingehalten werden. Dies kann wieder serverseitig geschehen. (1999/10/21)PUT-Methode und ihre VerwendungWir haben bisher die HTTP-Methode POST benutzt, um den Inhalt einer auf dem Rechner des Webclients liegenden Datei zum Webserver zu übertragen. Ein viel natürlicherer Weg ist der Einsatz der HTTP-Methode PUT.
Wie sieht der Dialog zwischen einem Webclient und einem Webserver aus, wenn der Client eine Datei mittels PUT senden möchte? Der Webclient kontaktiert wie gewohnt einen Webserver und sendet ihm eine Anforderung. Diese Anforderung beginnt jedoch nicht wie bisher mit dem Aufruf der GET- oder POST-Methode, sondern mit einem PUT-Aufruf. Am Anschluß an das Schlüsselwort PUT folgt die gewünschte Ziel-URL auf dem Webserver, sowie der Version des HTTP-Protokolls. Auf den nächsten Zeilen folgen weitere Informationen, wie etwa der virtuelle Host, der Name des sendenden Programms sowie die Länge der zu uploadenden Datei. Nach einer leeren Zeile folgt dann der Inhalt dieser zu übertragenden Datei. Eine Anforderung eines Webclients, der die PUT-Methode verwenden möchte, sieht also wie folgt aus:
PUT /putdest/atest.xxx HTTP/1.0 Host: sun-1.meta.net:8812 User-Agent: libwww-perl/5.41 Content-Length: 32 this is a test with many lines.
In diesem Beispiel hat ein Programm versucht, mit Hilfe der PUT-Methode eine aus zwei Zeilen und 32 Zeichen bestehenden Datei zum Webhost sun-1.meta.net:8812 zu senden. Die Datei sollte nach Vorstellungen des Webclients unter der URL /putdest/atest.xxx beim Webhost abgelegt werden, was zu einer URL von http://sun1.meta.net:8812/putdest/atest.xxx führen müßte.
An dieser Stelle sollte der angesprochene Webserver etwas mit den gesendeten Daten und URL tun und wie gewohnt mit einem HTTP-Antwortcode reagieren.
Schauen wir uns erst einmal den Code eines Webclients an, der diesen PUT-Aufruf erzeugt. Das Programm put-test.pl ist zunächst eine ganz normale Anwendung der LWP::*-Library und somit ein gewöhnlicher Webclient. Aus diesem Grunde wird auch ein LWP::UserAgent-Objekt erzeugt. Was jedoch hier neu ist, ist die Verwendung eines HTTP::Request::Common-Objekts zur Erzeugung eines PUT-Requests. Zusammen mit dem Request wird der Inhalt der eingelesenen lokalen Datei mitübertragen. Der entscheidende Code lautet:
# Wir erzeugen eine HTTP PUT-Anforderung und senden als Inhalt
# die in $content eingelesene Datei zum Webserver:
$ua = new LWP::UserAgent;
$res = $ua->request(PUT $url,
'Content' => $content);
Der Rückgabecode wird wie gewohnt abgefragt.
Testen wir dieses kleine Programm mal an einem Dummy-Server, den wir selbst manuell installieren. Dazu verwenden wir das Tool nc, und werden manuell auf die PUT-Anforderung reagieren:
farid@sun-1:~> nc -l -p 8812 PUT /a/path/file.txt HTTP/1.0 Host: localhost:8812 User-Agent: libwww-perl/5.41 Content-Length: 305 root::0:root other::1: bin::2:root,bin,daemon sys::3:root,bin,sys,adm adm::4:root,adm,daemon uucp::5:root,uucp mail::6:root tty::7:root,tty,adm lp::8:root,lp,adm nuucp::9:root,nuucp staff::10: daemon::12:root,daemon sysadmin::14: nobody::60001: noaccess::60002: nogroup::65534: users::101: www::102:farid HTTP/1.0 200 OK ^C punt! farid@sun-1:~>
Hier wurden die Benutzereingaben fett angezeigt. Nach dem Aufruf des nc-Kommandos passiert erst einmal gar nichts. In einem anderen Fenster rufen wir nun put-test.pl wie folgt auf:
farid@sun-1:~> put-test.pl http://localhost:8812/a/path/file.txt /etc/group OK: farid@sun-1:~>
Was ist hier geschehen? Das put-test.pl-Programm hat eine Verbindung mit dem auf Port 8812 wartenden nc-Programm aufgebaut (siehe Kapitel 17) und anschließ die PUT-Anforderung samt Inhalt der Datei /etc/group zum nc-Server gesendet.
Nach dem Senden der Datei wartet nun put-test.pl auf eine Antwort des Servers. Diese senden wir ihm selbst, indem wir manuell im nc-Fenster den Code HTTP/1.0 200 OK getippt haben, gefolgt von einer leeren Zeile. Anschließend beenden wir (d.h. der Server nc) die Verbindung, hier speziell indem wir CTRL-C drücken und somit sowohl die Verbindung beenden als auch den nc-Server abbrechen.
Genau das geschieht, wenn Sie vom Netscape-Composer aus, eine Datei mittels Publish zu einem Webserver senden. Probieren wir es doch einfach mal aus. Wir erzeugen eine kleine Datei im Netscape-Composer und senden diese dann zum neugestarteten nc-Server.
So sieht die Eingabemaske des Netscape-Composer aus, wenn wir auf den Publish-Button geklickt haben:
Dieses Fenster erscheint, während der Übertragung der Datei zum nc-Server:
Beim nc-Server sieht es hingegen so aus:
farid@sun-1:~> nc -l -p 8812 PUT /a/path/atest.html HTTP/1.0 Connection: Keep-Alive User-Agent: Mozilla/4.5 [en] (X11; I; SunOS 5.6 i86pc) Pragma: no-cache Host: localhost:8812 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Encoding: gzip Accept-Language: en Accept-Charset: iso-8859-1,*,utf-8 Content-Length: 399 <!doctype html public "-//w3c//dtd html 4.0 transitional//en"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <meta name="Author" content="Farid Hajji"> <meta name="GENERATOR" content="Mozilla/4.5 [en] (X11; I; SunOS 5.6 i86pc) [Netscape]"> <title>atest</title> </head> <body> this is a simple test <br>with two lines </body> </html> HTTP/1.0 200 OK farid@sun-1:~>
Wieder wurden unsere Eingaben fett hervorgehoben.
Beachten Sie diesmal den Inhalt der HTTP-Felder. Im ersten Beispiel war unser Client die LWP::*-Library, im zweiten Beispiel hingegen der Netscape (a.k.a. Mozilla):
User-Agent: libwww-perl/5.41 User-Agent: Mozilla/4.5 [en] (X11; I; SunOS 5.6 i86pc)
Beachten Sie auch die PUT-URLs!
All dies ist schön und gut, aber wir haben bisher als Server nur den nc benutzt und zu allem Überfluß die HTTP-Rückgabecodes manuell zurückgesendet. Wie wäre es mit einem echten Webserver?
Im folgenden werden wir den Apache-Webserver verwenden, um die PUT-Anforderungen entgegenzunehmen und zu verarbeiten.
PUT-Methode im einem mod_perl-HandlerEine naheliegende Methode, den Apache-Webserver zu konfigurieren, damit er die PUT-Methode verwendet, ist, einen Bereich des URL-Baumes dafür zu reservieren und für jede Anforderung innerhalb dieses Bereiches einen mod_perl-Handler verantwortlich zu erklären.
Wenn Sie Ihren Apache-Webserver, wie im Buch empfohlen, zusammen mit mod_perl kompiliert haben, können Sie nun folgende Zeilen zu Ihrer Konfigurationsdatei ~www/conf/httpd.conf (bzw. der im Buch empfohlenen eigenen Datei ~www/conf/perl.conf) hinzufügen:
<Location /putperl>
SetHandler perl-script
PerlHandler Apache::PutHandler
<Limit GET HEAD POST PUT>
order deny,allow
deny from all
allow from .meta.net
</Limit>
</Location>
Natürlich sollten Sie .meta.net durch den Namen Ihrer (echten oder fiktiven) Domain ersetzen. Bevor Sie den Webserver neustarten, sollten Sie das folgende Programm PutHandler.pm nach ~www/lib/perl/Apache/PutHandler.pm kopieren und für den http-User (nobody) lesbar machen. Nun starten Sie Ihren Webserver durch mit /etc/init.d/apachectl restart.
Versuchen wir es nun einfach. Zunächst probieren wir es mit dem selbstgeschriebenen put-test.pl Webclient:
farid@sun-1:~> put-test.pl http://sun-1.meta.net/putperl/a/long/path/firstfile.txt /etc/group OK: farid@sun-1:~> ls -l /tmp/first* -rw-r--r-- 1 nobody nobody 305 Oct 22 01:24 /tmp/firstfile.txt farid@sun-1:~> head -3 /tmp/firstfile.txt root::0:root other::1: bin::2:root,bin,daemon
Das scheint ja richtig funktioniert zu haben. Probieren wir es nun mit dem Netscape Composer. Auch hier funktioniert es problemlos.
Wie sieht es nun mit der GET-Methode aus?
farid@sun-1:~> nc sun-1.meta.net 80 GET /putperl/firstfile.txt HTTP/1.0 HTTP/1.1 200 OK Date: Thu, 21 Oct 1999 23:31:02 GMT Server: Apache/1.3.6 (Unix) mod_perl/1.21 Connection: close Content-Type: text/plain root::0:root other::1: bin::2:root,bin,daemon sys::3:root,bin,sys,adm adm::4:root,adm,daemon uucp::5:root,uucp mail::6:root tty::7:root,tty,adm lp::8:root,lp,adm nuucp::9:root,nuucp staff::10: daemon::12:root,daemon sysadmin::14: nobody::60001: noaccess::60002: nogroup::65534: users::101: www::102:farid farid@sun-1:~>
Auch mit Hilfe eines beliebigen Browsers, kann die Seite unter der URL /putperl/firstfile.txt angefordert werden:
Interessant ist hier, daß die URL /putperl und alles, was darunter liegt, nicht innerhalb der DocumentRoot liegt, sondern virtuell mit Hilfe des mod_perl Handlers PutHandler.pm erzeugt bzw. verarbeitet wird. In unserem Beispiel sind die Dateien physikalisch unter /tmp abgelegt. Sie werden im Falle des File-Uploades mittels PUT vom PutHandler.pm dorthin abgelegt und während eines GET vom selben PutHandler.pm aus /tmp ausgelesen und weitergesendet, als kämen sie von der URL /putperl. Das ist der Sinn der <Location /putperl>-Direktive in der Konfigurationsdatei.
Interessant ist hier natürlich, daß anstatt die Datei physikalisch unter /tmp zu speichern, diese auch einer DBI-Datenbank oder einem beliebigen anderen Programm zur Analyse und Speicherung übergeben werden könnte! Sowohl beim Upload, als auch beim Download würde dann die DBI-Datenbank bzw. dieses andere Programm konsultiert und würde die Datei jeweils wieder rekonstruieren. Versuchen Sie es!
Beachten Sie die Denial-of-Service Attacken! Was oben dazu gesagt wurde, gilt natürlich in einem noch größeren Umfang hier bei mod_perl! Sie sollten auch hier möglicherweise eine eingeschränkte Policy fahren und nur zugelassenen Usern den Zugriff auf die PUT-Methode erlauben und auch dann nur mit Quotas!
Warum wurde eigentlich nicht der Apache-Webserver selbst mit der Beantwortung der PUT-Methode beauftragt? Das wäre im Prinzip möglich gewesen. Leider hatte der Apache-Webserver bis einschließlig Version 1.3.6 (evtl. spätere Versionen ebenfalls) noch keinen builtin PUT-Handler. In diesem Fall würde die nicht abgefangene PUT-Anforderung beim Default-Handler des Apache-Webservers landen und dieser würde diese ablehnen.
Ein weiterer Grund, die PUT-Methode selbst mit Hilfe des mod_perl-Moduls und einem Handler wie etwa PutHandler.pm zu realisieren, ist die oben erwähnte Flexibilität bei
PUT-Methode mit einem CGI-HandlerIn Vorbereitung.
FTP-Methode mit der LWP-LibraryIn Vorbereitung.
FTP-Methode mit einem Java-AppletIn Vorbereitung.
Thanks to Barry Grotjahn für die Frage nach File-Upload Techniken, die zu dieser Seite führte.
[Prev] [Up] [Next]
[Alte Quelle]
| Last modified: $Date: 2006/05/18 12:55:47 $ FH. Search :: Sitemap :: Disclaimer :: Copyright :: Privacy |
|