Оновлення 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, щоб вони навіть не робили все з нуля, як вони заявляють.
Якщо питання було "Як реалізувати статичний файловий сервер з нуля, як навчальну вправу", то якраз і відповіді, як це зробити, слід розмістити - але навіть тоді ми повинні очікувати, що вони принаймні будуть правильними . Крім того, нерозумно вважати, що той, хто хоче подати зображення, може захотіти подавати більше зображень у майбутньому, щоб можна було стверджувати, що написання конкретного користувальницького статичного файлового сервера, який може обслуговувати лише один файл із жорстко закодованим шляхом дещо короткозорий. Мабуть важко уявити, що кожен, хто шукає відповідь про те, як подати зображення, буде задоволений рішенням, яке обслуговує лише одне зображення замість загального рішення для обслуговування будь-якого зображення.
Коротше кажучи, питання полягає в тому, як подати зображення, а відповідь на це - використовувати відповідний модуль, щоб зробити це в a безпечним, заздалегідь формуваним та надійним способом, який можна читати, ремонтувати та захищати від майбутнього , використовуючи кращі практики професійного вузла розвиток. Але я погоджуюся, що чудовим доповненням до такої відповіді було б показати спосіб реалізувати ту саму функціональність вручну, але, на жаль, кожна спроба зробити це не вдалося досі. І тому я написав кілька нових прикладів.
Після цього короткого вступу, ось мої п’ять прикладів, що виконують роботу на 5 різних рівнях абстракції.
Мінімальна функціональність
У кожному прикладі подаються файли з public каталогу та підтримує функцію minumum:
- Типи MIME для найбільш поширених файлів
- подає HTML, JS, CSS, звичайний текст та зображення
- служить
index.htmlіндексом каталогу за замовчуванням
- відповідає кодами помилок для відсутніх файлів
- відсутність уразливості проходу шляху
- відсутність гоночних умов під час читання файлів
Я перевіряв кожну версію на версії 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:
Все перевірено на версії 4, 5, 6 та 7 у вузлі.
Дивитися також
Інші відповідні відповіді: