Делаем проект на Node.js с использованием Mongoose, Express, Cluster. Часть 2.1

от автора

Введение

Здраствуйте, дорогие хабровчане! Сегодня у нас в основном будут маленькие изменения, но изменений много.
В этой части мы будем:

  • Создавать свой логгер
  • Записывать в лог запросы и время их обработки
  • Исправлять ошибки, которые мы допустили в первой части.
  • Разбираться с авторизациеей
  • Разбираться с некоторыми классами
  • Конфиги!

Логи

Для логов мы будем использовать самодельный модуль. Создадим папку logger. В нем будет файл index.js.

var stackTrace = require('stack-trace');  // Для получения имени родительского модуля var util = require('util'); //util.inspect() var path = require('path'); //path.relative() path.sep var projectname = require('../package').name; //package.json -> project name  module.exports = class Logger // Класс логера :) {     constructor()     {         function generateLogFunction(level) // Функция генератор функий логгера :)         {             return function(message,meta)             {                 //var d = Date.now(); // Будем потом записовать время вызова                 var mes = this.module + " -- ";                 mes += level + " -- ";                 mes += message; // прицепить сообщение                 if(meta) mes += "  " + util.inspect(meta) + " "; // Записать доп инфу (Object||Error)                 mes += '\n'; // Конец строки :)                  this.write(mes);                 // Записать во все потоки наше сообщение             }         };          this.trace = stackTrace.get()[1]; // Получить стек вызова         this.filename = this.trace.getFileName(); // Получить имя файла которое вызвало конструктор         this.module = projectname + path.sep + path.relative('.',this.filename); // Записать име модуля         this.streams = [process.stdout]; // Потоки в которые мы будем записовать логи         // В дальнейшем здесь будет стрим к файлу         this.log = generateLogFunction('Log'); // Лог поведения         this.info = generateLogFunction('Info'); // Лог информативный         this.error = generateLogFunction('Error'); // Лог ошибок         this.warn = generateLogFunction('Warning'); // Лог предупреждений     }     write(d)     {         this.streams.forEach((stream)=>{             stream.write(d);         });     } } 

А теперь про синтаксис использования.

var logger = new require('./logger')(); //... logger.info('Hello, world');

Почему мы используем new? Для того что бы получить имя файла в котором был создан логер. Ибо запуск stack-trace каждый раз когда мы пишем в лог будет использовать много ресурсов.
Заменим все console на logger. Оставлю все на волю вашего IDE 🙂

NOTE: В папке doc и node_modules есть файлы использующие console. Будьте осторожны!

Так-же заменим в файле worker.js console.error на throw
Вот так:

app.listen(3000,function(err){     if(err) throw err;     // Если есть ошибка сообщить об этом     logger.log(`Running server at port 3000!`)      // Иначе сообщить что мы успешно соединились с мастером     // И ждем сообщений от клиентов });

Почему мы не используем winston и другие модули для работы с логами? Ответ прост: winston показывает маленькую производительность. И не только винстон. Тоже самое касается многих модулей. Как оказалось после некоторого тестирования наш самодельный модуль показывает 4-8 раза больше производительности чем многие другие модули 🙂

Время обработки запроса

Для того что бы видеть какие запросы пришли на сервер и сколько времени заняла ее обработка мы напишем свой middleware. В папке bin создадим файл rt.js

var Logger = require('../logger'); var logger = new Logger();  module.exports = function(req,res,next) {     // Засечь начало     var beginTime = Date.now();     // В конце ответа     res.on('finish',()=>{         var d =  Date.now();// получить дату в мс         logger.log('Reponse time: ' + (d - beginTime),{             url:req.url, // записать в лог куда пришел запрос (Включает urlencode string :)             time:(d - beginTime) // сколько прошло времени         });     });     // Передать действие другому обработчику     next(); }

А в worker.js до каких либо обработчиков добавим:

// Время ответа app.use(require('./rt'));

Здесь мы используем собственный модуль потому что все остальные модули НЕ умеют логировать только гарантированно отправленные (хотя бы до ОС) запросы.

Разница приложения от роутера

В контроллере мы видели express() для создания мини приложения и потом мы монтировали его в проект с помощью app.use() но express не рекомендуют так делать. Мы заменим express() на new express.Router() в файле контроллера:

 // Пример: var Router = require('express').Router; var app = new Router();  // app.use(....) // app.get(....) // etc

Какие проблемы возникнут с express()? Самое важное. Мы не можем изменить настройки во всем приложении. К тому же не можем использовать app.locals. И еще по какой-то не понятной причине оно НЕ передает куки (Почему так?).

Конфигурация

Создадим папку config.
В папке у нас будет файл index.js, где мы будем получать все настройки, складывать, парсить и даже грабить корованы вставлять при необходимости нужные поля, если они отсутствуют.

module.exports = require('./config');

А в файле config.json:

{     "port":8080,     "mongoUri":"mongodb://127.0.0.1/armleo-test" }

NOTE: Если нет включенных сетей в windows то localhost не работает и надо использовать 127.0.0.1
В файле worker.js добавим в начале:

var config = require('../config');

А последние строки превращаются в зайца следующее:

// Запустим сервер на порту 3000 и сообщим об этом в консоли. // Все Worker-ы  должны иметь один и тот же порт app.listen(config.port,function(err){     if(err) throw err;     // Если есть ошибка сообщить об этом     logger.log(`Running server at port ${config.port}!`);     // Иначе сообщить что мы успешно соединились с мастером     // И ждем сообщений от клиентов });

Мы ведь помним, что нам еще нужно изменить строки в dbinit.js? Так сделаем это.

// 10 line bin/dbinit.js // Подключимся к серверу MongoDB var config = require('../config'); mongoose.connect(config.mongoUri,{     server:{         poolSize: 10         // Поставим количество подключений в пуле         // 10 рекомендуемое количество для моего проекта.         // Вам возможно понадобится и то меньше...     } });

ES6

Мы заменим все нужные var на const и let
Маленькое изменение, но оно мне нравится!

Авторизация

Теперь к авторизации! Для авторизации будем использовать Passport.js. В нашем проекте НЕ нужна регистрация.
Создадим контроллер auth.js. В контроллере нам нужны парсеры входных данных

let app = new (require('express').Router)();  const models = require('./../models');  const passport = require('passport'); const LocalStrategy = require('passport-local').Strategy;  app.use(passport.initialize()); app.use(passport.session());  passport.use(new LocalStrategy(     function(username, password, done) {         models.User.findOne({ username: username }, function (err, user) {         if (err) { return done(err); }         if (!user) {             return done(null, false, { message: 'Incorrect username.' });         }         if (user.password != password) {             return done(null, false, { message: 'Incorrect password.' });         }         return done(null, user);         });     } ));  passport.serializeUser(function(user, done) {     done(null, user._id); });  passport.deserializeUser(function(id, done) {     models.User.findById(id, function(err, user) {         done(err, user);     }); });  app.post('/login',     passport.authenticate('local', {         successRedirect: '/',         failureRedirect: '/login'     }) ); app.get('/login',function(req,res,next) {     if(req.user) return res.redirect('/');      res.render('login',{         user:req.user     }); });  module.exports = app;

Для авторизации мы в views/login.html добавим форму.

<form action="/login" method="POST">     <input name="username"/>     <br/>     <input name="password"/>     <br/>     <input type="submit"/> </form>

Для пользователей придумаем модель.

Посты!

Установим модули:

npm i mongoose-url-slugs --save

Создадим модель постов в папке models файл post.js:

// Загрузим mongoose т.к. нам требуется несколько классов или типов для нашей модели const mongoose = require('mongoose'); const URLSlugs = require('mongoose-url-slugs'); // Создаем новую схему! let postSchema = new mongoose.Schema({     title:{         type:String, // тип: String         required:[true,"titleRequired"],         // Данное поле обязательно. Если его нет вывести ошибку с текстом titleRequired         // Максимальная длинна 32 Юникод символа (Unicode symbol != byte)         minlength:[6,"tooShort"],         unique:true // Оно должно быть уникальным     },     text:{         type:String, // тип String          required:[true,"textRequired"]         // Думаю здесь все тоже очевидно     },     // Здесь будут и другие поля, но сейчас еще рано их сюда ставить!     // Например коментарии     // Оценки     // и тд      // slug:String });  // Теперь подключим плагины (внешние модули)  // Подключим генератор на основе названия postSchema.plugin(URLSlugs('title'));  // Компилируем и Экспортируем модель module.exports = mongoose.model('Post',postSchema);

А в файле models/index.js:

module.exports = {     // Загрузить модель юзера (пользователя)     // На *nix-ах все файлы чувствительны к регистру     User:require('./user'),     Post:require('./post') }; // Не забудем точку с запятой!

Создадим контроллер для создания/редактирования постов!
В файле controllers/index.js:

const Logger = require('../logger'); const logger = new Logger();  let app = new (require('express').Router)();  app.use(require('./auth')); app.use(require('./home')); app.use(require('./post'));  module.exports = app;

А в файле controllers/post.js:

let app = new (require('express').Router)(); const models = require("../models");  app.get('/post', function(req,res,next) {     if(!req.user) return res.redirect('/login');     res.render('addpost',{         user:req.user     }); });  app.post('/post', function(req, res, next) {     if(!req.user) return res.redirect('/login');     let post = new models.Post(req.body);     post.save()         .then(()=>{             res.redirect('/post/' + post.slug);         }).catch(next); });  app.get('/post/:slug',(req, res, next)=>{     models.Post.findOne({         slug:req.params.slug     }).exec().then((post)=>{         if(!post) res.redirect('/#notfound');         res.render('post',{             user:req.user,             post         });     }).catch(next); });  module.exports = app;

И соответсвенно образы!
Создания views/addpost.html

<form method="POST" action="/post">     <input name="title"/>     <br/>     <input name="text"/>     <br/>     <input type="submit"/> </form>

Отображения views/post.html

{{#post}}     <h1>{{title}}</h1>     <br/>     {{text}} {{/post}}

Немного доделаем образ views/index.html

{{#user}}     Hello {{username}} {{/user}} {{^user}}     Login <a href="/login">here!</a> {{/user}} {{#posts}} <br/><a href="/post/{{slug}}">{{title}}</a> {{/posts}}

Доделаем контроллер controllers/home.js:

let app = new (require('express').Router)(); const models = require('../models');  app.get('/',(req,res,next)=>{     //Создадим новый handler который сидит по пути `/`     models.Post.find({}).exec().then((posts)=>{          res.render('index',{             user:req.user,             posts         });         // Отправим рендер образа под именем index     }).catch(next); });  module.exports = app;

В bin/worker.js добавим парсер urlencode:

app.use(bodyParser.urlencoded());

GitHub

Наш проект вы можете найти на гитхабе вот тут

Конец второй части!

На этом конец второй части. В след. частях мы выделим немного времени HTTPS, сервисам в Ubuntu, Хешировать пароль, Каптче (recaptcha) и комментариям с некоторой статистикой и введем поддержку markdown для постов, Заменим MongoDB для сессий на Redis, Добавим кеширования.

ссылка на оригинал статьи https://habrahabr.ru/post/314560/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *