Grundlegender statischer Dateiserver in NodeJS

85

Ich versuche, einen statischen Dateiserver in nodejs eher als Übung zum Verständnis von node als als perfekten Server zu erstellen. Ich kenne Projekte wie Connect und Node-Static sehr gut und beabsichtige, diese Bibliotheken für produktionsfähigeren Code zu verwenden, aber ich verstehe auch gerne die Grundlagen meiner Arbeit. In diesem Sinne habe ich eine kleine server.js codiert:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
    var uri = url.parse(req.url).pathname;
    var filename = path.join(process.cwd(), uri);
    path.exists(filename, function(exists) {
        if(!exists) {
            console.log("not exists: " + filename);
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.write('404 Not Found\n');
            res.end();
        }
        var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
        res.writeHead(200, mimeType);

        var fileStream = fs.createReadStream(filename);
        fileStream.pipe(res);

    }); //end path.exists
}).listen(1337);

Meine Frage ist zweifach

  1. Ist dies der "richtige" Weg, um grundlegende HTML-Dateien usw. im Knoten zu erstellen und zu streamen, oder gibt es eine bessere / elegantere / robustere Methode?

  2. Tut die .pipe () im Knoten im Grunde nur Folgendes?

.

var fileStream = fs.createReadStream(filename);
fileStream.on('data', function (data) {
    res.write(data);
});
fileStream.on('end', function() {
    res.end();
});

Vielen Dank an alle!

slapthelownote
quelle
2
Ich habe ein Modul geschrieben, mit dem Sie dies tun können, ohne die Flexibilität zu beeinträchtigen. Außerdem werden alle Ihre Ressourcen automatisch zwischengespeichert. Überprüfen Sie es heraus: github.com/topcloud/cachemere
Jon
2
Ein bisschen lustig, dass Sie (?) Wählen, um '404 Not Found' mit dem HTTP-Statuscode '200 OK' zurückzugeben. Wenn unter der URL keine Ressource gefunden werden kann, sollte der entsprechende Code 404 sein (und was Sie in den Dokumententext schreiben, ist normalerweise von untergeordneter Bedeutung). Sie werden sonst viele Benutzeragenten (einschließlich Webcrawler und andere Bots) verwirren, die ihnen Dokumente ohne echten Wert geben (die sie möglicherweise auch zwischenspeichern).
Am
1
Vielen Dank. Arbeitet noch viele Jahre danach gut.
statosdotcom
1
Vielen Dank! Dieser Code funktioniert einwandfrei. Verwenden Sie jetzt jedoch fs.exists()anstelle des path.exists()obigen Codes. Prost! und ja! nicht vergessen return:
Kaushal28

Antworten:

44
  • Ihr Basisserver sieht gut aus, außer:

    Es returnfehlt eine Aussage.

    res.write('404 Not Found\n');
    res.end();
    return; // <- Don't forget to return here !!
    

    Und:

    res.writeHead(200, mimeType);

    sollte sein:

    res.writeHead(200, {'Content-Type':mimeType});

  • Ja pipe()macht das im Grunde, es pausiert / setzt auch den Quellstrom fort (falls der Empfänger langsamer ist). Hier ist der Quellcode der pipe()Funktion: https://github.com/joyent/node/blob/master/lib/stream.js

Stewe
quelle
2
Was passiert, wenn der Dateiname wie blah.blah.css lautet?
ShrekOverflow
2
mimeType soll in diesem Fall
bla sein
5
Ist das nicht das Problem? Wenn Sie Ihre eigenen schreiben, fragen Sie nach solchen Fehlern. Gute Lernübungen, aber ich lerne, "verbinden" zu schätzen, anstatt meine eigenen zu rollen. Das Problem mit dieser Seite ist, dass die Leute nur herausfinden möchten, wie ein einfacher Dateiserver ausgeführt wird, und dass zuerst ein Stapelüberlauf auftritt. Diese Antwort ist richtig, aber die Leute suchen nicht danach, nur eine einfache Antwort. Ich musste das einfachere selbst herausfinden, also leg es hier hin.
Jason Sebring
1
+1, wenn Sie keinen Link zu einer Lösung in Form einer Bibliothek einfügen, sondern tatsächlich eine Antwort auf die Frage schreiben.
Shawn Whinnery
57

Weniger ist mehr

Gehen Sie einfach zuerst zur Eingabeaufforderung Ihres Projekts und verwenden Sie

$ npm install express

Dann schreiben Sie Ihren app.js Code wie folgt:

var express = require('express'),
app = express(),
port = process.env.PORT || 4000;

app.use(express.static(__dirname + '/public'));
app.listen(port);

Sie würden dann einen "öffentlichen" Ordner erstellen, in dem Sie Ihre Dateien ablegen. Ich habe es zuerst auf die härtere Weise versucht, aber Sie müssen sich um MIME-Typen kümmern, die nur zeitaufwändige Dinge zuordnen müssen, und sich dann um Antworttypen usw. usw. usw. kümmern. Nein, danke.

Jason Sebring
quelle
2
+1 Es gibt viel zu sagen, wenn Sie getesteten Code verwenden, anstatt Ihren eigenen zu rollen.
JCollum
1
Ich habe versucht, die Dokumentation zu lesen, kann aber nicht viel finden. Können Sie erklären, was Ihr Snippet tut? Ich habe versucht, diese spezielle Variante zu verwenden, und ich weiß nicht, was durch was ersetzt werden kann.
Onaclov2000
3
Wenn Sie die Verzeichnisliste möchten, fügen Sie einfach .use (connect.directory ('public')) direkt nach der Zeile connect.static hinzu und ersetzen Sie public durch Ihren Pfad. Entschuldigung für die Entführung, aber ich denke, es klärt die Dinge für mich auf.
Onaclov2000
1
Sie können auch jQuery verwenden! Dies ist keine Antwort auf die Frage des OP, sondern eine Lösung für ein Problem, das es nicht einmal gibt. OP gab an, dass der Zweck dieses Experiments darin bestand, Node zu lernen.
Shawn Whinnery
1
@ JasonSebring Warum require('http')in der zweiten Zeile?
Xiao Peng - ZenUML.com
19

Ich mag es auch zu verstehen, was unter der Haube vor sich geht.

Ich habe einige Dinge in Ihrem Code bemerkt, die Sie wahrscheinlich bereinigen möchten:

  • Es stürzt ab, wenn der Dateiname auf ein Verzeichnis verweist, da exist wahr ist und versucht wird, einen Dateistream zu lesen. Ich habe fs.lstatSync verwendet, um die Existenz eines Verzeichnisses zu bestimmen.

  • Die HTTP-Antwortcodes werden nicht korrekt verwendet (200, 404 usw.)

  • Während MimeType ermittelt wird (aus der Dateierweiterung), wird es in res.writeHead nicht richtig eingestellt (wie von stewe hervorgehoben).

  • Um mit Sonderzeichen umzugehen, möchten Sie wahrscheinlich den Uri verlassen

  • Es folgt blind Symlinks (könnte ein Sicherheitsrisiko sein)

Vor diesem Hintergrund sind einige der Apache-Optionen (FollowSymLinks, ShowIndexes usw.) sinnvoller. Ich habe den Code für Ihren einfachen Dateiserver wie folgt aktualisiert:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
  var uri = url.parse(req.url).pathname;
  var filename = path.join(process.cwd(), unescape(uri));
  var stats;

  try {
    stats = fs.lstatSync(filename); // throws if path doesn't exist
  } catch (e) {
    res.writeHead(404, {'Content-Type': 'text/plain'});
    res.write('404 Not Found\n');
    res.end();
    return;
  }


  if (stats.isFile()) {
    // path exists, is a file
    var mimeType = mimeTypes[path.extname(filename).split(".").reverse()[0]];
    res.writeHead(200, {'Content-Type': mimeType} );

    var fileStream = fs.createReadStream(filename);
    fileStream.pipe(res);
  } else if (stats.isDirectory()) {
    // path exists, is a directory
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('Index of '+uri+'\n');
    res.write('TODO, show index?\n');
    res.end();
  } else {
    // Symbolic link, other?
    // TODO: follow symlinks?  security?
    res.writeHead(500, {'Content-Type': 'text/plain'});
    res.write('500 Internal server error\n');
    res.end();
  }

}).listen(1337);
Jeff Ward
quelle
4
kann ich vorschlagen "var mimeType = mimeTypes [path.extname (Dateiname) .split (". "). reverse () [0]];" stattdessen? Einige Dateinamen haben mehr als ein "." zB "my.cool.video.mp4" oder "download.tar.gz"
synchronisiert
Verhindert dies irgendwie, dass jemand eine URL wie den Ordner /../../../ home / user / jackpot.privatekey verwendet? Ich sehe den Join, um sicherzustellen, dass der Pfad stromabwärts liegt, aber ich frage mich, ob die Verwendung der Notationstyp ../../../ dies umgehen wird oder nicht. Vielleicht werde ich es selbst testen.
Reynard
Es funktioniert nicht. Ich bin mir nicht sicher warum, aber das ist schön zu wissen.
Reynard
Schön, ein RegEx-Match kann auch die Erweiterung sammeln. var mimeType = mimeTypes[path.extname(filename).match(/\.([^\.]+)$/)[1]];
John Mutuma
4
var http = require('http')
var fs = require('fs')

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'content-type': 'text/plain' })

  fs.createReadStream(process.argv[3]).pipe(res)
})

server.listen(Number(process.argv[2]))
Chí Nguyễn
quelle
4
Vielleicht möchten Sie das etwas näher erläutern.
Nathan Tuggy
3

Wie wäre es mit diesem Muster, bei dem nicht separat überprüft wird, ob die Datei vorhanden ist

        var fileStream = fs.createReadStream(filename);
        fileStream.on('error', function (error) {
            response.writeHead(404, { "Content-Type": "text/plain"});
            response.end("file not found");
        });
        fileStream.on('open', function() {
            var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
            response.writeHead(200, {'Content-Type': mimeType});
        });
        fileStream.on('end', function() {
            console.log('sent file ' + filename);
        });
        fileStream.pipe(response);
Aerik
quelle
1
Sie haben den Mimetyp im Erfolgsfall vergessen. Ich verwende dieses Design, aber anstatt die Streams sofort zu leiten, leite ich sie im 'open'-Ereignis des Dateistreams weiter: writeHead für den Mimetyp, dann Pipe. Das Ende wird nicht benötigt: readable.pipe .
GeH
Geändert gemäß dem Kommentar von @GeH.
Brett Zamir
Sollte seinfileStream.on('open', ...
Petah
2

Ich habe eine httpServer-Funktion mit zusätzlichen Funktionen für den allgemeinen Gebrauch basierend auf der Antwort von @Jeff Ward erstellt

  1. custtom dir
  2. index.html gibt zurück, wenn req === dir

Verwendung:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

Vielen Dank.


quelle
0

Das st-Modul erleichtert das Bereitstellen statischer Dateien. Hier ist ein Auszug aus README.md:

var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
  var stHandled = mount(req, res);
  if (stHandled)
    return
  else
    res.end('this is not a static file')
}).listen(1338)
Kaore
quelle
0

@ JasonSebring Antwort zeigte mir in die richtige Richtung, aber sein Code ist veraltet. So machen Sie es mit der neuesten connectVersion.

var connect = require('connect'),
    serveStatic = require('serve-static'),
    serveIndex = require('serve-index');

var app = connect()
    .use(serveStatic('public'))
    .use(serveIndex('public', {'icons': true, 'view': 'details'}))
    .listen(3000);

Im connect GitHub Repository gibt es andere Middlewares, die Sie verwenden können.

ffleandro
quelle
Ich habe stattdessen nur Express verwendet, um eine einfachere Antwort zu erhalten. In der neuesten Express-Version ist die Statik eingebrannt, aber sonst nicht viel. Vielen Dank!
Jason Sebring
Wenn man sich die connectDokumentation ansieht , ist es nur ein wrapperfür middleware. Alle anderen interessanten middlewarestammen aus dem expressRepository, sodass Sie diese APIs technisch mit dem verwenden können express.use().
ffleandro