File server statico di base in NodeJS


85

Sto cercando di creare un file server statico in nodejs più come esercizio per comprendere node che come server perfetto. Conosco bene progetti come Connect e node-static e intendo utilizzare queste librerie per un codice più pronto per la produzione, ma mi piace anche capire le basi di ciò con cui sto lavorando. Con questo in mente, ho programmato un piccolo 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);

La mia domanda è duplice

  1. È questo il modo "giusto" per creare e trasmettere in streaming html di base ecc. In node o esiste un metodo migliore / più elegante / più robusto?

  2. Il .pipe () nel nodo fondamentalmente sta solo facendo quanto segue?

.

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

Grazie a tutti!


2
Ho scritto un modulo che ti consente di farlo senza compromettere la flessibilità. Inoltre, memorizza automaticamente nella cache tutte le risorse. Dai
Jon

2
Un po 'strano che tu abbia scelto (?) Per restituire "404 Not Found" con il codice di stato HTTP "200 OK". Se non è presente alcuna risorsa nell'URL, il codice appropriato dovrebbe essere 404 (e ciò che scrivi nel corpo del documento è di solito di secondaria importanza). Altrimenti confonderai molti programmi utente (inclusi web crawler e altri bot) fornendo loro documenti senza valore reale (che potrebbero anche memorizzare nella cache).
amn

1
Grazie. Ancora lavorando bene molti anni dopo.
statosdotcom

1
Grazie! questo codice funziona perfettamente. Ma ora usa fs.exists()invece che path.exists()nel codice sopra. Saluti! e si! non dimenticare return:
Kaushal28

NOTA : 1) fs.exists() è deprecato . Utilizzare fs.access()o meglio ancora, come per il caso d'uso di cui sopra, fs.stat(). 2) url.parse è deprecato ; usa invece la nuova new URLinterfaccia.
rags2riches il

Risposte:


44
  • Il tuo server di base sembra buono, tranne:

    Manca una returndichiarazione.

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

    E:

    res.writeHead(200, mimeType);

    dovrebbe essere:

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

  • pipe(), fondamentalmente, mette in pausa / riprende il flusso di origine (nel caso in cui il ricevitore sia più lento). Ecco il codice sorgente della pipe()funzione: https://github.com/joyent/node/blob/master/lib/stream.js


2
cosa succederà se il nome del file è come blah.blah.css?
ShrekOverflow

2
mimeType deve essere blah in quel caso xP
ShrekOverflow

5
Non è questo il problema però? se scrivi tu stesso, stai chiedendo questi tipi di bug. Buon esercizio di apprendimento, ma sto imparando ad apprezzare il "collegamento" piuttosto che il mio. Il problema con questa pagina è che le persone cercano solo di scoprire come creare un semplice file server e lo stack overflow viene visualizzato per primo. Questa risposta è giusta ma le persone non la cercano, solo una semplice risposta. Ho dovuto scoprire io stesso quello più semplice, quindi mettilo qui.
Jason Sebring

1
+1 per non aver incollato un collegamento a una soluzione sotto forma di libreria ma in realtà per aver scritto una risposta alla domanda.
Shawn Whinnery

57

Meno è meglio

Vai prima al prompt dei comandi sul tuo progetto e usa

$ npm install express

Quindi scrivi il tuo codice app.js in questo modo:

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

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

Dovresti quindi creare una cartella "pubblica" in cui posizionare i file. Ho provato prima nel modo più difficile, ma devi preoccuparti dei tipi MIME che devono solo mappare cose che richiedono tempo e poi preoccuparti dei tipi di risposta, ecc. Ecc. Ecc .... no grazie.


2
+1 C'è molto da dire sull'utilizzo di codice testato invece di rotolare il proprio.
jcollum

1
Ho provato a guardare la documentazione, ma non riesco a trovare molto, puoi spiegare cosa sta facendo il tuo snippet? Ho provato a utilizzare questa particolare variazione e non so cosa può essere sostituito con cosa.
onaclov2000

3
Se vuoi che l'elenco delle directory, aggiungi semplicemente .use (connect.directory ('public')) subito dopo la riga connect.static, sostituendo public, con il tuo percorso. Scusa per il dirottamento, ma penso che chiarisca le cose per me.
onaclov2000

1
Potresti anche 'Usa jQuery'! Questa non è una risposta alla domanda del PO, ma una soluzione a un problema che nemmeno esiste. OP ha affermato che lo scopo di questo esperimento era imparare Node.
Shawn Whinnery

1
@ JasonSebring Perché require('http')in seconda fila?
Xiao Peng - ZenUML.com

19

Mi piace anche capire cosa sta succedendo sotto il cofano.

Ho notato alcune cose nel tuo codice che probabilmente vorresti ripulire:

  • Si blocca quando filename punta a una directory, perché esiste è vero e cerca di leggere un flusso di file. Ho usato fs.lstatSync per determinare l'esistenza della directory.

  • Non utilizza correttamente i codici di risposta HTTP (200, 404, ecc.)

  • Mentre MimeType viene determinato (dall'estensione del file), non viene impostato correttamente in res.writeHead (come ha sottolineato Stewe)

  • Per gestire caratteri speciali, probabilmente vorrai annullare l'escape dell'uri

  • Segue ciecamente i collegamenti simbolici (potrebbe essere un problema di sicurezza)

Detto questo, alcune delle opzioni di Apache (FollowSymLinks, ShowIndexes, ecc.) Iniziano ad avere più senso. Ho aggiornato il codice per il tuo semplice file server come segue:

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
posso suggerire "var mimeType = mimeTypes [path.extname (filename) .split (". "). reverse () [0]];" anziché? alcuni nomi di file hanno più di un "." ad esempio "my.cool.video.mp4" o "download.tar.gz"
non sincronizzato

Questo in qualche modo impedisce a qualcuno di usare un URL come la cartella /../../../ home / user / jackpot.privatekey? Vedo il join per garantire che il percorso sia a valle, ma mi chiedo se l'uso del tipo di notazione ../../../ lo aggiri o no. Forse lo proverò io stesso.
Reynard

Non funziona. Non sono sicuro del perché, ma è bello saperlo.
Reynard

bello, una corrispondenza RegEx può anche raccogliere l'estensione; 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
Potrei spiegarlo un po 'di più.
Nathan Tuggy

3

Che ne dici di questo modello, che evita di controllare separatamente che il file esista

        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
hai dimenticato il tipo MIME in caso di successo. Sto usando questo design, ma invece di convogliare immediatamente i flussi, li sto convogliando nell'evento 'open' del filestream: writeHead per il mimetype, quindi pipe. La fine non è necessaria: readable.pipe .
GeH

Modificato secondo il commento di @GeH.
Brett Zamir

Dovrebbe esserefileStream.on('open', ...
Petah

2

Ho creato una funzione httpServer con funzionalità extra per uso generale basata sulla risposta di @Jeff Ward

  1. custtom dir
  2. index.html restituisce se req === dir

Utilizzo:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

Grazie.


0

il modulo st semplifica la gestione dei file statici. Ecco un estratto di 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

La risposta di @ JasonSebring mi ha indicato nella giusta direzione, tuttavia il suo codice è obsoleto. Ecco come lo fai con la connectversione più recente.

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

In connect GitHub Repository ci sono altri middleware che puoi usare.


Ho solo usato express invece per una risposta più semplice. La versione più recente espressa ha la statica incorporata ma non molto altro. Grazie!
Jason Sebring

Guardando la connectdocumentazione, è solo wrapperper middleware. Tutti gli altri elementi interessanti middlewareprovengono dal expressrepository, quindi tecnicamente potresti usare quelle API usando il express.use().
ffleandro
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.