Базовый статический файловый сервер в 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

Stewe
источник
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