Mercurial > hg > nginx-site
comparison 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 |
comparison
equal
deleted
inserted
replaced
2576:4c8d0b37932d | 2577:67fd664e2612 |
---|---|
1 <?xml version="1.0"?> | |
2 | |
3 <!-- | |
4 Copyright (C) Nginx, Inc. | |
5 --> | |
6 | |
7 <!DOCTYPE article SYSTEM "../../../../dtd/article.dtd"> | |
8 | |
9 <article name="Использование модулей Node.js в njs" | |
10 link="/ru/docs/njs/node_modules.html" | |
11 lang="en" | |
12 rev="4"> | |
13 | |
14 <section id="intro"> | |
15 | |
16 <para> | |
17 Часто разработчику приходится использовать сторонний код и, | |
18 как правило, такой код доступен в виде библиотеки. | |
19 В JavaScript концепция модулей является новой и | |
20 до недавнего времени не была стандартизированa. | |
21 До сих пор множество платформ или браузеров не поддерживают модули, | |
22 по этой причине практически невозможно повторно использовать код. | |
23 В данной статье приводятся способы повторного использования | |
24 кода в njs при помощи <link url="https://nodejs.org/">Node.js</link>. | |
25 </para> | |
26 | |
27 <note> | |
28 В примерах статьи используется функциональность | |
29 <link doc="index.xml">njs</link> | |
30 <link doc="changes.xml" id="njs0.3.8">0.3.8</link> | |
31 </note> | |
32 | |
33 <para> | |
34 При добавлении стороннего кода в njs | |
35 может возникнуть несколько проблем: | |
36 | |
37 <list type="bullet"> | |
38 | |
39 <listitem> | |
40 большое количество файлов, ссылающихся друг на друга, и их зависимости | |
41 </listitem> | |
42 | |
43 <listitem> | |
44 платформозависимые API | |
45 </listitem> | |
46 | |
47 <listitem> | |
48 языковые конструкции нового стандарта | |
49 </listitem> | |
50 | |
51 </list> | |
52 </para> | |
53 | |
54 <para> | |
55 Однако это не является чем-то новым или специфичным для njs. | |
56 Разработчикам JavaScript приходится часто иметь дело с подобными случаями, | |
57 например при поддержке нескольких несхожих платформ | |
58 с разными свойствами. | |
59 Данные проблемы можно разрешить при помощи следующих инструментов: | |
60 | |
61 <list type="bullet"> | |
62 | |
63 <listitem> | |
64 Большое количество файлов, ссылающихся друг на друга, и их зависимости | |
65 <para> | |
66 Решение: слияние всего независимого кода в один файл. | |
67 Для этих целей могут использоваться утилиты | |
68 <link url="http://browserify.org/">browserify</link> или | |
69 <link url="https://webpack.js.org/">webpack</link>, | |
70 позволяющие преобразовать проект в один файл, содержащий | |
71 код и все зависимости. | |
72 </para> | |
73 </listitem> | |
74 | |
75 <listitem> | |
76 Платформозависимые API | |
77 <para> | |
78 Решение: использование библиотек, реализующих подобные API | |
79 в платформонезависимом режиме, однако в ущерб производительности. | |
80 Определённая функциональность может быть также реализована при помощи | |
81 <link url="https://polyfill.io/v3/">polyfill</link>. | |
82 </para> | |
83 </listitem> | |
84 | |
85 <listitem> | |
86 Языковые конструкции нового стандарта | |
87 <para> | |
88 Решение: трансплирование кода— | |
89 ряд преобразований, | |
90 заменяющих новые функции языка в соответствии со старым стандартом. | |
91 Для этих целей может использоваться | |
92 <link url="https://babeljs.io/"> babel</link>. | |
93 </para> | |
94 </listitem> | |
95 | |
96 </list> | |
97 </para> | |
98 | |
99 <para> | |
100 В статье также используются две относительно большие | |
101 библиотеки на основе npm: | |
102 | |
103 <list type="bullet"> | |
104 | |
105 <listitem> | |
106 <link url="https://www.npmjs.com/package/protobufjs">protobufjs</link>— | |
107 библиотека для создания и парсинга protobuf-сообщений, используемая | |
108 протоколом <link url="https://grpc.io/">gRPC</link> | |
109 </listitem> | |
110 | |
111 <listitem> | |
112 <link url="https://www.npmjs.com/package/dns-packet">dns-packet</link>— | |
113 библиотека для обработки пакетов протокола DNS | |
114 </listitem> | |
115 | |
116 </list> | |
117 </para> | |
118 | |
119 </section> | |
120 | |
121 | |
122 <section id="environment" name="Окружение"> | |
123 | |
124 <para> | |
125 <note> | |
126 В статье описываются общие принципы работы | |
127 и не ставится цель описания подробных сценариев работы с Node.js | |
128 и JavaScript. | |
129 Перед выполнением команд | |
130 необходимо ознакомиться с документацией соответствующих пакетов. | |
131 </note> | |
132 Сначала, предварительно установив и запустив Node.js, необходимо создать | |
133 пустой проект и установить зависимости; | |
134 для выполнения нижеперечисленных команд необходимо | |
135 находиться в рабочем каталоге: | |
136 <example> | |
137 $ mkdir my_project && cd my_project | |
138 $ npx license choose_your_license_here > LICENSE | |
139 $ npx gitignore node | |
140 | |
141 $ cat > package.json <<EOF | |
142 { | |
143 "name": "foobar", | |
144 "version": "0.0.1", | |
145 "description": "", | |
146 "main": "index.js", | |
147 "keywords": [], | |
148 "author": "somename <some.email@example.com> (https://example.com)", | |
149 "license": "some_license_here", | |
150 "private": true, | |
151 "scripts": { | |
152 "test": "echo \"Error: no test specified\" && exit 1" | |
153 } | |
154 } | |
155 EOF | |
156 $ npm init -y | |
157 $ npm install browserify | |
158 </example> | |
159 </para> | |
160 | |
161 </section> | |
162 | |
163 | |
164 <section id="protobuf" name="Protobufjs"> | |
165 | |
166 <para> | |
167 Библиотека предоставляет парсер | |
168 для определения интерфейса <literal>.proto</literal>, | |
169 а также генератор кода для парсинга и генерации сообщений. | |
170 </para> | |
171 | |
172 <para> | |
173 В данном примере используется | |
174 файл | |
175 <link url="https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto">helloworld.proto</link> | |
176 из примеров gRPC. | |
177 Целью является создание двух сообщений: | |
178 <literal>HelloRequest</literal> и | |
179 <literal>HelloResponse</literal>. | |
180 Также используется | |
181 <link url="https://github.com/protobufjs/protobuf.js/blob/master/README.md#reflection-vs-static-code">статический</link> | |
182 режим protobufjs вместо динамически генерируемых классов, так как | |
183 njs не поддерживает динамическое добавление новых функций | |
184 из соображений безопасности. | |
185 </para> | |
186 | |
187 <para> | |
188 Затем устанавливается библиотека, | |
189 из определения протокола генерируется код JavaScript, | |
190 реализующий маршалинг сообщений: | |
191 <example> | |
192 $ npm install protobufjs | |
193 $ npx pbjs -t static-module helloworld.proto > static.js | |
194 </example> | |
195 </para> | |
196 | |
197 <para> | |
198 Таким образом файл <literal>static.js</literal> становится новой зависимостью, | |
199 хранящей необходимый код для реализации обработки сообщений. | |
200 Функция <literal>set_buffer()</literal> содержит код, использующий | |
201 библиотеку для создания буфера с сериализованным | |
202 сообщением <literal>HelloRequest</literal>. | |
203 Код находится в файле <literal>code.js</literal>: | |
204 <example> | |
205 var pb = require('./static.js'); | |
206 | |
207 // Пример использования библиотеки protobuf: подготовка буфера к отправке | |
208 function set_buffer(pb) | |
209 { | |
210 // назначение полей gRPC payload | |
211 var payload = { name: "TestString" }; | |
212 | |
213 // создание объекта | |
214 var message = pb.helloworld.HelloRequest.create(payload); | |
215 | |
216 // сериализация объекта в буфер | |
217 var buffer = pb.helloworld.HelloRequest.encode(message).finish(); | |
218 | |
219 var n = buffer.length; | |
220 | |
221 var frame = new Uint8Array(5 + buffer.length); | |
222 | |
223 frame[0] = 0; // флаг 'compressed' | |
224 frame[1] = (n & 0xFF000000) >>> 24; // длина: uint32 в сетевом порядке байт | |
225 frame[2] = (n & 0x00FF0000) >>> 16; | |
226 frame[3] = (n & 0x0000FF00) >>> 8; | |
227 frame[4] = (n & 0x000000FF) >>> 0; | |
228 | |
229 frame.set(buffer, 5); | |
230 | |
231 return frame; | |
232 } | |
233 | |
234 var frame = set_buffer(pb); | |
235 </example> | |
236 </para> | |
237 | |
238 <para> | |
239 Для проверки работоспособности необходимо выполнить код при помощи node: | |
240 <example> | |
241 $ node ./code.js | |
242 Uint8Array [ | |
243 0, 0, 0, 0, 12, 10, | |
244 10, 84, 101, 115, 116, 83, | |
245 116, 114, 105, 110, 103 | |
246 ] | |
247 </example> | |
248 Результатом является закодированный фрейм <literal>gRPC</literal>. | |
249 Теперь фрейм можно запустить с njs: | |
250 <example> | |
251 $ njs ./code.js | |
252 Thrown: | |
253 Error: Cannot find module "./static.js" | |
254 at require (native) | |
255 at main (native) | |
256 </example> | |
257 </para> | |
258 | |
259 <para> | |
260 Так как модули не поддерживаются, то операция завершается получением исключения. | |
261 В этом случае можно использовать утилиту <literal>browserify</literal> | |
262 или другую подобную утилиту. | |
263 </para> | |
264 | |
265 <para> | |
266 Попытка обработки файла <literal>code.js</literal> завершится | |
267 большим количеством JS-кода, который предполагается запускать в браузере, | |
268 то есть сразу после загрузки. | |
269 Однако необходимо получить другой результат— | |
270 экспортируемую функцию, на которую | |
271 можно сослаться из конфигурации nginx. | |
272 Для этого потребуется создание кода-обёртки. | |
273 <note> | |
274 В целях упрощения в примерах данной статьи | |
275 используется <link doc="cli.xml">интерфейс комадной строки</link> njs. | |
276 На практике для запуска кода обычно используется njs-модуль для nginx. | |
277 </note> | |
278 </para> | |
279 | |
280 <para> | |
281 Файл <literal>load.js</literal> содержит код, загружающий библиотеку, | |
282 храняющую дескриптор в глобальном пространстве имён: | |
283 <example> | |
284 global.hello = require('./static.js'); | |
285 </example> | |
286 Данный код будет заменён объединённым содержимым. | |
287 Код будет использовать дескриптор "<literal>global.hello</literal>" для доступа | |
288 к библиотеке. | |
289 </para> | |
290 | |
291 <para> | |
292 Затем для получения всех зависимостей в один файл | |
293 код обрабатыается утилитой <literal>browserify</literal>: | |
294 <example> | |
295 $ npx browserify load.js -o bundle.js -d | |
296 </example> | |
297 В результате генерируется объёмный файл, содержащий все зависимости: | |
298 <example> | |
299 (function(){function...... | |
300 ... | |
301 ... | |
302 },{"protobufjs/minimal":9}]},{},[1]) | |
303 //# sourceMappingURL.............. | |
304 </example> | |
305 Для получения результирующего файла "<literal>njs_bundle.js</literal>" | |
306 необходимо объединить "<literal>bundle.js</literal>" и следующий код: | |
307 <example> | |
308 // Пример использования библиотеки protobuf: подготовка буфера к отправке | |
309 function set_buffer(pb) | |
310 { | |
311 // назначение полей gRPC payload | |
312 var payload = { name: "TestString" }; | |
313 | |
314 // создание объекта | |
315 var message = pb.helloworld.HelloRequest.create(payload); | |
316 | |
317 // сериализация объекта в буфер | |
318 var buffer = pb.helloworld.HelloRequest.encode(message).finish(); | |
319 | |
320 var n = buffer.length; | |
321 | |
322 var frame = new Uint8Array(5 + buffer.length); | |
323 | |
324 frame[0] = 0; // флаг 'compressed' | |
325 frame[1] = (n & 0xFF000000) >>> 24; // длина: uint32 в сетевом порядке байт | |
326 frame[2] = (n & 0x00FF0000) >>> 16; | |
327 frame[3] = (n & 0x0000FF00) >>> 8; | |
328 frame[4] = (n & 0x000000FF) >>> 0; | |
329 | |
330 frame.set(buffer, 5); | |
331 | |
332 return frame; | |
333 } | |
334 | |
335 // функции, вызываемые снаружи | |
336 function setbuf() | |
337 { | |
338 return set_buffer(global.hello); | |
339 } | |
340 | |
341 // вызов кода | |
342 var frame = setbuf(); | |
343 console.log(frame); | |
344 </example> | |
345 Для проверки работоспособности необходимо запустить файл при помощи node: | |
346 <example> | |
347 $ node ./njs_bundle.js | |
348 Uint8Array [ | |
349 0, 0, 0, 0, 12, 10, | |
350 10, 84, 101, 115, 116, 83, | |
351 116, 114, 105, 110, 103 | |
352 ] | |
353 </example> | |
354 Дальнейшие шаги выполняются при помощи njs: | |
355 <example> | |
356 $ /njs ./njs_bundle.js | |
357 Uint8Array [0,0,0,0,12,10,10,84,101,115,116,83,116,114,105,110,103] | |
358 </example> | |
359 Теперь необходимо задействовать njs API для преобразования | |
360 массива в байтовую строку для дальнейшего использования модулем nginx. | |
361 Данный код необходимо добавить перед последней строкой: | |
362 <example> | |
363 if (global.njs) { | |
364 return String.bytesFrom(frame) | |
365 } | |
366 </example> | |
367 Проверка работоспособности: | |
368 <example> | |
369 $ njs ./njs_bundle.js |hexdump -C | |
370 00000000 00 00 00 00 0c 0a 0a 54 65 73 74 53 74 72 69 6e |.......TestStrin| | |
371 00000010 67 0a |g.| | |
372 00000012 | |
373 </example> | |
374 Экспортируемая функция получена. | |
375 Парсинг ответа может быть сделан аналогичным способом: | |
376 <example> | |
377 function parse_msg(pb, msg) | |
378 { | |
379 // преобразование байтовой строки в массив целых чисел | |
380 var bytes = msg.split('').map(v=>v.charCodeAt(0)); | |
381 | |
382 if (bytes.length < 5) { | |
383 throw 'message too short'; | |
384 } | |
385 | |
386 // первые 5 байт являются фреймом gRPC (сжатие + длина) | |
387 var head = bytes.splice(0, 5); | |
388 | |
389 // проверка правильной длины сообщения | |
390 var len = (head[1] << 24) | |
391 + (head[2] << 16) | |
392 + (head[3] << 8) | |
393 + head[4]; | |
394 | |
395 if (len != bytes.length) { | |
396 throw 'header length mismatch'; | |
397 } | |
398 | |
399 // вызов protobufjs для декодирования сообщения | |
400 var response = pb.helloworld.HelloReply.decode(bytes); | |
401 | |
402 console.log('Reply is:' + response.message); | |
403 } | |
404 </example> | |
405 </para> | |
406 | |
407 </section> | |
408 | |
409 | |
410 <section id="dnspacket" name="Пакет DNS"> | |
411 | |
412 <para> | |
413 В примере используется библиотека для создания и парсинга пакетов DNS. | |
414 Эта библиотека, а также её зависимости, | |
415 использует современные языковые конструкции, не поддерживаемые в njs. | |
416 Для поддержки таких конструкций | |
417 потребуется дополнительный шаг: транспилирование исходного кода. | |
418 </para> | |
419 | |
420 <para> | |
421 Необходимо установить дополнительные пакеты node: | |
422 <example> | |
423 $ npm install @babel/core @babel/cli @babel/preset-env babel-loader | |
424 $ npm install webpack webpack-cli | |
425 $ npm install buffer | |
426 $ npm install dns-packet | |
427 </example> | |
428 Файл конфигурации webpack.config.js: | |
429 <example> | |
430 const path = require('path'); | |
431 | |
432 module.exports = { | |
433 entry: './load.js', | |
434 mode: 'production', | |
435 output: { | |
436 filename: 'wp_out.js', | |
437 path: path.resolve(__dirname, 'dist'), | |
438 }, | |
439 optimization: { | |
440 minimize: false | |
441 }, | |
442 node: { | |
443 global: true, | |
444 }, | |
445 module : { | |
446 rules: [{ | |
447 test: /\.m?js$$/, | |
448 exclude: /(bower_components)/, | |
449 use: { | |
450 loader: 'babel-loader', | |
451 options: { | |
452 presets: ['@babel/preset-env'] | |
453 } | |
454 } | |
455 }] | |
456 } | |
457 }; | |
458 </example> | |
459 В данном случае используется режим "<literal>production</literal>". | |
460 Конструкция "<literal>eval</literal>" не используется, так как | |
461 не поддерживается njs. | |
462 Точкой входа является файл <literal>load.js</literal>: | |
463 <example> | |
464 global.dns = require('dns-packet') | |
465 global.Buffer = require('buffer/').Buffer | |
466 </example> | |
467 Сначала необходимо создать единый файл для библиотек, как в предыдущих примерах: | |
468 <example> | |
469 $ npx browserify load.js -o bundle.js -d | |
470 </example> | |
471 Затем необходимо обработать утилитой webpack, что также запускает babel: | |
472 <example> | |
473 $ npx webpack --config webpack.config.js | |
474 </example> | |
475 Команда создаёт файл <literal>dist/wp_out.js</literal>, являющийся | |
476 трансплицированной версией <literal>bundle.js</literal>. | |
477 Далее необходимо объединить этот файл с <literal>code.js</literal>, | |
478 хранящим код: | |
479 <example> | |
480 function set_buffer(dnsPacket) | |
481 { | |
482 // create DNS packet bytes | |
483 var buf = dnsPacket.encode({ | |
484 type: 'query', | |
485 id: 1, | |
486 flags: dnsPacket.RECURSION_DESIRED, | |
487 questions: [{ | |
488 type: 'A', | |
489 name: 'google.com' | |
490 }] | |
491 }) | |
492 | |
493 return buf; | |
494 } | |
495 </example> | |
496 В данном примере генерируемый код не обёрнут в функцию, | |
497 явного вызова не требуется. | |
498 Результат доступен в каталоге "<literal>dist</literal>": | |
499 <example> | |
500 $ cat dist/wp_out.js code.js > njs_dns_bundle.js | |
501 </example> | |
502 Далее осуществляется вызов кода в конце файла: | |
503 <example> | |
504 var b = setbuf(1); | |
505 console.log(b); | |
506 </example> | |
507 И затем выполнение кода при помощи node: | |
508 <example> | |
509 $ node ./njs_dns_bundle_final.js | |
510 Buffer [Uint8Array] [ | |
511 0, 1, 1, 0, 0, 1, 0, 0, | |
512 0, 0, 0, 0, 6, 103, 111, 111, | |
513 103, 108, 101, 3, 99, 111, 109, 0, | |
514 0, 1, 0, 1 | |
515 ] | |
516 </example> | |
517 Тестирование и запуск кода вместе с njs: | |
518 <example> | |
519 $ njs ./njs_dns_bundle_final.js | |
520 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] | |
521 </example> | |
522 </para> | |
523 | |
524 <para> | |
525 Ответ можно распарсить следующим способом: | |
526 <example> | |
527 function parse_response(buf) | |
528 { | |
529 var bytes = buf.split('').map(v=>v.charCodeAt(0)); | |
530 | |
531 var b = global.Buffer.from(bytes); | |
532 | |
533 var packet = dnsPacket.decode(b); | |
534 | |
535 var resolved_name = packet.answers[0].name; | |
536 | |
537 // ожидаемое имя 'google.com', согласно запросу выше | |
538 } | |
539 </example> | |
540 | |
541 </para> | |
542 | |
543 </section> | |
544 | |
545 </article> |