Podstawowy serwer plików statycznych w NodeJS


85

Próbuję stworzyć statyczny serwer plików w nodejs bardziej jako ćwiczenie do zrozumienia węzła niż jako doskonały serwer. Dobrze znam projekty takie jak Connect i node-static i zamierzam w pełni wykorzystać te biblioteki do tworzenia kodu gotowego do produkcji, ale lubię też rozumieć podstawy tego, nad czym pracuję. Mając to na uwadze, napisałem mały server.js:

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);

Moje pytanie jest dwojakie

  1. Czy jest to „właściwy” sposób tworzenia i przesyłania strumieniowego podstawowego kodu HTML itp. W węźle, czy też istnieje lepsza / bardziej elegancka / solidniejsza metoda?

  2. Czy .pipe () w węźle zasadniczo wykonuje następujące czynności?

.

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

Dziękuję wszystkim!


2
Napisałem moduł, który pozwala ci to robić bez ograniczania elastyczności. Również automatycznie buforuje wszystkie twoje zasoby. Sprawdź to: github.com/topcloud/cachemere
Jon

2
Trochę zabawne, że wybrałeś (?), Aby zwrócić „404 Not Found” z kodem statusu HTTP „200 OK”. Jeśli pod adresem URL nie można znaleźć zasobu, odpowiedni kod powinien mieć wartość 404 (a to, co piszesz w treści dokumentu, ma zwykle drugorzędne znaczenie). W przeciwnym razie wprowadzisz w błąd wielu klientów użytkownika (w tym roboty sieciowe i inne boty), udostępniając im dokumenty bez rzeczywistej wartości (które również mogą przechowywać w pamięci podręcznej).
amn

1
Dzięki. Po wielu latach nadal dobrze się pracuje.
statosdotcom

1
Dzięki! ten kod działa doskonale. Ale teraz użyj fs.exists()zamiast path.exists()w powyższym kodzie. Twoje zdrowie! i tak! nie zapomnij return:
Kaushal28

UWAGA : 1) fs.exists() jest przestarzały . Stosowanie fs.access()lub nawet lepiej jak w powyższym przypadku użycia, fs.stat(). 2) url.parse jest przestarzałe ; new URLzamiast tego użyj nowszego interfejsu.
rags2riches

Odpowiedzi:


44
  • Twój podstawowy serwer wygląda dobrze, z wyjątkiem:

    Brakuje returnoświadczenia.

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

    I:

    res.writeHead(200, mimeType);

    Powinien być:

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

  • Tak pipe(), w zasadzie to również wstrzymuje / wznawia strumień źródłowy (w przypadku, gdy odbiornik jest wolniejszy). Oto kod źródłowy pipe()funkcji: https://github.com/joyent/node/blob/master/lib/stream.js


2
co się stanie, jeśli nazwa pliku będzie taka jak blah.blah.css?
ShrekOverflow

2
W takim przypadku
mimeType

5
Czy to nie jest problem? jeśli piszesz własny, pytasz o tego typu błędy. Dobre ćwiczenie uczenia się, ale uczę się bardziej doceniać „łączenie”, a nie własne. Problem z tą stroną polega na tym, że ludzie szukają tylko po to, aby dowiedzieć się, jak zrobić prosty serwer plików i najpierw pojawia się przepełnienie stosu. Ta odpowiedź jest prawidłowa, ale ludzie jej nie szukają, tylko prosta odpowiedź. Sam musiałem znaleźć prostszy, więc umieść go tutaj.
Jason Sebring

1
+1 za nie wklejanie linku do rozwiązania w postaci biblioteki, ale za napisanie odpowiedzi na pytanie.
Shawn Whinnery

57

Mniej znaczy więcej

Po prostu przejdź najpierw do wiersza poleceń w swoim projekcie i użyj

$ npm install express

Następnie napisz swój kod app.js w następujący sposób:

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

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

Utworzyłbyś wtedy folder „publiczny”, w którym umieszczasz swoje pliki. Najpierw spróbowałem tego trudniej, ale musisz się martwić typami mime, które wymagają tylko mapowania rzeczy, które są czasochłonne, a następnie martwienia się o typy odpowiedzi itp. Itd. Itd. Nie, dziękuję.


2
+1 Jest wiele do powiedzenia na temat używania przetestowanego kodu zamiast rozwijania własnego.
jcollum

1
Próbowałem przejrzeć dokumentację, ale nie mogę znaleźć zbyt wiele. Czy możesz wyjaśnić, co robi Twój fragment? Próbowałem użyć tej konkretnej odmiany i nie wiem, co można zastąpić czym.
onaclov2000

3
Jeśli chcesz wyświetlić katalog, po prostu dodaj .use (connect.directory ('public')) bezpośrednio po linii connect.static, zastępując public, swoją ścieżką. Przepraszam za porwanie, ale myślę, że to wszystko wyjaśnia.
onaclov2000

1
Równie dobrze możesz „Użyj jQuery”! To nie jest odpowiedź na pytanie PO, ale rozwiązanie problemu, który w ogóle nie istnieje. OP stwierdził, że celem tego eksperymentu była nauka Node.
Shawn Whinnery

1
@JasonSebring Dlaczego require('http')w drugiej linii?
Xiao Peng - ZenUML.com

19

Lubię też rozumieć, co się dzieje pod maską.

Zauważyłem kilka rzeczy w Twoim kodzie, które prawdopodobnie chcesz wyczyścić:

  • Awaria, gdy nazwa_pliku wskazuje na katalog, ponieważ istnieje prawda i próbuje odczytać strumień pliku. Użyłem fs.lstatSync do określenia istnienia katalogu.

  • Nie używa poprawnie kodów odpowiedzi HTTP (200, 404 itd.)

  • Podczas określania MimeType (z rozszerzenia pliku), nie jest on ustawiany poprawnie w res.writeHead (jak wskazał stewe)

  • Aby obsłużyć znaki specjalne, prawdopodobnie chcesz wyłączyć uri

  • Ślepo podąża za linkami symbolicznymi (może to stanowić zagrożenie dla bezpieczeństwa)

Biorąc to pod uwagę, niektóre opcje Apache (FollowSymLinks, ShowIndexes itp.) Zaczynają mieć więcej sensu. Zaktualizowałem kod twojego prostego serwera plików w następujący sposób:

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);

4
czy mogę zasugerować "var mimeType = mimeTypes [ścieżka.nazwa_tekstu (nazwa_pliku) .split (". "). reverse () [0]];" zamiast? niektóre nazwy plików mają więcej niż jeden znak „.” np. „my.cool.video.mp4” lub „download.tar.gz”
niezsynchronizowano

Czy to w jakiś sposób powstrzymuje kogoś przed użyciem adresu URL takiego jak folder /../../../ home / user / jackpot.privatekey? Widzę złączenie, aby upewnić się, że ścieżka jest w dół, ale zastanawiam się, czy użycie notacji typu ../../../ obejdzie to, czy nie. Może sam to przetestuję.
Reynard

To nie działa. Nie jestem pewien dlaczego, ale dobrze to wiedzieć.
Reynard

fajnie, dopasowanie RegEx może również zbierać rozszerzenie; 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]))

4
Chciałbym to trochę bardziej wyjaśnić.
Nathan Tuggy

3

A co z tym wzorcem, który pozwala uniknąć oddzielnego sprawdzania, czy plik istnieje

        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);

1
zapomniałeś typu MIME w przypadku sukcesu. Używam tego projektu, ale zamiast natychmiastowego przesyłania strumieniowego strumieni, przesyłam je w zdarzeniu „open” strumienia pliku: writeHead dla typu MIME, a następnie potok. Koniec nie jest potrzebny: readable.pipe .
GeH

Zmodyfikowano zgodnie z komentarzem @GeH.
Brett Zamir

Powinno byćfileStream.on('open', ...
Petah

2

Stworzyłem funkcję httpServer z dodatkowymi funkcjami do ogólnego użytku na podstawie odpowiedzi @Jeff Ward

  1. custtom reż
  2. index.html zwraca if req === dir

Stosowanie:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

Dzięki.


0

moduł st sprawia obsługujących pliki statyczne łatwe. Oto fragment pliku 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)

0

Odpowiedź @JasonSebring wskazała mi właściwy kierunek, jednak jego kod jest nieaktualny. Oto, jak to zrobić w najnowszej connectwersji.

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);

W connect repozytorium GitHub dostępne są inne oprogramowanie pośredniczące.


Zamiast tego użyłem ekspresu, aby uzyskać prostszą odpowiedź. W najnowszej wersji ekspresowej pojawiło się statyczne, ale niewiele więcej. Dzięki!
Jason Sebring,

Patrząc na connectdokumentację, jest to tylko wrapperfor middleware. Wszystkie inne interesujące middlewarepochodzą z expressrepozytorium, więc technicznie rzecz biorąc, możesz użyć tych interfejsów API za pomocą express.use().
ffleandro
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.