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:
express.static
express
connect
http
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: