Datei-Upload mit Node.js
Willemers Informatik-Ecke
POST-Formular und Auswertung Session und Cookies

Formidable installieren

Für das Hochladen von Bildern hilft die zusätzliche Bibliothek formidable. Sie sorgt dafür, dass die in den Inputs vom Typ File angegebenen Datei hochgeladen werden. Diese muss explizit installiert werden, typischerweise per npm:
npm install formidable
Nach dem Aufruf von formidable landen die Dateien unter einem fortlaufenden Namen in einem temporären Verzeichnis. Nachdem sie dort angekommen sind, müssen sie in das eigentliche Zielverzeichnis verschoben werden und ihren ursprünglichen Namen wieder erhalten.

Danach wird eine Seite mit dem hochgeladenen Bild angezeigt. Dazu wird die HTML-IMG-Tag verwendet. Das Nachladen eines Bildes führt aber zu einem erneuten HTTP-GET-Aufruf führt, den der Server natürlich beantworten muss.

Beispielprogramm

Das Programm soll ein minimales Eingabeformular anbieten. Damit soll ein Bild hochgeladen und ein Titel vergeben werden. Auf dem nächsten Bildschirm wird das Bild und der Titel angezeigt.

Alle drei Aktionen geschehen im gleichen Server. Die Aktionen werden über die URL-Pfade unterschieden. Dies wird häufig mit Express erledigt. In diesem Beispiel wird eine einfache Fallunterscheidung für das Routing verwendet und je nach URL-Pfad eine eigene Funktion aufgerufen.

var http = require('http');
var formidable = require('formidable');
var fs = require('fs');
const path = require("path");

http.createServer(function (req, res) {
    if (req.url == '/') {
        return showUploadForm(res);
    } else  if (req.url == '/fileupload') {
        return loadFileAndShow(req, res);
    } else if (req.url.match('/images/*')) {
        return sendImage(req, res);
    }
}).listen(8080);

Laden des Formulars

Anfragen auf das Wurzelverzeichnis führen dazu, dass ein HTML-Formular auf dem Browser erscheint.
function showUploadForm(res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write('<form action="fileupload" method="post" enctype="multipart/form-data">');
    res.write('Bild: <input type="file" name="filetoupload"><br>');
    res.write('Titel: <input type="edit" name="titel"><br>');
    res.write('<input type="submit">');
    res.write('</form>');
    return res.end();
}
Die direkte Ausgabe der HTML-Zeilen ist bei größeren Formularen natürlich etwas umständlich. Da der HTML-Code nicht dynamisch verändert wird, ist es bei umfangreicherem HTML-Code sinnvoller, diesen in einer eigenständigen HTML-Datei abzulegen und die HTML-Datei an den Aufrufer zu senden.

Hochladen der Datei

Durch die ACTION im Formular wird eine POST-Anfrage auf /filetoupload ausgelöst. Das Rahmenprogramm führt daraufhin in die Funktion loadFileAndShow, die hier vorgestellt wird.

Gleich zu Anfang wird formidable aufgerufen, das zuvor mit require eingebunden wurde. Über deren Parameter wird unter anderem der Pfad festgelegt, in den die hochgeladenen Dateien zunächst abgelegt werden sollen.

var formidable = require('formidable');
// ...
function loadFileAndShow(req, res) {
    const form = formidable(
            { multiples: false,
              uploadDir: __dirname,
              keepExtensions: true
            });

Als Parameter wird ein JSON-Objekt übergeben, das verschiedene Einstellungen festmacht.

Nach dem Durchlaufen der Methode parse ist die Datei hochgeladen worden und befinden sich in dem angegebenen Verzeichnis. Nun soll sie in das eigentliche Zielverzeichnis kommen und ihren ursprünglichen Namen wieder erhalten. Das erledigt die Funktion fs.rename.

Hat die Umbenennung und damit die Verschiebung funktioniert, kann ein neues HTML-Dokument zurückgesandt werden, indem das Bild und der Titel angezeigt wird.

function loadFileAndShow(req, res) {
    const form = formidable({ multiples: false,
            uploadDir: __dirname,     // vorläufiger Ort des Uploads
            keepExtensions: true });
    form.parse(req, function(err, fields, files) {
        if (err) { // Ein Problem? Melde das dem Aufrufer
            res.writeHead(400, { 'Content-Type': 'text/plain' });
            res.end(""+(err));
            return;
        }
        if (files.filetoupload) {
            // Es ist etwas hochgeladen worden. Die Datei in das Zielverzeichnis bringen
            const imageTarget = path.join(__dirname, "images", files.filetoupload.originalFilename);
            imageFile = path.join("images", files.filetoupload.originalFilename);
            fs.rename(files.filetoupload.filepath, imageTarget, function(err) {
                if (err) { // Ist das Verschieben schiefgegangen?
                    res.writeHead(400, { 'Content-Type': 'text/plain' });
                    res.end(""+(err));
                    return;
                }
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.write('<img src="'+ imageFile+'"><br>');
                // Achtung: <img src="xxxx"> führt zu einem weiteren HTTP-GET
                res.write('Titel: '+fields.titel+'<br>');
                res.end();
                return;
            });
        }
    });
}

Anzeigen der Bilder

Der IMG-Tag von HTML führt zu einer neuen HTTP-GET-Anfrage. Aus diesem Grund ist es clever, die Bilder in einem eigenen Verzeichnis zu lagern. Das erleichter das Routing. So wird die hier vorgestellte Funktion sendImage aufgerufen, wenn die URL /image angefragt wurde.

Zum Senden der Bilddatei wird fs.readFile eingesetzt.

function sendImage(req, res) {
    // Hier landen die <img src=/images/xxx> - GET-Anfragen
    fs.readFile(path.join(__dirname, req.url), function(error, data) {
        if (data) {
            res.write(data);
            res.end();
        } else { // error: Die Datei gibt es wohl nicht oder ist defekt
            res.writeHead(404);
            res.write(error.toString());
            res.end("\nDateifehler");
        }
    });
}