UNPKG

pyconnector

Version:

Bridge the gap between Node.JS and Python applications

174 lines (143 loc) 3.99 kB
'use strict' const ZMQ = require('zeromq/v5-compat') const ChildProcess = require('child_process') const { EventEmitter } = require('events') // connector class class PyConnector { constructor (options) { // set options this._opts = this._processOptions(Object.assign( { port: 24001, endpoint: null, launcher: 'python', path: null, cwd: null, respawn: 0, local: true }, options)) // event callbacks this._events = new EventEmitter() this._connector = ZMQ.socket('req') // spawn Python process this._process = null if (this._opts.local && this._opts.path !== null) { this._processSpawn() } // query identifiers this._qid = 0 // connect and handle messages this._connector.connect(this._opts.endpoint) this._connector.on('message', (data) => { this._responseHandle(data) }) } _responseHandle (data) { // response decoding const response = JSON.parse(data.toString()) const evt = `q_${response._p}_${response._id}` // pass data this._events.emit(evt, response) } // summon handler process _processSpawn () { const ns = this let launcher = this._opts.launcher const pargs = [this._opts.path, '--pynodeport', this._opts.port] const popts = { stdio: 'inherit' } if (launcher == null) launcher = pargs.shift() if (this._opts.cwd != null) popts.cwd = this._opts.cwd // create child process this._process = ChildProcess.spawn( launcher, pargs, popts ) // error handling this._process.on('error', (e) => { console.error(`PyConnector cannot start process ${this._opts.path}`) console.error(e) ns._process = null }) // close event this._process.on('close', () => { ns._process = null // respawn timeout option if (ns._opts.respawn > 0) { // hopefully restart setTimeout(ns._processSpawn, ns._opts.respawn) } }) } // set options _processOptions (opts) { opts.local = false // custom endpoint if (opts.endpoint !== null) { // string endpoint if (opts.endpoint.substring) { // grab port value let parts = opts.endpoint.split(':') opts.port = parseInt(parts.pop()) // check if string contains protocol if (opts.endpoint.indexOf('//') >= 0) { parts = opts.endpoint.split('//').slice(1) opts.endpoint = `tcp://${parts[0]}` opts.respawn = 0 return opts } opts.endpoint = `tcp://${opts.endpoint}` opts.respawn = 0 return opts } // numeric endpoint opts.port = opts.endpoint } // local instance opts.endpoint = `tcp://127.0.0.1:${opts.port}` opts.local = true return opts } // retrieve all available paths handled by Python routes (callback) { // use the query function return this.query('__pyroutes', {}, callback) } // query Python endpoint query (path, args, callback) { // increment identifier const qdata = { _p: path, _id: this._qid++, args } // asyncronous response const query = new Promise((resolve, reject) => { // set callback this._events.once(`q_${qdata._p}_${qdata._id}`, (response) => { resolve(response.data) // run callback if (callback) callback(response.data) }) // send JSON query this._connector.send(JSON.stringify(qdata)) }).catch( // handle errors (e) => { // log console.error(e) // remove listeners for erroneous event this._events.removeAllListeners(`q_${qdata._p}_${qdata._id}`) return e }) return query } end () { if (this._process !== null) { this._process.kill('SIGINT') this._process.kill() } this._connector.close() } }; module.exports = PyConnector