Mercurial > hg > nginx-site
diff xml/ru/docs/njs/node_modules.xml @ 2577:67fd664e2612
Translated "Using node modules with njs" into Russian.
author | Yaroslav Zhuravlev <yar@nginx.com> |
---|---|
date | Thu, 06 Aug 2020 14:49:00 +0100 |
parents | xml/en/docs/njs/node_modules.xml@4c8d0b37932d |
children | fca42223b9fc |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xml/ru/docs/njs/node_modules.xml Thu Aug 06 14:49:00 2020 +0100 @@ -0,0 +1,545 @@ +<?xml version="1.0"?> + +<!-- + Copyright (C) Nginx, Inc. + --> + +<!DOCTYPE article SYSTEM "../../../../dtd/article.dtd"> + +<article name="Использование модулей Node.js в njs" + link="/ru/docs/njs/node_modules.html" + lang="en" + rev="4"> + +<section id="intro"> + +<para> +Часто разработчику приходится использовать сторонний код и, +как правило, такой код доступен в виде библиотеки. +В JavaScript концепция модулей является новой и +до недавнего времени не была стандартизированa. +До сих пор множество платформ или браузеров не поддерживают модули, +по этой причине практически невозможно повторно использовать код. +В данной статье приводятся способы повторного использования +кода в njs при помощи <link url="https://nodejs.org/">Node.js</link>. +</para> + +<note> +В примерах статьи используется функциональность +<link doc="index.xml">njs</link> +<link doc="changes.xml" id="njs0.3.8">0.3.8</link> +</note> + +<para> +При добавлении стороннего кода в njs +может возникнуть несколько проблем: + +<list type="bullet"> + +<listitem> +большое количество файлов, ссылающихся друг на друга, и их зависимости +</listitem> + +<listitem> +платформозависимые API +</listitem> + +<listitem> +языковые конструкции нового стандарта +</listitem> + +</list> +</para> + +<para> +Однако это не является чем-то новым или специфичным для njs. +Разработчикам JavaScript приходится часто иметь дело с подобными случаями, +например при поддержке нескольких несхожих платформ +с разными свойствами. +Данные проблемы можно разрешить при помощи следующих инструментов: + +<list type="bullet"> + +<listitem> +Большое количество файлов, ссылающихся друг на друга, и их зависимости +<para> +Решение: слияние всего независимого кода в один файл. +Для этих целей могут использоваться утилиты +<link url="http://browserify.org/">browserify</link> или +<link url="https://webpack.js.org/">webpack</link>, +позволяющие преобразовать проект в один файл, содержащий +код и все зависимости. +</para> +</listitem> + +<listitem> +Платформозависимые API +<para> +Решение: использование библиотек, реализующих подобные API +в платформонезависимом режиме, однако в ущерб производительности. +Определённая функциональность может быть также реализована при помощи +<link url="https://polyfill.io/v3/">polyfill</link>. +</para> +</listitem> + +<listitem> +Языковые конструкции нового стандарта +<para> +Решение: трансплирование кода— +ряд преобразований, +заменяющих новые функции языка в соответствии со старым стандартом. +Для этих целей может использоваться +<link url="https://babeljs.io/"> babel</link>. +</para> +</listitem> + +</list> +</para> + +<para> +В статье также используются две относительно большие +библиотеки на основе npm: + +<list type="bullet"> + +<listitem> +<link url="https://www.npmjs.com/package/protobufjs">protobufjs</link>— +библиотека для создания и парсинга protobuf-сообщений, используемая +протоколом <link url="https://grpc.io/">gRPC</link> +</listitem> + +<listitem> +<link url="https://www.npmjs.com/package/dns-packet">dns-packet</link>— +библиотека для обработки пакетов протокола DNS +</listitem> + +</list> +</para> + +</section> + + +<section id="environment" name="Окружение"> + +<para> +<note> +В статье описываются общие принципы работы +и не ставится цель описания подробных сценариев работы с Node.js +и JavaScript. +Перед выполнением команд +необходимо ознакомиться с документацией соответствующих пакетов. +</note> +Сначала, предварительно установив и запустив Node.js, необходимо создать +пустой проект и установить зависимости; +для выполнения нижеперечисленных команд необходимо +находиться в рабочем каталоге: +<example> +$ mkdir my_project && cd my_project +$ npx license choose_your_license_here > LICENSE +$ npx gitignore node + +$ cat > package.json <<EOF +{ + "name": "foobar", + "version": "0.0.1", + "description": "", + "main": "index.js", + "keywords": [], + "author": "somename <some.email@example.com> (https://example.com)", + "license": "some_license_here", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} +EOF +$ npm init -y +$ npm install browserify +</example> +</para> + +</section> + + +<section id="protobuf" name="Protobufjs"> + +<para> +Библиотека предоставляет парсер +для определения интерфейса <literal>.proto</literal>, +а также генератор кода для парсинга и генерации сообщений. +</para> + +<para> +В данном примере используется +файл +<link url="https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto">helloworld.proto</link> +из примеров gRPC. +Целью является создание двух сообщений: +<literal>HelloRequest</literal> и +<literal>HelloResponse</literal>. +Также используется +<link url="https://github.com/protobufjs/protobuf.js/blob/master/README.md#reflection-vs-static-code">статический</link> +режим protobufjs вместо динамически генерируемых классов, так как +njs не поддерживает динамическое добавление новых функций +из соображений безопасности. +</para> + +<para> +Затем устанавливается библиотека, +из определения протокола генерируется код JavaScript, +реализующий маршалинг сообщений: +<example> +$ npm install protobufjs +$ npx pbjs -t static-module helloworld.proto > static.js +</example> +</para> + +<para> +Таким образом файл <literal>static.js</literal> становится новой зависимостью, +хранящей необходимый код для реализации обработки сообщений. +Функция <literal>set_buffer()</literal> содержит код, использующий +библиотеку для создания буфера с сериализованным +сообщением <literal>HelloRequest</literal>. +Код находится в файле <literal>code.js</literal>: +<example> +var pb = require('./static.js'); + +// Пример использования библиотеки protobuf: подготовка буфера к отправке +function set_buffer(pb) +{ + // назначение полей gRPC payload + var payload = { name: "TestString" }; + + // создание объекта + var message = pb.helloworld.HelloRequest.create(payload); + + // сериализация объекта в буфер + var buffer = pb.helloworld.HelloRequest.encode(message).finish(); + + var n = buffer.length; + + var frame = new Uint8Array(5 + buffer.length); + + frame[0] = 0; // флаг 'compressed' + frame[1] = (n & 0xFF000000) >>> 24; // длина: uint32 в сетевом порядке байт + frame[2] = (n & 0x00FF0000) >>> 16; + frame[3] = (n & 0x0000FF00) >>> 8; + frame[4] = (n & 0x000000FF) >>> 0; + + frame.set(buffer, 5); + + return frame; +} + +var frame = set_buffer(pb); +</example> +</para> + +<para> +Для проверки работоспособности необходимо выполнить код при помощи node: +<example> +$ node ./code.js +Uint8Array [ + 0, 0, 0, 0, 12, 10, + 10, 84, 101, 115, 116, 83, + 116, 114, 105, 110, 103 +] +</example> +Результатом является закодированный фрейм <literal>gRPC</literal>. +Теперь фрейм можно запустить с njs: +<example> +$ njs ./code.js +Thrown: +Error: Cannot find module "./static.js" + at require (native) + at main (native) +</example> +</para> + +<para> +Так как модули не поддерживаются, то операция завершается получением исключения. +В этом случае можно использовать утилиту <literal>browserify</literal> +или другую подобную утилиту. +</para> + +<para> +Попытка обработки файла <literal>code.js</literal> завершится +большим количеством JS-кода, который предполагается запускать в браузере, +то есть сразу после загрузки. +Однако необходимо получить другой результат— +экспортируемую функцию, на которую +можно сослаться из конфигурации nginx. +Для этого потребуется создание кода-обёртки. +<note> +В целях упрощения в примерах данной статьи +используется <link doc="cli.xml">интерфейс комадной строки</link> njs. +На практике для запуска кода обычно используется njs-модуль для nginx. +</note> +</para> + +<para> +Файл <literal>load.js</literal> содержит код, загружающий библиотеку, +храняющую дескриптор в глобальном пространстве имён: +<example> +global.hello = require('./static.js'); +</example> +Данный код будет заменён объединённым содержимым. +Код будет использовать дескриптор "<literal>global.hello</literal>" для доступа +к библиотеке. +</para> + +<para> +Затем для получения всех зависимостей в один файл +код обрабатыается утилитой <literal>browserify</literal>: +<example> +$ npx browserify load.js -o bundle.js -d +</example> +В результате генерируется объёмный файл, содержащий все зависимости: +<example> +(function(){function...... +... +... +},{"protobufjs/minimal":9}]},{},[1]) +//# sourceMappingURL.............. +</example> +Для получения результирующего файла "<literal>njs_bundle.js</literal>" +необходимо объединить "<literal>bundle.js</literal>" и следующий код: +<example> +// Пример использования библиотеки protobuf: подготовка буфера к отправке +function set_buffer(pb) +{ + // назначение полей gRPC payload + var payload = { name: "TestString" }; + + // создание объекта + var message = pb.helloworld.HelloRequest.create(payload); + + // сериализация объекта в буфер + var buffer = pb.helloworld.HelloRequest.encode(message).finish(); + + var n = buffer.length; + + var frame = new Uint8Array(5 + buffer.length); + + frame[0] = 0; // флаг 'compressed' + frame[1] = (n & 0xFF000000) >>> 24; // длина: uint32 в сетевом порядке байт + frame[2] = (n & 0x00FF0000) >>> 16; + frame[3] = (n & 0x0000FF00) >>> 8; + frame[4] = (n & 0x000000FF) >>> 0; + + frame.set(buffer, 5); + + return frame; +} + +// функции, вызываемые снаружи +function setbuf() +{ + return set_buffer(global.hello); +} + +// вызов кода +var frame = setbuf(); +console.log(frame); +</example> +Для проверки работоспособности необходимо запустить файл при помощи node: +<example> +$ node ./njs_bundle.js +Uint8Array [ + 0, 0, 0, 0, 12, 10, + 10, 84, 101, 115, 116, 83, + 116, 114, 105, 110, 103 +] +</example> +Дальнейшие шаги выполняются при помощи njs: +<example> +$ /njs ./njs_bundle.js +Uint8Array [0,0,0,0,12,10,10,84,101,115,116,83,116,114,105,110,103] +</example> +Теперь необходимо задействовать njs API для преобразования +массива в байтовую строку для дальнейшего использования модулем nginx. +Данный код необходимо добавить перед последней строкой: +<example> +if (global.njs) { + return String.bytesFrom(frame) +} +</example> +Проверка работоспособности: +<example> +$ njs ./njs_bundle.js |hexdump -C +00000000 00 00 00 00 0c 0a 0a 54 65 73 74 53 74 72 69 6e |.......TestStrin| +00000010 67 0a |g.| +00000012 +</example> +Экспортируемая функция получена. +Парсинг ответа может быть сделан аналогичным способом: +<example> +function parse_msg(pb, msg) +{ + // преобразование байтовой строки в массив целых чисел + var bytes = msg.split('').map(v=>v.charCodeAt(0)); + + if (bytes.length < 5) { + throw 'message too short'; + } + + // первые 5 байт являются фреймом gRPC (сжатие + длина) + var head = bytes.splice(0, 5); + + // проверка правильной длины сообщения + var len = (head[1] << 24) + + (head[2] << 16) + + (head[3] << 8) + + head[4]; + + if (len != bytes.length) { + throw 'header length mismatch'; + } + + // вызов protobufjs для декодирования сообщения + var response = pb.helloworld.HelloReply.decode(bytes); + + console.log('Reply is:' + response.message); +} +</example> +</para> + +</section> + + +<section id="dnspacket" name="Пакет DNS"> + +<para> +В примере используется библиотека для создания и парсинга пакетов DNS. +Эта библиотека, а также её зависимости, +использует современные языковые конструкции, не поддерживаемые в njs. +Для поддержки таких конструкций +потребуется дополнительный шаг: транспилирование исходного кода. +</para> + +<para> +Необходимо установить дополнительные пакеты node: +<example> +$ npm install @babel/core @babel/cli @babel/preset-env babel-loader +$ npm install webpack webpack-cli +$ npm install buffer +$ npm install dns-packet +</example> +Файл конфигурации webpack.config.js: +<example> +const path = require('path'); + +module.exports = { + entry: './load.js', + mode: 'production', + output: { + filename: 'wp_out.js', + path: path.resolve(__dirname, 'dist'), + }, + optimization: { + minimize: false + }, + node: { + global: true, + }, + module : { + rules: [{ + test: /\.m?js$$/, + exclude: /(bower_components)/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'] + } + } + }] + } +}; +</example> +В данном случае используется режим "<literal>production</literal>". +Конструкция "<literal>eval</literal>" не используется, так как +не поддерживается njs. +Точкой входа является файл <literal>load.js</literal>: +<example> +global.dns = require('dns-packet') +global.Buffer = require('buffer/').Buffer +</example> +Сначала необходимо создать единый файл для библиотек, как в предыдущих примерах: +<example> +$ npx browserify load.js -o bundle.js -d +</example> +Затем необходимо обработать утилитой webpack, что также запускает babel: +<example> +$ npx webpack --config webpack.config.js +</example> +Команда создаёт файл <literal>dist/wp_out.js</literal>, являющийся +трансплицированной версией <literal>bundle.js</literal>. +Далее необходимо объединить этот файл с <literal>code.js</literal>, +хранящим код: +<example> +function set_buffer(dnsPacket) +{ + // create DNS packet bytes + var buf = dnsPacket.encode({ + type: 'query', + id: 1, + flags: dnsPacket.RECURSION_DESIRED, + questions: [{ + type: 'A', + name: 'google.com' + }] + }) + + return buf; +} +</example> +В данном примере генерируемый код не обёрнут в функцию, +явного вызова не требуется. +Результат доступен в каталоге "<literal>dist</literal>": +<example> +$ cat dist/wp_out.js code.js > njs_dns_bundle.js +</example> +Далее осуществляется вызов кода в конце файла: +<example> +var b = setbuf(1); +console.log(b); +</example> +И затем выполнение кода при помощи node: +<example> +$ node ./njs_dns_bundle_final.js +Buffer [Uint8Array] [ + 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 6, 103, 111, 111, + 103, 108, 101, 3, 99, 111, 109, 0, + 0, 1, 0, 1 +] +</example> +Тестирование и запуск кода вместе с njs: +<example> +$ njs ./njs_dns_bundle_final.js +Uint8Array [0,1,1,0,0,1,0,0,0,0,0,0,6,103,111,111,103,108,101,3,99,111,109,0,0,1,0,1] +</example> +</para> + +<para> +Ответ можно распарсить следующим способом: +<example> +function parse_response(buf) +{ + var bytes = buf.split('').map(v=>v.charCodeAt(0)); + + var b = global.Buffer.from(bytes); + + var packet = dnsPacket.decode(b); + + var resolved_name = packet.answers[0].name; + + // ожидаемое имя 'google.com', согласно запросу выше +} +</example> + +</para> + +</section> + +</article>