134 lines
4.0 KiB
JavaScript
134 lines
4.0 KiB
JavaScript
import { Server } from 'net'
|
|
import { unlink as fileDelete } from 'fs'
|
|
import btc from 'better-try-catch'
|
|
import ON_DEATH from 'death' //this is intentionally ugly
|
|
import bunyan from 'bunyan'
|
|
import Stream from 'delimiter-stream'
|
|
|
|
export default class Socket extends Server {
|
|
constructor (path, opts={}) {
|
|
super()
|
|
// set or tcp socket
|
|
if (typeof(path)!=='string') {
|
|
opts = path
|
|
this.listen_opts = { host: opts.host || '127.0.0.1', port: opts.port || 8080}
|
|
} else this.listen_opts = { path: path }
|
|
this.spp = opts.spp || 'processPacket'
|
|
// logging
|
|
this.log_file=opts.log_file || './socket.log'
|
|
this.log_opts = {streams:[]}
|
|
this.log_opts.name = opts.name ? opts.name : 'uci-unix-socket-consumer'
|
|
this.log_opts.streams.push({level: 'info',path: this.log_file })
|
|
if (opts.log) this.log_opts.streams.push({level: 'info',stream: process.stdout})
|
|
this.log = bunyan.createLogger(this.log_opts)
|
|
|
|
} // end constructor
|
|
|
|
|
|
async create (app={}) {
|
|
|
|
return new Promise( async (resolve,reject) => {
|
|
|
|
ON_DEATH( async () => {
|
|
this.log.info('\nhe\'s dead jim')
|
|
await this.destroy()
|
|
|
|
})
|
|
|
|
process.once('SIGUSR2', async () => {
|
|
await this.destroy
|
|
process.kill(process.pid, 'SIGUSR2')
|
|
})
|
|
|
|
this.on('error', async (err) => {
|
|
// recover from socket file that was not removed
|
|
if (err.code === 'EADDRINUSE') {
|
|
let path = this.listen_opts.path
|
|
if (path) { // if TCP socket should already be dead
|
|
this.log.info({socket: path}, 'already exists...deleting')
|
|
await fileDelete(path)
|
|
return await this.listen.bind(this)(this.listen_opts, app)
|
|
}
|
|
}
|
|
// otherwise fatally exit
|
|
this.log.info(err, 'creating socket')
|
|
reject(err)
|
|
})
|
|
|
|
let [err, res] = await btc(this.listen.bind(this))(this.listen_opts,app)
|
|
if (err) reject(err)
|
|
resolve(res)
|
|
|
|
}) // end promise
|
|
|
|
} // end create
|
|
|
|
async listen (opts,app) {
|
|
|
|
super.listen(opts, async (err, res) => {
|
|
|
|
if (err) return err
|
|
// this gets called for each client connection and is unique to each
|
|
this.on('connection', (socket) => {
|
|
|
|
this.log.info('server: new consumer connecting')
|
|
let packet = new Stream()
|
|
|
|
socket.on('data', async (chunk) => {
|
|
packet.write(chunk)
|
|
})
|
|
|
|
// when a complete JSON packet arrives process the packet
|
|
packet.on('data', async (strJSON) => {
|
|
|
|
let [err, packet] = btc(JSON.parse)(strJSON)
|
|
if (!err) {
|
|
this.log.info({packet:packet},'Server: packet received to socket')
|
|
|
|
// set default packet processing
|
|
// console.log('==========',app.spp,'====',this.spp)
|
|
this.spp = app.spp || this.spp
|
|
// console.log('==========',app.spp,'====',this.spp)
|
|
|
|
if (!app[this.spp]) {
|
|
// app.spp = 'processPacket'
|
|
app.processPacket = async (packet) => {
|
|
packet.res='echoed'
|
|
this.log.info({packet:packet},'packet being sent to consumer')
|
|
return packet }
|
|
}
|
|
socket.write(JSON.stringify(await app[this.spp].bind(app)(packet))+'\n' )
|
|
}
|
|
else {
|
|
this.log.info(`bad packet JSON syntax \n ${strJSON}`)
|
|
let error = {
|
|
error: 'bad packet JSON syntax sent',
|
|
packet: strJSON
|
|
}
|
|
socket.write(JSON.stringify(error))
|
|
}
|
|
|
|
}) // end incoming string stream listerner
|
|
this.log.info('Server: sending handshake to consumer')
|
|
socket.write('{"ready":true}\n')
|
|
}) // end connected consumer
|
|
|
|
this.log.info({socket: this.listen_opts},'socket created')
|
|
return res
|
|
}) // end listen callback
|
|
|
|
}
|
|
|
|
|
|
async destroy () {
|
|
|
|
this.log.info('closing down socket')
|
|
await this.close()
|
|
this.log.info('all connections closed....exiting')
|
|
process.exit()
|
|
|
|
} // end destroy
|
|
|
|
|
|
} // end class
|