18Nov

23_3
Итак, из чего состоит app.js?

Он состоит из того, что мы с самого начала делаем  домен, и запускаем в нем все наше приложение. Здесь подключаются все модули, создается сервер и т.д. Почему  мы подключаем модули здесь? Потому что во время подключения модулей могут подключаться какие-то другие модули, а те могут подключать – третьи, и т.д.

Многие из этих модулей я вообще не знаю. Они используются в каких-то библиотеках. При подключении модулей и их инициализации могут создавать какие-то объекты – EventEmitters. Я хочу быть уверенным, что эти EventEmitter подключаться именно к домену server.domain. Но основная задача этого кода – это запустить http.server:

server = http.createServer (function(req, res) {

       var reqDomain = domain.create();
        reqDomain.add(req);
        reqDomain.add(res);

        reqDomain.on('error', function(err) {
          res.statusCode = 500;
          res.end("Sorry, " + err);
           // ...
          serverDomain.emit('error', err);
        });

        reqDomain.run(function() {
           handler(req, res);
         });
       });

     server.listen(3000);

В обработчике запроса мы тоже создаем новый домен:

var reqDomain = domain.create();
        reqDomain.add(req);
        reqDomain.add(res);

Этот домен будет заниматься обработкой ошибок, которые появились во время данного конкретного запроса. Его преимущество – это то, что он знает про запрос. У него есть обработчик on.error, который, если какая-то ошибка, может ответить: 500, извините, ошибка. И не только ответить, он может  записать вlog именно то, что такой запрос пришел и такая-то ошибка. Таким образом, вы, как разработчик, будете видеть, что у Вас был такой запрос, и мне будет гораздо проще эту ошибку воспроизвести и исправить.

reqDomain.on('error', function(err) {
  res.statusCode = 500;
  res.end("Sorry, " + err);
  console.error('Error for req = ', req);

Мы привязываем к домену объекты req и res, поскольку они, во-первых, являются EventEmitters, во-вторых, они были созданы во внешнем коде Node.js. Поэтому мы вынуждены привязать их явно.

Допустим, выполняется handler. Произошла ошибка. Мы ответили человеку: “до свидания, извини, тут проблема” . Записали все, что надо в log. У нас один сервер поддерживает множество клиентов. Если те клиенты общались как-то между собой, то в произвольном месте выскочившая ошибка, которая может быть достаточно серьезной  – создаст кучу проблем. Обратите внимание, домены какие попало ошибки здесь у нас не ловят,

Обычно ошибки мы перехватываем в callback, try catch, а это именно какая-то программная ошибка, скорее всего. Соответственно, ее появление означает, что сервер сейчас находится в непонятном состоянии, и оставлять его в нем достаточно опасно. Поэтому хорошо бы этот сервер прибить и поднять новый чистый процесс Node.js.  Есть, конечно, ситуации, в которых можно оставить сервер работать дальше, но давайте делать безопаснее. Для этого  вызываем

     serverDomain.emit('error', err);

таким образом  передаем работу с ошибкой on.error. Обращаем ваше внимание еще на такой нюанс, домены, конечно, похожи на try catch, но это все-таки не они. Если в этом on.error сделать throw, то управление в serverDomain.on не попадет.  Вместо этого такой throw отовсюду выпадет и повалит процесс.

 reqDomain.on('error', function(err) {
          res.statusCode = 500;
          res.end("Sorry, " + err);
          console.error('Error for req = ', req);
            
          throw err;
          serverDomain.emit('error', err);
        });

Поэтому вызываем  внешний домен именно через Emit . Дальше обработчик на внешнем домене будет заниматься тем, что завершит обработку ошибки и, по возможности, мягко загасит текущий процесс сервера. На код ниже внимание можете не обращать:

 if (server) server.close();

   setTimeout(function () {
       process.exit(1);
   }, 1000).unref();

Он схематичный.  Нам здесь важны именно домены. Мы получили рабочую схему для вложенной обработки ошибок. По возможности мы стараемся обработать ошибки в контексте запроса:

  reqDomain.on('error', function(err) {
          res.statusCode = 500;
          res.end("Sorry, " + err);
          console.error('Error for req = ', req);

          serverDomain.emit('error', err);
        });

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

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

Возможно ли такое, что во время обработки запроса ниже, ошибка, которая произойдет, выскочит за пределы reqDomainв app.js?

if (req.url == '/') {  
  
    fs.readFile('no-such-file', function(err, content) {  
  
      if (err) throw err; // JSON.parse("invalid!")  
  
      res.end(content);  
    });  
  
  } else {  
    res.statusCode = 404;  
    res.end("Not Found");  
  }  
  
}; 

Ответ: да, возможно. Чтобы это продемонстрировать,  немного модифицируем пример, этот пример справедлив для старых версий Node.js, в сегодняшних версиях (а это 7.1) таких проблем нет, также как и самого модуля Domain, т.к. он устарел (пример может понадобиться для понимания Legacy Code):

var domain = require('domain');
var serverDomain = domain.create();

var server;

serverDomain.on('error', function(err) {
    console.error("Server error", err);
    if (server) server.close();

    setTimeout(function () {
        process.exit(1);
    }, 1000).unref();
});

serverDomain.run(function() {
    var http = require('http');
    var handler = require('./handler');


    server = http.createServer (function(req, res) {

        var reqDomain = domain.create();
        reqDomain.add(req);
        reqDomain.add(res);

        reqDomain.on('error', function(err) {
            res.statusCode = 500;
            res.end("Sorry, " + err);

            console.error('Error for req = ', req);

            serverDomain.emit('error', err);
        });

        reqDomain.run(function() {
            handler(req, res);
        });
    });

    server.listen(3000);
});

В handler.js у нас есть база данных, это может быть MongoDB, MySQL, Redis (необходимо иметь установленный Redis у себя на компьютере и клиент для него, установите последний с помощью:

npm install redis 

сама база устанавливается отдельно).

у них у всех одна и та же проблема с  при работе с доменами.

//var database = require ('mongodb');
//var mysql = require ('mysql');
var redis = require('redis').createClient();

module.exports = function handler(req, res) {
    if (req.url == '/') {

        redis.get('data', function(err, data) {

            throw new Error("redis callback"); //

        });

    } else {
        res.statusCode = 404;
        res.end("Not Found");
    }

};

Для того, чтобы ответить посетителю, нужно сделать запрос к базе. Дальше мы должны что-то сделать с данными, возможно, обработать или могут понадобиться другие запросы.  Так или иначе в процессе произошла какая-то ошибка. Куда же она попадет? Запускаем и переходим на страницу:

127.0.0.1:3000

Видим, что соединение было разорвано. Server error – это то, что выводит домен ниже:

serverDomain.on('error', function(err) {
    console.error("Server error", err);
    if (server) server.close();

    setTimeout(function () {
        process.exit(1);
    }, 1000).unref();
});

То есть, ошибка throw new Error ("redis callback") вовсе не была перехвачена обработчиком:

        reqDomain.on('error', function(err) {
                res.statusCode = 500;
                res.end("Sorry, " + err);

                console.error('Error for req = ', req);

она пошла сразу в верхнюю часть. Почему? Ведь ошибка возникла в процессе работы обработчика function handler(req, res) , как так получилось, что она вышла из requestDomain ?

Для того, чтобы ответить на этот вопрос необходимо вспомнить, что существуют всего 2 способа, как домен переходит в callback асинхронных вызовов. Первый – это внутренний метод Node.js: встроенный setTimeout, fs.readFile и т.д. Они интегрированы с доменами и передают их дальше в callback. Второй – это всякие  EventEmitter. Если бы EventEmitter  был создан в контексте домена, то он к нему привязывается.

Метод redis.get, который мы здесь вызываем, использует объект redis, который был создан вне контекста запроса, а при подключении модуля. В Node.js это стандартная практика, одно соединение используется для множества запросов. Соответственно, если мы хотим получить какие-то данные, то мы даем объекту redis то, что мы хотим получить (data), и какой-то callback. Дальше этот объект что-то делает, что-то отсылает в базу данных, и у него есть внутренние EventEmitter, которые были созданы вместе с ним, и которые будут генерировать события, когда база что-то ответит. И эти внутренние EventEmitter при генерации события передадут домен. Какой? Конечно же, тот, с которым они были созданы, то внешний домен из app.js :

serverDomain.run(function() {.....});

Получится, что этот callback будет вызван в домене serverDomain и не сохранит контекста запроса. Дальше, если этот callback вызвал какие-то другие функции, то, соответственно, мы получим тот же домен, и requestDomain для нас будет потерян.

Что делать? Есть 2 варианта. Первый – это поставить специальный вызов process.domain.bind.

 redis.get('data', process.domain.bind(function(err, data) {

            throw new Error("redis callback"); 

        }));

Этот вызов возвращает обертку вокруг функции, которая привязывает его к текущему активному домену, то есть, домену запроса. Если мы сейчас запустим наш сервер, то все будет хорошо. (В текущей версии Node.js – 7.1 все сработает и без этой записи, но для старых версий можно встретить и такую запись).

Все, что надо, сработало. Вы должны увидеть в браузере:
Sorry, Error: redis callback;

Код урока вы сможете найти здесь|

23_end

Материалы для статьи взяты из следующего скринкаста

We are looking forward to meeting you on our website soshace.com

21. Уроки Node.js. Writable Поток Ответа res, Метод pipe. Pt.2

По окончании файла наступит событие end, в обработчике которого мы завершим ответ вызовом res.end. Таким образом, будет закрыто исходящее соединение, потому что файл полностью отослан. Получившийся код является весьма универсальным:

2. Уроки Express.js . Логгер, Конфигурация, Шаблонизация с EJS. Часть 1.

Всем привет! Для того чтобы дальше разрабатывать это приложение, нам нужно сделать еще две важные вещи , а именно, конфигурация и логирование. Для того чтобы конфигурировать, будем использовать модуль nconf:

Уроки React. Урок 6.

Давайте продолжим наш разговор об инфраструктуре вокруг React. Но для начала давайте немного изменим нашу структуру и поместим компонент Article в отдельную директорию Articel,создадим ее для начала. Имя нашего компонента на Index.js и добавим в него следующий код:

Leave a Reply