Verschiedene Möglichkeiten und verschiedene Enctypes Dateien hochzuladen, moderne Alternative zu Perl CGI.pm und ein neuer Enctype
Meine Weiterentwicklung von Lincoln Steins Library CGI.pm
führt über einen neuen Enctype: multipart/slice-data.
Diese Anwendung zeigt verschiedene Möglichkeiten des Datei Hochladen über den Browser. Welche Möglichkkeiten das sind, ist anhand der Beschriftungen der Schaltflächen ersichtlich. Hinweis: Zum Testen können Sie mehrere Dateien auswählen, eine serverseitige Speicherung erfolgt nicht.
Die herkömmliche Methode: für das Formular werden die Attribute enctype="multipart/form-data"
und method="POST"
konfiguriert und zum Hochladen wird ein Submit ausgelöst. Zurück zum Formular gelangen Sie über den am Browser befindlichen Button.
Alter Schrott nur neu verpackt. Das JavaScript Objekt FormData
implementiert nur den alten Enctype multipart/form-data
, der JS Code ist einfach.
Hinweis: Bei diesem Enctype wird die Längenangabe einer Datei clientseitig nicht ermittelt und auch nicht im Request gesendet! Die Demo zeigt also die Dateilänge wie sie erst nach dem Upload, also serverseitig festgestellt wurde.
Ebenfalls ein einfacher JS Code jedoch mit einem proprietären Enctype multipart/slice-data
welcher die zu übertragenden Daten auf eine moderne Art und Weise binary safe serialisiert. Der Vorteil dieses Enctype gegenüber multipart/form-data
ist eine wesentlich geringere CPU Lastigkeit beim Parsen desselben serverseitig.
Hinweis: Bei diesem Enctype wird die Längenangabe einer Datei clientseitig festgestellt und auch im Request übermittelt. Neu ist auch, gegenüber multipart/form-data
, daß File.lastModified
übertragen wird.
Beim Enctype multipart/form-data
(JavaScript FormData
) wird die Größenangabe einer hochzuladenden Datei nicht clientseitig ermittelt und somit im Request nicht mitgesendet. Der proprietäre Enctype multipart/slice-data
hingegen nutzt die FileAPI
moderner Browser und ermittelt die Dateilänge bereits clientseitig.
Transparenz: In der Anwendung $file->content_length
ist dieser Unterschied nicht sichtbar.
Wesentlich für die serverseitige Verarbeitung übertragener Datenstrukturen ist die Angabe des Enctype aus welchem der Requestheader Content-Type generiert wird. Somit kann der serverseitige Parser eine, über die hier verwendeteten Enctypes, einheitliche Datenstruktur herstellen. Und dies bedeutet für die Abwärtskompatibilität meiner Entwicklungen zu CGI.pm
, daß die Methode $cgi->param()
, also der auf dieser Methode aufbauende Code, fast unverändert übernommen werden kann.
Ist, daß die param()
-Methode Objekte bzw. Instanzen der Klasse xCGI::File
liefert, was die weitere Verarbeitung hochgeladener Dateien bestimmt. Beispiel:
# Skalarer Kontext liefert das 1. Dateiobjekt my $file = $self->param('upspot'); # Getter-Methoden für Attribute $file->content_type(); $file->content_length(); # Original Filename $file->filename(); # Dateiinhalt, Overload, quasi __toString() my $content = $file; # Neu in multipart/slice-data $file->mtime; # liefert die lokale LastModified # der Datei in Sekunden # Localtime anschaulich in $file->mtime_local # z.B. Wed Dec 11 13:01:42 2013
Wenn <input type="file" multiple>
gesetzt wurde, liefert die param()
-Methode also mehrere Instanzen.
Zum Vergleich mit legacy CGI.pm
wird der dafür relevante Code gezeigt. Das Beispiel benutzt File::Copy
, damit ist der Unterschied geringfügig und beschränkt sich auf die unterschiedliche Handhabe des Dateiobjekts hinsichtlich Dateiname und Dateihandle. Kopiert wird also von Handle zu Handle.
use strict; use warnings; use File::Copy qw(copy); my $control = sub{ my $self = shift; if( my @files = $self->param('upspot') ){ foreach my $f (@files){ # neu mit xCGI.pm copy( $f->iohandle, $uploaddir.$f->filename ) or die $!; # legacy CGI.pm copy($f, $uploaddir.$f) or die $!; $fh->close; } $self->{CONTENT} = "Alle Dateien wurden gespeichert!"; } else{ $self->{CONTENT} = "Keine Dateien, aber alles OK!"; } };
Der CODE ist denkbar einfach. Das Uploadfeld hat den Namen upspot
und erlaubt eine Mehrfachauswahl (multiple). Zu jeder eingefügten Datei liefert xCGI
eine Instanz der Klasse xCGI::File
womit z.B. die Methode filename()
aufgerufen werden kann, welche den clientseitigen Namen der jeweiligen Datei liefert; mit diesem Namen wird die Datei dann auch gespeichert serverseitig.
Grundlage sei ein Dateiobjekt: $file = $cgi->param('uploadfield')
wobei $cgi
eine Instanz der Klasse CGI
ist.
Legacy CGI.pm | xCGI.pm + ParseMultipart.pm | |
---|---|---|
Original Dateiname | $file |
$file->filename() |
Handle serverseitig | $file |
$file->iohandle() |
Dateiinhalt | Handle lesen oder kopieren |
$file |
Content-Type | $cgi->uploadInfo($file)->{'Content-Type'} |
$file->content_type() |
LastModified | Nicht übermittelt |
$file->mtime() |
LastModified | Nicht übermittelt |
$file->mtime_local() |
Dateilänge | -s $file |
$file->content_length() |
|
|
Das Modul xCGI.pm
implementiert ein Schichtenmodell und lädt je nach mitgeliefertem Content-Type den entsprechenden Parser. Damit ist die gesamte Übertragungsstecke transparent, d.h., daß bspw. ein Content-Type application/json
ohne Weiteres ausgetauscht werden kann gegen den Enctype application/x-www-form-urlencoded
ohne daß der CODE zur Datenverarbeitung geändert werden muss: Die Anwendung der param()
-Methode ist immer dieselbe. Ebenso wurde clientseitig ein universeller Serializer entwickelt, welcher in einem Zwischenschritt eine universelle Datenstruktur liefert aus welcher beliebig verschiedene Enctypes erzeugt werden können. Selbstverständlich ist auch das Modul xCGI.pm
um beliebige Enctypes erweiterbar, womit bei Bedarf relevanter CODE nachgeladen wird.
Aufgrund der geschaffenen Transparenz vom Serialisieren eines Webformulars über den Transportlayer HTTP bis zur vom serverseitigen Parser gelieferten Datenstruktur sind die Enctypes multipart/form-data
und multipart/slice-data
untereinander austauschbar. Die Serialisierung des Letzeren erfolgt über das Datenmodell Entity/Attribute/Value
, in Perl auch bekannt als Hash of Hashes
. Der Slice-Begriff ist in Perl ebenfalls geläufig, hier die Kurzform: [{},{}..{}]
; die einzelnen Array-Elemente sind Hashreferenzen. Schließlich ist es in Perl auch recht einfach, zwischen Hash und Array zu transformieren.
Auf den Browser bezogen sind das in erster Linie Daten aus Formularen. Allgemein jedoch zählen hierzu auch Webservices, RPC (Remote Procedure Call) usw.. Das Ziel dieses Layers besteht darin, die erhobenen Daten in einer bestimmten Datenstuktur zusammenzufassen. In der Regel beschreibt diese Datenstruktur einen abstrakten Datentyp, also ein Array, Objekt oder Hash, was die Zusammengehörigkeit der Daten manifestiert. Natürlich sind da auch primitive Datentypen möglich. JavaScript: request.js::sampleform()
erzeugt aus den Feldern eines Formulars ein Array mit Objekten.
Dieser Layer ist dafür zuständig, daß die Daten transportsicher verpackt werden. Einfach ausgedrückt, wird aus einer Datentruktur eine Datei bzw. Sequenz erzeugt, die sowohl speicherfähig als auch transportfähig ist. Der wahlfreie Zugriff auf die bis dahin gelieferte Datenstruktur geht verloren. Damit dieser nach dem Tranport wiederhergestellt werden kann, wird ein bestimmter Enctype vereinbart den der Serialize-Algorithmus also bestimmt. Ein solcher Enctype ist z.B. multipart/form-data
welcher die Daten auch binary safe überträgt. JavaScript: EAV.js::EAV.slice2blob()
erzeugt den proprietären Enctype multipart/slice-data
.
In diesem Layer spielt sich alles auf einem Low Level, auf Byteebene ab. Das T in HTTP steht zwar für Text, übertragen jedoch werden Bytes im Message Body. Der vereinbarte Enctype wird im Requestheader Content-Type
übertragen und die Länge des MessageBody im Header Content-Length
als Anzahl der gesendeten Bytes. Der MessageBody ist praktisch eine Datei die genausogut auf einer Festplatte gespeichert werden kann. Protokoll: HTTP
ist für den Transport zuständig.
Entsprechend dem vereinbarten Enctype (Content-Type) wird in diesem Layer die in (1) erzeugte Datenstruktur wiederhergestellt. Gleichzeitig werden die einzelnen Bestandteile wieder in den wahlfreien Zugriff (Random Access) gebracht. Zum Beispiel die übertragenen Parameter als Schlüssel-Werte-Paare, wobei der Wert über den namentlichen Schlüssel direkt adressierbar ist. Perl: ParseMultipart
stellt die Daten aus dem Enctype multipart/form-data
für den wahlfeien Zugriff wieder her, Datenstruktur siehe Demo/Formular.
Hiermit sind wir in der serverseitigen Anwendung angelangt. Transparenz schließlich heißt, daß die Layer 2, 3 und 4 im Code der Anwendung weder sichtbar sind noch eine Rolle spielen. Die Anwendung kennt also weder den Enctype, noch das Transportprotokoll und auch nicht die Requestmethode.
Was in JavaScript File.name
ist serverseitig $file->filename
. Und natürlich File
als Objekt samt Inhalt und weiterer Attribute. Diese Transparenz war schon immer die Philosophie in Lincoln Steins Library CGI.pm
. Eine Weiterentwicklung, die seit Jahren überfällig ist, erfordert natürlich eine gewisse Planung und auch dabei ist ein Schichtenmodell äußerst hilfreich. Und selbstverständlich OOP mit ihren Mächtigkeiten wie Inherit, Overloading, Delegation und Dependency Injection.
Eine Gegenüberstellung von PHP's Datenstruktur in $_FILE
Array ( [upspot] => Array ( [name] => Array ( [0] => ehmetsklinge.jpg [1] => eichelberg.jpg ) [type] => Array ( [0] => image/jpeg [1] => image/jpeg ) [tmp_name] => Array ( [0] => C:\WINDOWS\Temp\phpB.tmp [1] => C:\WINDOWS\Temp\phpC.tmp ) [error] => Array ( [0] => 0 [1] => 0 ) [size] => Array ( [0] => 11761 [1] => 17479 ) ) )
mit der hier zugrunde liegende Datenstruktur in Perl
'upspot[]' => [ bless( { 'content_length' => '17479', 'content_type' => 'image/jpeg', 'filename' => 'eichelberg.jpg', 'iohandle' => bless( \*Symbol::GEN4, 'IO::String' ), 'mtime' => '1386763302055', 'mtime_gm' => 'Wed Dec 11 12:01:42 2013', 'mtime_local' => 'Wed Dec 11 13:01:42 2013', 'name' => 'upspot[]' }, 'xCGI::File' ), bless( { 'content_length' => '11761', 'content_type' => 'image/jpeg', 'filename' => 'ehmetsklinge.jpg', 'iohandle' => bless( \*Symbol::GEN5, 'IO::String' ), 'mtime' => '1386765476226', 'mtime_gm' => 'Wed Dec 11 12:37:56 2013', 'mtime_local' => 'Wed Dec 11 13:37:56 2013', 'name' => 'upspot[]' }, 'xCGI::File' ) ]
zeigt, welcher Fortschritt mit OOP/Perl möglich ist: Eine Clientseitige Datenstruktur so zu übertragen, daß sie serverseitig genauso aussieht. Natürlich ist sowas auch mit PHP und sicher auch mit anderen Programmiersprachen möglich, nur hat das eben noch keiner gemacht.
Als das Buzzwort Web 2.0 aufkam schrieb mal jemand: Webanwendungen werden in Zukunft nicht nur wie Desktopanwendungen aussehen sondern auch dementsprechend funktionieren. Das ist also die Grundidee die dahintersteckt: Ein Benutzer arbeitet mit Webanwendungen so wie er es mit Desktopanwendungen gewohnt ist, er wird keinen Unterschied feststellen. So fügt ein Anwender beispielsweise Bilder, Texte und andere Dateien in ein vorhandenes Dokument ein und klickt auf Speichern, ohne daß die ganze Seite damit neu geladen wird: Die Übertragung der Daten findet im Hintergrund statt ohne daß der Anwender damit etwas mitbekommt, sämtliche an diesem Pozess beteiligte Layer sind für den Benutzer unsichtbar. Und auf den Code der Anwendung bezogen, sind diese Layer durchsichtig (transparent).
Transparenz ist die wesentliche Grundlage, Progressive Enhancement ist nur ein neuer Begriff.
Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. sos@rolfrost.de und wenn Sie möchten daß mein Prepaid nicht verfällt dürfen Sie mich auch gerne anrufen 01625 26 40 76.