Робимо проект на 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
:
{
"порту":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 символ (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, Додамо кешування.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.