Продолжаем наш урок. Давайте немного усложним этот пример, добавив работу с файлами вот таким образом:
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
мы имеем двустороннюю ссылку server
↔
domain
. В результате получается, что память может быть очищена не от сервера в отдельности, не от домена в отдельности, а от них обоих вместе. То есть, если есть какой-то долго живущий домен, и к нему через 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
?
Мы поговорим об этом в следующей статье. До скорых встреч. Код нашего урока можно найти здесь.
Материалы для статьи взяты из следующего скринкаста.
We are looking forward to meeting you on our website soshace.com