16Nov

node23_2

Продолжаем наш урок. Давайте немного усложним этот пример, добавив работу с файлами вот таким образом:

var domain = require('domain');
var fs = require('fs');
var d = domain.create(), server;

d.on('error', function(err) {
    console.error("Domain has caught %s", err);
});


d.run(function() {

    setTimeout(function() {
      fs.readFile(__filename, function() {
          ERROR();
      });
        }, 1000);

});

Запускаем. Как ни странно, оно все еще работает. Почему? Как домен из данной функции перекочевал в fs.readFile(__filename, function()? Да все оттуда же. Потому что внутренняя реализация функции readFile знает про домены. Когда она запускает callback, то она запускает его в контексте того же домена. Благодаря этому все хорошо.

Рассмотрим последний пример.  Создаем новый объект:

server = new http.Server();

всю эту логику, которая генерирует ошибку, поместим внутрь обработчика события:

server.on('boom', function() {
    setTimeout(function() {
        fs.readFile(__filename, function() {
            ERROR();
        });
    }, 1000);
});

И для того, чтобы еще больше все запутать, вынесем этот код вниз. То есть, мы создаем объект в одном месте, а ошибка будет вообще после того, как запуск в домене закончился. Обработает ли ее домен? Запускаем наш код, полностью он выглядит вот так:

var domain = require('domain');
var fs = require('fs'), http = require('http');

var d = domain.create(), server;

d.on('error', function(err) {
    console.error("Domain has caught %s", err);
});

d.run(function() {

    server = new http.Server();

});

server.on('boom', function() {
    setTimeout(function() {
        fs.readFile(__filename, function() {
            ERROR();
        });
    }, 1000);
});

server.emit('boom');

Как видим, все отлично обработалось. Каким образом эта функция узнала про домен? Все благодаря интеграции. Ведь сервер – это EventEmitter. Этот модуль знает про домены, и, когда создается любой EventEmitter, если есть текущий активный домен, то он получает ссылку на него, где ее можно вывести. добавим console.log:

server = new http.Server();  
console.log(server.domain); 

Запускаем. Все, есть. После того, как EventEmitter привязан к домену, любые обработчики он запускает именно в контексте этого домена. Конечно же, если сервер создается вне активного домена, то ни о каком server.domain не может идти и речи. Впрочем, с такими объектами тоже можно иметь дело, необходимо лишь добавить их домен вручную: специальный вызов add:

server = new http.Server();

d.run(function() {

    d.add(server);

    console.log(server.domain);

});

Если мы сейчас это вызовем, то все будет хорошо, ошибка перехвачена. Единственная тонкость, которую здесь надо иметь в  виду, это управление памятью. Дело в том, что если EventEmitter создан именно в контексте домена, то он получает ссылку, в данном случае server.domain. А если он был создан ранее и добавлен через add, то он получает не только сам ссылку на этот домен, но еще и домен ссылается на него. То есть, у домена есть специальный массив members, где он ссылается на все, что ссылается через  add. Во всяком случае, такова текущая реализация. Соответственно, получается, что с add мы имеем двустороннюю ссылку serverdomain. В результате получается, что память может быть очищена не от сервера в отдельности, не от домена в отдельности, а от них обоих вместе. То есть, если есть какой-то долго живущий домен, и к нему через add  прибавляется много всего, то память не будет очищена, пока домен не умрет, либо пока когда это станет возможным, не будет вызов d.remove(server). Как правило, в скриптах эти штуки используют редко, обычно стараются создавать все, что нужно, внутри домена, и таких проблем не возникает.

Вернемся к примеру, который рассматривали в первой части статьи, то есть, к серверу. Перейдите на commit под названием domain_1-11:

Как вы считаете, в чем же дело? Почему ошибка в обработчике запроса вылетела и не была обработана доменом? Как это поправить? Дело в том, что сервер создан вне домена. Таким образом, handler это обработчик события request. Так как сервер создан вне домена, то при вызове обработчика никакой домен ему не передается, и throw, как и раньше, валит весь процесс. Чтобы все было хорошо, достаточно взять этот объект server и создать его внутри вызова run:

serverDomain.run(function() {  
  var server = require('./server');  
  server.listen(3000);  
});

Вот так будет все нормально, поскольку сервер привязан к домену. Проверим. Запускаем код. Вызываем Chrome и переходим по тому же url:

http://127.0.0.1:3000/

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

А как нам ответить? Здесьserver.domain перехватил ошибку, но никакой информации о том, где она, у него нет. Для того, чтобы ее получить, мы будем создавать домен отдельно для каждого запроса. Выглядит это следующим образом. Есть два файла. Первый  app.js – главный, основной файл приложения:

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');
    //var database = require ('mongodb');

     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);
});

Он занимается тем, что запускает сервер, создает домены. Второй – handler.js, модуль, который занимается обработкой запросов:

var fs = require('fs');

module.exports = function handler(req, res) {
    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");
    }

};

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

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

to-be-continued-series-8

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

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

6. Уроки Node.js. Util и Наследование

Всем привет! Тема ближайших статей: Самые часто используемые модули Node.js.

Первым методом, который мы изучим, будет метод util inspect модуля, встроенного util. Этот метод позволят красиво вывести любой объект, даже если у этого объекта, как в этом примере, есть ссылка на самого себя.

Leave a Reply