Основний статичний файловий сервер у 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.