Comment servir une image à l'aide de nodejs


148

J'ai un logo qui réside sur le site public / images / logo.gif. Voici mon code nodejs.

http.createServer(function(req, res){
  res.writeHead(200, {'Content-Type': 'text/plain' });
  res.end('Hello World \n');
}).listen(8080, '127.0.0.1');

Cela fonctionne mais quand je demande localhost: 8080 / logo.gif alors je ne reçois évidemment pas le logo.

Quels changements dois-je faire pour servir une image.

Réponses:


225

Mise à jour 2016

Exemples avec Express et sans Express qui fonctionnent réellement

Cette question date de plus de 5 ans, mais chaque réponse a quelques problèmes .

TL; DR

Faites défiler vers le bas pour voir des exemples pour diffuser une image avec:

  1. express.static
  2. express
  3. connect
  4. http
  5. net

Tous les exemples sont également sur GitHub: https://github.com/rsp/node-static-http-servers

Les résultats des tests sont disponibles sur Travis: https://travis-ci.org/rsp/node-static-http-servers

introduction

Après plus de 5 ans depuis que cette question a été posée, il n'y a qu'une seule bonne réponse de generalhenry mais même si cette réponse ne pose aucun problème avec le code, elle semble avoir quelques problèmes de réception . Il a été commenté que cela "n'explique pas grand-chose d'autre que comment compter sur quelqu'un d'autre pour faire le travail" et le fait que de nombreuses personnes ont voté ce commentaire montre clairement que beaucoup de choses doivent être clarifiées.

Tout d'abord, une bonne réponse à "Comment servir des images à l'aide de Node.js" est de ne pas implémenter un serveur de fichiers statiques à partir de zéro et de le faire mal. Une bonne réponse consiste à utiliser un module comme Express qui fait correctement le travail .

En réponse aux commentaires qui disent que l'utilisation d'Express "n'explique pas grand chose d'autre que comment compter sur quelqu'un d'autre pour faire le travail", il faut noter que l'utilisation du httpmodule dépend déjà de quelqu'un d'autre pour faire le travail. Si quelqu'un ne veut compter sur personne pour faire le travail, alors au moins des sockets TCP bruts devraient être utilisés à la place - ce que je fais dans l'un de mes exemples ci-dessous.

Un problème plus grave est que toutes les réponses ici qui utilisent le httpmodule sont défectueuses . Ils introduisent des conditions de course , la résolution de chemin insécurité qui conduira à la vulnérabilité de parcours de chemin , le blocage d' E / S qui va complètement échouer à traiter des demandes simultanées à tous et d' autres problèmes subtils - ils sont complètement brisés comme des exemples de ce que la question porte sur, et pourtant ils utilisent déjà l'abstraction fournie par le httpmodule au lieu d'utiliser les sockets TCP, donc ils ne font même pas tout à partir de zéro comme ils le prétendent.

Si la question était «Comment implémenter le serveur de fichiers statiques à partir de zéro, comme exercice d'apprentissage», alors les réponses sur la façon de le faire devraient être affichées - mais même dans ce cas, nous devrions nous attendre à ce qu'elles soient au moins correctes . En outre, il n'est pas déraisonnable de supposer que quelqu'un qui souhaite servir une image pourrait vouloir servir plus d'images à l'avenir, de sorte que l'on pourrait affirmer que l'écriture d'un serveur de fichiers statiques personnalisé spécifique qui ne peut servir qu'un seul fichier avec un chemin codé en dur est un peu myope. Il semble difficile d'imaginer que quiconque cherche une réponse sur la façon de servir une image se contenterait d'une solution qui ne sert qu'une seule image au lieu d'une solution générale pour servir n'importe quelle image.

En bref, la question est de savoir comment servir une image et une réponse à cette question est d'utiliser un module approprié de le faire dans une manière sécurisée, preformant et fiable qui est lisible, maintenable et à l' épreuve tout en utilisant la meilleure pratique du nœud professionnel développement. Mais je suis d'accord qu'un excellent ajout à une telle réponse serait de montrer un moyen d'implémenter la même fonctionnalité manuellement, mais malheureusement, chaque tentative de le faire a échoué jusqu'à présent. Et c'est pourquoi j'ai écrit de nouveaux exemples.

Après cette brève introduction, voici mes cinq exemples faisant le travail sur 5 niveaux différents d'abstraction.

Fonctionnalité minimale

Chaque exemple sert des fichiers du publicrépertoire et prend en charge la fonctionnalité minimale de:

  • Types MIME pour les fichiers les plus courants
  • sert HTML, JS, CSS, texte brut et images
  • sert index.htmld'index de répertoire par défaut
  • répond avec des codes d'erreur pour les fichiers manquants
  • aucune vulnérabilité de traversée de chemin
  • pas de conditions de course lors de la lecture des fichiers

J'ai testé toutes les versions sur les versions 4, 5, 6 et 7 de Node.

express.static

Cette version utilise le express.staticmiddleware expressintégré du module.

Cet exemple a le plus de fonctionnalités et le moins de code.

var path = require('path');
var express = require('express');
var app = express();

var dir = path.join(__dirname, 'public');

app.use(express.static(dir));

app.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

express

Cette version utilise le expressmodule mais sans le express.staticmiddleware. La diffusion de fichiers statiques est implémentée en tant que gestionnaire d'itinéraire unique à l'aide de flux.

Cet exemple présente des contre-mesures simples de traversée de chemin et prend en charge un ensemble limité de types MIME les plus courants.

var path = require('path');
var express = require('express');
var app = express();
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

app.get('*', function (req, res) {
    var file = path.join(dir, req.path.replace(/\/$/, '/index.html'));
    if (file.indexOf(dir + path.sep) !== 0) {
        return res.status(403).end('Forbidden');
    }
    var type = mime[path.extname(file).slice(1)] || 'text/plain';
    var s = fs.createReadStream(file);
    s.on('open', function () {
        res.set('Content-Type', type);
        s.pipe(res);
    });
    s.on('error', function () {
        res.set('Content-Type', 'text/plain');
        res.status(404).end('Not found');
    });
});

app.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

connect

Cette version utilise le connectmodule qui est un niveau d'abstraction inférieur à express.

Cet exemple a des fonctionnalités similaires à la expressversion mais en utilisant des API légèrement inférieures.

var path = require('path');
var connect = require('connect');
var app = connect();
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

app.use(function (req, res) {
    var reqpath = req.url.toString().split('?')[0];
    if (req.method !== 'GET') {
        res.statusCode = 501;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Method not implemented');
    }
    var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
    if (file.indexOf(dir + path.sep) !== 0) {
        res.statusCode = 403;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Forbidden');
    }
    var type = mime[path.extname(file).slice(1)] || 'text/plain';
    var s = fs.createReadStream(file);
    s.on('open', function () {
        res.setHeader('Content-Type', type);
        s.pipe(res);
    });
    s.on('error', function () {
        res.setHeader('Content-Type', 'text/plain');
        res.statusCode = 404;
        res.end('Not found');
    });
});

app.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

http

Cette version utilise le httpmodule qui est l'API de plus bas niveau pour HTTP dans Node.

Cet exemple a des fonctionnalités similaires à la connectversion, mais utilise encore plus d'API de niveau inférieur.

var path = require('path');
var http = require('http');
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

var server = http.createServer(function (req, res) {
    var reqpath = req.url.toString().split('?')[0];
    if (req.method !== 'GET') {
        res.statusCode = 501;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Method not implemented');
    }
    var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
    if (file.indexOf(dir + path.sep) !== 0) {
        res.statusCode = 403;
        res.setHeader('Content-Type', 'text/plain');
        return res.end('Forbidden');
    }
    var type = mime[path.extname(file).slice(1)] || 'text/plain';
    var s = fs.createReadStream(file);
    s.on('open', function () {
        res.setHeader('Content-Type', type);
        s.pipe(res);
    });
    s.on('error', function () {
        res.setHeader('Content-Type', 'text/plain');
        res.statusCode = 404;
        res.end('Not found');
    });
});

server.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

net

Cette version utilise le netmodule qui est l'API de plus bas niveau pour les sockets TCP dans Node.

Cet exemple présente certaines des fonctionnalités de la httpversion, mais le protocole HTTP minimal et incomplet a été implémenté à partir de zéro. Puisqu'il ne prend pas en charge le codage fragmenté, il charge les fichiers en mémoire avant de les servir pour connaître la taille avant d'envoyer une réponse, car le stattage des fichiers puis le chargement introduiraient une condition de concurrence.

var path = require('path');
var net = require('net');
var fs = require('fs');

var dir = path.join(__dirname, 'public');

var mime = {
    html: 'text/html',
    txt: 'text/plain',
    css: 'text/css',
    gif: 'image/gif',
    jpg: 'image/jpeg',
    png: 'image/png',
    svg: 'image/svg+xml',
    js: 'application/javascript'
};

var server = net.createServer(function (con) {
    var input = '';
    con.on('data', function (data) {
        input += data;
        if (input.match(/\n\r?\n\r?/)) {
            var line = input.split(/\n/)[0].split(' ');
            var method = line[0], url = line[1], pro = line[2];
            var reqpath = url.toString().split('?')[0];
            if (method !== 'GET') {
                var body = 'Method not implemented';
                con.write('HTTP/1.1 501 Not Implemented\n');
                con.write('Content-Type: text/plain\n');
                con.write('Content-Length: '+body.length+'\n\n');
                con.write(body);
                con.destroy();
                return;
            }
            var file = path.join(dir, reqpath.replace(/\/$/, '/index.html'));
            if (file.indexOf(dir + path.sep) !== 0) {
                var body = 'Forbidden';
                con.write('HTTP/1.1 403 Forbidden\n');
                con.write('Content-Type: text/plain\n');
                con.write('Content-Length: '+body.length+'\n\n');
                con.write(body);
                con.destroy();
                return;
            }
            var type = mime[path.extname(file).slice(1)] || 'text/plain';
            var s = fs.readFile(file, function (err, data) {
                if (err) {
                    var body = 'Not Found';
                    con.write('HTTP/1.1 404 Not Found\n');
                    con.write('Content-Type: text/plain\n');
                    con.write('Content-Length: '+body.length+'\n\n');
                    con.write(body);
                    con.destroy();
                } else {
                    con.write('HTTP/1.1 200 OK\n');
                    con.write('Content-Type: '+type+'\n');
                    con.write('Content-Length: '+data.byteLength+'\n\n');
                    con.write(data);
                    con.destroy();
                }
            });
        }
    });
});

server.listen(3000, function () {
    console.log('Listening on http://localhost:3000/');
});

Téléchargez des exemples

J'ai posté tous les exemples sur GitHub avec plus d'explications.

Exemples avec express.static, express, connect, httpet net:

Autre projet utilisant uniquement express.static:

Des tests

Les résultats des tests sont disponibles sur Travis:

Tout est testé sur les versions 4, 5, 6 et 7 de Node.

Voir également

Autres réponses connexes:


8
Meilleure et complète réponse à la question. Dommage, je ne peux vous voter qu'une seule fois.
— Kulvar

3
Il devrait y avoir un moyen de réorganiser les questions vintage comme celle-ci! Je viens de perdre environ une heure à essayer de faire fonctionner la réponse à 110 voix. Enfin j'ai fait défiler vers le bas juste pour vérifier. Votre réponse pourrait (devrait) être un manuel sur le sujet.
— Thailandian

2
Meilleure réponse et en détail. Devrait être la réponse acceptée marquée. Merci mon pote !!!
— Prabodh M

Je ne sais pas pourquoi quelqu'un utiliserait Express. Je l'ai fait aussi au début probablement parce que tout le monde l'a fait. Puis j'ai réalisé que l'utilisation du module http de Node était la bonne approche. C'est pour cela qu'il est prévu. Vous bénéficiez d'une grande flexibilité. Vous comprenez le protocole HTTP et vous pouvez déboguer facilement. Express fournit beaucoup de jargon et une couche mince sur le module http qui est facile à implémenter avec un codage brut avec le module http. Je recommande fortement à tout utilisateur d'Express ou de tout autre module similaire de s'en éloigner et d'utiliser directement le module http.
— Ensoleillé

1
une note que je voudrais mentionner pour les débutants comme moi: lorsque nous déclarons un dossier comme statique en utilisant, express.staticnous pouvons récupérer l'image en appelant l'url http://ip:port/path_after_the_static_folder, nous n'avons pas besoin de mentionner le dossier statique lui-même pour servir l'image. Bien que nous puissions ajouter cela en utilisant app.use('/static', express.static(imagePath))pour plus de commodité comme documentation par: expressjs.com/en/starter/static-files.html
— Rakibul Haq

160

Je suis d'accord avec les autres affiches que finalement, vous devriez utiliser un framework, tel qu'Express ... mais d'abord vous devez aussi comprendre comment faire quelque chose de fondamental comme ça sans bibliothèque, pour vraiment comprendre ce que la bibliothèque résume pour vous. les étapes sont

  1. Analysez la requête HTTP entrante, pour voir quel chemin l'utilisateur demande
  2. Ajouter un chemin dans une instruction conditionnelle pour que le serveur réponde
  3. Si l'image est demandée, lisez le fichier image sur le disque.
  4. Servir le type de contenu d'image dans un en-tête
  5. Servir le contenu de l'image dans le corps

Le code ressemblerait à quelque chose comme ça (non testé)

fs = require('fs');
http = require('http');
url = require('url');


http.createServer(function(req, res){
  var request = url.parse(req.url, true);
  var action = request.pathname;

  if (action == '/logo.gif') {
     var img = fs.readFileSync('./logo.gif');
     res.writeHead(200, {'Content-Type': 'image/gif' });
     res.end(img, 'binary');
  } else { 
     res.writeHead(200, {'Content-Type': 'text/plain' });
     res.end('Hello World \n');
  }
}).listen(8080, '127.0.0.1');

27
Vous ne devez pas utiliser readFileSync au milieu d'une réponse. Soit une charge de synchronisation doit être utilisée sur le premier tic, soit la méthode async doit être utilisée. codr.cc/s/5d0b73d6/js
— generalhenry

1
Je suis à bord avec vous sur la version de synchronisation, pour la version asynchrone cependant, je pensais que le danger d'utiliser des opérations non bloquantes pour les fichiers, était que cela pouvait envoyer la réponse avant que le fichier entier ne soit lu, et finir par vous quitter avec un fichier partiel servi à l'utilisateur? Avez-vous besoin d'utiliser un encodage fragmenté si vous utilisez une lecture de fichier asynchrone?
— noli le

1
fs.readFileSync ne rappelle pas tant que le fichier entier n'est pas chargé, il n'est donc pas nécessaire de gérer les morceaux. La gestion des blocs est principalement destinée au transfert de fichiers réseau (car les choses peuvent prendre plus de temps que prévu).
— generalhenry

9
La ligne res.end(img);doit êtreres.end(img, 'binary'); . Bon travail!
— Honza Pokorny

3
+1 pour "mais vous devez d'abord comprendre comment faire quelque chose de fondamental comme celui-ci sans bibliothèque, pour vraiment comprendre ce que la bibliothèque résume pour vous .."
— Sunny

67

Vous devez utiliser le framework express .

npm install express

puis

var express = require('express');
var app = express();
app.use(express.static(__dirname + '/public'));
app.listen(8080);

puis l'URL localhost: 8080 / images / logo.gif devrait fonctionner.


18
Sûr, mais n'explique pas grand-chose d'autre que comment compter sur quelqu'un d'autre pour faire le travail.
— LeeGee

J'ai ajouté une version de nœud vanille (juste les modules de base).
— generalhenry

+ I C'est la seule réponse correcte publiée à ce jour. Je l'explique dans ma réponse plus en détail.
— rsp

15

Il est trop tard mais aide quelqu'un, j'utilise node version v7.9.0etexpress version 4.15.0

si votre structure de répertoires ressemble à ceci:

your-project
   uploads
   package.json
   server.js

Code server.js:

var express         = require('express');
var app             = express();
app.use(express.static(__dirname + '/uploads'));// you can access image 
 //using this url: http://localhost:7000/abc.jpg
//make sure `abc.jpg` is present in `uploads` dir.

//Or you can change the directory for hiding real directory name:

`app.use('/images', express.static(__dirname+'/uploads/'));// you can access image using this url: http://localhost:7000/images/abc.jpg


app.listen(7000);

14

Version du nœud Vanilla comme demandé:

var http = require('http');
var url = require('url');
var path = require('path');
var fs = require('fs');

http.createServer(function(req, res) {
  // parse url
  var request = url.parse(req.url, true);
  var action = request.pathname;
  // disallow non get requests
  if (req.method !== 'GET') {
    res.writeHead(405, {'Content-Type': 'text/plain' });
    res.end('405 Method Not Allowed');
    return;
  }
  // routes
  if (action === '/') {
    res.writeHead(200, {'Content-Type': 'text/plain' });
    res.end('Hello World \n');
    return;
  }
  // static (note not safe, use a module for anything serious)
  var filePath = path.join(__dirname, action).split('%20').join(' ');
  fs.exists(filePath, function (exists) {
    if (!exists) {
       // 404 missing files
       res.writeHead(404, {'Content-Type': 'text/plain' });
       res.end('404 Not Found');
       return;
    }
    // set the content type
    var ext = path.extname(action);
    var contentType = 'text/plain';
    if (ext === '.gif') {
       contentType = 'image/gif'
    }
    res.writeHead(200, {'Content-Type': contentType });
    // stream the file
    fs.createReadStream(filePath, 'utf-8').pipe(res);
  });
}).listen(8080, '127.0.0.1');

2
Vous ne voulez pas vérifier fs.exists(condition de concurrence), ce serait une meilleure habitude de détecter une erreur lors de la tuyauterie.
— Brendan le

Bien que la vérification existe dans ce cas n'est pas la méthode du nœud, tout le reste de cette réponse est 1 million de fois meilleur que la réponse acceptée.
— Ninjaxor

1
Je suis d'accord avec @BrendanAshworth. Les conditions de course sont présentes dans presque toutes les réponses ici. J'en ai écrit plus dans ma réponse . Mais bravo pour l'écrire avec des flux. Presque toutes les autres réponses utilisent readFileSync qui est bloquant et ne doit être utilisé dans aucun gestionnaire d'événements.
— rsp

1
var filePath = path.resolve ('public', '.' + parts.pathname); response.writeHead (200, {'Content-Type': mime.lookup (parts.pathname)}); mime - package mime-type de npm
— Rijen

14

var http = require('http');
var fs = require('fs');

http.createServer(function(req, res) {
  res.writeHead(200,{'content-type':'image/jpg'});
  fs.createReadStream('./image/demo.jpg').pipe(res);
}).listen(3000);
console.log('server running at 3000');

simple et direct, bien mieux que les réponses expresses .. cela mérite 1000 votes positifs. Juste un conseil: peut - être supprimer l'extrait de code et de le remplacer avec du texte seulement javascript
— bluejayke

serait-il possible de passer un paramètre pour que './image/:jpg puisse servir n'importe quelle image?
— Prav

13

J'aime utiliser Restify pour les services REST. Dans mon cas, j'avais créé un service REST pour servir des images et ensuite si une source d'image retournait 404/403, je voulais renvoyer une image alternative. Voici ce que j'ai trouvé en combinant certaines des choses ici:

function processRequest(req, res, next, url) {
    var httpOptions = {
        hostname: host,
        path: url,
        port: port,
        method: 'GET'
    };

    var reqGet = http.request(httpOptions, function (response) {
        var statusCode = response.statusCode;

        // Many images come back as 404/403 so check explicitly
        if (statusCode === 404 || statusCode === 403) {
            // Send default image if error
            var file = 'img/user.png';
            fs.stat(file, function (err, stat) {
                var img = fs.readFileSync(file);
                res.contentType = 'image/png';
                res.contentLength = stat.size;
                res.end(img, 'binary');
            });

        } else {
            var idx = 0;
            var len = parseInt(response.header("Content-Length"));
            var body = new Buffer(len);

            response.setEncoding('binary');

            response.on('data', function (chunk) {
                body.write(chunk, idx, "binary");
                idx += chunk.length;
            });

            response.on('end', function () {
                res.contentType = 'image/jpg';
                res.send(body);
            });

        }
    });

    reqGet.on('error', function (e) {
        // Send default image if error
        var file = 'img/user.png';
        fs.stat(file, function (err, stat) {
            var img = fs.readFileSync(file);
            res.contentType = 'image/png';
            res.contentLength = stat.size;
            res.end(img, 'binary');
        });
    });

    reqGet.end();

    return next();
}

Vous ne devez jamais utiliser readFileSync à l'intérieur des gestionnaires d'événements. Il s'agit d'une opération synchrone qui bloque l'ensemble de votre processus lors de la lecture du fichier. Je l'ai expliqué dans ma réponse plus en détail.
— rsp

5

Cela peut être un peu hors sujet, car vous vous interrogez sur le service de fichiers statiques via Node.js en particulier (où fs.createReadStream('./image/demo.jpg').pipe(res) est en fait une bonne idée), mais en production, vous voudrez peut-être que votre application Node gère les tâches, qui ne peuvent pas être traitées autrement , et déchargement statique servant par exemple Nginx.

Cela signifie moins de codage dans votre application et une meilleure efficacité, car les proxys inverses sont par conception idéaux pour cela.


3

Permettez-moi d'ajouter aux réponses ci-dessus, que l'optimisation des images et la diffusion d'images réactives améliorent considérablement les temps de chargement des pages, car> 90% du trafic Web sont des images. Vous voudrez peut-être prétraiter les images à l'aide de modules JS / Node tels que imagemin et les plug-ins associés, idéalement pendant le processus de construction avec Grunt ou Gulp .

Optimiser les images signifie traiter pour trouver un type d'image idéal et sélectionner une compression optimale pour atteindre un équilibre entre la qualité d'image et la taille du fichier.

Servir des images responsives se traduit par la création automatique de plusieurs tailles et formats de chaque image et l'utilisation srcsetdans votre html vous permet de servir un ensemble d'images optimal (c'est-à-dire le format et les dimensions idéaux, donc la taille de fichier optimale) pour chaque navigateur).

L'automatisation du traitement d'image pendant le processus de construction signifie l'incorporer une seule fois et la présentation d'images optimisées plus loin, ce qui nécessite un minimum de temps supplémentaire.

Quelques bonnes lectures sur les images responsives , la minification en général, le module de nœud imagemin et l' utilisation de srcset .


1

Cette méthode fonctionne pour moi, ce n'est pas dynamique mais va droit au but:

const fs      = require('fs');
const express = require('express');
const app     = express();

app.get( '/logo.gif', function( req, res ) {

  fs.readFile( 'logo.gif', function( err, data ) {

    if ( err ) {

      console.log( err );
      return;
    }

    res.write( data );
    return res.end();
  });

});

app.listen( 80 );

0

//This method involves directly integrating HTML Code in the res.write
//first time posting to stack ...pls be kind

const express = require('express');
const app = express();
const https = require('https');

app.get("/",function(res,res){
    res.write("<img src="+image url / src +">");
    res.send();
});

app.listen(3000, function(req, res) {
  console.log("the server is onnnn");
});

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.