comparison xml/en/docs/njs/node_modules.xml @ 2576:4c8d0b37932d

Corrected syntax and style of "Using node modules with njs".
author Yaroslav Zhuravlev <yar@nginx.com>
date Thu, 06 Aug 2020 14:46:58 +0100
parents bda080989b6c
children fca42223b9fc
comparison
equal deleted inserted replaced
2575:2839ad72d8ef 2576:4c8d0b37932d
7 <!DOCTYPE article SYSTEM "../../../../dtd/article.dtd"> 7 <!DOCTYPE article SYSTEM "../../../../dtd/article.dtd">
8 8
9 <article name="Using node modules with njs" 9 <article name="Using node modules with njs"
10 link="/en/docs/njs/node_modules.html" 10 link="/en/docs/njs/node_modules.html"
11 lang="en" 11 lang="en"
12 rev="3"> 12 rev="4">
13 13
14 <section id="intro" name="Introduction"> 14 <section id="intro">
15 15
16 <para> 16 <para>
17 Often, a developer wants to use 3rd-party code, usually available as a library 17 Often, a developer wants to use 3rd-party code,
18 of some kind. 18 usually available as a library of some kind.
19 In the Javascript world, the concept of a module is relatively new, so there 19 In the JavaScript world, the concept of a module is relatively new,
20 was no standard until recently. 20 so there was no standard until recently.
21 Many platforms (browsers) still don't support modules, which makes code 21 Many platforms (browsers) still don't support modules, which makes code
22 reuse harder. 22 reuse harder.
23 The njs does not (yet) support modules, too. 23 This article describes ways to reuse
24 This article describes ways to overcome this limitation, using the 24 <link url="https://nodejs.org/">Node.js</link> code in njs.
25 <link url="https://nodejs.org/">Node.js</link> ecosystem as an example.
26 </para> 25 </para>
27 26
28 <note> 27 <note>
29 Examples in this article use features that appeared in 28 Examples in this article use features that appeared in
30 <link doc="index.xml">njs</link> 29 <link doc="index.xml">njs</link>
31 <link doc="changes.xml" id="njs0.3.8">0.3.8</link> 30 <link doc="changes.xml" id="njs0.3.8">0.3.8</link>
32 </note> 31 </note>
33 32
34 <para> 33 <para>
35 There is a number of issues that may arise when 3rd-party code is added to njs: 34 There is a number of issues
35 that may arise when 3rd-party code is added to njs:
36 36
37 <list type="bullet"> 37 <list type="bullet">
38 38
39 <listitem>Multiple files that reference each other, and their 39 <listitem>
40 dependencies</listitem> 40 Multiple files that reference each other and their dependencies
41 41 </listitem>
42 <listitem>Platform-specific APIs</listitem> 42
43 43 <listitem>
44 <listitem>Modern standard language constructions</listitem> 44 Platform-specific APIs
45 </listitem>
46
47 <listitem>
48 Modern standard language constructions
49 </listitem>
45 50
46 </list> 51 </list>
47 52 </para>
48 </para> 53
49 54 <para>
50 <para> 55 The good news is that such problems are not something new or specific to njs.
51 The good news is that such problems are not something new or 56 JavaScript developers face them daily
52 specific to njs. 57 when trying to support multiple disparate platforms
53 Javascript developers face them daily when trying to support multiple 58 with very different properties.
54 disparate platforms with very different properties.
55 There are instruments designed to resolve the above-mentioned issues. 59 There are instruments designed to resolve the above-mentioned issues.
56 </para>
57
58 <para>
59 60
60 <list type="bullet"> 61 <list type="bullet">
61 62
62 <listitem> 63 <listitem>
63 Multiple files that reference each other, and their dependencies 64 Multiple files that reference each other, and their dependencies
72 </listitem> 73 </listitem>
73 74
74 <listitem> 75 <listitem>
75 Platform-specific APIs 76 Platform-specific APIs
76 <para> 77 <para>
77 You can use multiple libraries that implement such APIs in a platform-agnostic 78 You can use multiple libraries that implement such APIs
78 manner (at the expense of performance, though). 79 in a platform-agnostic manner (at the expense of performance, though).
79 Particular features can also be implemented using the 80 Particular features can also be implemented using the
80 <link url="https://polyfill.io/v3/">polyfill</link> approach. 81 <link url="https://polyfill.io/v3/">polyfill</link> approach.
81 </para> 82 </para>
82 </listitem> 83 </listitem>
83 84
84 <listitem> 85 <listitem>
85 Modern standard language constructions 86 Modern standard language constructions
86 <para> 87 <para>
87 Such code can be transpiled: this means performing a number of transformations 88 Such code can be transpiled:
89 this means performing a number of transformations
88 that rewrite newer language features in accordance with an older standard. 90 that rewrite newer language features in accordance with an older standard.
89 For example, <link url="https://babeljs.io/"> babel</link> project can 91 For example, <link url="https://babeljs.io/"> babel</link> project
90 be used to this purpose. 92 can be used to this purpose.
91 </para> 93 </para>
92 </listitem> 94 </listitem>
93 95
94 </list> 96 </list>
95 97 </para>
96 </para>
97
98 98
99 <para> 99 <para>
100 In this guide, we will use two relatively large npm-hosted libraries: 100 In this guide, we will use two relatively large npm-hosted libraries:
101 101
102 <list type="bullet"> 102 <list type="bullet">
103 103
104 <listitem> 104 <listitem>
105 <link url="https://www.npmjs.com/package/protobufjs">protobufjs</link> - 105 <link url="https://www.npmjs.com/package/protobufjs">protobufjs</link>&mdash;
106 a library for creating and parsing protobuf messages used by the 106 a library for creating and parsing protobuf messages used by the
107 <link url="https://grpc.io/">gRPC</link> protocol. 107 <link url="https://grpc.io/">gRPC</link> protocol
108 108 </listitem>
109 </listitem> 109
110 110 <listitem>
111 <listitem> 111 <link url="https://www.npmjs.com/package/dns-packet">dns-packet</link>&mdash;
112 <link url="https://www.npmjs.com/package/dns-packet">dns-packet</link> - 112 a library for processing DNS protocol packets
113 a library for processing DNS protocol packets.
114 </listitem> 113 </listitem>
115 114
116 </list> 115 </list>
117
118 </para> 116 </para>
119 117
120 </section> 118 </section>
121 119
120
122 <section id="environment" name="Environment"> 121 <section id="environment" name="Environment">
123 122
124 <para> 123 <para>
125
126 <note> 124 <note>
127 This document mostly employs a generic approach and AVOIDS specific best 125 This document mostly employs a generic approach
128 practice advices concerning Node.js and the rapidly evolving JavaScript 126 and avoids specific best practice advices concerning Node.js
129 ecosystem. 127 and JavaScript.
130 Make sure to consult the corresponding package's manual BEFORE following the 128 Make sure to consult the corresponding package's manual
131 steps suggested here. 129 before following the steps suggested here.
132 </note> 130 </note>
133
134 First (assuming Node.js is installed and operational), let's create an 131 First (assuming Node.js is installed and operational), let's create an
135 empty project and install some dependencies; the commands below assume we're 132 empty project and install some dependencies;
136 in the working directory: 133 the commands below assume we are in the working directory:
137
138 <example> 134 <example>
139 $ mkdir my_project &amp;&amp; cd my_project 135 $ mkdir my_project &amp;&amp; cd my_project
140 $ npx license choose_your_license_here > LICENSE 136 $ npx license choose_your_license_here > LICENSE
141 $ npx gitignore node 137 $ npx gitignore node
142 138
160 </example> 156 </example>
161 </para> 157 </para>
162 158
163 </section> 159 </section>
164 160
161
165 <section id="protobuf" name="Protobufjs"> 162 <section id="protobuf" name="Protobufjs">
166 163
167 <para> 164 <para>
168 The library provides a parser for the <literal>.proto</literal> interface 165 The library provides a parser
169 definitions and a code generator for message parsing and generation. 166 for the <literal>.proto</literal> interface definitions
167 and a code generator for message parsing and generation.
170 </para> 168 </para>
171 169
172 <para> 170 <para>
173 In this example, we will use the 171 In this example, we will use the
174 <link url="https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto">helloworld.proto</link> 172 <link url="https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto">helloworld.proto</link>
175 file from the gRPC examples. 173 file
176 Our goal is to create two messages: <literal>HelloRequest</literal> and 174 from the gRPC examples.
175 Our goal is to create two messages:
176 <literal>HelloRequest</literal> and
177 <literal>HelloResponse</literal>. 177 <literal>HelloResponse</literal>.
178 We will use the 178 We will use the
179 <link url="https://github.com/protobufjs/protobuf.js/blob/master/README.md#reflection-vs-static-code">static</link> 179 <link url="https://github.com/protobufjs/protobuf.js/blob/master/README.md#reflection-vs-static-code">static</link>
180 mode of protobufjs instead of dynamically generating classes, because 180 mode of protobufjs instead of dynamically generating classes, because
181 njs doesn't support adding new functions dynamically due to security 181 njs doesn't support adding new functions dynamically
182 considerations. 182 due to security considerations.
183 </para> 183 </para>
184 184
185 <para> 185 <para>
186 Next, the library is installed and javascript code implementing 186 Next, the library is installed and
187 message marshalling is generated from the protocol definition: 187 the JavaScript code implementing message marshalling
188 is generated from the protocol definition:
188 <example> 189 <example>
189 $ npm install protobufjs 190 $ npm install protobufjs
190 $ npx pbjs -t static-module helloworld.proto > static.js 191 $ npx pbjs -t static-module helloworld.proto > static.js
191 </example> 192 </example>
192 193 </para>
193 </para> 194
194 195 <para>
195 <para> 196 Thus, the <literal>static.js</literal> file becomes our new dependency,
196 Thus, the <literal>static.js</literal> file becomes our new dependency, storing 197 storing all the code we need to implement message processing.
197 all the code we need to implement message processing.
198 The <literal>set_buffer()</literal> function contains code that uses the 198 The <literal>set_buffer()</literal> function contains code that uses the
199 library to create a buffer with the serialized <literal>HelloRequest</literal> 199 library to create a buffer with the serialized
200 message. 200 <literal>HelloRequest</literal> message.
201 The code resides in the <literal>code.js</literal> file: 201 The code resides in the <literal>code.js</literal> file:
202
203 <example> 202 <example>
204 var pb = require('./static.js'); 203 var pb = require('./static.js');
205 204
206 // Example usage of protobuf library: prepare a buffer to send 205 // Example usage of protobuf library: prepare a buffer to send
207 function set_buffer(pb) 206 function set_buffer(pb)
218 var n = buffer.length; 217 var n = buffer.length;
219 218
220 var frame = new Uint8Array(5 + buffer.length); 219 var frame = new Uint8Array(5 + buffer.length);
221 220
222 frame[0] = 0; // 'compressed' flag 221 frame[0] = 0; // 'compressed' flag
223 frame[1] = (n &amp; 0xFF000000) &gt;&gt;&gt; 24; // length: uint32 in network order 222 frame[1] = (n &amp; 0xFF000000) &gt;&gt;&gt; 24; // length: uint32 in network byte order
224 frame[2] = (n &amp; 0x00FF0000) &gt;&gt;&gt; 16; 223 frame[2] = (n &amp; 0x00FF0000) &gt;&gt;&gt; 16;
225 frame[3] = (n &amp; 0x0000FF00) &gt;&gt;&gt; 8; 224 frame[3] = (n &amp; 0x0000FF00) &gt;&gt;&gt; 8;
226 frame[4] = (n &amp; 0x000000FF) &gt;&gt;&gt; 0; 225 frame[4] = (n &amp; 0x000000FF) &gt;&gt;&gt; 0;
227 226
228 frame.set(buffer, 5); 227 frame.set(buffer, 5);
234 </example> 233 </example>
235 </para> 234 </para>
236 235
237 <para> 236 <para>
238 To ensure it works, we execute the code using node: 237 To ensure it works, we execute the code using node:
239
240 <example> 238 <example>
241 $ node ./code.js 239 $ node ./code.js
242 Uint8Array [ 240 Uint8Array [
243 0, 0, 0, 0, 12, 10, 241 0, 0, 0, 0, 12, 10,
244 10, 84, 101, 115, 116, 83, 242 10, 84, 101, 115, 116, 83,
245 116, 114, 105, 110, 103 243 116, 114, 105, 110, 103
246 ] 244 ]
247 </example> 245 </example>
248
249 You can see that this got us a properly encoded <literal>gRPC</literal> frame. 246 You can see that this got us a properly encoded <literal>gRPC</literal> frame.
250 Now let's run it with njs: 247 Now let's run it with njs:
251
252 <example> 248 <example>
253 $ njs ./code.js 249 $ njs ./code.js
254 Thrown: 250 Thrown:
255 Error: Cannot find module "./static.js" 251 Error: Cannot find module "./static.js"
256 at require (native) 252 at require (native)
264 or other similar tool. 260 or other similar tool.
265 </para> 261 </para>
266 262
267 <para> 263 <para>
268 An attempt to process our existing <literal>code.js</literal> file will result 264 An attempt to process our existing <literal>code.js</literal> file will result
269 in a bunch of JS code that is supposed to run in a browser, i.e. immediately 265 in a bunch of JS code that is supposed to run in a browser,
270 upon loading. 266 i.e. immediately upon loading.
271 This isn't something we actually want. 267 This isn't something we actually want.
272 Instead, we want to have an exported function that 268 Instead, we want to have an exported function that
273 can be referenced from the nginx configuration. 269 can be referenced from the nginx configuration.
274 This requires some wrapper code. 270 This requires some wrapper code.
275
276 <note> 271 <note>
277 In this guide, we use njs cli in all examples for the sake of simplicity. 272 In this guide, we use
273 njs <link doc="cli.xml">cli</link> in all examples for the sake of simplicity.
278 In real life, you will be using nginx njs module to run your code. 274 In real life, you will be using nginx njs module to run your code.
279 </note> 275 </note>
280
281 </para> 276 </para>
282 277
283 <para> 278 <para>
284 The <literal>load.js</literal> file contains the library-loading code that 279 The <literal>load.js</literal> file contains the library-loading code that
285 stores its handle in the global namespace: 280 stores its handle in the global namespace:
290 Our code will be using the "<literal>global.hello</literal>" handle to access 285 Our code will be using the "<literal>global.hello</literal>" handle to access
291 the library. 286 the library.
292 </para> 287 </para>
293 288
294 <para> 289 <para>
295 Next, we process it with browserify to get all dependencies into a single file: 290 Next, we process it with <literal>browserify</literal>
291 to get all dependencies into a single file:
296 <example> 292 <example>
297 $ npx browserify load.js -o bundle.js -d 293 $ npx browserify load.js -o bundle.js -d
298 </example> 294 </example>
299 295 The result is a huge file that contains all our dependencies:
300 The result is huge file that contains all our dependencies:
301
302 <example> 296 <example>
303 (function(){function...... 297 (function(){function......
304 ... 298 ...
305 ... 299 ...
306 },{"protobufjs/minimal":9}]},{},[1]) 300 },{"protobufjs/minimal":9}]},{},[1])
307 //# sourceMappingURL.............. 301 //# sourceMappingURL..............
308 </example> 302 </example>
309
310 To get final "<literal>njs_bundle.js</literal>" file we concatenate 303 To get final "<literal>njs_bundle.js</literal>" file we concatenate
311 "<literal>bundle.js</literal>" and the following code: 304 "<literal>bundle.js</literal>" and the following code:
312
313 <example> 305 <example>
314 // Example usage of protobuf library: prepare a buffer to send 306 // Example usage of protobuf library: prepare a buffer to send
315 function set_buffer(pb) 307 function set_buffer(pb)
316 { 308 {
317 // set fields of gRPC payload 309 // set fields of gRPC payload
326 var n = buffer.length; 318 var n = buffer.length;
327 319
328 var frame = new Uint8Array(5 + buffer.length); 320 var frame = new Uint8Array(5 + buffer.length);
329 321
330 frame[0] = 0; // 'compressed' flag 322 frame[0] = 0; // 'compressed' flag
331 frame[1] = (n &amp; 0xFF000000) &gt;&gt;&gt; 24; // length: uint32 in network order 323 frame[1] = (n &amp; 0xFF000000) &gt;&gt;&gt; 24; // length: uint32 in network byte order
332 frame[2] = (n &amp; 0x00FF0000) &gt;&gt;&gt; 16; 324 frame[2] = (n &amp; 0x00FF0000) &gt;&gt;&gt; 16;
333 frame[3] = (n &amp; 0x0000FF00) &gt;&gt;&gt; 8; 325 frame[3] = (n &amp; 0x0000FF00) &gt;&gt;&gt; 8;
334 frame[4] = (n &amp; 0x000000FF) &gt;&gt;&gt; 0; 326 frame[4] = (n &amp; 0x000000FF) &gt;&gt;&gt; 0;
335 327
336 frame.set(buffer, 5); 328 frame.set(buffer, 5);
346 338
347 // call the code 339 // call the code
348 var frame = setbuf(); 340 var frame = setbuf();
349 console.log(frame); 341 console.log(frame);
350 </example> 342 </example>
351
352 Let's run the file using node to make sure things still work: 343 Let's run the file using node to make sure things still work:
353 <example> 344 <example>
354 $ node ./njs_bundle.js 345 $ node ./njs_bundle.js
355 Uint8Array [ 346 Uint8Array [
356 0, 0, 0, 0, 12, 10, 347 0, 0, 0, 0, 12, 10,
357 10, 84, 101, 115, 116, 83, 348 10, 84, 101, 115, 116, 83,
358 116, 114, 105, 110, 103 349 116, 114, 105, 110, 103
359 ] 350 ]
360 </example> 351 </example>
361
362 Now let's proceed further with njs: 352 Now let's proceed further with njs:
363
364 <example> 353 <example>
365 $ /njs ./njs_bundle.js 354 $ /njs ./njs_bundle.js
366 Uint8Array [0,0,0,0,12,10,10,84,101,115,116,83,116,114,105,110,103] 355 Uint8Array [0,0,0,0,12,10,10,84,101,115,116,83,116,114,105,110,103]
367 </example> 356 </example>
368
369 The last thing will be to use njs-specific API to convert 357 The last thing will be to use njs-specific API to convert
370 array into byte string, so it could be usable by nginx module. 358 array into byte string, so it could be usable by nginx module.
371 We can add the following snippet before the last line: 359 We can add the following snippet before the last line:
372 <example> 360 <example>
373 if (global.njs) { 361 if (global.njs) {
374 return String.bytesFrom(frame) 362 return String.bytesFrom(frame)
375 } 363 }
376 </example> 364 </example>
377
378 Finally, we got it working: 365 Finally, we got it working:
379
380 <example> 366 <example>
381 $ njs ./njs_bundle.js |hexdump -C 367 $ njs ./njs_bundle.js |hexdump -C
382 00000000 00 00 00 00 0c 0a 0a 54 65 73 74 53 74 72 69 6e |.......TestStrin| 368 00000000 00 00 00 00 0c 0a 0a 54 65 73 74 53 74 72 69 6e |.......TestStrin|
383 00000010 67 0a |g.| 369 00000010 67 0a |g.|
384 00000012 370 00000012
416 </example> 402 </example>
417 </para> 403 </para>
418 404
419 </section> 405 </section>
420 406
407
421 <section id="dnspacket" name="DNS-packet"> 408 <section id="dnspacket" name="DNS-packet">
422 409
423 <para> 410 <para>
424 This example uses a library for generation and parsing of DNS packets. 411 This example uses a library for generation and parsing of DNS packets.
425 This a case worth considering because the library and its dependencies 412 This a case worth considering because the library and its dependencies
433 $ npm install @babel/core @babel/cli @babel/preset-env babel-loader 420 $ npm install @babel/core @babel/cli @babel/preset-env babel-loader
434 $ npm install webpack webpack-cli 421 $ npm install webpack webpack-cli
435 $ npm install buffer 422 $ npm install buffer
436 $ npm install dns-packet 423 $ npm install dns-packet
437 </example> 424 </example>
438
439 The configuration file, webpack.config.js: 425 The configuration file, webpack.config.js:
440 <example> 426 <example>
441 const path = require('path'); 427 const path = require('path');
442 428
443 module.exports = { 429 module.exports = {
468 }; 454 };
469 </example> 455 </example>
470 Note we are using "<literal>production</literal>" mode. 456 Note we are using "<literal>production</literal>" mode.
471 In this mode webpack does not use "<literal>eval</literal>" construction 457 In this mode webpack does not use "<literal>eval</literal>" construction
472 not supported by njs. 458 not supported by njs.
473
474 The referenced <literal>load.js</literal> file is our entry point: 459 The referenced <literal>load.js</literal> file is our entry point:
475 <example> 460 <example>
476 global.dns = require('dns-packet') 461 global.dns = require('dns-packet')
477 global.Buffer = require('buffer/').Buffer 462 global.Buffer = require('buffer/').Buffer
478 </example> 463 </example>
479
480 We start the same way, by producing a single file for the libraries: 464 We start the same way, by producing a single file for the libraries:
481
482 <example> 465 <example>
483 $ npx browserify load.js -o bundle.js -d 466 $ npx browserify load.js -o bundle.js -d
484 </example> 467 </example>
485
486 Next, we process the file with webpack, which itself invokes babel: 468 Next, we process the file with webpack, which itself invokes babel:
487
488 <example> 469 <example>
489 $ npx webpack --config webpack.config.js 470 $ npx webpack --config webpack.config.js
490 </example> 471 </example>
491
492 This command produces the <literal>dist/wp_out.js</literal> file, which is a 472 This command produces the <literal>dist/wp_out.js</literal> file, which is a
493 transpiled version of <literal>bundle.js</literal>. 473 transpiled version of <literal>bundle.js</literal>.
494
495 We need to concatenate it with <literal>code.js</literal> 474 We need to concatenate it with <literal>code.js</literal>
496 that stores our code: 475 that stores our code:
497
498 <example> 476 <example>
499 function set_buffer(dnsPacket) 477 function set_buffer(dnsPacket)
500 { 478 {
501 // create DNS packet bytes 479 // create DNS packet bytes
502 var buf = dnsPacket.encode({ 480 var buf = dnsPacket.encode({
510 }) 488 })
511 489
512 return buf; 490 return buf;
513 } 491 }
514 </example> 492 </example>
515
516 Note that in this example generated code is not wrapped into function and we 493 Note that in this example generated code is not wrapped into function and we
517 do not need to call it explicitly. 494 do not need to call it explicitly.
518 The result is in the "<literal>dist</literal>" directory: 495 The result is in the "<literal>dist</literal>" directory:
519
520 <example> 496 <example>
521 $ cat dist/wp_out.js code.js > njs_dns_bundle.js 497 $ cat dist/wp_out.js code.js > njs_dns_bundle.js
522 </example> 498 </example>
523
524 Let's call our code at the end of a file: 499 Let's call our code at the end of a file:
525 <example> 500 <example>
526 var b = setbuf(1); 501 var b = setbuf(1);
527 console.log(b); 502 console.log(b);
528 </example> 503 </example>
529
530 And execute it using node: 504 And execute it using node:
531
532 <example> 505 <example>
533 $ node ./njs_dns_bundle_final.js 506 $ node ./njs_dns_bundle_final.js
534 Buffer [Uint8Array] [ 507 Buffer [Uint8Array] [
535 0, 1, 1, 0, 0, 1, 0, 0, 508 0, 1, 1, 0, 0, 1, 0, 0,
536 0, 0, 0, 0, 6, 103, 111, 111, 509 0, 0, 0, 0, 6, 103, 111, 111,
537 103, 108, 101, 3, 99, 111, 109, 0, 510 103, 108, 101, 3, 99, 111, 109, 0,
538 0, 1, 0, 1 511 0, 1, 0, 1
539 ] 512 ]
540 </example> 513 </example>
541
542 Make sure this works as expected, and then run it with njs: 514 Make sure this works as expected, and then run it with njs:
543 <example> 515 <example>
544 $ njs ./njs_dns_bundle_final.js 516 $ njs ./njs_dns_bundle_final.js
545 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] 517 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]
546 </example> 518 </example>
561 var resolved_name = packet.answers[0].name; 533 var resolved_name = packet.answers[0].name;
562 534
563 // expected name is 'google.com', according to our request above 535 // expected name is 'google.com', according to our request above
564 } 536 }
565 </example> 537 </example>
566
567 </para> 538 </para>
568 539
569 </section> 540 </section>
570 541
571 </article> 542 </article>