Node.js: пишем простой сервер с middleware, используя Connect

Предисловие. Пара слов о том, почему Node.js

Да, Node.js – это Вещь (с большой буквы, как и написано).
Чем больше я читаю и узнаю о нём, и пробую его, тем больше он мне нравится. С Ruby и Python‘ом пока не складывается (все мои проекты были на PHP, поэтому поддержка новой платформы была не актуальна – это учить новые языки, новые библиотеки, соглашения, короче, всю платформу, тогда как на PHP я программирую более-менее активно уже более 10 лет).

Но вот пришёл Node.js, который может заменить собой полностью серверную платформу, и он использует язык, на котором я также программирую свыше 10 лет – великий и ужасный JavaScript.

Причём пользоваться им легко и приятно, как оказалось, с учётом npmnode package manager‘а, который предоставляет быстрый и удобный доступ к тысячам или десяткам тысяч пакетов для Node.js с учётом всех зависимостей и прочая и прочая.

Пишем собственный (custom) middleware

Итак, давайте сваяем простой сервер на Connect, используя добавочно свою middleware. У меня в папке git скопилось наверное сотня разных проектов, и для серфинга по ним я использую либо Total Commander для просмотра и редактирования файлов, либо браузер ч/з запущенный сервер Apache или nginx также для наглядного просмотра папок и файлов.

Но с приходом Node.js логичней будет использовать сервер на нём для тренировки и вообще.

Так как в проектах на git частенько используется файл README.md в Markdown-разметке, добавим к нашему серверу функцию просмотра таких файлов в виде HTML на лету, написав простенький middleware.

Как известно, middleware для Connect/Express – это функция с тремя параметрами – req, res и next – запрос, ответ и функция обратного вызова для продолжения цепочки обработки запроса клиента.

Если наш middleware оканчивает запрос, он должен послать команду res.end(…) (в случае использования Connect‘а, в Express выбор богаче, это отдельная тема) для отдачи ответа клиенту, иначе он должен вызвать функцию next без параметров для обычного продолжения обработки, либо next(err) – с объектом ошибки – для дальнейшей обработки и рендеринга возникшей ошибки.

Вкратце алгоритм нашего middleware (назовём его any_md) будет таков: если строка запрашиваемого ресурса оканчивается на `.md`, то мы читаем файл ресурса, прогоняем его через marked (модуль формирования HTML из MD) и отдаём клиенту как обычный HTML файл. Считается, что файлы расположены начиная от текущей папки (git).
Круто? Круто. Неслыханно? Да. Восхитительно? Ещё как!

Вот как это выглядит на JavaScript под платформу Node.js:

// MIDDLEWARE: отдаём запрошенный файл .md как .html
var any_md = function(req, res, next){
// это md-файл?
if(req.url.toLowerCase().slice(-3) == '.md') {
 fs.readFile('.' + req.url, function(err, data){
  if(err) return next(err); // ошибку отправляем дальше
 
  // преобразуем MD -> HTML и отсылаем клиенту
  var html = '<!doctype html>\n<html lang="en">\n' +
  '<head>\n\t<meta charset="utf-8">\n\t<title>' + 
  req.url.slice(1) + '</title>\n</head>\n' + 
  '<body>\n' + marked(data.toString()) + '\n</body>\n</html>';
 
  res.writeHead(200, {
   'Content-Length': Buffer.byteLength(html),
   'Content-Type': 'text/html; charset=utf-8',
  });
  res.end(html);
 });
}
else
 next();
};

Тут можно рассмотреть вопрос кэширования таких запросов, но так как сервером по-умолчанию будем пользоваться только мы да возможно пара-тройка приглашенных друзей, то можно ограничиться тем, что есть.

Так же в нашем сервере мы не рассматриваем кастомную обработку ошибок, оставляя её на совесть Connect‘а, точнее модуля errorhandler.

Готовый node.js сервер на Connect

Для нашего сервера мы будем использовать следующие middleware (модули) для Connect/Express:

  • compression – gzip/deflate сжатие данных на лету
  • morgan – бывший logger, лог запросов на консоль либо в файл
  • serve-favicon – отдача браузеру favicon.ico
  • serve-index – отдача списка файлов (каталога) клиенту
  • serve-static – отдача статических файлов (т.е. в нашем случае всех) клиенту
  • errorhandler – обработка ошибок
  • marked – рендер md разметки в html

Итак, для создания сервера заходим в папку git, где у нас расположены папки десятков или сотен git-проектов, и выполняем команду:

npm install -g errorhandler connect compression morgan serve-favicon serve-index serve-static marked

Заметьте, что мы установили все модули глобально, чтобы не загрязнять нашу папку git папкой node_modules. В Windows может возникнуть проблема доступа к глобально установленным модулям, тогда следует прописать такой параметр среды:

NODE_PATH=C:\Users\Andrey\AppData\Roaming\npm\node_modules

Тут `Andrey` надо поменять на имя пользователя, которое используете вы.
Также модули compression, morgan, serve-favicon, и в принципе errorhandler, которые отвечают за сжатие запроса, логирование, обслуживание favicon и обработку ошибок, не обязательны и используются тут в качестве примера создания более функционального сервера.

Затем создаём такой файл Node-сервера с именем git-server.js:

/* **************************************** */
/* Статический сервер на текущий каталог    */
/* с возможностью просмотров папок и файлов */
/* **************************************** */
var connect = require('connect');
var errorhandler = require('errorhandler');
var compression = require('compression');	// gzip/deflate
var morgan = require('morgan'); // logger
var favicon = require('serve-favicon');	// favorite icon
var serveIndex = require('serve-index');	// directory
var serveStatic = require('serve-static');	// static
 
var fs = require('fs');
var marked = require('marked');
 
// MIDDLEWARE: отдаём запрошенный файл .md как .html
var any_md = function(req, res, next){
 // это md-файл?
 if(req.url.toLowerCase().slice(-3) == '.md') {
   fs.readFile('.' + req.url, function(err, data){
    if(err) return next(err);
    // преобразуем MD -> HTML и отсылаем клиенту
    var html = '<!doctype html>\n<html lang="en">\n' +
    '<head>\n\t<meta charset="utf-8">\n\t<title>' + 
    req.url.slice(1) + '</title>\n</head>\n' + 
    '<body>\n' + marked(data.toString()) + '\n</body>\n</html>';
 
    res.writeHead(200, {
       'Content-Length': Buffer.byteLength(html),
       'Content-Type': 'text/html; charset=utf-8',
    });
    res.end(html);
   });
 }
 else
  next();
};
 
var app = connect()
.use(morgan('combined'))
.use(favicon('./favicon.ico'))
.use(any_md)
.use(compression({level: 3, memLevel: 5}))
.use(serveStatic('.'))
.use(serveIndex('.', {'icons': true}));
 
//if (process.env.NODE_ENV === 'development') {
// такую обработку ошибок использовать
// только при разработке и тестинге 
app.use(errorhandler());
//}
 
app.listen(80, function(){
	console.log('Server started at localhost port 80');
});

Для использования модуля favicon‘а закиньте файл favicon.ico сюда же, в каталог git, если лень, закомментируйте строчку `.use(favicon(‘./favicon.ico’))`.

Итак, запускаем наш сервер:

node git-server.js

Сервер работает по адресу localhost, порт 80 (в Linux для использования этого порта могут понадобиться права root, поэтому лучше поменять порт на 3000, например).

Теперь заходим браузером по адресу localhost (если мы используем порт 80 его тут указывать не нужно) и видим список папок git-проектов. Зайдя в какую-нибудь, где есть файл README.md (или любой md-файл), щёлкнем на нём и увидим его как обычный HTML-файл в браузере.

На сим всё, откланиваюсь, крепкой чашечки кофе утром и сладкого бокала чая с чебрецом и лимоном вечером,
а также вкусной котлеты по-киевски, которую я сейчас пойду кушать.

Но главное – это любовь и хорошее настроения, блин.