Статьи

Взгляд снаружи Рубин: Node.js

В этом, последнем выпуске из цикла « Извлечение внешнего мира », мы рассмотрим серверный JavaScript с Node.

Что такое узел?

Node — это набор библиотек, которые позволяют JavaScript запускаться вне браузера, где рассматриваемый JavaScript — это движок V8 от Chrome. Его главная цель — упростить и облегчить создание сетевых клиентов и серверов. Node был запущен еще в 2009 году Райаном Далем, а проект в настоящее время спонсируется Джойентом, его работодателем.

Зачем использовать это?

В двух словах: скорость и масштабируемость. Механизм JavaScript V8, который лежит в основе Node, известен своей скоростью, а подход «Evented I / O», используемый Node, делает его масштабируемым и способным с легкостью обрабатывать многие одновременные соединения.

Evented I / O

Node использует архитектуру цикла событий, на дизайн которой частично повлияла машина событий Ruby . Если вы не знакомы с циклами событий, они определены в Википедии как:

программная конструкция, которая ожидает и отправляет события или сообщения в программе. Он работает путем опроса некоторого внутреннего или внешнего «провайдера событий», который обычно блокируется до прибытия события, а затем вызывает соответствующий обработчик события («отправляет событие»)…. Цикл событий почти всегда работает асинхронно с источником сообщения.

По сути, Node постоянно проверяет наличие событий, будь то получение HTTP-запроса или доступ к файлу на диске. Когда происходит заданное событие, выполняется функция обратного вызова для обработки событий.

Этот тип обработки событий часто случается с нами в повседневной жизни. Сколько раз вы звонили кому-то и должны были оставить сообщение, чтобы он перезвонил вам? Пока вы ждете, пока они перезвонят, вы можете заниматься другими делами.

Один из примеров, использованных в слайдах Райана Даля из его выступления в 2009 году на JSConf.eu, рассматривает доступ к базе данных:

var result = db.query("select * from T"); // use result 

Затем задается уместный вопрос: что делает ваше программное обеспечение, когда оно запрашивает базу данных? Ответ: обычно ничего. Тот же код, реализованный в цикле событий, может выглядеть примерно так:

 db.query("select...", function(result) { // use result }); 

Программа может сразу вернуться к циклу событий и не должна ждать завершения запроса к базе данных. Когда это закончится, цикл обработки событий отправит функцию обратного вызова.

Узел работает в одном потоке, точно так же, как JavaScript работает в одном потоке в браузере. Таким образом, вы должны попытаться убедиться, что все операции ввода-вывода в вашем коде Node не блокируются — вы не хотите блокировать цикл обработки событий, выполняя некоторые ресурсоемкие вычисления или синхронный ввод-вывод. К счастью, Node поставляется с набором основных модулей, которые предоставляют неблокирующие интерфейсы для доступа к таким вещам, как файловые и сетевые ресурсы.

Установка

Исходный код узла доступен в OS X и Linux, и есть файл node.exe, который можно загрузить для Windows. Вы можете установить узел через менеджер пакетов; на момент написания статьи существуют пакеты для Debian, Ubuntu, openSUSE, Arch Linux, homebrew и macports в OS X и Chocolatey в Windows.

Если вы хотите собрать Node самостоятельно на OS X или Linux, вам нужно запустить командную строку и ввести следующее:

 curl -O http://nodejs.org/dist/node-v0.4.12.tar.gz tar xzvf node-v0.4.12.tar.gz cd node-v0.4.12.tar.gz ./configure --prefix=/usr/local/node make make install 

NB. Здесь мы устанавливаем Node в /usr/local но, если вам удобно собирать пакеты из исходного кода, не стесняйтесь изменять это по своему усмотрению.

После установки Node установите переменную среды $NODE_PATH чтобы Node знал, где искать модули:

 echo 'export NODE_PATH=/opt/node:/opt/node/lib/node_modules' >> ~/.bashrc # ~/.bash_profile or ~/.profile on some systems 

Если /usr/local/node/bin еще нет в вашем $PATH вы можете добавить его следующим образом:

 echo 'export PATH=$PATH:/opt/node/bin' >> ~/.bashrc # ~/.bash_profile or ~/.profile on some systems 

Затем введите следующее, чтобы перезагрузить ваш новый $PATH :

 . ~/.bashrc # ~/.bash_profile or ~/.profile on some systems 

Написание кода

Давайте начнем с рассмотрения канонической программы «Привет, мир», на этот раз в виде веб-сервера:

 var http = require('http'); http.createServer(function(req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, World!'); }).listen(4567, '127.0.0.1'); console.log('Server running at http://127.0.0.1:4567'); 

Вы можете либо сохранить этот код в файле, который называется что-то вроде server.js и запустить его, набрав в своем терминале node server.js , или, альтернативно, просто набрать в командной строке node, чтобы запустить узел в интерактивном режиме — эквивалент Ruby’s irb — и скопируйте и вставьте код в терминал. В этом коде мы:

  1. Импорт модуля http с использованием функции require
  2. Вызов функции createServer модуля http и передача ей функции обратного вызова; эта функция будет запускаться каждый раз, когда Node получает новое соединение
  3. Сообщая узлу, какой порт и IP-адрес мы хотим, чтобы наш сервер работал / прослушивался
  4. Запись сообщения на консоль, чтобы мы знали, что наш сервер запущен и работает

Помните ранее, когда мы упоминали о масштабируемости Node?

веб-сервер «hello world» … многие клиентские соединения могут обрабатываться одновременно. Узел сообщает операционной системе (через epoll, kqueue, / dev / poll или select), что он должен быть уведомлен, когда устанавливается новое соединение, и затем он переходит в спящий режим. Если кто-то новый подключается, он выполняет обратный вызов. Каждое соединение — это только небольшое выделение кучи.
— с http://nodejs.org/#about

Создание и обработка ваших собственных событий

Независимо от того, сколько вы используете базовых модулей Node, придет время, когда вам нужно будет генерировать и обрабатывать свои собственные или чьи-либо другие пользовательские события. Node поставляется с классом EventEmitter, который помогает нам в этом. Здесь есть два основных метода, которые нас интересуют: они включены и emit . При подключении и обработке наших пользовательских событий мы можем наследовать от EventEmitter, чтобы присоединить эти методы к нашему коду. Давайте рассмотрим простой сервер, чтобы проиллюстрировать это:

 var util = require('util'), EventEmitter = require('events').EventEmitter; var Server = function() { console.log('init'); }; util.inherits(Server, EventEmitter); var s = new Server(); s.on('error', function() { console.log('error...'); }); ... s.emit('error'); 

В этом примере:

  1. Мы используем метод util модуля util , чтобы наш класс Server наследовал от EventEmitter
  2. Мы устанавливаем функцию обратного вызова для события error, вызывая теперь унаследованный метод on
  3. Вызов emit на нашем экземпляре сервера вызовет функцию обратного вызова ошибки

Узлы Модули

Узел использует модульную систему CommonJS. Цель CommonJS — предоставить общий API для JavaScript на сервере, который можно использовать независимо от интерпретатора JavaScript, поэтому тот же код, который выполняется на Node, также можно запускать без изменений на Rhino. CommonJS также определяет, как модули могут быть загружены с помощью require и как вам нужно использовать объект exports при написании ваших собственных модулей.

Если вы посмотрите на предыдущий пример кода, вы увидите, что мы использовали require импортировать некоторые из основных модулей. CommonJS оговаривает, что у каждого модуля будет доступ к функции require , и что require должно возвращать экспортированный API модуля (подробнее об этом позже). Когда вы передаете require имя модуля, вы можете передать ему имя модуля «верхнего уровня» или «относительного» модуля, т. Е. Идентификатор модуля начинается с . или ..

Теперь, что подразумевалось под «экспортированным API»? Что ж, CommonJS также заявляет, что каждый модуль будет иметь доступ к объекту exports , и вы определяете API вашего модуля, явно добавляя к нему функции и переменные; любые функции и переменные, которые не добавляются в exports остаются закрытыми для модуля.

Мы можем проиллюстрировать это, написав наш собственный модуль, jedimindtrick . Поскольку Node использует подход «один файл на модуль», сохраните приведенный ниже код в файле с именем jedimindtrick.js :

 // jedimindtrick.js var droid1 = 'R2-D2', droid2 = 'C-3PO'; exports.mindTrick = function(d1, d2) { if (droid1 === d1 && droid2 === d2) { console.log("These aren't the droids you're looking for"); } } moveAlong = function() { console.log("Move along...move along"); } 

Затем в командной строке измените каталог так, чтобы вы jedimindtrick.js в той же папке, где вы только что сохранили jedimindtrick.js , введите node и нажмите <Enter> чтобы запустить интерактивный режим Node, и введите следующее:

 var jedi = require('./jedimindtrick'); jedi.mindTrick('R2-D2', 'C-3PO'); jedi.moveAlong(); 

В этом коде мы передаем относительный путь к нашему модулю, который require , вызываем экспортированную функцию mindTrick вызвать функцию moveAlong . Но, поскольку это не было добавлено к объекту exports в нашем модуле, мы TypeError: Object [object Object] has no method 'moveAlong' .

Хостинг

Наконец, короткое слово о хостинге. В Node wiki есть список хостинг-провайдеров . Хорошей новостью для программистов на Ruby является то, что хостинг Node предоставляется Heroku .

Ну, это был быстрый взгляд на узел. Мы рассмотрели, что такое Node и его потенциальные преимущества, как вы можете его установить, рассмотрели несколько коротких примеров кода и кратко рассмотрели модули Node и то, как вы можете написать свой собственный. Это ни в коем случае не исчерпывающее руководство, но, надеюсь, если вы еще не взглянули на Node, это подтолкнет вас к этому.

Дальнейшее чтение