Serveur de fichiers statiques de base dans NodeJS


85

J'essaie de créer un serveur de fichiers statique dans nodejs plus comme un exercice pour comprendre le nœud que comme un serveur parfait. Je connais bien des projets comme Connect et node-static et j'ai pleinement l'intention d'utiliser ces bibliothèques pour un code plus prêt pour la production, mais j'aime aussi comprendre les bases de ce avec quoi je travaille. Dans cet esprit, j'ai codé un petit 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);

Ma question est double

  1. Est-ce la "bonne" façon de créer et de diffuser du code HTML de base, etc.

  2. Le .pipe () dans node fait-il essentiellement ce qui suit?

.

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

Merci tout le monde!


2
J'ai écrit un module qui vous permet de faire cela sans compromettre la flexibilité. Il met également automatiquement en cache toutes vos ressources. Découvrez-le: github.com/topcloud/cachemere
Jon

2
Un peu drôle que vous choisissiez (?) Pour renvoyer '404 Not Found' avec le code d'état HTTP '200 OK'. S'il n'y a pas de ressource à trouver à l'URL, le code approprié doit être 404 (et ce que vous écrivez dans le corps du document est généralement d'une importance secondaire). Sinon, vous dérouterez beaucoup d'agents utilisateurs (y compris les robots d'exploration et autres robots) en leur donnant des documents sans valeur réelle (qu'ils peuvent également mettre en cache).
amn le

1
Merci. Je travaille toujours bien de nombreuses années après.
statosdotcom

1
Merci! ce code fonctionne parfaitement. Mais maintenant, utilisez fs.exists()plutôt que path.exists()dans le code ci-dessus. À votre santé! et ouais! n'oubliez pas return:
Kaushal28

REMARQUE : 1) fs.exists() est obsolète . Utilisez fs.access()ou encore mieux que pour le cas d'utilisation ci - dessus, fs.stat(). 2) url.parse est obsolète ; utilisez new URLplutôt la nouvelle interface.
rags2riches le

Réponses:


44
  • Votre serveur de base a l'air bien, sauf:

    Il returnmanque une déclaration.

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

    Et:

    res.writeHead(200, mimeType);

    devrait être:

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

  • Oui pipe()fait essentiellement cela, il met également en pause / reprend le flux source (au cas où le récepteur serait plus lent). Voici le code source de la pipe()fonction: https://github.com/joyent/node/blob/master/lib/stream.js


2
que se passera-t-il si le nom du fichier est comme blah.blah.css?
ShrekOverflow

2
mimeType doit être bla dans ce cas xP
ShrekOverflow

5
N'est-ce pas le hic? si vous écrivez le vôtre, vous demandez ces types de bogues. Bon exercice d'apprentissage mais j'apprends à apprécier "connecter" plutôt que de rouler le mien. Le problème avec cette page est que les gens cherchent juste à savoir comment faire un simple serveur de fichiers et que le débordement de pile survient en premier. Cette réponse est juste mais les gens ne la recherchent pas, juste une réponse simple. Je devais trouver le plus simple moi-même, alors mettez-le ici.
Jason Sebring

1
+1 pour ne pas coller de lien vers une solution sous la forme d'une bibliothèque, mais pour avoir écrit une réponse à la question.
Shawn Whinnery

57

Moins est plus

Allez simplement à l'invite de commande sur votre projet et utilisez

$ npm install express

Ensuite, écrivez votre code app.js comme ceci:

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

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

Vous créeriez alors un dossier «public» dans lequel vous placez vos fichiers. J'ai d'abord essayé la méthode la plus difficile, mais vous devez vous soucier des types de mime, qui consiste simplement à mapper des éléments qui prennent du temps, puis à vous soucier des types de réponse, etc. etc. etc., non merci.


2
+1 Il y a beaucoup à dire sur l'utilisation de code testé au lieu de lancer le vôtre.
jcollum

1
J'ai essayé de consulter la documentation, mais je n'arrive pas à trouver grand-chose, pouvez-vous expliquer ce que fait votre extrait? J'ai essayé d'utiliser cette variante particulière et je ne sais pas ce qui peut être remplacé par quoi.
onaclov2000

3
Si vous voulez la liste des répertoires, ajoutez simplement .use (connect.directory ('public')) juste après la ligne connect.static, en remplaçant public, par votre chemin. Désolé pour le détournement, mais je pense que cela clarifie les choses pour moi.
onaclov2000

1
Vous pourriez aussi bien utiliser jQuery! Ce n'est pas une réponse à la question du PO mais une solution à un problème qui n'existe même pas. OP a déclaré que le but de cette expérience était d'apprendre Node.
Shawn Whinnery

1
@JasonSebring Pourquoi require('http')en deuxième ligne?
Xiao Peng - ZenUML.com

19

J'aime aussi comprendre ce qui se passe sous le capot.

J'ai remarqué quelques éléments dans votre code que vous souhaitez probablement nettoyer:

  • Il se bloque lorsque le nom de fichier pointe vers un répertoire, car existe est vrai et il essaie de lire un flux de fichiers. J'ai utilisé fs.lstatSync pour déterminer l'existence du répertoire.

  • Il n'utilise pas correctement les codes de réponse HTTP (200, 404, etc.)

  • Pendant que MimeType est déterminé (à partir de l'extension de fichier), il n'est pas défini correctement dans res.writeHead (comme l'a souligné stewe)

  • Pour gérer les caractères spéciaux, vous voudrez probablement échapper à l'URI

  • Il suit aveuglément les liens symboliques (pourrait être un problème de sécurité)

Compte tenu de cela, certaines des options apache (FollowSymLinks, ShowIndexes, etc.) commencent à avoir plus de sens. J'ai mis à jour le code de votre serveur de fichiers simple comme suit:

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
puis-je suggérer "var mimeType = mimeTypes [path.extname (filename) .split (". "). reverse () [0]];" au lieu? certains noms de fichiers ont plus d'un "." par exemple "my.cool.video.mp4" ou "download.tar.gz"
non synchronisé le

Cela empêche-t-il quelqu'un d'utiliser une URL comme le dossier /../../../ home / user / jackpot.privatekey? Je vois la jointure pour m'assurer que le chemin est en aval, mais je me demande si l'utilisation du type de notation ../../../ permet de contourner cela ou non. Je vais peut-être le tester moi-même.
Reynard

Ça ne marche pas. Je ne sais pas pourquoi, mais c'est bon à savoir.
Reynard

sympa, une correspondance RegEx peut également collecter l'extension; 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
Je voudrais peut-être expliquer cela un peu plus.
Nathan Tuggy

3

Que diriez-vous de ce modèle, qui évite de vérifier séparément que le fichier existe

        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
vous avez oublié le type MIME en cas de succès. J'utilise cette conception, mais au lieu de rediriger immédiatement les flux, je les envoie dans l'événement 'open' du filestream: writeHead pour le type mime, puis pipe. La fin n'est pas nécessaire: readable.pipe .
GeH

Modifié selon le commentaire de @GeH.
Brett Zamir

Devrait êtrefileStream.on('open', ...
Petah

2

J'ai créé une fonction httpServer avec des fonctionnalités supplémentaires pour un usage général basé sur la réponse @Jeff Ward

  1. répertoire personnalisé
  2. index.html renvoie si req === dir

Usage:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

Merci.


0

le module st facilite la diffusion de fichiers statiques. Voici un extrait de 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 réponse de @JasonSebring m'a orienté dans la bonne direction, mais son code est obsolète. Voici comment procéder avec la dernière 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);

Dans le connect référentiel GitHub, vous pouvez utiliser d'autres middlewares.


Je viens d'utiliser express à la place pour une réponse plus simple. La dernière version express a la charge statique mais pas grand-chose d'autre. Merci!
Jason Sebring

En regardant la connectdocumentation, ce n'est qu'un wrapperpour middleware. Tous les autres éléments intéressants middlewareproviennent du expressréférentiel, vous pouvez donc techniquement utiliser ces API en utilisant le express.use().
ffleandro
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.