Введение
Здраствуйте, дорогие хабровчане! Сегодня у нас в основном будут маленькие изменения, но изменений много.
В этой части мы будем:
- Создавать свой логгер
- Записывать в лог запросы и время их обработки
- Исправлять ошибки, которые мы допустили в первой части.
- Разбираться с авторизациеей
- Разбираться с некоторыми классами
- Конфиги!
Логи
Для логов мы будем использовать самодельный модуль. Создадим папку 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/
Добавить комментарий