Соответственно, что бы мы не записали, отправляется одно и то же. Давайте мы это поправим. Сообщения у нас отправляются методом POST
. Для того, чтобы считать этот метод из req
, нужно поработать с ним, как с потоком. Для этого посмотрим на следующую схему, которая описывает жизненный цикл запроса, а именно объектов req
и res
.
Эти знания нм далее еще понадобятся. Итак, запрос пришел. Если этот запрос get
, то у get
запросов нет тела. У них есть заголовки: url
и прочие браузерные, которые полностью пересылаются и обрабатываются сразу же. Если же метод POST
, то он содержит тело, которое необходимо считать: работа с req
как с потоком. В нашем случае мы использовали для отправки сообщений именно этот метод, поскольку get
, как мы говорили ранее, нельзя использовать для изменения данных, он предназначен только для их получения. Так что берем req и вешаем на него необходимые обработчики. Вот так это может выглядеть (server.js
):
http.createServer(function(req, res) { switch (req.url) { case '/': sendFile("index.html", res); break; case '/subscribe': chat.subscribe(req, res); break; case '/publish': var body = ''; req .on('readable', function() { var chunk = req.read(); if (chunk !== null) { body += chunk; } }) .on('end', function() { body = JSON.parse(body); chat.publish(body.message); res.end("ok"); }); break; default: res.statusCode = 404; res.end("Not found"); } }).listen(3000);
При получении очередного пакета данных, мы прибавляем его к временной переменной, а затем, когда данные полностью получены, разбираем их как JSON
, после чего публикуем в чат body.message
. Эта команда разошлет сообщение всем, кто сейчас подписался на subscribe
. Но с текущим запросом она ничего не сделает, поэтому текущий запрос, через который был отправлен POST
, необходимо тоже явно завершить, например, указав, что все прошло хорошо :
res.end("ok")
Такой код в принципе будет работать.
С другой стороны, если мы его пишем, то предполагаем, что наши клиенты святые, они всегда присылают valid JSON
, никогда не хотят ничего плохого сделать, что, конечно же, не так. Клиенты бывают разные. Поэтому давайте посмотрим, что необходимо здесь изменить. Первое – это прием JSON
, потому что в том случае, если он invalid
, то команда JSON.parse(body)
выбросит исключение, и оно повалит вообще весь процесс.
Так что давайте будем его отлавливать. Если произошла ошибка, то отвечаем клиенту: извините, запрос, который вы прислали, некорректен, добавим в наш case
:
case '/publish': var body = ''; req .on('readable', function() { var chunk = req.read(); if (chunk !== null) { body += chunk; } }) .on('end', function() { try { body = JSON.parse(body); } catch (e) { res.statusCode = 400; res.end("Bad Request"); return; } chat.publish(body.message); res.end("ok"); }); break;
Это как раз обозначает код 400, и заканчиваем с ним общение.Еще одна проблема безопасности, которая здесь есть, это работа с памятью. Если вдруг клиент очень злой или, наоборот, очень щедрый, и захочет пересылать нам все содержимое своего жесткого диска, то то, что он присылает, будет бесконечно накапливаться в переменной body
вскоре переполнит всю память, и сервер упадет. Поэтому, чтобы этого не происходило, добавим в case '/publish'
небольшую проверку:
case '/publish': var body = ''; req .on('readable', function() { var chunk = req.read(); if (chunk !== null) { body += chunk; } console.log(body.length); if (body.length > 1000) { res.statusCode = 413; res.end("Your message is too big for my little chat"); } }) .on('end', function() { if (res.statusCode === 413) return; try { body = JSON.parse(body); } catch (e) { res.statusCode = 400; res.end("Bad Request"); return; } chat.publish(body.message); res.end("ok"); }); break;
Если после определенного пакета длина body стала слишком большой, то, 413 – означает, что тело запроса слишком большое, и тоже завершим общение с данным клиентом.Запускаем получившийся сервер и проверяем чат, лучше это сделать в двух разных браузерах для наглядности.
Наш чат уже готов, но осталась одна небольшая деталь, а именно, что происходит, если клиент прекратил соединение? Давайте взглянем еще раз на модуль chat
. Здесь видно, что приходящие клиенты только прибавляются в массив. Ни один из клиентов с массива не удаляется. Соответственно, если вдруг клиент закрыл браузер, и таким образом соединение завершилось, то получится, что в этом массиве лишнее соединение, которое уже закрыто. С одной стороны, это может быть нормально, потому что запись этого соединения никаких побочных и плохих эффектов не даст, просто ничего не произойдет. С другой стороны, зачем нам вообще лишнее соединение? К счастью, отловить момент закрытия соединения очень просто:
exports.subscribe = function(req, res) { console.log("subscribe"); clients.push(res); res.on('close', function() { clients.splice(clients.indexOf(res), 1); }); };
Это событие срабатывает в том случае, если соединение закрыто до его окончания сервером, то есть, либо мы вызываем res.end
, либо, если мы этого не делаем, соединение просто обрывается или destroy
, то событие close
. При этом удаляем соединение из массива. Эту операцию можно сделать более эффективно, если вместо массива использовать объект. Но из массивом тоже все будет работать. Чтобы это проверить, сделаем setInterval
, который будет выводить каждые 2 секунды длину текущего массива clients
:
setInterval(function() { console.log(clients.length); }, 2000);
Запускаем в браузерах, проверяя работает ли чат. Он работает. Теперь обновляем страницу, при этом текущее соединение будет закрыто, а открыто новое. Обновляем. Как видно, активных соединений все еще 2.
Код урока доступен для скачивания в нашем репозитории.
Материал урока взят из следующего скринкаста.
We are looking forward to meeting you on our website soshace.com