qlik-oss / enigma.js

JavaScript library for consuming Qlik's Associative Engine.
MIT License
210 stars 82 forks source link

Reload App Script #771

Closed davidgutierrez closed 4 years ago

davidgutierrez commented 4 years ago

Description

I been working in a Script that allows the reload of an app under petition, but im getting a "socket hang up". error:

Steps to Reproduce

Script Used:

/* eslint import/no-unresolved:0, import/extensions:0, no-console:0 */
const enigma    = require('enigma.js');
const WebSocket = require('ws');
const schema    = require('enigma.js/schemas/12.20.0.json');
const path      = require('path');
const fs        = require('fs');

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

var args        = process.argv.slice(2)
var urlws       = args[0]; // path
var nombreDoc   = args[1];
var userDir     = args[2];  // The Sense Enterprise-configured user directory
                            // for the user you want to identify as
var userId      = args[3];  // The user to use when creating the session
var certPath    = args[4];  // Path to a local folder containing the Sense
                            // Enterprise exported certificates

var readCert    = filename => fs.readFileSync(path.resolve(__dirname, certPath, filename)); 
// Helper function to read the contents of the certificate files:

var certificates = {
    cert: fs.readFileSync(path.resolve(certPath, 'client.pem')),
    key: fs.readFileSync(path.resolve(certPath, 'client_key.pem')),
    root: fs.readFileSync(path.resolve(certPath, 'root.pem'))
    };

var session     = enigma.create({
 schema,
 url: `${urlws}`,
 // Notice the non-standard second parameter here, this is how you pass in
 // additional configuration to the 'ws' npm library, if you use a different
 // library you may configure this differently:
 createSocket: url => new WebSocket(url, {
ca: [certificates.root],
        cert: certificates.cert,
        key: certificates.key,
headers: {
 'X-Qlik-User': `UserDirectory=${encodeURIComponent(userDir)}; UserId=${encodeURIComponent(userId)}`,
},
 }),
});

start();

function start(){
    var docId = '';
    // bind traffic events to log what is sent and received on the socket:
    session.on('traffic:sent', data => console.log('sent:', data));
    session.on('traffic:received', data => console.log('received:', data));

    session.open()
    .then( (global) => {
        var docList =global.getDocList();
        docList.then(function(docList) {
            console.log('Reloaded',nombreDoc);
            docId = docList.filter(obj => obj.qTitle ==nombreDoc )[0].qDocId;           
            console.log(docId);
            global.openDoc(docId).then((app) => {reload(app)})
                .catch((err) => {
                    console.log(err);
                    process.exit(1);
                });
        }).catch((error) => {
                console.log('Error using docList:', error);
                process.exit(1);
        });

    })
    .catch((error) => {
        console.log('Error using global:', error);
        process.exit(1);
    });
}

async function reload(app){
    await app.doReload();
    console.log('Completed');
    save(app);  
}

 function save(app){
     app.doSave().then(function (value) {
        console.log('save promise returned',value);
        session.close();
   }, function(reason) {
        setTimeout(() =>{
            console.log('razon',reason);
            guardar(app);
            },50000);
   })
}

Is called from a console using this command:

node reloadApp.js wss://<<IP QLIK ENTERPRISE>>:4747/app/engineData "<<APP NAME>>" "<<user directory>>" "<<USER>>" "./"
Expected behavior

This must uptate the apication.

Actual behavior
Error using global: ErrorEvent {
  target: WebSocket {
    _events: [Object: null prototype] {
      open: [Function],
      close: [Function],
      error: [Function],
      message: [Function]
    },
    _eventsCount: 4,
    _maxListeners: undefined,
    readyState: 3,
    protocol: '',
    _binaryType: 'nodebuffer',
    _closeFrameReceived: false,
    _closeFrameSent: false,
    _closeMessage: '',
    _closeTimer: null,
    _closeCode: 1006,
    _extensions: {},
    _receiver: null,
    _sender: null,
    _socket: null,
    _bufferedAmount: 0,
    _isServer: false,
    _redirects: 0,
    url: 'wss://<<IP QLIK ENTERPRISE>>:4747/app/engineData',
    _req: null
  },
  type: 'error',
  message: 'socket hang up',
  error: Error: socket hang up
      at connResetException (internal/errors.js:570:14)
      at TLSSocket.socketOnEnd (_http_client.js:440:23)
      at TLSSocket.emit (events.js:215:7)
      at endReadableNT (_stream_readable.js:1184:12)
      at processTicksAndRejections (internal/process/task_queues.js:80:21) {
    code: 'ECONNRESET'
  }
}

Environment

Library
[X ] Node.js
[ ] Browser
Operating system
[X ] Windows
[ ] OSX
[ X] Linux
Qlik Sense
[ ] Desktop
[ X] Enterprise

Versions

willenjs commented 4 years ago

I'm having a similar issue but surely not the same. I don't want to hijack your issue, just sharing.

I'm getting an "Error: Socket closed. (onMaxParallelSessionsExceeded)" but I'm consistently closing it for every single session.

session.on('opened', () => { global.semaphore++; console.log('opened: ${this.semaphore}'); }); session.on('closed', () => { global.semaphore--; console.log('closed: ${this.semaphore}'); });

this never exceeds 1.

Node.js + Qlik Sense April 2020 + Windows .

axelssonHakan commented 4 years ago

@davidgutierrez When do you get "socket hang up". I tried against a Qlik Sense Enterprise and can't reproduce this behaviour

Did a small change to make the whole script async

const enigma = require('enigma.js');
const WebSocket = require('ws');
const schema = require('enigma.js/schemas/12.20.0.json');
const path = require('path');
const fs = require('fs');

// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

var args = process.argv.slice(2)
var urlws = args[0];
var nombreDoc = args[1];
var userDir = args[2];
var userId = args[3];
var certPath = args[4];

var session = enigma.create({
  schema,
  url: `${urlws}`,
  createSocket: url => new WebSocket(url, {
    ca: fs.readFileSync(path.resolve(certPath, 'root.pem')),
    cert: fs.readFileSync(path.resolve(certPath, 'client.pem')),
    key: fs.readFileSync(path.resolve(certPath, 'client_key.pem')),
    headers: {
      'X-Qlik-User': `UserDirectory=${encodeURIComponent(userDir)}; UserId=${encodeURIComponent(userId)}`,
    },
  }),
});

(async () => {
  const global = await session.open();
  const docList = await global.getDocList();
  docId = docList.filter(obj => obj.qTitle == nombreDoc)[0].qDocId;
  console.log(`\nStarting to RELOAD the app '${nombreDoc}' with 'docId'=${docId}`);

  const app = await global.openDoc(docId);
  console.log(`\t- Opening the app`);

  await app.doReload();
  console.log(`\t- Reloading the app`);

  await app.doSave();
  console.log(`\t- Saving the app`);

  const appInfo = await app.getAppProperties();
  console.log(`The app '${nombreDoc}' was last reloaded at:${appInfo.qLastReloadTime}`);

  session.close();
  console.log(`Closing the session`);
})().catch(err => {
  console.error(err);
});
axelssonHakan commented 4 years ago

@willenjs when connecting through the Qlik Sense Proxy the closed sockets are quarantined for some time before they can be "re-used". This is for preventing misusage of the license model. I'm not sure what the "cool-down" time is for closed connections.

If you need more connection simultaneous you could use certificates and connect directly to the engine

willenjs commented 4 years ago

Thank you very much @axelssonHakan . The default 'cool-down' seems to be something around ~5 mins. I will definitely change it to certificates or jwt?.

axelssonHakan commented 4 years ago

Marking this as resolved and closing issue

davidgutierrez commented 4 years ago

it worked very well

/* eslint import/no-unresolved:0, import/extensions:0, no-console:0 */
const enigma = require('enigma.js');
const WebSocket = require('ws');
const schema = require('enigma.js/schemas/12.20.0.json');
const path = require('path');
const fs = require('fs');

//process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";

var args = process.argv.slice(2)
var urlws = args[0];
var nombreDoc = args[1];
var userDir = args[2];
var userId = args[3];
var certPath = args[4];

var session = enigma.create({
  schema,
  url: urlws,
  createSocket: url => new WebSocket(url, {
    ca: fs.readFileSync(path.resolve(certPath, 'root.pem')),
    cert: fs.readFileSync(path.resolve(certPath, 'client.pem')),
    key: fs.readFileSync(path.resolve(certPath, 'client_key.pem')),
    headers: {
      'X-Qlik-User': `UserDirectory=${encodeURIComponent(userDir)}; UserId=${encodeURIComponent(userId)}`,
    },
  }),
});

(async () => {
  const global = await session.open();
  const docList = await global.getDocList();
console.log(docList);
  docId = docList.filter(obj => obj.qTitle == nombreDoc)[0].qDocId;
  console.log(`\nStarting to RELOAD the app ${nombreDoc} with docId=${docId}`);

  const app = await global.openDoc(docId);
  console.log('\t- Opening the app');

  await app.doReload();
  console.log('\t- Reloading the app');

  await app.doSave();
  console.log('\t- Saving the app');

  const appInfo = await app.getAppProperties();
  console.log(`The app ${nombreDoc} was last reloaded at:${appInfo.qLastReloadTime}`);

  session.close();
  console.log('Closing the session');
})().catch(err => {
  console.error(err);
});

i just made a small changes to your code.

And found that the problem the "socket hang up" is more that the certificates are not longer working outside of the server... is more a configuration of the server problem.