Он состоит из того, что мы с самого начала делаем домен, и запускаем в нем все наше приложение. Здесь подключаются все модули, создается сервер и т.д. Почему мы подключаем модули здесь? Потому что во время подключения модулей могут подключаться какие-то другие модули, а те могут подключать – третьи, и т.д.
Многие из этих модулей я вообще не знаю. Они используются в каких-то библиотеках. При подключении модулей и их инициализации могут создавать какие-то объекты – 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;
Код урока вы сможете найти здесь|
Материалы для статьи взяты из следующего скринкаста
We are looking forward to meeting you on our website soshace.com