Обновление 2016
Примеры с Express и без Express, которые действительно работают
Этот вопрос старше 5 лет, но у каждого ответа есть свои проблемы .
TL; DR
Прокрутите вниз, чтобы найти примеры изображений:
express.static
express
connect
http
net
Все примеры также есть на GitHub: https://github.com/rsp/node-static-http-servers
Результаты тестов доступны на Travis: https://travis-ci.org/rsp/node-static-http-servers
Введение
Спустя более 5 лет после того, как этот вопрос был задан , генерал Генри получил только один правильный ответ , но, хотя у этого ответа нет проблем с кодом, у него, похоже, есть проблемы с приемом . Было отмечено, что это «не объясняет ничего, кроме как полагаться на кого-то другого, чтобы выполнить работу», и тот факт, что многие люди проголосовали за этот комментарий, ясно показывает, что многие вещи нуждаются в разъяснении.
Прежде всего, хороший ответ на вопрос «Как обслуживать изображения с помощью Node.js» - это не реализация статического файлового сервера с нуля и плохая работа. Хороший ответ - использовать такой модуль, как Express, который выполняет свою работу правильно .
Отвечая на комментарии, в которых говорится, что использование Express «не объясняет ничего, кроме как полагаться на кого-то другого для выполнения работы», следует отметить, что использование httpмодуля уже зависит от кого-то другого для выполнения работы. Если кто-то не хочет полагаться на кого-то, чтобы выполнить работу, то вместо этого следует использовать хотя бы сырые TCP-сокеты - что я и делаю в одном из моих примеров ниже.
Более серьезная проблема состоит в том, что все ответы здесь, которые используют httpмодуль, сломаны . Они вводят условия гонки , небезопасное разрешение пути , которое приведет к уязвимости обхода пути , блокируют ввод / вывод , который вообще не сможет обслуживать любые параллельные запросы, и другие тонкие проблемы - они полностью разбиты как примеры того, о чем вопрос задается, и тем не менее они уже используют абстракцию, предоставляемую httpмодулем, вместо использования сокетов TCP, поэтому они даже не делают все с нуля, как утверждают.
Если вопрос был «Как реализовать статический файловый сервер с нуля, как учебное упражнение», то непременно ответьте на вопрос, как это сделать, но все же следует ожидать, что они будут по крайней мере правильными . Кроме того, вполне разумно предположить, что тот, кто хочет обслуживать изображение, может захотеть предоставить больше изображений в будущем, поэтому можно утверждать, что написание специального пользовательского статического файлового сервера, который может обслуживать только один файл с жестко заданным путем, является несколько близоруким Кажется трудно представить, что любой, кто ищет ответ о том, как обслуживать изображение, будет доволен решением, которое обслуживает только одно изображение, а не общим решением для обслуживания любого изображения.
Короче говоря, вопрос состоит в том, как обслуживать изображение, и ответом на него является использование соответствующего модуля, чтобы сделать это безопасным, предварительно отформатированным и надежным способом, который удобочитаем, обслуживаем и перспективен при использовании лучших практик профессионального узла. развитие. Но я согласен, что отличным дополнением к такому ответу будет показ способа реализовать ту же функциональность вручную, но, к сожалению, каждая попытка сделать это потерпела неудачу до сих пор. И именно поэтому я написал несколько новых примеров.
После этого краткого вступления, вот мои пять примеров, делающих работу на 5 разных уровнях абстракции.
Минимальная функциональность
Каждый пример обслуживает файлы из publicкаталога и поддерживает минимальную функциональность:
- Типы MIME для наиболее распространенных файлов
- обслуживает HTML, JS, CSS, простой текст и изображения
- служит
index.htmlиндексом каталога по умолчанию
- отвечает с кодами ошибок для отсутствующих файлов
- нет пути обхода уязвимостей
- нет условий гонки при чтении файлов
Я тестировал каждую версию на Node версий 4, 5, 6 и 7.
express.static
Эта версия использует express.staticвстроенное промежуточное программное обеспечение expressмодуля.
Этот пример обладает большей функциональностью и наименьшим количеством кода.
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
Эта версия использует expressмодуль, но без express.staticпромежуточного программного обеспечения. Обслуживание статических файлов реализовано в виде одного обработчика маршрута с использованием потоков.
Этот пример имеет простые контрмеры обхода пути и поддерживает ограниченный набор наиболее распространенных типов MIME.
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
Эта версия использует connectмодуль, который на один уровень абстракции ниже, чем express.
Этот пример имеет схожую функциональность с expressверсией, но использует API более низкого уровня.
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
Эта версия использует httpмодуль, который является API-интерфейсом самого низкого уровня для HTTP в Node.
Этот пример имеет схожую функциональность с connectверсией, но использует еще более низкоуровневые API.
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
В этой версии используется netмодуль, являющийся API-интерфейсом самого низкого уровня для сокетов TCP в Node.
Этот пример имеет некоторые функциональные возможности httpверсии, но минимальный и неполный протокол HTTP был реализован с нуля. Поскольку он не поддерживает chunked-кодировку, он загружает файлы в память, а затем отправляет их, чтобы узнать их размер перед отправкой ответа, потому что проверка файлов и последующая загрузка приведут к состязанию.
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/');
});
Скачать примеры
Я разместил все примеры на GitHub с дополнительными пояснениями.
Примеры с express.static, express, connect, httpи net:
Другой проект, использующий только express.static:
тесты
Результаты тестов доступны на Travis:
Все тестируется на Node версий 4, 5, 6 и 7.
Смотрите также
Другие связанные ответы: