# HG changeset patch # User Yaroslav Zhuravlev # Date 1596721740 -3600 # Node ID 67fd664e26127a18653b0cf545642b4af0a0653d # Parent 4c8d0b37932dadde7ed16898fa9ce29ba69cb7ce Translated "Using node modules with njs" into Russian. diff -r 4c8d0b37932d -r 67fd664e2612 xml/ru/GNUmakefile --- a/xml/ru/GNUmakefile Thu Aug 06 14:46:58 2020 +0100 +++ b/xml/ru/GNUmakefile Thu Aug 06 14:49:00 2020 +0100 @@ -112,6 +112,7 @@ njs/examples \ njs/install \ njs/reference \ + njs/node_modules \ njs/typescript \ TOP = \ diff -r 4c8d0b37932d -r 67fd664e2612 xml/ru/docs/njs/index.xml --- a/xml/ru/docs/njs/index.xml Thu Aug 06 14:46:58 2020 +0100 +++ b/xml/ru/docs/njs/index.xml Thu Aug 06 14:49:00 2020 +0100 @@ -89,7 +89,7 @@ -Использование модулей Node.js в njs + diff -r 4c8d0b37932d -r 67fd664e2612 xml/ru/docs/njs/node_modules.xml --- /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 @@ + + + + + + +
+ +
+ + +Часто разработчику приходится использовать сторонний код и, +как правило, такой код доступен в виде библиотеки. +В JavaScript концепция модулей является новой и +до недавнего времени не была стандартизированa. +До сих пор множество платформ или браузеров не поддерживают модули, +по этой причине практически невозможно повторно использовать код. +В данной статье приводятся способы повторного использования +кода в njs при помощи Node.js. + + + +В примерах статьи используется функциональность +njs +0.3.8 + + + +При добавлении стороннего кода в njs +может возникнуть несколько проблем: + + + + +большое количество файлов, ссылающихся друг на друга, и их зависимости + + + +платформозависимые API + + + +языковые конструкции нового стандарта + + + + + + +Однако это не является чем-то новым или специфичным для njs. +Разработчикам JavaScript приходится часто иметь дело с подобными случаями, +например при поддержке нескольких несхожих платформ +с разными свойствами. +Данные проблемы можно разрешить при помощи следующих инструментов: + + + + +Большое количество файлов, ссылающихся друг на друга, и их зависимости + +Решение: слияние всего независимого кода в один файл. +Для этих целей могут использоваться утилиты +browserify или +webpack, +позволяющие преобразовать проект в один файл, содержащий +код и все зависимости. + + + + +Платформозависимые API + +Решение: использование библиотек, реализующих подобные API +в платформонезависимом режиме, однако в ущерб производительности. +Определённая функциональность может быть также реализована при помощи +polyfill. + + + + +Языковые конструкции нового стандарта + +Решение: трансплирование кода— +ряд преобразований, +заменяющих новые функции языка в соответствии со старым стандартом. +Для этих целей может использоваться + babel. + + + + + + + +В статье также используются две относительно большие +библиотеки на основе npm: + + + + +protobufjs— +библиотека для создания и парсинга protobuf-сообщений, используемая +протоколом gRPC + + + +dns-packet— +библиотека для обработки пакетов протокола DNS + + + + + +
+ + +
+ + + +В статье описываются общие принципы работы +и не ставится цель описания подробных сценариев работы с Node.js +и JavaScript. +Перед выполнением команд +необходимо ознакомиться с документацией соответствующих пакетов. + +Сначала, предварительно установив и запустив Node.js, необходимо создать +пустой проект и установить зависимости; +для выполнения нижеперечисленных команд необходимо +находиться в рабочем каталоге: + +$ 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 + + + +
+ + +
+ + +Библиотека предоставляет парсер +для определения интерфейса .proto, +а также генератор кода для парсинга и генерации сообщений. + + + +В данном примере используется +файл +helloworld.proto +из примеров gRPC. +Целью является создание двух сообщений: +HelloRequest и +HelloResponse. +Также используется +статический +режим protobufjs вместо динамически генерируемых классов, так как +njs не поддерживает динамическое добавление новых функций +из соображений безопасности. + + + +Затем устанавливается библиотека, +из определения протокола генерируется код JavaScript, +реализующий маршалинг сообщений: + +$ npm install protobufjs +$ npx pbjs -t static-module helloworld.proto > static.js + + + + +Таким образом файл static.js становится новой зависимостью, +хранящей необходимый код для реализации обработки сообщений. +Функция set_buffer() содержит код, использующий +библиотеку для создания буфера с сериализованным +сообщением HelloRequest. +Код находится в файле code.js: + +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); + + + + +Для проверки работоспособности необходимо выполнить код при помощи node: + +$ node ./code.js +Uint8Array [ + 0, 0, 0, 0, 12, 10, + 10, 84, 101, 115, 116, 83, + 116, 114, 105, 110, 103 +] + +Результатом является закодированный фрейм gRPC. +Теперь фрейм можно запустить с njs: + +$ njs ./code.js +Thrown: +Error: Cannot find module "./static.js" + at require (native) + at main (native) + + + + +Так как модули не поддерживаются, то операция завершается получением исключения. +В этом случае можно использовать утилиту browserify +или другую подобную утилиту. + + + +Попытка обработки файла code.js завершится +большим количеством JS-кода, который предполагается запускать в браузере, +то есть сразу после загрузки. +Однако необходимо получить другой результат— +экспортируемую функцию, на которую +можно сослаться из конфигурации nginx. +Для этого потребуется создание кода-обёртки. + +В целях упрощения в примерах данной статьи +используется интерфейс комадной строки njs. +На практике для запуска кода обычно используется njs-модуль для nginx. + + + + +Файл load.js содержит код, загружающий библиотеку, +храняющую дескриптор в глобальном пространстве имён: + +global.hello = require('./static.js'); + +Данный код будет заменён объединённым содержимым. +Код будет использовать дескриптор "global.hello" для доступа +к библиотеке. + + + +Затем для получения всех зависимостей в один файл +код обрабатыается утилитой browserify: + +$ npx browserify load.js -o bundle.js -d + +В результате генерируется объёмный файл, содержащий все зависимости: + +(function(){function...... +... +... +},{"protobufjs/minimal":9}]},{},[1]) +//# sourceMappingURL.............. + +Для получения результирующего файла "njs_bundle.js" +необходимо объединить "bundle.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; +} + +// функции, вызываемые снаружи +function setbuf() +{ + return set_buffer(global.hello); +} + +// вызов кода +var frame = setbuf(); +console.log(frame); + +Для проверки работоспособности необходимо запустить файл при помощи node: + +$ node ./njs_bundle.js +Uint8Array [ + 0, 0, 0, 0, 12, 10, + 10, 84, 101, 115, 116, 83, + 116, 114, 105, 110, 103 +] + +Дальнейшие шаги выполняются при помощи njs: + +$ /njs ./njs_bundle.js +Uint8Array [0,0,0,0,12,10,10,84,101,115,116,83,116,114,105,110,103] + +Теперь необходимо задействовать njs API для преобразования +массива в байтовую строку для дальнейшего использования модулем nginx. +Данный код необходимо добавить перед последней строкой: + +if (global.njs) { + return String.bytesFrom(frame) +} + +Проверка работоспособности: + +$ 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 + +Экспортируемая функция получена. +Парсинг ответа может быть сделан аналогичным способом: + +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); +} + + + +
+ + +
+ + +В примере используется библиотека для создания и парсинга пакетов DNS. +Эта библиотека, а также её зависимости, +использует современные языковые конструкции, не поддерживаемые в njs. +Для поддержки таких конструкций +потребуется дополнительный шаг: транспилирование исходного кода. + + + +Необходимо установить дополнительные пакеты node: + +$ npm install @babel/core @babel/cli @babel/preset-env babel-loader +$ npm install webpack webpack-cli +$ npm install buffer +$ npm install dns-packet + +Файл конфигурации webpack.config.js: + +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'] + } + } + }] + } +}; + +В данном случае используется режим "production". +Конструкция "eval" не используется, так как +не поддерживается njs. +Точкой входа является файл load.js: + +global.dns = require('dns-packet') +global.Buffer = require('buffer/').Buffer + +Сначала необходимо создать единый файл для библиотек, как в предыдущих примерах: + +$ npx browserify load.js -o bundle.js -d + +Затем необходимо обработать утилитой webpack, что также запускает babel: + +$ npx webpack --config webpack.config.js + +Команда создаёт файл dist/wp_out.js, являющийся +трансплицированной версией bundle.js. +Далее необходимо объединить этот файл с code.js, +хранящим код: + +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; +} + +В данном примере генерируемый код не обёрнут в функцию, +явного вызова не требуется. +Результат доступен в каталоге "dist": + +$ cat dist/wp_out.js code.js > njs_dns_bundle.js + +Далее осуществляется вызов кода в конце файла: + +var b = setbuf(1); +console.log(b); + +И затем выполнение кода при помощи node: + +$ 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 +] + +Тестирование и запуск кода вместе с njs: + +$ 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] + + + + +Ответ можно распарсить следующим способом: + +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', согласно запросу выше +} + + + + +
+ +