Cómo servir una imagen usando nodejs


147

Tengo un logotipo que reside en el público / images / logo.gif. Aquí está mi código nodejs.

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

Funciona, pero cuando solicito localhost: 8080 / logo.gif, obviamente no obtengo el logotipo.

Qué cambios necesito hacer para mostrar una imagen.

Respuestas:


224

Actualización 2016

Ejemplos con Express y sin Express que realmente funcionan

Esta pregunta tiene más de 5 años, pero cada respuesta tiene algunos problemas .

TL; DR

Desplácese hacia abajo para ver ejemplos para mostrar una imagen con:

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

Todos los ejemplos también están en GitHub: https://github.com/rsp/node-static-http-servers

Los resultados de las pruebas están disponibles en Travis: https://travis-ci.org/rsp/node-static-http-servers

Introducción

Después de más de 5 años desde que esta pregunta fue hecha sólo hay una respuesta correcta por generalhenry pero a pesar de que la respuesta no tiene problemas con el código, que parece tener algunos problemas con la recepción . Se comentó que "no explica mucho más que cómo confiar en alguien más para hacer el trabajo" y el hecho de cuántas personas han votado este comentario claramente muestra que muchas cosas necesitan aclaración.

En primer lugar, una buena respuesta a "Cómo servir imágenes usando Node.js" no es implementar un servidor de archivos estático desde cero y hacerlo mal. Una buena respuesta es usar un módulo como Express que hace el trabajo correctamente .

En respuesta a los comentarios que dicen que el uso de Express "no explica mucho más que cómo confiar en que otra persona haga el trabajo" , debe tenerse en cuenta que el uso del httpmódulo ya depende de otra persona para hacer el trabajo. Si alguien no quiere depender de nadie para hacer el trabajo, entonces se deben usar al menos sockets TCP sin procesar, lo que hago en uno de mis ejemplos a continuación.

Un problema más grave es que todas las respuestas aquí que usan el httpmódulo están rotas . Presentan condiciones de carrera , resolución de ruta insegura que conducirá a vulnerabilidad de recorrido de ruta , bloqueo de E / S que fallará por completo en atender cualquier solicitud concurrente y otros problemas sutiles: están completamente rotos como ejemplos de lo que pregunta la pregunta, y Sin embargo, ya usan la abstracción que proporciona el httpmódulo en lugar de usar sockets TCP, por lo que ni siquiera hacen todo desde cero como afirman.

Si la pregunta era "Cómo implementar un servidor de archivos estático desde cero, como un ejercicio de aprendizaje", entonces, por supuesto, responde cómo se debe publicar eso, pero incluso entonces deberíamos esperar que al menos sean correctos . Además, no es irrazonable suponer que alguien que quiere servir una imagen puede querer servir más imágenes en el futuro, por lo que se podría argumentar que escribir un servidor de archivos estático personalizado específico que puede servir solo un archivo único con ruta codificada es algo miope. Parece difícil imaginar que cualquiera que busque una respuesta sobre cómo servir una imagen se contentaría con una solución que sirve solo una imagen en lugar de una solución general para servir cualquier imagen.

En resumen, la pregunta es cómo servir una imagen y una respuesta a eso es usar un módulo apropiado para hacerlo de una manera segura, preformante y confiable que sea legible, mantenible y a prueba de futuro mientras se usa la mejor práctica del Nodo profesional desarrollo. Pero estoy de acuerdo en que una gran adición a dicha respuesta sería mostrar una forma de implementar la misma funcionalidad de forma manual, pero lamentablemente cada intento de hacerlo ha fallado hasta ahora. Y es por eso que escribí algunos ejemplos nuevos.

Después de esta breve introducción, aquí están mis cinco ejemplos haciendo el trabajo en 5 niveles diferentes de abstracción.

Funcionalidad mínima

Cada ejemplo sirve archivos del publicdirectorio y admite la funcionalidad mínima de:

  • Tipos MIME para los archivos más comunes
  • sirve HTML, JS, CSS, texto plano e imágenes
  • sirve index.html como índice de directorio predeterminado
  • responde con códigos de error para archivos faltantes
  • sin vulnerabilidades de recorrido
  • sin condiciones de carrera al leer archivos

Probé cada versión en las versiones Node 4, 5, 6 y 7.

express.static

Esta versión utiliza el express.staticmiddleware incorporado deexpress módulo.

Este ejemplo tiene la mayor funcionalidad y la menor cantidad de código.

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

Esta versión usa el expressmódulo pero sin elexpress.static middleware. El servicio de archivos estáticos se implementa como un controlador de ruta única que utiliza secuencias.

Este ejemplo tiene contramedidas transversales de ruta simple y admite un conjunto limitado de los tipos MIME más comunes.

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

Esta versión utiliza el connectmódulo, que es un nivel de abstracción inferior aexpress .

Este ejemplo tiene una funcionalidad similar a la expressversión pero utiliza API de palanca ligeramente inferior.

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

Esta versión utiliza el httpmódulo que es la API de nivel más bajo para HTTP en Node.

Este ejemplo tiene una funcionalidad similar a la connectversión pero utiliza aún más API de nivel inferior.

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

Esta versión utiliza el netmódulo que es la API de nivel más bajo para sockets TCP en Node.

Este ejemplo tiene algunas de las funcionalidades de la httpversión, pero el protocolo HTTP mínimo e incompleto se ha implementado desde cero. Como no es compatible con la codificación fragmentada, carga los archivos en la memoria antes de servirlos para conocer el tamaño antes de enviar una respuesta, ya que declarar los archivos y luego cargarlos introduciría una condición de carrera.

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/');
});

Descargar ejemplos

Publiqué todos los ejemplos en GitHub con más explicaciones.

Ejemplos con express.static, express, connect, httpy net:

Otro proyecto usando solo express.static:

Pruebas

Los resultados de las pruebas están disponibles en Travis:

Todo se prueba en las versiones Node 4, 5, 6 y 7.

Ver también

Otras respuestas relacionadas:


8
La mejor y completa respuesta a la pregunta. Lástima, solo puedo votarte una vez.
— Kulvar

3
¡Debería haber una manera de renovar preguntas antiguas como esta! Acabo de perder una hora más o menos tratando de que la respuesta de 110 votos funcione. Finalmente me desplacé hacia abajo solo para comprobar. Su respuesta podría (debería) ser un libro de texto sobre el tema.
— Tailandia

2
La mejor respuesta y con todo detalle. Debe estar marcado la respuesta aceptada. Gracias amigo !!!
— Prabodh M

No sé por qué alguien usaría Express. Yo también lo hice al principio, probablemente porque todos los demás lo hicieron. Entonces me di cuenta de que usar el módulo http de Node es el enfoque correcto. Para eso está previsto. Obtienes mucha flexibilidad. Entiende el protocolo HTTP y puede depurarlo fácilmente. Express proporciona una gran cantidad de jerga y una capa delgada sobre el módulo http que es fácil de implementar con codificación sin formato con el módulo http. Recomiendo encarecidamente a cualquier usuario de Express o cualquier otro módulo que se aleje de ellos y use el módulo http directamente.
— Soleado

1
una nota que me gustaría mencionar para los novatos como yo: cuando declaramos que una carpeta está estática express.static, podemos obtener la imagen llamando a la url http://ip:port/path_after_the_static_folder, no necesitamos mencionar la carpeta estática en sí para servir la imagen. Aunque podemos agregar eso usando app.use('/static', express.static(imagePath))por conveniencia como documentación par en: expressjs.com/en/starter/static-files.html
— Rakibul Haq

160

Estoy de acuerdo con los otros carteles en que eventualmente, deberías usar un marco, como Express ... pero primero también debes entender cómo hacer algo fundamental como esto sin una biblioteca, para entender realmente lo que la biblioteca abstrae para ti ... los pasos son

  1. Analice la solicitud HTTP entrante para ver qué ruta solicita el usuario
  2. Agregue una ruta en la declaración condicional para que el servidor responda
  3. Si se solicita la imagen, lea el archivo de imagen del disco.
  4. Servir el tipo de contenido de imagen en un encabezado
  5. Sirve el contenido de la imagen en el cuerpo

El código se vería así (no probado)

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
No debe usar readFileSync en el medio de una respuesta. Se debe usar una carga de sincronización en el primer tic o se debe usar el método asíncrono. codr.cc/s/5d0b73d6/js
— generalhenry

1
Estoy de acuerdo con usted en la versión de sincronización, sin embargo, para la versión asíncrona, pensé que el peligro de usar operaciones sin bloqueo para los archivos era que podría enviar la respuesta antes de que se leyera todo el archivo y terminar dejándolo. con un archivo parcial que se sirve al usuario? ¿Necesita usar codificación fragmentada si usa una lectura de archivo asíncrono?
— noli

1
fs.readFileSync no devuelve la llamada hasta que se carga todo el archivo, por lo que no hay necesidad de manejar fragmentos. El manejo de fragmentos es principalmente para la transferencia de archivos de red (ya que las cosas pueden tardar más de lo esperado).
— generalhenry

9
La línea res.end(img);debería ser res.end(img, 'binary');. ¡Buen trabajo!
— Honza Pokorny

3
+1 para "pero primero también debes entender cómo hacer algo fundamental como esto sin una biblioteca, para entender realmente lo que la biblioteca abstrae para ti ..."
— Sunny

67

Deberías usar el framework express .

npm install express

y entonces

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

y luego la URL localhost: 8080 / images / logo.gif debería funcionar.


18
Seguro, pero no explica mucho más que cómo confiar en otra persona para hacer el trabajo.
— LeeGee

Agregué una versión de nodo de vainilla (solo módulos centrales).
— generalhenry

+ I Esta es la única respuesta correcta publicada hasta ahora. Lo explico en mi respuesta con más detalles.
— rsp

15

Es demasiado tarde pero ayuda a alguien, estoy usando node version v7.9.0yexpress version 4.15.0

si la estructura de tu directorio es algo como esto:

your-project
   uploads
   package.json
   server.js

código 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

Versión de nodo de vainilla según lo solicitado:

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
No desea verificar fs.exists(condición de carrera), sería un mejor hábito detectar un error al realizar la tubería.
— Brendan

Aunque la comprobación existe en este caso no es la forma de nodo, todo lo demás sobre esta respuesta es 1 millón de veces mejor que la respuesta aceptada.
— Ninjaxor

1
Estoy de acuerdo con @BrendanAshworth. Las condiciones de carrera están presentes en casi todas las respuestas aquí. Escribí más sobre eso en mi respuesta . Pero felicitaciones por escribirlo con secuencias. Casi todas las otras respuestas usan readFileSync que está bloqueando y no debe usarse en ningún controlador de eventos.
— rsp

1
var filePath = path.resolve ('public', '.' + parts.pathname); response.writeHead (200, {'Content-Type': mime.lookup (parts.pathname)}); mime - paquete tipo mime 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 y directo, mucho mejor que las respuestas expresas ... esto merece 1000 votos a favor. Solo un consejo: tal vez elimine el fragmento de código y reemplácelo con solo texto de JavaScript
— bluejayke

¿sería posible pasar un parámetro para que './image/:jpg pueda servir cualquier imagen?
— Prav

13

Me gusta usar Restify para los servicios REST. En mi caso, había creado un servicio REST para servir imágenes y luego, si una fuente de imagen devolvía 404/403, quería devolver una imagen alternativa. Esto es lo que se me ocurrió al combinar algunas de las cosas aquí:

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

Usted debe no utilizar readFileSync interior de controladores de eventos. Esta es una operación sincrónica que bloquea todo el proceso mientras lee el archivo. Lo expliqué en mi respuesta con más detalle.
— rsp

5

Esto puede estar un poco fuera de tema, ya que está preguntando acerca de la publicación de archivos estáticos a través de Node.js específicamente (donde en fs.createReadStream('./image/demo.jpg').pipe(res)realidad es una buena idea), pero en producción es posible que desee que su aplicación Node maneje tareas, que de lo contrario no se pueden abordar , y descarga estática que sirve, por ejemplo, a Nginx.

Esto significa menos codificación dentro de su aplicación y una mejor eficiencia ya que los proxys inversos son ideales por diseño para esto.


3

Permítanme agregar a las respuestas anteriores, que la optimización de imágenes y el servicio de imágenes receptivas ayudan a los tiempos de carga de la página dramáticamente, ya que> 90% del tráfico web son imágenes. Es posible que desee preprocesar imágenes utilizando módulos JS / Node como imagemin y complementos relacionados, idealmente durante el proceso de compilación con Grunt o Gulp .

Optimizar imágenes significa procesar para encontrar un tipo de imagen ideal y seleccionar una compresión óptima para lograr un equilibrio entre la calidad de la imagen y el tamaño del archivo.

Servir imágenes receptivas se traduce en crear varios tamaños y formatos de cada imagen automáticamente y usarsrcset en su html le permite servir un conjunto de imágenes óptimo (es decir, el formato y las dimensiones ideales, por lo tanto, el tamaño de archivo óptimo) para cada navegador).

La automatización del procesamiento de imágenes durante el proceso de construcción significa incorporarla una vez y presentar imágenes optimizadas más adelante, lo que requiere un tiempo adicional mínimo.

Una gran leer en imágenes sensibles , minimización de, en general imagemin módulo de nodo y el uso de srcset .


1

Este método funciona para mí, no es dinámico, sino directo al grano:

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.