Amazon S3 ist ein einfacher Key-Value Store, wo man auch sehr große Daten unterbringen kann. Gerade wenn man Dateien nicht auf dem selben Server speichern möchte oder ein Cluster betreiben will, ist S3 eine gute Alternative zu FTP oder NFS-Laufwerken. Der Server ist über HTTP zu erreichen und es gibt für alle möglichen Sprachen Clients. Eine S3 kompatible Implementierung ist MinIO. Ich hab hier ein kleines Beispiel gebaut, wo ich ein Bild hochlade und wieder downloade und danach alles auch wieder aufräume. Metadata habe ich auch genutzt. Wenn man komplexere Anwendungen hat wird man wohl eher nicht SaveAs verwenden sondern den mitgelieferten Stream aus dem Array verwenden.
Das Minio Docker-Image:
sudo docker pull minio/minio
sudo docker run -p 9080:9000 -e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" -e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" minio/minio server /data
Nach dem wir nun unser File-Object haben, wollen wir es in kleine Teile zerlegen, die wir dann einzelnd hoch laden
können. JavaScript kann seit einiger Zeit super mit Dateien umgehen. Direkter Zugriff auf das Dateisystem ist natürlich nicht möglich aber Öffnen- und Speicherdialoge reichen ja auch. Um anders Datenabzulegen gibt es noch die indexeddb von JavaScript auf dich in vielleicht in einem weitern Eintrag mal eingehe. Aber ansosnten kommen wir mit Öffnen/Drag and Drop und Speichern vollkommen aus.
function createChunksOfFile(file,chunkSize){
var chunks=new Array();
var filesize=file.size;
var counter=0;
while(filesize>counter){
var chunk=file.slice(counter,(counter+chunkSize));
counter=counter+chunkSize;
chunks[chunks.length]=chunk;
}
return chunks;
}
Die Methode arbeitet an sich ganz einfach. Die Schleife läuft so lange wie die kopierte Größe kleiner ist als die Gesamtgröße der Datei. Bei slice(from,to) gibt man den Anfang und das Ende an. Wenn das Ende hinter dem realen Ende der Datei liegt wird nur das was noch vorhanden war kopiert und kein Fehler geworfen, was es uns hier sehr einfach macht. Wir addieren also z.B. bei jeden Durchlauf 250000 auf die aktuelle Kopie-Größe rauf, bis wir über der Dateigröße liegen. Bei jedem dieser Durchläufe wird von der Position der Größe der Kopie bis zu der Größe + Größe des zu kopierenden Teils, der Teil der Datei mit in ein Array kopiert.
Am Ende haben wir also ein Array mit den Teilen der Datei in der korrekten Reihenfolge.
Man hätte natürlich vorher die Anzahl der Teile ausrechenen können und dann mit einer for-Schleife und für jeden Teil die Position in der Datei berechnen können.. ich fand es so aber erstmal einfacher.
var chunks=createChunksOfFile(file,250000);
Damit erhalten wir Array mit alleien Teilen der Datei zu je ~250KB. Eine Datei von 1MB hätte also ~4 Teile. Alles ungefähr weil eben 250000 keine exakten 250KB sind (1024Byte wären ja 1KB... aber das will uns hier mal nicht interessieren).
Ein einfacher Fileupload ist einfach zu erstellen. Ein <input> vom Typ "file" in eine Form. "method" auf "post" und "action" auf die Zielseite. enctype="multipart/form-data" nicht vergessen und schon ist alles erledigt.
Nun hat diese Implementierung natürlich ihre Grenzen und bei heuten Web-Anwendungen will man oft nicht mehr nur einfach eine Datei hochladen. Man will Bilder vorher bearbeiten und skalieren. Den Fortschritt des Uploads sehen und mehrere Dateien in einer Queue hochladen lassen. Früher nutzte man für sowas Flash.. aber Flash ist tot. Heute hat man HTML5 und JavaScript. Damit kann man alles realisieren was man sich für einen File-Upload wünscht. Die File-API hilft die Dateien zu Laden (aus einem <input> oder auch per Drag and Drop). Per Notification kann man den Benutzer über den Zustand der Uploads informieren, z.B. eine Benachrichtigung ausgeben wenn eine besonders große Datei fertig hochgeladen wurde.
Der hier entworfene File-Upload ist natürlich auch nicht perfekt, aber er funktioniert gut in mehreren Projekten und Scripte um die Hochgeladenen Dateien entgegen zunehmen und zu Speichern, lassen sich gut in PHP oder als Servlet realsieren.
Für Tests reicht ein einfaches PHP-Script:
PHP:
<?php
//see $_REQUEST["lastChunk"] to know if it is the last chunk-part or not
if(isset($_FILES["upfile"])){
file_put_contents($_REQUEST["filename"],file_get_contents($_FILES["upfile"]["tmp_name"]),FILE_APPEND);
}
?>
oder für Java (FileIOToolKit ist eine eigene Klasse wo append einfach das byte[] an eine Datei ran hängt oder damit eine neue Datei erzeugt, wenn diese noch nicht existieren sollte):
String filename = request.getParameter("filename");
for (Part part : request.getParts()) {
if (part.getName().equals("upfile")) {
byte[] out = new byte[part.getInputStream().available()];
part.getInputStream().read(out);
Wie man hier schon sieht wird die Datei nicht als ganzes hochgeladen sondern in mehreren Stücken. Das hat den Vorteil, dass man sehr sein den Fortschritt beim Upload bestimmen kann.
Das Laden einer Datei ist relativ einfach. Das Drag and Drop oder das onChange einer <input> vom Typ "file" liefern jeweils ein Event, dass die Dateien enthält (es können immer mehrere sein!).
Hier ein einfaches Beispiel, wobei ein boolean verwendet wird, um die beiden Arten zu unterscheiden:
var files = null; // FileList object
if(!nodragndrop){
files=evt.dataTransfer.files; //input
}
else{
files=evt.target.files; //drag and drop
}
Wir werden aber das Laden ignorieren und davon ausgehen, dass man ein File-Object hat, egal ob aus einer Datei oder vielleicht auch vom JavaScript-Code erzeugt (eine Anleitung wie man die Data-URL in sowas umwandelt findet man in einem älteren Beitrag, wo man lernt wie man ein Bild vor dem Upload automatisch verkleinert).
Im nächsten Teil wird das File dann in kleine Teile zerlegt.
Eigentlich wollte ich mein neustes Projekt in den nächsten Tagen mal online bringen... aber eben gemerkt, dass nicht auf einen New Nintendo 3DS XL läuft. Es gibt zu viele verschiedene Plattformen und deren Namen werden auch immer länger ;-) Mal gucken ob es AngularJS liegt oder an meinem File-Upload-Code.
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?