Базовый статический файловый сервер в 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 Not Found» с кодом состояния HTTP «200 OK». Если по URL-адресу нет ресурса, тогда соответствующий код должен быть 404 (а то, что вы пишете в теле документа, обычно имеет второстепенное значение). В противном случае вы запутаете множество пользовательских агентов (включая веб-сканеры и других ботов), предоставляя им документы без реальной ценности (которые они также могут кэшировать).
— amn

1
Спасибо. Все еще прекрасно работает много лет спустя.
— statosdotcom

1
Благодарность! этот код работает отлично. Но теперь используйте fs.exists()вместо path.exists()в приведенном выше коде. Ура! и да! не забывайте return:
— Kaushal28

Примечание : 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
— ShrekOverflow

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 (как указал Стю)

  • Для обработки специальных символов вы, вероятно, захотите отменить экранирование uri

  • Он слепо следует символическим ссылкам (может быть проблемой безопасности)

Учитывая это, некоторые из параметров 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 (filename) .split (". "). reverse () [0]];" вместо? некоторые имена файлов содержат более одного символа "." например, "my.cool.video.mp4" или "download.tar.gz"
— несинхронизировано

Это каким-то образом мешает кому-то использовать URL-адрес, например папку /../../../ home / user / jackpot.privatekey? Я вижу соединение, чтобы гарантировать, что путь идет вниз по потоку, но мне интересно, поможет ли использование нотации ../../../ обойти это или нет. Пожалуй, сам протестирую.
— 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
вы забыли mimetype в случае успеха. Я использую этот дизайн, но вместо того, чтобы сразу передавать потоки по конвейеру, я подключаю их к событию open в файловом потоке: writeHead для mimetype, затем конвейер. Конец не нужен: readable.pipe .
— GeH

Изменено в соответствии с комментарием @GeH.
— Бретт Замир

Должно бытьfileStream.on('open', ...
— Петах

2

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

  1. custtom dir
  2. index.html возвращается, если req === dir

Применение:

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 05
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.