Основний статичний файловий сервер у NodeJS


85

Я намагаюся створити статичний файловий сервер у nodejs більше як вправу зрозуміти node, ніж як ідеальний сервер. Я добре знаю такі проекти, як Connect та node-static, і повністю маю намір використовувати ці бібліотеки для більш готового до виробництва коду, але я також люблю розуміти основи того, з чим я працюю. Маючи це на увазі, я закодував невеликий server.js:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
    var uri = url.parse(req.url).pathname;
    var filename = path.join(process.cwd(), uri);
    path.exists(filename, function(exists) {
        if(!exists) {
            console.log("not exists: " + filename);
            res.writeHead(200, {'Content-Type': 'text/plain'});
            res.write('404 Not Found\n');
            res.end();
        }
        var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
        res.writeHead(200, mimeType);

        var fileStream = fs.createReadStream(filename);
        fileStream.pipe(res);

    }); //end path.exists
}).listen(1337);

Моє питання двояке

  1. Це "правильний" спосіб створити та потоково передавати базовий html тощо у вузлі чи існує кращий / елегантніший / надійніший метод?

  2. Чи .pipe () у вузлі в основному робить наступне?

.

var fileStream = fs.createReadStream(filename);
fileStream.on('data', function (data) {
    res.write(data);
});
fileStream.on('end', function() {
    res.end();
});

Дякую всім!


2
Я написав модуль, який дозволяє вам це робити без шкоди для гнучкості. Він також автоматично кешує всі ваші ресурси. Перевірте це: github.com/topcloud/cachemere
— Джон

2
Трохи смішно, що ви вибрали (?) Повернути "404 не знайдено" із кодом статусу HTTP "200 OK". Якщо за URL-адресою немає ресурсу, тоді відповідним кодом має бути 404 (а те, що ви пишете в тілі документа, як правило, має другорядне значення). В іншому випадку ви заплутаєте багато користувацьких агентів (включаючи веб-сканери та інших ботів), надаючи їм документи без реальної вартості (які вони також можуть кешувати).
— ранку

1
Дякую. Все ще гарно працюю через багато років.
— statosdotcom

1
Дякую! цей код працює ідеально. Але тепер використовуйте fs.exists()замість path.exists()наведеного вище коду. На здоров’я! і так! не забувайте return:
— Каушаль28

Примітка : 1) fs.exists() є застарілим . Використання fs.access()або навіть краще , так як для вищезгаданого випадку використання fs.stat(). 2) url.parse є застарілим ; використовувати new URLзамість цього новіший інтерфейс.
— rags2riches

Відповіді:


44
  • Ваш базовий сервер виглядає добре, за винятком:

    Існує returnзаява відсутня.

    res.write('404 Not Found\n');
    res.end();
    return; // <- Don't forget to return here !!
    

    І:

    res.writeHead(200, mimeType);

    має бути:

    res.writeHead(200, {'Content-Type':mimeType});

  • Так pipe(), в основному це робить, він також призупиняє / відновлює вихідний потік (у випадку, якщо приймач працює повільніше). Ось вихідний код pipe()функції: https://github.com/joyent/node/blob/master/lib/stream.js


2
що станеться, якщо ім'я файлу схоже на blah.blah.css?
— ShrekOverflow

2
mimeType в такому випадку має бути бла,
— xP

5
Хіба це не тертя? якщо ви пишете власні, ви просите про такі типи помилок. Хороше навчання перевершує, але я вчусь цінувати "підключення", а не котити своє. Проблема цієї сторінки полягає в тому, що люди прагнуть просто з’ясувати, як зробити простий файловий сервер, і переповнення стека з’являється першим. Ця відповідь правильна, але люди не шукають її, а лише просту відповідь. Мені довелося самостійно з’ясувати простіший, тому помістіть його сюди.
— Джейсон Себрінг,

1
+1 за не вставлення посилання на рішення у вигляді бібліотеки, а фактичне написання відповіді на запитання.
— Shawn Whinnery

57

Менше значить більше

Просто спочатку натисніть командний рядок у своєму проекті та використовуйте

$ npm install express

Потім напишіть свій код app.js так:

var express = require('express'),
app = express(),
port = process.env.PORT || 4000;

app.use(express.static(__dirname + '/public'));
app.listen(port);

Потім ви створили "загальнодоступну" папку, куди ви розміщуєте файли. Спочатку я спробував це складнішим способом, але вам доведеться турбуватися про типи mime, які просто потребують картографування того, що займає багато часу, а потім турбуватися про типи відповідей і т. Д. І т. Д. І т. Д. Не дякую.


2
+1 Можна багато сказати про тестування коду замість того, щоб прокручувати власний.
— jcollum

1
Я спробував переглянути документацію, але, здається, не знайшов багато, чи можете ви пояснити, що робить ваш фрагмент? Я намагався використати саме цю варіацію, і я не знаю, що можна чим замінити.
— onaclov2000

3
Якщо вам потрібен список каталогів, просто додайте .use (connect.directory ('public')) відразу після рядка connect.static, замінивши public на ваш шлях. Вибачте за викрадення, але я думаю, що це для мене все прояснює.
— onaclov2000

1
Ви також можете використати jQuery! Це не відповідає на питання ОП, а вирішує проблему, яка навіть не існує. OP заявив, що метою цього експерименту було вивчення Node.
— Shawn Whinnery

1
@JasonSebring Чому require('http')на другому рядку?
— Сяо Пен - ZenUML.com

19

Мені також подобається розуміти, що відбувається під капотом.

Я помітив у вашому коді кілька речей, які ви, мабуть, хочете очистити:

  • Він аварійно завершує роботу, коли ім’я файлу вказує на каталог, оскільки існує істина, і він намагається прочитати потік файлів. Я використовував fs.lstatSync для визначення існування каталогу.

  • Він неправильно використовує коди відповідей HTTP (200, 404 тощо)

  • Поки MimeType визначається (із розширення файлу), він неправильно встановлений у res.writeHead (як зазначив Стю)

  • Щоб обробляти спеціальні символи, ви, мабуть, хочете уникнути урі

  • Він сліпо слідує за символічними посиланнями (може бути проблемою безпеки)

З огляду на це, деякі параметри apache (FollowSymLinks, ShowIndexes тощо) починають мати більше сенсу. Я оновив код для вашого простого файлового сервера наступним чином:

var http = require('http'),
    url = require('url'),
    path = require('path'),
    fs = require('fs');
var mimeTypes = {
    "html": "text/html",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "js": "text/javascript",
    "css": "text/css"};

http.createServer(function(req, res) {
  var uri = url.parse(req.url).pathname;
  var filename = path.join(process.cwd(), unescape(uri));
  var stats;

  try {
    stats = fs.lstatSync(filename); // throws if path doesn't exist
  } catch (e) {
    res.writeHead(404, {'Content-Type': 'text/plain'});
    res.write('404 Not Found\n');
    res.end();
    return;
  }


  if (stats.isFile()) {
    // path exists, is a file
    var mimeType = mimeTypes[path.extname(filename).split(".").reverse()[0]];
    res.writeHead(200, {'Content-Type': mimeType} );

    var fileStream = fs.createReadStream(filename);
    fileStream.pipe(res);
  } else if (stats.isDirectory()) {
    // path exists, is a directory
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('Index of '+uri+'\n');
    res.write('TODO, show index?\n');
    res.end();
  } else {
    // Symbolic link, other?
    // TODO: follow symlinks?  security?
    res.writeHead(500, {'Content-Type': 'text/plain'});
    res.write('500 Internal server error\n');
    res.end();
  }

}).listen(1337);

4
чи можу я запропонувати "var mimeType = mimeTypes [path.extname (ім'я файлу) .split (". "). reverse () [0]];" замість цього? деякі імена файлів мають більше одного "." наприклад, "my.cool.video.mp4" або "download.tar.gz"
— несинхронізовано

Це якимось чином зупиняє когось у використанні URL-адреси, наприклад, папки /../../../ home / user / jackpot.privatekey? Я бачу приєднання, щоб переконатись, що шлях проходить вниз за течією, але мені цікаво, чи використовуватиметься позначення типу ../../../, що це обійде чи ні. Можливо, я сам це перевірю.
— Reynard

Вона не працює. Я не впевнений, чому, але це приємно знати.
— Reynard

приємно, збіг RegEx також може збирати розширення; var mimeType = mimeTypes[path.extname(filename).match(/\.([^\.]+)$/)[1]];
— Джон Мутума

4
var http = require('http')
var fs = require('fs')

var server = http.createServer(function (req, res) {
  res.writeHead(200, { 'content-type': 'text/plain' })

  fs.createReadStream(process.argv[3]).pipe(res)
})

server.listen(Number(process.argv[2]))

4
Можливо, мені хочеться пояснити це ще трохи.
— Натан Таггі

3

Як щодо цього шаблону, який дозволяє уникати перевірки окремо, що файл існує

        var fileStream = fs.createReadStream(filename);
        fileStream.on('error', function (error) {
            response.writeHead(404, { "Content-Type": "text/plain"});
            response.end("file not found");
        });
        fileStream.on('open', function() {
            var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
            response.writeHead(200, {'Content-Type': mimeType});
        });
        fileStream.on('end', function() {
            console.log('sent file ' + filename);
        });
        fileStream.pipe(response);

1
ви забули mimeype на випадок успіху. Я використовую цей дизайн, але замість того, щоб негайно конвеювати потоки, я конвеюю їх у випадку "відкритого" потоку файлів: writeHead для mimeype, потім pipe. Кінець не потрібен: readable.pipe .
— GeH

Змінено відповідно до коментаря @GeH.
— Brett Zamir

Має бутиfileStream.on('open', ...
— Петах

2

Я створив функцію httpServer з додатковими функціями для загального користування на основі відповіді @Jeff Ward

  1. кастомтом реж
  2. index.html повертає, якщо req === реж

Використання:

httpServer(dir).listen(port);

https://github.com/kenokabe/ConciseStaticHttpServer

Дякую.


0

модуль й полегшує обслуговування статичних файлів. Ось витяг README.md:

var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
  var stHandled = mount(req, res);
  if (stHandled)
    return
  else
    res.end('this is not a static file')
}).listen(1338)

0

Відповідь @JasonSebring вказав мені в правильному напрямку, однак його код застарів. Ось як ви це робите за допомогою найновішої connectверсії.

var connect = require('connect'),
    serveStatic = require('serve-static'),
    serveIndex = require('serve-index');

var app = connect()
    .use(serveStatic('public'))
    .use(serveIndex('public', {'icons': true, 'view': 'details'}))
    .listen(3000);

У connect сховищі GitHub є інші проміжні засоби, якими ви можете скористатися.


Я просто використовував експрес замість цього для більш простої відповіді. В найновішій експрес-версії використовується статична інформація, але не багато іншого. Дякую!
— Джейсон Себрінг,

Переглядаючи connectдокументацію, це лише wrapperдля middleware. Все інше цікаве middlewareз expressрепозиторію, тому технічно ви можете використовувати ці API за допомогою express.use().
— ffleandro
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.