Всем привет! На этом занятии мы познакомимся с Node.js уже в роли веб-сервера. Создадим для этого новое приложение. Настройте пожалуйста свой редактор таким образом чтобы он знал что вы делаете именно Node.js-проект и поддерживал соответствующие автодополнения и глобальные переменные. Далее создадим server.js и в нем первым делом подключаю модуль:
var http = require('http');
Об этом модуле мы еще поговорим подробнее, но на текущий момент нам необходим из него один объект:
var server = new http.Server();
Как следует из его названия, он умеет слушать ip-порт и отвечать на входящие запросы. Для того, чтобы дать ему ip-порт, используется команда:
server.listen(1337, '127.0.0.1');
А для того, чтобы отвечать на запросы, используются события. Сервер является EventEmitter, и при входящих запросах инициируется соответствующее событие, которое называется request. Его обработчик получает два объекта:
server.on('request', function(req, res) { res.end("Hello, world!"); });
Первый request – это входящий запрос, он содержит информацию, которая присылает браузер, включая, в частности, url пришедшего запроса. И второй параметр – это объект ответа. Из первого мы читаем, во второй пишем. Конкретно эта функция тут же заканчивает выполнение запроса, отсылая фразу «Hello, world!». Давайте посмотрим, как это работает. Для этого я создам конфигурацию у себя в WebSorm 2016 для запуска Node.js приложения. Выбираем Node.js, там уже указан путь к Node. Добавляем путь к исполнимому файлу. Пожалуйста повторите эту процедуру но уже в своем редакторе. Запустим наш server. Работает.
Теперь зайдем на url браузера – http://127.0.0.1:1337/ . Замечательно! По виду все работает. Чтоб в этом убедиться, создадим переменную
var counter = 0; server.on('request', function(req, res) { res.end("Hello, world!"+ ++counter); });
которая будет вводить текущий считчик запросов. Захожу в браузер и попытаюсь обновить страницу. Но мои попытки не приносят результата. Почему? Node.js устроен так, что запустившись и считав файл модуля, в данном случае это главный файл, он же и единственный, Node создает из него объект module, и этим объектом в дальнейшее пользуется. Соответственно, при изменениях данного файла Node просто не подбирает эти изменения, потому что файл уже обработан, а объект module готов. Чтобы заставить Node перечитать файл, самый простой способ – запустить сервер еще раз. Но если я сейчас нажму play, то произойдет ошибка: EADDRINUSE – это означает, что адрес уже используется, потому что Node server.js попыталась запуститься еще раз, не прекратив действия уже запущенной программы. Предыдущий сервер уже занял этот ip-порт. Что делать? Например, можно взять и подредактировать конфигурацию, добавив галочку в Single instance only (сервер может быть запущен в единственном экземпляре). Жмем повторный play, который нам предложить убить текущий запуск. Попрошу не предупреждать об этом в будущем, а просто убивать его. Теперь снова повторное нажатие кнопки play или Ctrl+R будут перезапускать текущий сервер, а вовсе не делать новый. Если вы работаете в console, то это вообще не проблема. Убиваете текущий Node.js, стартуете новый. В дальнейшем мы посмотрим, как это оптимизировать.
Теперь перехожу в браузер и нажимаю Ctrl+R. Как видим, счетчик запросов увеличивается не на 1, а на 2. Это потому что браузер устроен так, что вместе с собственной страничкой он делает еще один запрос. Он делается на url favicon.ico. В данном случае, мы никакой favicon.ico не отдаем, поэтому браузер каждый раз его повторяет. Получается, что одно обновление страницы приводит к двум запросам, по крайней мере, в текущем контексте, в Chrome.
Мы сделали наш первый сервер на Node.js. В дальнейшем будем работать, чтобы сделать его гораздо мощнее, интереснее. Перед тем, как мы продолжим, небольшая ремарка: если вы смотрите сам сайт Node.js, то пример, аналогичный нашему. там выглядит немножко по-другому. Основное отличие в том, что здесь используется new server, а там http createServer. Это одно и то же. Здесь обработчик запроса ставится явно, а там он передается аргументам. Передача аргументов – это тоже установка обработчика, просто такой дополнительный синтаксис. Он чуть-чуть короче, но я выбрал для примера этот синтаксис, потому что он более нагляден и показывает то, что реально происходит.
А теперь, если вы внимательно слушали, то у вас не вызовет проблемы ответить на следующий небольшой вопрос. Здесь есть обработчик события request, скажите, пожалуйста, как, не глядя в документацию, сказать, какие еще есть события для сервера и когда они вызываются, в каком порядке при обработке этого запроса? Почему я прошу не смотреть в документацию? Тут две причины. Во-первых, так интереснее, во-вторых, сервер (var server = new http.Server() ), на самом деле, наследует net.Server, модуля net, а он наследует EventEmitter:
// http.Server→net.Server→EventEmitter
Вот такая иерархия. Соответственно, некоторые события сервера описаны вот здесь, в http.Server а некоторые описаны в документации к net.Server:
И просто, взглянув в книгу, понять точный порядок событий достаточно сложно. Сейчас я расскажу, как это сделать. Подсказка находиться вот в этой строке:
// http.Server→net.Server→ EventEmitter
Сервер является EventEmitter. Это означает, что все события генерируются вызовом server.emit. понятно, что мы сами не вызываем этот метод, в данном случае его вызывает сам Node.js, а мы просто ставим обработчики. Но ничто нам не мешает переопределить этот метод на наш собственный. Метод emit принимает название события и необходимые данные для него:
var counter = 0; var emit = server.emit; server.emit = function(event/*, arg1, arg2,... */) { };
В данном случае это будут req, res. Но нас интересуют только названия событий, которые мы при помощи console.log будем выводить. А далее мы буде передавать вызов исходному методу emit:
var emit = server.emit; server.emit = function(event/*, arg1, arg2,... */) { console.log(event); return emit.apply(this, arguments); }; server.on('request', function(req, res) { res.end("Hello, world!"+ ++counter); });
Посмотрим, что получилось. Перезапустим сервер. И тут же видим первое событие – listening. Это означает, что сервер начал слушать соединение:
server.listen(1337, '127.0.0.1');
Далее, захожу в браузер и перехожу по адресу. Обратите внимание, я нажимаю Ctrl+R, и request увеличивается, причем, даже по 2 штуки. А connection одно-другое, а новых нет, потому что это событие connection возникает тогда, когда браузер открывает серверу новое сетевое соединение, а request присылает запрос. Браузер устроен так, что одно сетевое соединение он старается использовать по максимуму. Называется это Keep–Alive. Он его сохраняет и по нему гонит новые запросы.
Итак, мы создали простейший веб-сервер, познакомились с соответствующим объектом http.Server и событиями, которые в нем возникают.
Код нашего урока можно найти в нашем репозитории.
Материалы урока взяты из следующего скринкаста.
We are looking forward to meeting you on our website soshace.com