• Medio ambiente
  • Protobufjs
  • Paquete DNS

A menudo, un desarrollador desea usar código de terceros, generalmente disponible como una biblioteca de algún tipo. En el mundo de JavaScript, el concepto de módulo es relativamente nuevo, por lo que no existía un estándar hasta hace poco. Muchas plataformas (navegadores) todavía no admiten módulos, lo que dificulta la reutilización del código. Este artículo describe formas de reutilizar Node.js código en njs.

Los ejemplos de este artículo usan características que aparecieron en njs 0.3.8

Hay una serie de problemas que pueden surgir cuando se agrega código de terceros a njs:

  • Varios archivos que hacen referencia entre sí y sus dependencias.
  • API específicas de la plataforma
  • Construcciones de lenguaje estándar moderno

La buena noticia es que estos problemas no son algo nuevo ni específico de njs. Los desarrolladores de JavaScript los enfrentan a diario cuando intentan admitir múltiples plataformas dispares con propiedades muy diferentes. Existen instrumentos diseñados para resolver los problemas antes mencionados.

  • Varios archivos que hacen referencia entre sí y sus dependencias

    Esto se puede resolver fusionando todo el código interdependiente en un solo archivo. Herramientas como navegar o paquete web acepte un proyecto completo y produzca un solo archivo que contenga su código y todas las dependencias.

  • API específicas de la plataforma

    Puede utilizar varias bibliotecas que implementan dichas API de forma independiente de la plataforma (aunque a expensas del rendimiento). También se pueden implementar características particulares utilizando el polyfill Acercarse.

  • Construcciones de lenguaje estándar moderno

    Dicho código se puede transpilar: esto significa realizar una serie de transformaciones que reescriben las características del lenguaje más nuevas de acuerdo con un estándar anterior. Por ejemplo, Babel proyecto se puede utilizar para este propósito.

En esta guía, usaremos dos bibliotecas alojadas en npm relativamente grandes:

  • protobufjs – una biblioteca para crear y analizar mensajes protobuf utilizados por el gRPC protocolo
  • paquete-dns – una biblioteca para procesar paquetes de protocolo DNS

Medio ambiente

Este documento emplea principalmente un enfoque genérico y evita recomendaciones específicas sobre las mejores prácticas relacionadas con Node.js y JavaScript. Asegúrese de consultar el manual del paquete correspondiente antes de seguir los pasos sugeridos aquí.

Primero (asumiendo que Node.js está instalado y operativo), creemos un proyecto vacío e instalemos algunas dependencias; los siguientes comandos asumen que estamos en el directorio de trabajo:

$ 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 <[email protected]> (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

Protobufjs

La biblioteca proporciona un analizador para .proto definiciones de interfaz y un generador de código para el análisis y la generación de mensajes.

En este ejemplo, usaremos el helloworld.proto archivo de los ejemplos de gRPC. Nuestro objetivo es crear dos mensajes: HelloRequest y HelloResponse. Usaremos el estático modo de protobufjs en lugar de generar clases dinámicamente, porque njs no admite la adición de nuevas funciones dinámicamente debido a consideraciones de seguridad.

A continuación, se instala la biblioteca y se genera el código JavaScript que implementa la clasificación de mensajes a partir de la definición del protocolo:

$ npm install protobufjs
$ npx pbjs -t static-module helloworld.proto > static.js

Por lo tanto, la static.js file se convierte en nuestra nueva dependencia, almacenando todo el código que necesitamos para implementar el procesamiento de mensajes. los set_buffer() La función contiene código que usa la biblioteca para crear un búfer con el serializado. HelloRequest mensaje. El código reside en el code.js expediente:

var pb = require('./static.js');

// Example usage of protobuf library: prepare a buffer to send
function set_buffer(pb)
{
    // set fields of gRPC payload
    var payload = { name: "TestString" };

    // create an object
    var message = pb.helloworld.HelloRequest.create(payload);

    // serialize object to buffer
    var buffer = pb.helloworld.HelloRequest.encode(message).finish();

    var n = buffer.length;

    var frame = new Uint8Array(5 + buffer.length);

    frame[0] = 0;                        // 'compressed' flag
    frame[1] = (n & 0xFF000000) >>> 24;  // length: uint32 in network byte order
    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);

Para asegurarnos de que funcione, ejecutamos el código usando el nodo:

$ node ./code.js
Uint8Array [
    0,   0,   0,   0,  12, 10,
   10,  84, 101, 115, 116, 83,
  116, 114, 105, 110, 103
]

Puede ver que esto nos consiguió una codificación adecuada. gRPC cuadro. Ahora ejecutémoslo con njs:

$ njs ./code.js
Thrown:
Error: Cannot find module "./static.js"
    at require (native)
    at main (native)

Los módulos no son compatibles, por lo que hemos recibido una excepción. Para superar este problema, usemos browserify u otra herramienta similar.

Un intento de procesar nuestro code.js archivo dará como resultado un montón de código JS que se supone que se ejecuta en un navegador, es decir, inmediatamente después de la carga. Esto no es algo que realmente queremos. En su lugar, queremos tener una función exportada a la que se pueda hacer referencia desde la configuración de nginx. Esto requiere algún código de envoltura.

En esta guía, usamos njs cli en todos los ejemplos en aras de la simplicidad. En la vida real, utilizará el módulo nginx njs para ejecutar su código.

los load.js El archivo contiene el código de carga de la biblioteca que almacena su identificador en el espacio de nombres global:

global.hello = require('./static.js');

Este código será reemplazado por contenido combinado. Nuestro código utilizará el “global.hello“manejar para acceder a la biblioteca.

A continuación, lo procesamos con browserify para obtener todas las dependencias en un solo archivo:

$ npx browserify load.js -o bundle.js -d

El resultado es un archivo enorme que contiene todas nuestras dependencias:

(function(){function......
...
...
},{"protobufjs/minimal":9}]},{},[1])
//# sourceMappingURL..............

Para llegar final “njs_bundle.js“archivo que concatenamos”bundle.js“y el siguiente código:

// Example usage of protobuf library: prepare a buffer to send
function set_buffer(pb)
{
    // set fields of gRPC payload
    var payload = { name: "TestString" };

    // create an object
    var message = pb.helloworld.HelloRequest.create(payload);

    // serialize object to buffer
    var buffer = pb.helloworld.HelloRequest.encode(message).finish();

    var n = buffer.length;

    var frame = new Uint8Array(5 + buffer.length);

    frame[0] = 0;                        // 'compressed' flag
    frame[1] = (n & 0xFF000000) >>> 24;  // length: uint32 in network byte order
    frame[2] = (n & 0x00FF0000) >>> 16;
    frame[3] = (n & 0x0000FF00) >>>  8;
    frame[4] = (n & 0x000000FF) >>>  0;

    frame.set(buffer, 5);

    return frame;
}

// functions to be called from outside
function setbuf()
{
    return set_buffer(global.hello);
}

// call the code
var frame = setbuf();
console.log(frame);

Ejecutemos el archivo usando el nodo para asegurarnos de que las cosas sigan funcionando:

$ node ./njs_bundle.js
Uint8Array [
    0,   0,   0,   0,  12, 10,
   10,  84, 101, 115, 116, 83,
  116, 114, 105, 110, 103
]

Ahora continuemos con njs:

$ njs ./njs_bundle.js
Uint8Array [0,0,0,0,12,10,10,84,101,115,116,83,116,114,105,110,103]

Lo último será usar la API específica de njs para convertir la matriz en una cadena de bytes, por lo que podría ser utilizada por el módulo nginx. Podemos agregar el siguiente fragmento antes de la línea. return frame; }:

if (global.njs) {
    return String.bytesFrom(frame)
}

Finalmente, lo hicimos funcionar:

$ 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

Este es el resultado esperado. El análisis de respuesta se puede implementar de manera similar:

function parse_msg(pb, msg)
{
    // convert byte string into integer array
    var bytes = msg.split('').map(v=>v.charCodeAt(0));

    if (bytes.length < 5) {
        throw 'message too short';
    }

    // first 5 bytes is gRPC frame (compression + length)
    var head = bytes.splice(0, 5);

    // ensure we have proper message length
    var len = (head[1] << 24)
              + (head[2] << 16)
              + (head[3] << 8)
              + head[4];

    if (len != bytes.length) {
        throw 'header length mismatch';
    }

    // invoke protobufjs to decode message
    var response = pb.helloworld.HelloReply.decode(bytes);

    console.log('Reply is:' + response.message);
}

Paquete DNS

Este ejemplo utiliza una biblioteca para generar y analizar paquetes DNS. Este es un caso que vale la pena considerar porque la biblioteca y sus dependencias usan construcciones de lenguaje moderno que aún no son compatibles con njs. A su vez, esto requiere de nosotros un paso adicional: transpilar el código fuente.

Se necesitan paquetes de nodos adicionales:

$ npm install @babel/core @babel/cli @babel/preset-env babel-loader
$ npm install webpack webpack-cli
$ npm install buffer
$ npm install dns-packet

El archivo de configuración, 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']
                }
            }
        }]
    }
};

Tenga en cuenta que estamos usando “production“modo. En este modo, el paquete web no utiliza”eval“construcción no admitida por njs. La referencia load.js archivo es nuestro punto de entrada:

global.dns = require('dns-packet')
global.Buffer = require('buffer/').Buffer

Comenzamos de la misma manera, produciendo un solo archivo para las bibliotecas:

$ npx browserify load.js -o bundle.js -d

A continuación, procesamos el archivo con webpack, que a su vez invoca a babel:

$ npx webpack --config webpack.config.js

Este comando produce el dist/wp_out.js archivo, que es una versión transpilada de bundle.js. Necesitamos concatenarlo con code.js que almacena nuestro código:

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;
}

Tenga en cuenta que en este ejemplo, el código generado no está incluido en la función y no es necesario llamarlo explícitamente. El resultado está en el “dist“directorio:

$ cat dist/wp_out.js code.js > njs_dns_bundle.js

Llamemos a nuestro código al final de un archivo:

var b = set_buffer(global.dns);
console.log(b);

Y ejecútelo usando el nodo:

$ 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
]

Asegúrese de que esto funcione como se esperaba y luego ejecútelo con 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]

La respuesta se puede analizar de la siguiente manera:

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;

    // expected name is 'google.com', according to our request above
}