Ответ 1
Обновление 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 лет с момента появления этого вопроса существует один правильный ответ generalhenry, но даже хотя у этого ответа нет проблем с кодом, у него, похоже, есть проблемы с приемом. Было прокомментировано, что он "не объясняет многое, кроме как полагаться на кого-то другого, чтобы выполнить свою работу", и тот факт, сколько людей проголосовали за этот комментарий, ясно показывает, что многие вещи нуждаются в разъяснении.
Прежде всего, хороший ответ на вопрос "Как использовать изображения с помощью Node.js" не реализует статический файловый сервер с нуля и делает это плохо. Хороший ответ с использованием модуля, например Express, который правильно выполняет работу.
Ответы на комментарии, которые говорят, что использование Express "не объясняет многое, кроме как полагаться на кого-то другого, чтобы выполнить работу", следует отметить, что с использованием http
модуля уже полагается на кого-то другого, чтобы выполнить свою работу. Если кто-то не хочет полагаться на кого-либо, чтобы выполнить задание, вместо этого следует использовать как минимум необработанные сокеты TCP, которые я делаю в одном из приведенных ниже примеров.
Более серьезная проблема заключается в том, что все ответы здесь, в которых используется модуль http
, сломанный. Они вносят условия гонки, небезопасного разрешения пути, что приведет к уязвимости, блокировке ввода/вывода, которая будет полностью не могут выполнять любые параллельные запросы вообще и другие тонкие проблемы - они полностью разбиты как примеры того, о чем спрашивает вопрос, и тем не менее они уже используют абстракцию, предоставляемую модулем http
вместо использования сокетов TCP, чтобы они даже не делали все с нуля, как они утверждают.
Если возник вопрос: "Как реализовать статический файловый сервер с нуля, как упражнение для обучения", то, во всяком случае, ответы, как это сделать, должны быть опубликованы, но даже тогда мы должны ожидать, что они будут по крайней мере правильными. Кроме того, небезосновательно предположить, что кто-то, кто хочет обслуживать изображение, может захотеть показывать больше изображений в будущем, поэтому можно утверждать, что запись определенного пользовательского статического файлового сервера, который может обслуживать только один файл с жестко закодированным путем, несколько близоруких. Кажется, трудно представить, что любой, кто ищет ответ о том, как обслуживать изображение, будет удовлетворен решением, которое обслуживает только одно изображение вместо общего решения для обслуживания любого изображения.
Вкратце, вопрос заключается в том, как обслуживать изображение, и ответ на этот вопрос заключается в использовании соответствующего модуля для этого безопасного, предварительного и надежного способа, который доступен для чтения, поддерживаемый и перспективный при использовании лучшей практики профессиональной разработки Node. Но я согласен с тем, что отличное дополнение к такому ответу будет показывать способ реализовать одну и ту же функциональность вручную, но, к сожалению, каждая попытка сделать это пока не удалась. Вот почему я написал несколько новых примеров.
После этого краткого введения, вот мои пять примеров, выполняющих работу на 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 был реализован с нуля. Поскольку он не поддерживает кодирование с чередованием, он загружает файлы в память, прежде чем обслуживать их, чтобы узнать размер перед отправкой ответа, потому что статистика файлов, а затем загрузка приведет к состоянию гонки.
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.
См. также
Другие связанные ответы: