Начало работы с Node.js
Мы начнем с основ: никаких предварительных знаний Node.js не требуется. Цель этой книги — начать работу с Node.js и убедиться, что вы понимаете, как писать приложение с использованием этой платформы.
В первой главе вы узнаете, что такое Node.js, как установить её на свой компьютер и как начать с ней работать — так что в следующих главах можно будет приступить к реальной разработке. Приступим!
Node.js в двух словах
Node.js — это среда выполнения JavaScript, построенная на JavaScript-движке V8 из Chrome. В основе Node.js лежит событийно-управляемая модель с неблокирующими операциями I/O, что делает её легкой и эффективной.
Другими словами: Node.js предлагает вам возможность писать невероятно производительный серверный код с использованием JavaScript. Как говорится в официальном описании: Node.js — это среда выполнения, использующая тот же JavaScript-движок V8, который вы можете найти в браузере Google Chrome. Но этого недостаточно для успеха Node.js. В Node.js используется libuv — кросс-платформенная библиотека поддержки с акцентом на асинхронный ввод-вывод.
С точки зрения разработчика, Node.js однопоточна, но под капотом libuv использует треды, события файловой системы, реализует цикл событий, включает в себя тред-пулинг и так далее. В большинстве случаев вы не будете взаимодействовать с libuv напрямую.
Установка Node.js для старта
Последнюю версию Node.js вы можете найти на официальном сайте: https://nodejs.org/en/download/.
При таком подходе довольно легко начать работу, но если позже вы захотите добавить в систему больше версий Node.js, лучше начать использовать nvm (node version manager) — диспетчер версий Node.js.
После его установки вы сможете использовать очень простой CLI API для смены версии Node.js:
Установка различных версий Node.js
Затем, если вы хотите проверить в работе экспериментальную версию:
Чтобы убедиться, что у вас установлена и запущена Node.js, выполните:
Если все в порядке, эта команда вернет номер версии текущего активного бинарного файла Node.js.
Использование нескольких версий Node.js
Если вы работаете над проектом, поддерживающим Node.js v4, вы можете начать использовать эту версию с помощью следующей команды:
Затем вы можете переключиться на Node.js v5 с помощью той же самой команды:
Хорошо, теперь мы знаем, как устанавливать Node.js и переключаться между её версиями, но в чём смысл?
С тех пор как был сформирован Node.js Foundation, Node.js имеет план релизов. Это очень похоже на другие проекты Linux Foundation. Это означает, что есть два релиза: стабильный и экспериментальный. В Node.js стабильными версиями с долговременной поддержкой (LTS) являются те, которые начинаются с четных чисел (4, 6, 8, …). Экспериментальные версии нумеруются нечетными числами (5, 7, …).
Мы рекомендуем использовать версию LTS в продакшене и пробовать новые возможности с экспериментальной версией.
Если вы используете Windows, здесь можно скачать альтернативу для nvm: nvm-windows.
Hello World
Чтобы начать работу с Node.js, давайте попробуем её в консоли! Запустите Node.js, просто набрав node :
Хорошо, давайте попробуем что-то напечатать:
После нажатия Enter вы получите следующее:
Не стесняйтесь играть с Node.js с помощью этого интерфейса: я обычно тестирую небольшие фрагменты кода здесь, если я не хочу помещать их в файл.
Пришло время создать наше приложение Hello Node.js!
Начнем с создания файла index.js . Откройте свою IDE (Atom, Sublime, Code — выбор за вами), создайте новый файл и сохраните его с именем index.js . Если вы закончили, скопируйте в него следующий фрагмент кода:
Чтобы запустить этот файл, вы должны снова открыть свой терминал и перейти в каталог, в котором размещён index.js .
Как только вы успешно переместитесь в нужное место, запустите файл, используя команду node index.js . Вы увидите, что эта команда будет выдавать тот же результат, что и раньше, выводя строку непосредственно в терминале.
Модульность для вашего приложения
Теперь у вас есть файл index.js , поэтому пришло время перейти на следующий уровень! Давайте создадим что-то более сложное, разделив наш исходный код на несколько JavaScript-файлов с целью удобочитаемости и поддерживаемости. Чтобы начать работу, вернитесь в свою IDE и создайте следующую структуру каталогов (с пустыми файлами), но пока не трогайте package.json , мы сгенерируем его автоматически на следующем шаге:
Каждый проект Node.js начинается с создания файла package.json . Вы можете думать о нем как о JSON-представлении приложения и его зависимостей. Он содержит имя вашего приложения, автора (вас) и все зависимости, необходимые для запуска приложения. Мы рассмотрим раздел зависимостей позже в главе «Использование NPM».
Вы можете интерактивно генерировать файл package.json с помощью команды npm init в терминале. После запуска команды вас попросят ввести некоторые данные, например имя вашего приложения, версию, описание и так далее. Не нужно беспокоиться, просто нажимайте Enter, пока не получите сформированный JSON и вопрос is it ok? . Нажмите Enter в последний раз и вуаля: ваш package.json был автоматически сгенерирован и помещен в папку вашего приложения. Если вы откроете этот файл в своей IDE, он будет очень похож на фрагмент кода ниже.
Хорошей практикой является добавление стартового скрипта в ваш пакет package.json . Как только вы это сделаете, как показано в примере выше, вы можете запустить приложение с помощью команды npm start . Это очень удобно, когда вы хотите развернуть свое приложение у PaaS-провайдера: они могут распознать команду start и использовать её для запуска приложения.
Теперь давайте вернемся к первому созданному вами файлу под названием index.js . Я рекомендую оставить этот файл очень компактным: только подключение самого приложения (файл index.js из подкаталога /app , созданного ранее). Скопируйте следующий код в свой файл index.js и сохраните:
Теперь пришло время приступить к созданию реального приложения. Откройте файл index.js из папки /app , чтобы создать очень простой пример: добавление массива чисел. В этом случае файл index.js будет содержать только числа, которые мы хотим добавить, а логика, требующая вычислений, должна быть помещена в отдельный модуль.
Вставьте этот код в файл index.js в каталоге /app .
Теперь вставьте фактическую бизнес-логику в файл calc.js , который можно найти в той же папке.
Чтобы проверить, всё ли вы сделали правильно, сохраните эти файлы, откройте терминал и введите npm start или node index.js . Если вы все сделали правильно, вы получите ответ: 19. Если что-то пошло не так, внимательно просмотрите лог в консоли и найдите проблему на его основе.
В следующей главе под названием «Использование NPM» мы рассмотрим, как использовать NPM — менеджер пакетов для JavaScript.
Как написать и запустить свою первую программу на Node.js
Node.js – это популярная открытая среда выполнения, которая может запускать JavaScript вне браузера при помощи механизма V8. Механизм V8 используется для обработки JavaScript в браузере Google Chrome. Среда Node обычно используется для разработки инструментов командной строки и веб-серверов.
Умея работать в Node.js, вы сможете писать код для фронтенда и бэкенда на одном языке. Так будет проще переключаться между контекстами. Кроме того, вы сможете использовать одни и те же библиотеки на фронтенде и бэкенде.
Благодаря поддержке асинхронного выполнения Node.js отлично справляется с задачами с высокой нагрузкой I/O, потому он отлично подходит для веб-разработки. Приложения реального времени (например, видео-стримы или приложения, которые непрерывно отправляют и получают данные) могут работать более эффективно, если написать их в Node.js.
В этом мануале вы узнаете, как написать свою первую программу в среде выполнения Node.js. Вы познакомитесь с некоторыми концепциями, специфичными для Node. Мы создадим программу, которая поможет пользователям проверять переменные среды в своей системе. Для этого мы научимся выводить строки на консоль, собирать пользовательский ввод и получать доступ к переменным среды.
Требования
- Установка Node.js (в данном мануале мы используем версию 10.16.0). Чтобы установить эту среду выполнения на macOS, обратитесь к мануалу Установка Node.js и настройка локальной среды разработки в macOS. Для Ubuntu выполните мануал Установка Node.js в Ubuntu 18.04 (рекомендуем устанавливать через PPA).
- Базовые навыки работы с JavaScript. Полезные руководства есть в нашем Информатории.
1: Вывод на консоль
Чтобы создать простейшую программу “Hello, World!”, откройте текстовый редактор (например, nano) и создайте новый файл:
В файл введите следующий код:
Объект console в Node.js предоставляет простые методы для записи в stdout, stderr или в любой другой поток Node.js (что в большинстве случаев является командной строкой). Метод log выводит в поток stdout, так что вы можете увидеть его в своей консоли.
В контексте Node.js потоки – это объекты, которые могут принимать (например поток stdout) или выводить данные (например сетевой сокет или файл). В случае потоков stdout и stderr любые отправленные им данные будут показаны в консоли. Одна из замечательных особенностей потоков заключается в том, что они легко перенаправляются, и вы можете, например, просто перенаправить вывод своей программы в файл.
Сохраните и закройте nano, нажав сочетание клавиш CTRL + X, при появлении запроса на сохранение файла нажмите Y. Теперь ваша программа готова к запуску.
2: Запуск программы
Чтобы запустить свою новую программу, используйте такую команду:
Программа hello.js запустится и выведет такой результат:
Интерпретатор Node.js прочитал файл и выполнил строку:
вызвав метод log глобального объекта console. Строка “Hello World” была передана в качестве аргумента функции log.
Кавычки необходимы в коде, чтобы определить границы строки, но в результате они не выводятся на экран.
Убедившись, что программа работает, мы можем сделать ее более интерактивной.
3: Получение пользовательского ввода с помощью аргументов командной строки
Каждый раз, когда вы запускаете программу Node.js «Hello, World!», она выдает один и тот же результат. Чтобы сделать программу более динамичной, давайте научим ее собирать информацию от пользователя и отображать ее на экране.
Инструменты командной строки обычно принимают различные аргументы, которые изменяют их поведение. Например, команда node с аргументом –version печатает установленную версию, а не запускает интерпретатор. На этом этапе мы научим код принимать пользовательский ввод с помощью аргументов командной строки.
Создайте новый файл arguments.js в nano:
Введите следующую строку:
Объект process – это глобальный объект Node.js, который содержит функции и данные, связанные с текущим запущенным процессом Node.js. Свойство argv – это массив строк, содержащий все аргументы командной строки, заданные программе.
Сохраните и закройте файл.
Теперь при запуске программы можно использовать аргументы:
node arguments.js hello world
Вы получите такой вывод:
[ ‘/usr/bin/node’,
‘/home/8host/first-program/arguments.js’,
‘hello’,
‘world’ ]
Первым аргументом в массиве process.argv всегда является местоположение двоичного файла Node.js, с помощью которого выполняется программа. Второй аргумент – это всегда местоположение запускаемого файла. Остальные аргументы – это то, что ввел пользователь, в данном случае hello и world.
Нас больше всего интересуют не те аргументы, которые по умолчанию предоставляет Node.js, а аргументы, введенные пользователем. Откройте файл arguments.js в редакторе:
Измените console.log (process.arg); таким образом:
Поскольку argv является массивом, вы можете использовать встроенный в JavaScript метод slice, который возвращает набор элементов. Используя функцию slice с аргументом 2, вы получаете все элементы argv, которые идут в массиве после второго элемента, то есть аргументы, введенные пользователем.
Перезапустите программу с помощью команды node и тех же аргументов, что и в прошлый раз:
node arguments.js hello world
Теперь вывод выглядит так:
Итак, программа может собирать пользовательские данные, давайте теперь научимся собирать данные из среды программы.
4: Доступ к переменным среды
Переменные среды – это данные типа «ключ-значение», хранящиеся вне программы и предоставляемые операционной системой. Как правило, они устанавливаются системой или пользователем и доступны всем запущенным процессам для конфигурации или определения состояния. Вы можете использовать объект process, чтобы получить доступ к переменным среды.
Используйте nano для создания нового файла environment.js:
Добавьте следующий код:
Объект env хранит все переменные среды, которые доступны, когда Node.js запускает программу.
Сохраните и закройте файл, а затем запустите файл environment.js с помощью команды node.
После запуска программы вы должны увидеть подобный вывод:
…
COLORTERM: ‘truecolor’,
SSH_AUTH_SOCK: ‘/run/user/1000/keyring/ssh’,
XMODIFIERS: ‘@im=ibus’,
DESKTOP_SESSION: ‘ubuntu’,
SSH_AGENT_PID: ‘1150’,
PWD: ‘/home/8host/first-program’,
LOGNAME: ‘8host’,
GPG_AGENT_INFO: ‘/run/user/1000/gnupg/S.gpg-agent:0:1’,
GJS_DEBUG_TOPICS: ‘JS ERROR;JS LOG’,
WINDOWPATH: ‘2’,
HOME: ‘/home/8host’,
USERNAME: ‘8host’,
IM_CONFIG_PHASE: ‘2’,
LANG: ‘en_US.UTF-8’,
VTE_VERSION: ‘5601’,
CLUTTER_IM_MODULE: ‘xim’,
GJS_DEBUG_OUTPUT: ‘stderr’,
LESSCLOSE: ‘/usr/bin/lesspipe %s %s’,
TERM: ‘xterm-256color’,
LESSOPEN: ‘| /usr/bin/lesspipe %s’,
USER: ‘8host’,
DISPLAY: ‘:0’,
SHLVL: ‘1’,
PATH:
‘/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin’,
DBUS_SESSION_BUS_ADDRESS: ‘unix:path=/run/user/1000/bus’,
_: ‘/usr/bin/node’,
OLDPWD: ‘/home/8host’ >
Имейте в виду, что многие переменные среды зависят от конфигурации и настроек вашей системы, и ваши выходные данные могут существенно отличаться от того, что вы видите здесь.
Вместо длинного списка переменных среды вам может потребоваться получить конкретную переменную. Давайте попробуем это сделать.
5: Доступ к конкретной переменной среды
На этом этапе мы научимся просматривать переменные среды с помощью глобального объекта process.env и выводить их значения на консоль.
Объект process.env выполняет простое сопоставление имен переменных среды и их значений, хранящихся в виде строк. Как и с другими объектами в JavaScript, вы получите доступ к отдельному свойству, ссылаясь на его имя в квадратных скобках.
Откройте файл environment.js для редактирования:
Измените console.log (process.env) так:
Сохраните файл и закройте файл. Теперь запустите программу environment.js:
Вывод теперь выглядит так:
Вместо того чтобы отображать весь объект, вы теперь выводите только свойство HOME для process.env, в котором хранится значение переменной $HOME.
Опять же, ваш вывод, вероятно, будет отличаться от того, что вы видите здесь, потому что он специфичен для вашей системы.
Теперь, когда вы можете извлекать конкретные переменные среды, вы можете улучшить свою программу: она может запрашивать у пользователя переменную, которую он хочет получить.
6: Извлечение аргумента в ответ на ввод пользователя
Давайте используем возможность чтения аргументов командной строки и переменных среды для создания утилиты, которая выводит значение переменной среды на экран.
С помощью nano создайте новый файл echo.js:
Добавьте следующий код:
const args = process.argv.slice(2);
console.log(process.env[args[0]]);
В первой строке echo.js в переменной args сохраняются все аргументы командной строки, предоставленные пользователем. Вторая строка отображает переменную среды, хранящуюся в первом элементе args, то есть первый аргумент командной строки, предоставленный пользователем.
Сохраните и закройте файл. Запустите программу:
node echo.js HOME
Аргумент HOME был сохранен в массиве args, который затем использовался для поиска значения в среде через объект process.env.
Теперь вы можете получить доступ к значению любой переменной среды в вашей системе. Чтобы убедиться в этом, попробуйте просмотреть следующие переменные: PWD, USER, PATH.
Получение отдельных переменных – это хорошо, но лучше бы позволить пользователям указывать количество переменных.
7: Просмотр нескольких переменных среды
В настоящее время приложение может выдавать только одну переменную среды за один раз. Было бы полезно научить программу принимать несколько аргументов командной строки и выводить соответствующие значения. Используйте nano, чтобы отредактировать echo.js:
Отредактируйте файл так:
const args = process.argv.slice(2);
args.forEach(arg => <
console.log(process.env[arg]);
>);
Метод forEach – это стандартный метод JavaScript для всех объектов массива. Он принимает функцию обратного вызова, которая используется при выполнении итерации по каждому элементу массива. Мы используем forEach для массива args, предоставляя ему функцию обратного вызова, которая выводит значение текущего аргумента в среде.
Сохраните и закройте файл. Теперь перезапустите программу с двумя аргументами:
node echo.js HOME PWD
Вы должны увидеть следующий вывод:
Функция forEach обеспечивает вывод каждого аргумента командной строки в массиве args.
Теперь программа может извлекать переменные, которые запрашивает пользователь. Осталось только разобраться с неверным пользовательским вводом.
8: Обработка неправильного пользовательского ввода
Попробуйте передать программе неправильный аргумент:
node echo.js HOME PWD NOT_DEFINED
Вывод будет выглядеть примерно так:
/home/8host
/home/8host/first-program
undefined
Первые две строки правильные, а последняя строка имеет значение undefined. В JavaScript неопределенное значение undefined означает, что переменной или свойству не было присвоено значение. Поскольку NOT_DEFINED не является допустимой переменной среды, ее значение отображается как undefined.
Лучше вместо этого показать пользователю сообщение об ошибке, если аргумент командной строки не найден в среде.
Отредактируйте код echo.js таким образом:
const args = process.argv.slice(2);
args.forEach(arg => <
let envVar = process.env[arg];
if (envVar === undefined) <
console.error(`Could not find «$
> else <
console.log(envVar);
>
>);
Мы изменили функцию обратного вызова для forEach, и теперь она делает следующие вещи:
- Получает значение аргумента командной строки в среде и сохраняет его в переменной envVar.
- Проверяет, не является ли значение envVar undefined.
- Если envVar undefined, функция выводит полезное сообщение о том, что значение не удалось найти.
- Если переменная среды была найдена, она выведет ее значение.
Примечание: Функция console.error выводит сообщение на экран через поток stderr, а console.log – через поток stdout. Когда вы запускаете эту программу через командную строку, вы не видите разницы между потоками stdout и stderr. Однако ошибки рекомендуется выводить через поток stderr, чтобы их было легче идентифицировать и обрабатывать другими программами, которые чувствуют эту разницу.
Теперь выполните следующую команду еще раз:
node echo.js HOME PWD NOT_DEFINED
На этот раз получится:
/home/8host
/home/8host/first-program
Could not find «NOT_DEFINED» in environment
Теперь, когда программа получает аргумент командной строки, который не соответствует ни одной переменной, она выводит четкое сообщение об ошибке.
Заключение
Начав с простой программы «Hello World», вы написали утилиту командной строки Node.js, которая считывает пользовательские аргументы и выводит переменные среды.
Если вы хотите продолжить работу, вы можете еще больше изменить поведение этой программы. Например, можно проверить аргументы командной строки перед выводом результатов. Если один из аргументов не определяется, вы можете вернуть ошибку, и пользователь получит вывод, только если все аргументы будут соответствовать переменным среды.
Node.js учебник
Начало работы с Node.js
Node.js представляет собой основанную на событиях, неблокирующую асинхронную инфраструктуру ввода-вывода, которая использует движок Google V8 JavaScript. Он используется для разработки приложений, которые сильно используют возможность запуска JavaScript как на клиенте, так и на стороне сервера и, следовательно, извлекают выгоду из повторного использования кода и отсутствия переключения контекста. Это open-source и кросс-платформенный. Приложения Node.js написаны на чистом JavaScript и могут выполняться в среде Node.js на Windows, Linux и т. Д. .
Версии
| Версия | Дата выхода |
|---|---|
| v8.2.1 | 2017-07-20 |
| v8.2.0 | 2017-07-19 |
| v8.1.4 | 2017-07-11 |
| v8.1.3 | 2017-06-29 |
| v8.1.2 | 2017-06-15 |
| v8.1.1 | 2017-06-13 |
| v8.1.0 | 2017-06-08 |
| v8.0.0 | 2017-05-30 |
| v7.10.0 | 2017-05-02 |
| v7.9.0 | 2017-04-11 |
| v7.8.0 | 2017-03-29 |
| v7.7.4 | 2017-03-21 |
| v7.7.3 | 2017-03-14 |
| v7.7.2 | 2017-03-08 |
| v7.7.1 | 2017-03-02 |
| v7.7.0 | 2017-02-28 |
| v7.6.0 | 2017-02-21 |
| v7.5.0 | 2017-01-31 |
| v7.4.0 | 2017-01-04 |
| v7.3.0 | 2016-12-20 |
| v7.2.1 | 2016-12-06 |
| v7.2.0 | 2016-11-22 |
| v7.1.0 | 2016-11-08 |
| версия 7.0.0 | 2016-10-25 |
| v6.11.0 | 2017-06-06 |
| v6.10.3 | 2017-05-02 |
| v6.10.2 | 2017-04-04 |
| v6.10.1 | 2017-03-21 |
| v6.10.0 | 2017-02-21 |
| v6.9.5 | 2017-01-31 |
| v6.9.4 | 2017-01-05 |
| v6.9.3 | 2017-01-05 |
| v6.9.2 | 2016-12-06 |
| v6.9.1 | 2016-10-19 |
| v6.9.0 | 2016-10-18 |
| v6.8.1 | 2016-10-14 |
| v6.8.0 | 2016-10-12 |
| v6.7.0 | 2016-09-27 |
| v6.6.0 | 2016-09-14 |
| v6.5.0 | 2016-08-26 |
| v6.4.0 | 2016-08-12 |
| v6.3.1 | 2016-07-21 |
| v6.3.0 | 2016-07-06 |
| v6.2.2 | 2016-06-16 |
| v6.2.1 | 2016-06-02 |
| v6.2.0 | 2016-05-17 |
| v6.1.0 | 2016-05-05 |
| v6.0.0 | 2016-04-26 |
| v5.12.0 | 2016-06-23 |
| v5.11.1 | 2016-05-05 |
| v5.11.0 | 2016-04-21 |
| v5.10.1 | 2016-04-05 |
| v5.10 | 2016-04-01 |
| V5.9 | 2016-03-16 |
| версии 5.8 | 2016-03-09 |
| v5.7 | 2016-02-23 |
| v5.6 | 2016-02-09 |
| v5.5 | 2016-01-21 |
| v5.4 | 2016-01-06 |
| v5.3 | 2015-12-15 |
| v5.2 | 2015-12-09 |
| v5.1 | 2015-11-17 |
| v5.0 | 2015-10-29 |
| V4.4 | 2016-03-08 |
| v4.3 | 2016-02-09 |
| v4.2 | 2015-10-12 |
| v4.1 | 2015-09-17 |
| v4.0 | 2015-09-08 |
| io.js v3.3 | 2015-09-02 |
| io.js v3.2 | 2015-08-25 |
| io.js v3.1 | 2015-08-19 |
| io.js v3.0 | 2015-08-04 |
| io.js v2.5 | 2015-07-28 |
| io.js v2.4 | 2015-07-17 |
| io.js v2.3 | 2015-06-13 |
| io.js v2.2 | 2015-06-01 |
| io.js v2.1 | 2015-05-24 |
| io.js v2.0 | 2015-05-04 |
| io.js v1.8 | 2015-04-21 |
| io.js v1.7 | 2015-04-17 |
| io.js v1.6 | 2015-03-20 |
| io.js v1.5 | 2015-03-06 |
| io.js v1.4 | 2015-02-27 |
| io.js v1.3 | 2015-02-20 |
| io.js v1.2 | 2015-02-11 |
| io.js v1.1 | 2015-02-03 |
| io.js v1.0 | 2015-01-14 |
| v0.12 | 2016-02-09 |
| v0.11 | 2013-03-28 |
| V0.10 | 2013-03-11 |
| v0.9 | 2012-07-20 |
| v0.8 | 2012-06-22 |
| v0.7 | 2012-01-17 |
| v0.6 | 2011-11-04 |
| v0.5 | 2011-08-26 |
| v0.4 | 2011-08-26 |
| v0.3 | 2011-08-26 |
| v0.2 | 2011-08-26 |
| v0.1 | 2011-08-26 |
Сервер HTTP Hello World
Сначала установите Node.js для своей платформы.
В этом примере мы создадим HTTP-сервер, прослушивающий порт 1337, который отправляет Hello, World! в браузер. Обратите внимание, что вместо использования порта 1337 вы можете использовать любой номер порта по вашему выбору, который в настоящее время не используется какой-либо другой услугой.
Модуль http — это основной модуль Node.js (модуль, входящий в состав источника Node.js, который не требует установки дополнительных ресурсов). Модуль http предоставляет функциональные возможности для создания HTTP-сервера с использованием http.createServer() . Чтобы создать приложение, создайте файл, содержащий следующий код JavaScript.
Сохраните файл с любым именем файла. В этом случае, если мы назовем это hello.js мы можем запустить приложение, перейдя в каталог, в котором находится файл, и используя следующую команду:
После этого созданный сервер можно получить с помощью URL http: // localhost: 1337 или http://127.0.0.1:1337 в браузере.
Простая веб-страница появится с текстом «Hello, World!» Вверху, как показано на скриншоте ниже.
Командная строка Hello World
Node.js также можно использовать для создания утилит командной строки. В приведенном ниже примере читается первый аргумент из командной строки и выводится сообщение Hello.
Чтобы запустить этот код в системе Unix:
- Создайте новый файл и вставьте код ниже. Имя файла не имеет значения.
- Сделать этот файл исполняемым с помощью chmod 700 FILE_NAME
- Запустите приложение с помощью ./APP_NAME David
В Windows вы делаете шаг 1 и запускаете его с node APP_NAME David
Установка и запуск Node.js
Для начала установите Node.js на компьютер разработки.
Windows: перейдите на страницу загрузки и загрузите / запустите программу установки.
Mac: перейдите на страницу загрузки и загрузите / запустите программу установки. Кроме того, вы можете установить узел через Homebrew с помощью brew install node . Homebrew — это командный пакет для Macintosh, и больше информации об этом можно найти на веб-сайте Homebrew .
Linux: следуйте инструкциям для своего дистрибутива на странице установки командной строки .
Запуск программы узла
Чтобы запустить программу Node.js, просто запустите node app.js или nodejs app.js , где app.js является именем файла исходного кода вашего узла. Вам не нужно включать суффикс .js для узла, чтобы найти сценарий, который вы хотите запустить.
В качестве альтернативы в операционных системах на базе UNIX программа Node может быть выполнена как сценарий терминала. Для этого нужно начинать с shebang, указывающего на интерпретатор узла, например, узел #!/usr/bin/env node . Файл также должен быть установлен как исполняемый файл, который можно выполнить с помощью chmod . Теперь скрипт можно запустить из командной строки.
Развертывание приложения онлайн
Когда вы развертываете свое приложение в (размещенной в Node.js) размещенной среде, эта среда обычно предлагает переменную окружения PORT которую вы можете использовать для запуска вашего сервера. Изменение номера порта на process.env.PORT позволяет получить доступ к приложению.
Кроме того, если вы хотите получить доступ к этому автономному устройству во время отладки, вы можете использовать это:
где 3000 — номер порта в автономном режиме.
Отладка вашего приложения NodeJS
Вы можете использовать инспектора узлов. Запустите эту команду, чтобы установить ее через npm:
Затем вы можете отлаживать свое приложение, используя
Отладка изначально
Вы также можете отлаживать node.js изначально, запустив его следующим образом:
Чтобы остановить ваш отладчик точно в нужной строке кода, используйте это:
Для получения дополнительной информации см. Здесь .
В node.js 8 используйте следующую команду:
Затем откройте about://inspect в последней версии Google Chrome и выберите свой сценарий узла, чтобы получить отладочную версию DevTools от Chrome.
Hello World with Express
В следующем примере Express используется для создания HTTP-сервера, прослушивающего порт 3000, который отвечает «Hello, World!». Экспресс является широко используемой веб-картой, которая полезна для создания HTTP API.
Сначала создайте новую папку, например myApp . Перейдите в myApp и создайте новый файл JavaScript, содержащий следующий код (например, hello.js его hello.js ). Затем установите экспресс-модуль, используя npm install —save express из командной строки. Подробнее об установке пакетов см. В этой документации .
В командной строке выполните следующую команду:
Откройте ваш браузер и перейдите по http://localhost:3000 или http://127.0.0.1:3000 чтобы увидеть ответ.
Для получения дополнительной информации о структуре Express вы можете проверить раздел « Веб-приложения с экспрессом »
Приветственная базовая маршрутизация
Как только вы поймете, как создать HTTP-сервер с узлом, важно понять, как заставить его «делать» вещи на основе пути, к которому пользователь перешел. Это явление называется «маршрутизация».
Самый простой пример этого — проверить if (request.url === 'some/path/here') , а затем вызвать функцию, которая отвечает новым файлом.
Пример этого можно увидеть здесь:
Если вы продолжите определять свои «маршруты», подобные этому, вы получите одну массивную функцию обратного вызова, и мы не хотим такого гигантского беспорядка, поэтому давайте посмотрим, можем ли мы это очистить.
Во-первых, давайте сохраним все наши маршруты в объекте:
Теперь, когда мы сохранили 2 маршрута в объекте, теперь мы можем проверить их в нашем основном обратном вызове:
Теперь каждый раз, когда вы пытаетесь перемещаться по сайту, он проверяет наличие этого пути в ваших маршрутах и вызывает соответствующую функцию. Если маршрут не найден, сервер ответит 404 (не найден).
И там у вас есть — маршрутизация с помощью API HTTP Server очень проста.
TLS Socket: сервер и клиент
Единственными существенными отличиями между этим и обычным TCP-соединением являются закрытый ключ и общедоступный сертификат, который вам нужно будет установить в объект опции.
Как создать ключ и сертификат
Первым шагом в этом процессе безопасности является создание закрытого ключа. И что это за секретный ключ? В принципе, это набор случайных шумов, которые используются для шифрования информации. Теоретически вы можете создать один ключ и использовать его для шифрования всего, что хотите. Но лучше всего иметь разные ключи для конкретных вещей. Потому что, если кто-то украл ваш секретный ключ, это похоже на то, что кто-то украл ваши ключи от дома. Представьте себе, если вы использовали тот же ключ для блокировки своего автомобиля, гаража, офиса и т. Д.
openssl genrsa -out private-key.pem 1024
После того, как у нас есть наш закрытый ключ, мы можем создать CSR (запрос подписи сертификата), который является нашим запросом на то, чтобы секретный ключ был подписан модным авторитетом. Вот почему вы должны вводить информацию, связанную с вашей компанией. Эта информация будет видна авторитетом подписания и использована для проверки вас. В нашем случае не имеет значения, что вы набираете, поскольку на следующем этапе мы сами подпишем наш сертификат.
openssl req -new -key private-key.pem -out csr.pem
Теперь, когда мы закончили работу с бумагой, пришло время притвориться, что мы классный авторитет подписания.
openssl x509 -req -in csr.pem -signkey private-key.pem -out public-cert.pem
Теперь, когда у вас есть закрытый ключ и публичный сертификат, вы можете установить безопасное соединение между двумя приложениями NodeJS. И, как вы можете видеть в примере кода, это очень простой процесс.
Важный!
Поскольку мы создали публичный сертификат, честно говоря, наш сертификат бесполезен, потому что мы — ничто. Сервер NodeJS не будет доверять такому сертификату по умолчанию, и поэтому нам нужно сказать ему, чтобы он действительно доверял нашему сертификату со следующей опцией rejectUnauthorized: false. Очень важно : никогда не устанавливайте эту переменную в true в производственной среде.
Сервер TLS Socket
TLS Socket Client
Hello World в REPL
При вызове без аргументов Node.js запускает REPL (Read-Eval-Print-Loop), также известный как « оболочка узла ».
В командной строке введите node .
В командной строке узла > введите «Hello World!».
Основные модули
Node.js — движок Javascript (движок V8 для Google для Chrome, написанный на C ++), который позволяет запускать Javascript за пределами браузера. Хотя для расширения возможностей Node доступны многочисленные библиотеки, в комплект поставки входит набор основных модулей, реализующих основные функции.
В настоящее время в узле имеется 34 основных модуля:
Все основные модули на первый взгляд
утверждать
Модуль assert предоставляет простой набор тестов утверждения, которые можно использовать для проверки инвариантов.
буфер
До введения TypedArray в ECMAScript 2015 (ES6) язык JavaScript не имел механизма для чтения или обработки потоков двоичных данных. Класс Buffer был представлен как часть API Node.js, чтобы можно было взаимодействовать с октетными потоками в контексте таких потоков, как потоки TCP и операции с файловой системой.
Теперь, когда TypedArray был добавлен в ES6, класс Buffer реализует Uin t8Array API таким образом, который более оптимизирован и подходит для случаев использования Node.js.
C / C ++ _ аддоны
Node.js Addons — это динамически связанные общие объекты, написанные на C или C ++, которые могут быть загружены в Node.js с помощью функции require() и использованы так же, как если бы они были обычным модулем Node.js. Они используются, прежде всего, для обеспечения интерфейса между JavaScript, запущенным в библиотеках Node.js и C / C ++.
child_process
Модуль child_process предоставляет возможность генерировать дочерние процессы способом, похожим, но не идентичным, для popen (3).
Один экземпляр Node.js работает в одном потоке. Чтобы воспользоваться преимуществами многоядерных систем, пользователь иногда захочет запустить кластер из процессов Node.js для обработки нагрузки. Модуль кластера позволяет вам легко создавать дочерние процессы, которые используют общий доступ к портам сервера.
приставка
console модуль предоставляет простую консоль отладки, похожую на механизм консоли JavaScript, предоставляемый веб-браузерами.
крипто-
crypto модуль обеспечивает криптографическую функциональность , которая включает в себя набор оберток для хэша OpenSSL , , HMAC, шифр, расшифровывать, подписывать и проверять функции.
deprecated_apis
Node.js может испортить API, если: (a) использование API считается небезопасным; (b) был предоставлен улучшенный альтернативный API или (c) нарушение изменений в API ожидается в будущем крупном выпуске ,
DNS
Модуль dns содержит функции, принадлежащие двум различным категориям:
- Функции, которые используют базовые средства операционной системы для выполнения разрешения имен, и которые не обязательно выполняют какую-либо сетевую связь. Эта категория содержит только одну функцию: dns.lookup() .
- Функции, которые подключаются к реальному DNS-серверу для выполнения разрешения имен и всегда используют сеть для выполнения DNS-запросов. Эта категория содержит все функции в модуле dns кроме dns.lookup() .
домен
Этот модуль ожидает отмены . После того, как API-интерфейс замены будет завершен, этот модуль будет полностью устарел. У большинства конечных пользователей не должно быть причин использовать этот модуль. Пользователи, которые абсолютно должны обладать функциональностью, которую предоставляют домены, могут на это рассчитывать, но в будущем они должны будут перейти к другому решению.
Большая часть основного API-интерфейса Node.js построена вокруг идиоматической асинхронной архитектуры, управляемой событиями, в которой определенные типы объектов (называемые «эмиттеры») периодически излучают именованные события, которые вызывают функции-объекты («слушатели»).
фс
Файловый ввод-вывод обеспечивается простыми оболочками вокруг стандартных функций POSIX. Для использования этого модуля require('fs') . Все методы имеют асинхронные и синхронные формы.
Интерфейсы HTTP в Node.js предназначены для поддержки многих функций протокола, которые традиционно трудно использовать. В частности, большие, возможно, закодированные в блоке сообщения. Интерфейс не требует буферизации целых запросов или ответов — пользователь может передавать данные.
HTTPS
HTTPS — это протокол HTTP через TLS / SSL. В Node.js это реализовано как отдельный модуль.
модуль
Node.js имеет простую систему загрузки модуля. В Node.js файлы и модули соответствуют друг другу (каждый файл рассматривается как отдельный модуль).
сеть
net модуль предоставляет асинхронную сеть обертки. Он содержит функции для создания серверов и клиентов (называемых потоками). Вы можете включить этот модуль с require('net'); ,
Операционные системы
Модуль os предоставляет ряд методов, связанных с операционной системой.
дорожка
Модуль path предоставляет утилиты для работы с файловыми и каталогами.
Punycode
Версия модуля punycode, входящего в состав Node.js, устарела .
Строка запроса
Модуль querystring предоставляет утилиты для синтаксического анализа и форматирования строк запроса URL.
Модуль readline предоставляет интерфейс для чтения данных из Readable stream (например, process.stdin ) по одной строке за раз.
РЕПЛ
Модуль repl предоставляет реализацию Read-Eval-Print-Loop (REPL), которая доступна как в виде отдельной программы, так и в других приложениях.
Поток представляет собой абстрактный интерфейс для работы с потоковыми данными в Node.js. Модуль stream предоставляет базовый API, который упрощает сбор объектов, реализующих интерфейс потока.
Существует множество объектов потока, предоставляемых Node.js. Например, запрос на HTTP-сервер и process.stdout являются экземплярами потока.
string_decoder
Модуль string_decoder предоставляет API для декодирования объектов Buffer в строки таким образом, который сохраняет кодированные многобайтовые символы UTF-8 и UTF-16.
таймеры
Модуль timer предоставляет глобальный API для функций планирования, которые будут вызываться в некоторый будущий период времени. Поскольку функции таймера являются глобальными, нет необходимости require('timers') использовать API.
Функции таймера в Node.js реализуют аналогичный API как API таймеров, предоставляемый веб-браузерами, но используют другую внутреннюю реализацию, которая построена вокруг цикла событий Node.js.
tls_ (SSL)
Модуль tls обеспечивает реализацию протоколов безопасности транспортного уровня (TLS) и протокола Secure Socket Layer (SSL), которые построены поверх OpenSSL.
трассировка
Trace Event предоставляет механизм для централизации информации трассировки, созданной V8, ядром узла и кодом пользовательского пространства.
Трассировку можно включить, передав —trace-events-enabled при запуске приложения Node.js.
TTY
Модуль tty предоставляет tty.ReadStream и tty.WriteStream . В большинстве случаев нет необходимости или возможно использовать этот модуль напрямую.
dgram
Модуль dgram обеспечивает реализацию сокетов UDP Datagram.
URL
Модуль url предоставляет утилиты для разрешения URL-адресов и анализа.
Util
Модуль util в первую очередь предназначен для поддержки собственных внутренних API-интерфейсов Node.js. Однако многие утилиты также полезны для разработчиков приложений и модулей.
v8
Модуль v8 предоставляет API-интерфейсы, специфичные для версии V8, встроенной в двоичный файл Node.js.
Примечание . API и реализация могут быть изменены в любое время.
В.М.
Модуль vm предоставляет API для компиляции и запуска кода в контекстах виртуальной машины V8. Код JavaScript может быть скомпилирован и запущен немедленно или скомпилирован, сохранен и запущен позже.
Примечание . Модуль vm не является механизмом безопасности. Не используйте его для запуска ненадежного кода .
Zlib
Модуль zlib обеспечивает функции сжатия, реализованные с использованием Gzip и Deflate / Inflate.
Как запустить базовый веб-сервер HTTPS!
После установки в вашей системе узла node.js вы можете просто выполнить описанную ниже процедуру, чтобы получить базовый веб-сервер с поддержкой как HTTP, так и HTTPS!
Шаг 1. Создание центра сертификации
создайте папку, в которой вы хотите сохранить свой ключ и сертификат:
перейдите в этот каталог:
возьмите этот файл ca.cnf для использования в качестве ярлыка конфигурации:
создайте новый центр сертификации, используя эту конфигурацию:
openssl req -new -x509 -days 9999 -config ca.cnf -keyout ca-key.pem -out ca-cert.pem
теперь, когда у нас есть наш ca-key.pem сертификации в ca-key.pem и ca-cert.pem , давайте сгенерируем закрытый ключ для сервера:
openssl genrsa -out key.pem 4096
захватите этот файл server.cnf для использования в качестве ярлыка конфигурации:
сгенерировать запрос подписи сертификата с использованием этой конфигурации:
openssl req -new -config server.cnf -key key.pem -out csr.pem
openssl x509 -req -extfile server.cnf -days 999 -passin "pass:password" -in csr.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem
Шаг 2. Установите сертификат как корневой сертификат.
скопируйте свой сертификат в папку корневых сертификатов:
sudo cp ca-crt.pem /usr/local/share/ca-certificates/ca-crt.pem
обновить магазин CA:
Шаг 3. Запуск сервера узла.
Во-первых, вы хотите создать файл server.js , содержащий ваш фактический код сервера.
Минимальная настройка для HTTPS-сервера в Node.js будет примерно такой:
Если вы также хотите поддерживать HTTP-запросы, вам нужно сделать только эту небольшую модификацию:
Выразительный JavaScript: Node.js
Ученик спросил: «Программисты встарь использовали только простые компьютеры и программировали без языков, но они делали прекрасные программы. Почему мы используем сложные компьютеры и языки программирования?». Фу-Тзу ответил: «Строители встарь использовали только палки и глину, но они делали прекрасные хижины».
Мастер Юан-Ма, «Книга программирования»
На текущий момент вы учили язык JavaScript и использовали его в единственном окружении: в браузере. В этой и следующей главе мы кратко представим вам Node.js, программу, которая позволяет применять навыки JavaScript вне браузера. С ней вы можете написать всё, от утилит командной строки до динамических HTTP серверов.
Эти главы посвящены обучению важным идеям, составляющим Node.js и предназначены для передачи вам достаточного количества информации, чтобы вы могли писать полезные программы в этой среде. Они не пытаются быть всеобъемлющими справочниками по Node.
Код из предыдущих глав вы могли писать и исполнять прямо в браузере, но код из этой главы написан для Node и в браузере работать не будет.
Если вы хотите сразу запускать код из этой главы, начните с установки Node с сайта nodejs.org для вашей операционки. Также на этом сайте вы найдёте документацию по Node и его встроенным модулям.
Вступление
Одна из наиболее сложных проблем при написании систем, общающихся по сети – обработка ввода и вывода. Чтение и запись данных в сеть и из сети, на диск, и другие устройства. Перемещение данных требует времени, и грамотное планирование этих действий может сильно повлиять на время отклика системы для пользователя или сетевых запросов.
В традиционном методе обработки ввода и вывода принято, что функция, к примеру, readFile, начинает читать файл и прекращает работать только когда файл полностью прочитан. Это называется синхронным вводом-выводом (synchronous I/O, input/output).
Node был задуман с целью облегчить и упростить использование асинхронного I/O. Мы уже встречались с асинхронными интерфейсами, такими, как объект браузера XMLHttpRequest, обсуждавшийся в главе 17. Такой интерфейс позволяет скрипту продолжать работу, пока интерфейс делает свою, и вызывает функцию обратного вызова по окончанию работы. Таким образом в Node работает весь I/O.
JavaScript легко вписывается в систему типа Node. Это один из немногих языков, в которые не встроена система I/O. Поэтому JavaScript легко встраивается в довольно эксцентричный подход к I/O в Node и в результате не порождает две разных системы ввода и вывода. В 2009 году при разработке Node люди уже использовали I/O в браузере, основанный на обратных вызовах, поэтому сообщество вокруг языка было привычно к асинхронному стилю программирования.
Асинхронность
Попробую проиллюстрировать разницу в синхронном и асинхронном подходах в I/O на небольшом примере, где программа должна получить два ресурса из интернета, и затем сделать что-то с данными.
В синхронном окружении очевидным способом решения задачи будет сделать запросы последовательно. У этого метода есть минус – второй запрос начнётся только после окончания первого. Общее время будет не меньше, чем сумма времени на обработку двух запросов. Это неэффективное использование компьютера, который большую часть времени будет простаивать, пока происходит передача данных по сети.
Решение проблемы в синхронной системе – запуск дополнительных потоков контроля исполнения программы (в главе 14 мы их уже обсуждали). Второй поток может запустить второй запрос, и затем оба потока будут ждать возврата результата, после чего они заново будут синхронизированы для сведения работы в один результат.
На диаграмме жирные линии обозначают время нормальной работы программы, а тонкие – время ожидания I/O. В синхронной модели время, затраченное на I/O, входит во временной график каждого из потоков. В асинхронной, запуск действия по I/O приводит к разветвлению временной линии. Поток, запустивший I/O, продолжает выполнение, а I/O выполняется параллельно ему, по окончанию работы делая обратный вызов функции.
Поток выполнения программы для синхронного и асинхронного I/O
Ещё один способ выразить эту разницу: в синхронной модели ожидание окончания I/O неявное, а в асинхронной – явное, и находится под нашим непосредственным контролем. Но асинхронность работает в обе стороны. С её помощью выражать программы, не работающие по принципу прямой линии, проще, но выражать прямолинейные программы становится сложнее.
В главе 17 я уже касался того факта, что обратные вызовы привносят кучу шума и делают программу менее упорядоченной. Является ли такой подход в общем хорошей идеей – спорный вопрос. В любом случае, требуется время, чтобы привыкнуть к нему.
Но для системы, основанной на JavaScript, я бы сказал, что использование асинхронности с обратными вызовами имеет смысл. Одна из сильных сторон JavaScript – простота, и попытки добавить в программу несколько потоков привели бы к сильному усложнению. Хотя обратные вызовы не делают код простым, их идея очень проста и в то же время достаточно сильна для того, чтобы писать высокопроизводительные веб-серверы.
Команда node
Когда в вашей системе установлен Node.js, у вас появляется программа под названием node, которая запускает файлы JavaScript. Допустим, у вас есть файл hello.js со следующим кодом:
Вы можете выполнить свою программу из командной строки:
Метод console.log в Node действует так же, как в браузере. Выводит кусок текста. Но в Node текст выводится на стандартный вывод, а не в консоль JavaScript в браузере.
Если запустить node без файла, он выдаст вам строку запроса, в которой можно писать код на JavaScript и получать результат.
Переменная process, так же как и console, доступна в Node глобально. Она обеспечивает несколько способов для инспектирования и манипулирования программой. Метод exit заканчивает процесс, и ему можно передать код статуса окончания программы, который сообщает программе, запустившей node (в данном случае, программной оболочке), завершилась ли программа удачно (нулевой код) или с ошибкой (любое другое число).
Для доступа к аргументам командной строки, переданным программе, можно читать массив строк process.argv. В него также включены имя команды node и имя вашего скрипта, поэтому список аргументов начинается с индекса 2. Если файл showargv.js содержит только инструкцию console.log(process.argv), его можно запустить так:
Все стандартные глобальные переменные JavaScript — Array, Math, JSON, также есть в окружении Node. Но там отсутствует функционал, связанный с работой браузера, например document или alert.
Объект глобальной области видимости, который в браузере называется window, в Node имеет более осмысленное название global.
Модули
Кроме нескольких упомянутых переменных, вроде console и process, Node держит мало функционала в глобальной области видимости. Для доступа к остальным встроенным возможностям вам надо обращаться к системе модулей.
Система CommonJS, основанная на функции require, была описана в главе 10. Такая система встроена в Node и используется для загрузки всего, от встроенных модулей и скачанных библиотек до файлов, являющихся частями вашей программы.
При вызове require Node нужно преобразовать заданную строку в имя файла. Пути, начинающиеся с «/», «./» или «../», преобразуются в пути относительно текущего. «./» означает текущую директорию, «../» – директорию выше, а «/» – корневую директорию файловой системы. Если вы запросите «./world/world» из файла /home/marijn/elife/run.js, Node попробует загрузить файл /home/marijn/elife/world/world.js. Расширение .js можно опускать.
Когда передаётся строка, которая не выглядит как относительный или абсолютный путь, то предполагается, что это либо встроенный модуль, или модуль, установленный в директории node_modules. К примеру, require(«fs») выдаст вам встроенный модуль для работы с файловой системой, а require(«elife») попробует загрузить библиотеку из node_modules/elife/. Типичный метод установки библиотек – при помощи NPM, к которому я вернусь позже.
Для демонстрации давайте сделаем простой проект из двух файлов. Первый назовём main.js, и в нём будет определён скрипт, вызываемый из командной строки, предназначенный для искажения строк.
Файл garble.js определяет библиотеку искажения строк, которая может использоваться как заданной ранее программой для командной строки, так и другими скриптами, которым нужен прямой доступ к функции garble.
Замена module.exports вместо добавления к нему свойств позволяет нам экспортировать определённое значение из модуля. В данном случае, результатом запроса нашего модуля получится сама функция искажения.
Функция разбивает строку на символы, используя split с пустой строкой, и затем заменяет все символы на другие, чьи коды на 5 единиц больше. Затем она соединяет результат обратно в строку.
Теперь мы можем вызвать наш инструмент:
Установка через NPM
NPM, вскользь упомянутый в главе 10, это онлайн-хранилище модулей JavaScript, многие из которых написаны специально для Node. Когда вы ставите Node на компьютер, вы получаете программу npm, которая даёт удобный интерфейс к этому хранилищу.
К примеру, один из модулей NPM зовётся figlet, и он преобразует текст в “ASCII art”, рисунки, составленные из текстовых символов. Вот как его установить:
После запуска npm install NPM создаст директорию node_modules. Внутри неё будет директория figlet, содержащая библиотеку. Когда мы запускаем node и вызываем require(«figlet»), библиотека загружается и мы можем вызвать её метод text, чтобы вывести большие красивые буквы.
Что интересно, вместо простого возврата строки, в которой содержатся большие буквы, figlet.text принимает функцию для обратного вызова, которой он передаёт результат. Также он передаёт туда ещё один аргумент, error, который в случае ошибки будет содержать объект error, а в случае успеха – null.
Такой принцип работы принят в Node. Для создания букв figlet должен прочесть файл с диска, содержащий буквы. Чтение файла – асинхронная операция в Node, поэтому figlet.text не может вернуть результат немедленно. Асинхронность заразительна – любая функция, вызывающая асинхронную, сама становится асинхронной.
NPM – это больше, чем просто npm install. Он читает файлы package.json, содержащие информацию в формате JSON про программу или библиотеку, в частности, от каких библиотек она зависит. Выполнение npm install в директории, содержащей такой файл, автоматически приводит к установке всех зависимостей, и в свою очередь их зависимостей. Также инструмент npm используется для размещения библиотек в онлайновом хранилище NPM, чтобы другие люди могли их находить, скачивать и использовать.
Больше мы не будем углубляться в детали использования NPM. Обращайтесь на npmjs.org за документацией и простым поиском библиотек.
Модуль file system
Один из самых востребованных встроенных модулей Node – модуль “fs”, что означает «файловая система». Модуль обеспечивает функционал для работы с файлами и директориями.
К примеру, есть функция readFile, читающая файл и делающая обратный вызов с содержимым файла.
Второй аргумент readFile задаёт кодировку символов, в которой нужно преобразовывать содержимое файла в строку. Текст можно преобразовать в двоичные данные разными способами, но самым новым из них является UTF-8. Если у вас нет оснований полагать, что в файле содержится текст в другой кодировке, можно смело передавать параметр «utf8». Если вы не задали кодировку, Node выдаст вам данные в двоичной кодировке в виде объекта Buffer, а не строки. Это массивоподобный объект, содержащий байты из файла.
Схожая функция, writeFile, используется для записи файла на диск.
Здесь задавать кодировку не нужно, потому что writeFile полагает, что если ей на запись дали строку, а не объект Buffer, то её надо выводить в виде текста с кодировкой по умолчанию UTF-8.
Модуль “fs” содержит много полезного: функция readdir возвращает список файлов директории в виде массива строк, stat вернёт информацию о файле, rename переименовывает файл, unlink удаляет, и т.п. См. документацию на nodejs.org.
Многие функции “fs” имеют как синхронный, так и асинхронный вариант. К примеру, есть синхронный вариант функции readFile под названием readFileSync.
Синхронные функции использовать проще и полезнее для простых скриптов, где дополнительная скорость асинхронного метода не важна. Но заметьте – на время выполнения синхронного действия ваша программа полностью останавливается. Если ей надо отвечать на ввод пользователя или другим программам по сети, затыки ожидания синхронного I/O приводят к раздражающим задержкам.
Модуль HTTP
Ещё один основной модуль — «http». Он даёт функционал для создания HTTP серверов и HTTP запросов.
Вот всё, что нужно для запуска простейшего HTTP сервера:
Запустив скрипт на своей машины, вы можете направить браузер по адресу localhost:8000/hello, таким образом создав запрос к серверу. Он ответит небольшой HTML-страницей.
Функция, передаваемая как аргумент к createServer, вызывается при каждой попытке соединения с сервером. Переменные request и response – объекты, представляющие входные и выходные данные. Первый содержит информацию по запросу, например свойство url содержит URL запроса.
Чтобы отправить что-то назад, используются методы объекта response. Первый, writeHead, пишет заголовки ответа (см. главу 17). Вы даёте ему код статуса (в этом случае 200 для “OK”) и объект, содержащий значения заголовков. Здесь мы сообщаем клиенту, что он должен ждать документ HTML.
Затем идёт тело ответа (сам документ), отправляемое через response.write. Этот метод можно вызывать несколько раз, если хотите отправлять ответ по кускам, к примеру, передавая потоковые данные по мере их поступления. Наконец, response.end сигнализирует конец ответа.
Вызов server.listen заставляет сервер слушать запросы на порту 8000. Поэтому вам надо в браузере заходить на localhost:8000, а не просто на localhost (где портом по умолчанию будет 80).
Для остановки такого скрипта Node, который не завершается автоматически, потому что ожидает следующих событий (в данном случае, соединений), надо нажать Ctrl-C.
Настоящий веб-сервер делает гораздо больше того, что описано в примере. Он смотрит на метод запроса (свойство method), чтобы понять, какое действие пытается выполнить клиент, и на URL запроса, чтобы понять, на каком ресурсе это действие должно выполняться. Далее вы увидите более продвинутую версию сервера.
Чтобы сделать HTTP-клиент, мы можем использовать функцию модуля “http” request.
Первый аргумент request настраивает запрос, объясняя Node, с каким сервером будем общаться, какой путь будет у запроса, какой метод использовать, и т.д. Второй – функция. которую надо будет вызвать по окончанию запроса. Ей передаётся объект response, в котором содержится вся информация по ответу – к примеру, код статуса.
Как и объект response сервера, объект, возвращаемый request, позволяет передавать данные методом write и заканчивать запрос методом end. В примере не используется write, потому что запросы GET не должны содержать данных в теле.
Для запросов на безопасные URL (HTTPS), Node предлагает модуль https, в котором есть своя функция запроса, схожая с http.request.
Потоки
Мы видели два примера потоков в примерах HTTP – объект response, в который сервер может вести запись, и объект request, который возвращается из http.request
Потоки с возможностью записи – популярная концепция в интерфейсах Node. У всех потоков есть метод write, которому можно передать строку или объект Buffer. Метод end закрывает поток, а при наличии аргумента, выведет перед закрытием кусочек данных. Обоим методам можно задать функцию обратного вызова через дополнительный аргумент, которую они вызовут по окончанию записи или закрытию потока.
Возможно создать поток, показывающий на файл, при помощи функции fs.createWriteStream. Затем можно использовать метод write для записи в файл по кусочкам, а не целиком, как в fs.writeFile.
Потоки с возможностью чтения будут чуть сложнее. Как переменная request, переданная функции для обратного вызова на HTTP-сервер, так и переменная response, переданная на HTTP-клиент, являются потоками с возможностью чтения. (Сервер читает запрос и потом пишет ответы, а клиент сперва пишет запрос и затем читает ответ). Чтение из потока осуществляется через обработчики событий, а не через методы.
У объектов, создающих события в Node, есть метод on, схожий с методом браузера addEventListener. Вы даёте ему имя события и функцию, и он регистрирует эту функцию, чтоб её вызвали сразу, когда произойдёт событие.
У потоков с возможностью чтения есть события «data» и «end». Первое происходит при поступлении данных, второе – по окончанию. Эта модель подходит к потоковым данным, которые можно сразу обработать, даже если получен не весь документ. Файл можно прочесть в виде потока через fs.createReadStream.
Следующий код создаёт сервер, читающий тела запросов и отправляющий их в ответ потоком в виде текста из заглавных букв.
Переменная chunk, передаваемая обработчику данных, будет бинарным Buffer, который можно преобразовать в строку, вызвав его метод toString, который декодирует его из кодировки по умолчанию (UTF-8).
Следующий код, будучи запущенным одновременно с сервером, отправит запрос на сервер и выведет полученный ответ:
Пример пишет в process.stdout (стандартный вывод процесса, являющийся потоком с возможностью записи), а не в console.log. Мы не можем использовать console.log, так как он добавляет лишний перевод строки после каждого куска кода – это здесь не нужно.
Простой файловый сервер
Давайте скомбинируем наши новые знания о серверах HTTP и работе с файловой системой, и наведём мостик между ними: HTTP-сервер, предоставляющий удалённый доступ к файлам. У такого сервера много вариантов использования. Он позволяет веб-приложениям хранить и делиться данными, или может дать группе людей доступ к набору файлов.
Когда мы относимся к файлам, как к ресурсам HTTP, методы GET, PUT и DELETE можно использовать для чтения, записи и удаления файлов. Мы будем интерпретировать путь в запросе как путь к файлу.
Нам не надо открывать доступ ко всей файловой системе, поэтому мы будем интерпретировать эти пути как заданные относительно корневого каталога, и это будет каталог запуска скрипта. Если я запущу сервер из /home/marijn/public/ (или C:\Users\marijn\public\ на Windows), то запрос на /file.txt должен указать на /home/marijn/public/file.txt (или C:\Users\marijn\public\file.txt).
Программу мы будем строить постепенно, используя объект methods для хранения функций, обрабатывающих разные методы HTTP.
Этот код запустит сервер, возвращающий ошибки 405 – этот код используется для обозначения того, что запрошенный метод сервером не поддерживается.
Функция respond передаётся функциям, обрабатывающим разные методы, и работает как обратный вызов для окончания запроса. Она принимает код статуса HTTP, тело, и, возможно, тип содержимого. Если переданное тело – поток с возможностью чтения, у него будет метод pipe, который используется для передачи читаемого потока в записываемый. Если нет – предполагается, что это либо null (тело пустое), или строка, и тогда она передаётся напрямую в метод ответа end.
Чтобы получить путь из URL в запросе, функция urlToPath, используя встроенный модуль Node “url”, разбирает URL. Она принимает имя пути, нечто вроде /file.txt, декодирует, чтобы убрать экранирующие коды %20, и вставляет в начале точку, чтобы получить путь относительно текущего каталога.
Вам кажется, что функция urlToPath небезопасна? Вы правы. Вернёмся к этому вопросу в упражнениях.
Мы устроим метод GET так, чтобы он возвращал список файлов при чтении директории, и содержимое файла при чтении файла.
Вопрос на засыпку – какой тип заголовка Content-Type мы должны возвращать, читая файл. Поскольку в файле может быть всё, что угодно, сервер не может просто вернуть один и тот же тип для всех. Но NPM с этим может помочь. Модуль mime (индикаторы типа содержимого файла вроде text/plain также называются MIME types) знает правильный тип для огромного количества расширений файлов.
Запустив следующую команду npm в директории, где живёт скрипт сервера, вы сможете использовать require(«mime») для запросов к библиотеке типов.
Когда запрошенного файла не существует, правильным кодом ошибки для этого случая будет 404. Мы будем использовать fs.stat для возврата информации по файлу, чтобы выяснить, есть ли такой файл, и не директория ли это.
Поскольку запросы к диску занимают время, fs.stat работает асинхронно. Когда файла не существует, fs.stat передаст объект error со значением «ENOENT» свойства code в функцию обратного вызова. Было бы здорово, если бы Node определил разные типы ошибок для разных ошибок, но такого нет. Вместо этого он выдаёт запутанные коды в стиле Unix.
Все неожиданные ошибки мы будем выдавать с кодом 500, обозначающим, что на сервере проблема – в отличие от кодов, начинающихся на 4, говорящих о проблеме с запросом. В некоторых ситуациях это будет не совсем аккуратно, но для небольшой примерной программы этого будет достаточно.
Объект stats возвращаемый fs.stat, рассказывает нам о файле всё. Например, size – размер файла, mtime – дата модификации. Здесь нам нужно узнать, директория это или обычный файл – это нам сообщит метод isDirectory.
Для чтения списка файлов в директории мы используем fs.readdir, и через ещё один обратный вызов, возвращаем его пользователю. Для обычных файлов мы создаём читаемый поток через fs.createReadStream и передаём его в ответ, вместе с типом содержимого, который модуль “mime” выдал для этого файла.
Код обработки DELETE будет проще:
Возможно, вам интересно, почему попытка удаления несуществующего файла возвращает статус 204 вместо ошибки. Можно сказать, что при попытке удалить несуществующий файл, так как файла там уже нет, то запрос уже исполнен. Стандарт HTTP поощряет людей делать идемпотентные запросы – то есть такие, при которых многократный повтор одного и того же действия не приводит к разным результатам.
Когда ответ HTTP не содержит данных, можно использовать код статуса 204 (“no content”). Так как нам нужно обеспечить функции обратного вызова, которые либо сообщают об ошибке, или возвращают ответ 204 в разных ситуациях, я написал специальную функцию respondErrorOrNothing, которая создаёт такой обратный вызов.
Вот обработчик запросов PUT:
Здесь нам не нужно проверять существование файла – если он есть, мы его просто перезапишем. Опять мы используем pipe для передачи данных из читаемого потока в записываемый, в нашем случае – из запроса в файл. Если создать поток не удаётся, создаётся событие “error”, о чём мы сообщаем в ответе. Когда данные переданы успешно, pipe закроет оба потока, что приведёт к запуску события “finish”. А после этого мы можем отчитаться об успехе с кодом 204.
Полный скрипт сервера доступен на сайте: eloquentjavascript.net/code/file_server.js. Его можно скачать и запустить через Node для запуска собственного файлового сервера. Конечно, его можно менять и дополнять для решения упражнений или экспериментов.
Утилита командной строки curl, общедоступная на unix-системах, может использоваться для создания HTTP запросов. Следующий фрагмент тестирует наш сервер. Опция –X используется для задания метода запроса, а –d для включения тела запроса.
Первый запрос к file.txt завершается с ошибкой, поскольку файла ещё нет. Запрос PUT создаёт файл, и глядите-ка – следующий запрос его успешно получает. После его удаления через DELETE файл снова отсутствует.
Обработка ошибок
В коде файлового сервера есть шесть мест, где мы перенаправляем исключения, когда мы не знаем, как обрабатывать ошибки. Поскольку исключения не передаются автоматически в функции обратного вызова, но передаются им как аргументы, их надо каждый раз обрабатывать персонально. Это сводит на нет преимущество обработки исключений, а именно, возможность централизованно обрабатывать ошибки.
Что будет, когда что-то реально выбросит исключение в системе? Мы не используем блоки try, потому оно будет передано на самый верх стека вызовов. В Node это приводит к прекращению выполнения программы и выводу информации об исключении (вместе с отслеживанием стека) на стандартный вывод.
Поэтому наш сервер будет падать при возникновении проблем в коде – в отличие от проблем с асинхронностью, которые будут переданы как аргументы в функции вызова. Если нам надо обрабатывать все исключения, возникающие при обработке запроса, чтобы мы точно отправили ответ, нам надо добавлять блоки try/catch в каждом обратном вызове.
Это плохо. Много программ для Node написаны так, чтобы использовать как можно меньше работы с исключениями, подразумевая что в случае возникновения исключения программа не может его обработать, и поэтому надо падать.
Ещё один подход – использование обещаний, которые были описаны в главе 17. Они ловят исключения, выброшенные функциями обратного вызова и передают их как ошибки. В Node можно загрузить библиотеку promise и использовать её для обработки асинхронных вызовов. Немногие библиотеки Node интегрируют обещания, но обычно их довольно просто обернуть. Отличный модуль “promise” с NPM содержит функцию denodeify, которая берёт асинхронную функцию вроде fs.readFile и преобразовывает её в функцию, возвращающую обещание.
Для сравнения, я написал ещё одну версию файлового сервера с использованием обещаний, которую можно найти на eloquentjavascript.net/code/file_server_promises.js. Она почище, потому что функции теперь могут возвращать результаты, а не назначать обратные вызовы, и исключения передаются неявно.
Приведу несколько строк оттуда, чтобы продемонстрировать разницу в стилях.
Объект fsp, использующийся в коде, содержит варианты функций fs с обещаниями, обёрнутыми при помощи Promise.denodeify. Возвращаемый из обработчика метода объект, со свойствами code и body, становится окончательным результатом цепочки обещаний, и он используется для определения того, какой ответ отправить клиенту.
Функция inspectPath – простая обёртка вокруг fs.stat, обрабатывающая случай, когда файл не найден. В этом случае мы заменяем ошибку на успех, возвращающий null. Все остальные ошибки можно передавать. Когда обещание, возвращаемое из этих обработчиков, обламывается, сервер отвечает кодом 500.
Node – отличная простая система, позволяющая запускать JavaScript вне браузера. Изначально она разрабатывалась для работы по сети, чтобы играть роль узла в сети. Но она позволяет делать много всего, и если вы наслаждаетесь программированием на JavaScript, автоматизация ежедневных задач с Node работает отлично.
NPM предоставляет библиотеки для всего, что вам может прийти в голову (и даже для кое-чего, что вам не придёт в голову), и она позволяет скачивать и устанавливать их простой командой. Node также поставляется с набором встроенных модулей, включая “fs” для работы с файловой системой, и “http” для запуска HTTP серверов и создания HTTP запросов.
Весь ввод и вывод в Node делается асинхронно, если только вы не используете явно синхронный вариант функции, например fs.readFileSync. Вы предоставляете функции обратного вызова, а Node их вызывает в нужное время, когда операции I/O заканчивают работу.
Упражнения
И снова согласование содержания
В главе 17 первое упражнение было посвящено созданию запросов к eloquentjavascript.net/author, спрашивавших разные типы содержимого путём передачи разных заголовков Accept.
Сделайте это снова, используя функцию Node http.request. Запросите, по крайней мере, типы text/plain, text/html и application/json. Помните, что заголовки запроса можно передавать как объект в свойстве headers, первым аргументом http.request.
Выведите содержимое каждого ответа.
Устранение утечек
Для упрощения доступа к файлам я оставил работать сервер у себя на комьпютере, в директории /home/marijn/public. Однажды я обнаружил, что кто-то получил доступ ко всем моим паролям, которые я хранил в браузере. Что случилось?
Если вам это непонятно, вспомните функцию urlToPath, которая определялась так:
Теперь вспомните, что пути, передаваемые в функцию “fs”, могут быть относительными. Они могут содержать путь “../” в верхний каталог. Что будет, если клиент отправит запросы на URL вроде следующих:
Поменяйте функцию urlToPath для устранения подобной проблемы. Примите во внимание, что на Windows Node разрешает как прямые так и обратные слеши для задания путей.
Кроме этого, помедитируйте над тем фактом, что как только вы выставляете сырую систему в интернет, ошибки в системе могут быть использованы против вас и вашего компьютера.
Создание директорий
Хотя метод DELETE работает и при удалении директорий (через fs.rmdir), пока сервер не предоставляет возможности создания директорий.
Добавьте поддержку метода MKCOL, который должен создавать директорию через fs.mkdir. MKCOL не является основным методом HTTP, но он существует, именно для этого, в стандарте WebDAV, который содержит расширения HTTP, чтобы использовать его для записи ресурсов, а не только для их чтения.
Общественное место в сети
Так как файловый сервер выдаёт любые файлы и даже возвращает правильный заголовок Content-Type, его можно использовать для обслуживания веб-сайта. Так как он разрешает всем удалять и заменять файлы, это был бы интересный сайт – который можно изменять, портить и удалять всем, кто может создать правильный HTTP-запрос. Но это всё равно был бы веб-сайт.
Напишите простую HTML страницу с простым файлом JavaScript. Разместите их в директории, обслуживаемой сервером и откройте в браузере.
Затем, в качестве продвинутого упражнения, скомбинируйте все полученные знания из книги, чтобы построить более дружественный интерфейс для модификации веб-сайта изнутри самого сайта.
Используйте форму HTML (глава 18) для редактирования файлов, составляющих сайт, позволяя пользователю обновлять их на сервере через HTTP-запросы, как описано в главе 17.
Начните с одного файла, редактирование которого разрешено. Затем сделайте так, чтобы можно было выбирать файл для редактирования. Используйте тот факт, что наш файловый сервер возвращает списки файлов по запросу директории.
Не меняйте файлы непосредственно в коде файлового сервера – если вы сделаете ошибку, вы скорее всего испортите те файлы. Работайте в директории, недоступной снаружи, и копируйте их туда после тестирования.