Цель этой главы, научиться делать чат на Node.JS. Для начала, наш чат будет достаточно простой, всего лишь каждый кто заходить по этому url — localhost:3000 автоматически попадает в комнату, в которой получает сообщения. Например я набираю что то в одном окне браузера и то что я набрал появляется и в другом окне браузера, в данном случае оба браузера на одном компьютере но могут быть и на разных краях света.
Вот такой чат, просто обмен сообщениями. Мы будем делать его в начале без пользователей, без базы данных, без авторизации, такой вот простой, но качественно созданный чат. И так, поехали. Для начала как оно в общем будет устроено. Алгоритм общения с сервером, который изображен на этой схеме
Называется «long polling», что в переводе длинные запросы. Он с одной стороны очень простой, с другой стороны в девяносто процентах задач, когда нужно общаться с сервером, он отлично подходит. Посмотрим на него повнимательней. Когда клиент хочет получать данные от сервера, то он отправляет на сервер XMLHttpRequest, самый обычный запрос, но не обычной является его обработка сервером. Сервер, получив такой запрос, не будет сразу на него отвечать, а просто оставит запрос подвисшим, дальше в будущем, как только появятся данные для клиента, сервер ответит на этот запрос, клиент получит ответ, какое то сообщение, обработает его выведет сообщение и сделает новый запрос на сервер, сервер опять, если данных нет то подождет, подождет, как только данные появятся, тут же ответит. Фактически получается, что клиент все время старается держать рабочее соединение к серверу, по которому, как только данные будут готовы, он их сразу же получит. Соответствующий код на стороне клиента, выглядит так
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<html> <body class="container"> <p class="lead">Добро пожаловать в чат!</p> <form id="publish" class="form-inline"> <input type="text" name="message"/> <input type="submit" class="btn btn-primary" value="Отправить"/> </form> <ul id="messages"></ul> <script> publish.onsubmit = function(){ var xhr = new XMLHttpRequest(); xhr.open("POST", "/publish", true) xhr.send(JSON.stringify({message: this.elements.message.value})); this.elements.message.value = ''; return false; }; subscribe(); function subscribe(){ var xhr = new XMLHttpRequest(); xhr.open("GET", "/subscribe", true); xhr.onload = function(){ var li = document.createElement('li'); li.textContent = this.responseText; message.appendChild(li); subscribe(); }; xhr.onerror = xhr.onabort = function(){ setTimeout(subscribe, 500); }; xhr.send(''); } </script> </body> </html> |
Есть форма, для отправки сообщений
4 5 6 7 8 9 |
<form id="publish" class="form-inline"> <input type="text" name="message"/> <input type="submit" class="btn btn-primary" value="Отправить"/> </form> |
И есть список, «messages», куда сообщения приходят
9 10 11 |
<ul id="messages"></ul> |
При submit формы, создается XMLHttpRequest и сообщение обычным порядком постится на сервер
13 14 15 16 17 18 19 20 21 22 23 24 25 |
publish.onsubmit = function(){ var xhr = new XMLHttpRequest(); xhr.open("POST", "/publish", true) xhr.send(JSON.stringify({message: this.elements.message.value})); this.elements.message.value = ''; return false; }; |
Ну а для получения новых сообщений, как раз используется алгоритм long polling описанный ранее. Есть функция subscribe()
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
function subscribe(){ var xhr = new XMLHttpRequest(); xhr.open("GET", "/subscribe", true); xhr.onload = function(){ var li = document.createElement('li'); li.textContent = this.responseText; message.appendChild(li); subscribe(); }; xhr.onerror = xhr.onabort = function(){ setTimeout(subscribe, 500); }; xhr.send(''); } |
которая запускает XMLHttpRequest и говорит получи ка данные с этого url — xhr.open(«GET», «/subscribe», true); Когда будет получен ответ с сервера, он показывается в виде сообщения и заново вызывается функция subscribe();
32 33 34 35 36 37 38 39 40 |
xhr.onload = function(){ var li = document.createElement('li'); li.textContent = this.responseText; message.appendChild(li); subscribe(); }; |
то есть делается новый запрос. И так все это идет по кругу.
Исключение, если произошла ошибка или что то не так, в этом случае мы subscribe() тоже заново отправим, но с небольшой задержкой, чтобы не завалить сервер —
40 41 42 43 44 |
xhr.onerror = xhr.onabort = function(){ setTimeout(subscribe, 500); }; |
Обратим внимание, что этот код реализует алгоритм long polling, он не привязан к какому то конкретному чату, это просто код подписки на сообщение сервера, его можно расширять, можно добавлять различные каналы получения сообщений и так далее, но сейчас пока мы этого делать не будем, а перейдем к Node.JS.
Для серверной части у нас так же есть не большая заготовка
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
var http = require('http'); var fs = require('fs'); http.createServer(function(req, res){ switch(req.url){ case '/': sendFile("index.html", res); break; case '/subscribe': // ... break; case '/publish': // ... break; default: res.statusCode = 404; res.end("Not found"); } }).listen(3000); function sendFile(fileName, res){ var fileStream = fs.createReadStrem(fileName); fileStream .on('error', function(){ res.statusCode = 500; res.end("Server error"); }) .pipe(res) .on('close', function(){ fileStream.destroy(); }); } |
которая представляет собой Http server, умеющий отдавать index.html в качестве главной страницы. Так же будут два url, вот такой — ‘/subscribe’, для подписки на сообщение, и такой — ‘/publish’, для отправки сообщений. Они в точности такие же, какие вы видели в index.html.
Начнем реализацию с подписки. Функция subscribe, со странички index.html, будет отправлять длинные запросы именно на url ‘/subscribe’. Клиент, который отправил запрос на subscribe, с одной стороны не должен получить ответ прямо сейчас, с другой стороны мы должны запомнить, что он обратился за данными, чтобы потом, когда данные появятся, ему их передать. Для решения этой задачи создадим специальный объект который будет называться «chat» и «chat.subscribe» будет запоминать что пришел клиент, для этого мы передадим ему объекты req и res — «chat.subscribe(req, res)». Ну а «chat.publish(«….»)» будет пересылать это сообщение всем клиентам которые сейчас есть. Описывать этот объект chat я буду в отдельном модуле который расположу в текущей директории.