415 lines
8.8 KiB
JavaScript
415 lines
8.8 KiB
JavaScript
const EventEmitter = require('bare-events')
|
|
const { Duplex } = require('bare-stream')
|
|
const tcp = require('bare-tcp')
|
|
const pipe = require('bare-pipe')
|
|
const constants = require('./lib/constants')
|
|
|
|
const defaultReadBufferSize = 65536
|
|
|
|
exports.Socket = class NetSocket extends Duplex {
|
|
constructor(opts = {}) {
|
|
const {
|
|
readBufferSize = defaultReadBufferSize,
|
|
allowHalfOpen = false,
|
|
eagerOpen = false
|
|
} = opts
|
|
|
|
super({ eagerOpen, allowHalfOpen })
|
|
|
|
this._type = 0
|
|
this._state = 0
|
|
this._socket = null
|
|
|
|
this._opts = { readBufferSize, allowHalfOpen, eagerOpen }
|
|
|
|
this._pendingOpen = null
|
|
this._pendingWrite = null
|
|
this._pendingFinal = null
|
|
this._pendingDestroy = null
|
|
}
|
|
|
|
get connecting() {
|
|
return this._socket !== null && this._socket.connecting
|
|
}
|
|
|
|
get pending() {
|
|
return this._socket === null || this._socket.pending
|
|
}
|
|
|
|
get timeout() {
|
|
return this._socket === null ? undefined : this._socket.timeout
|
|
}
|
|
|
|
get readyState() {
|
|
return this._socket === null ? 'opening' : this._socket.readyState
|
|
}
|
|
|
|
get localAddress() {
|
|
return this._socket === null ? undefined : this._socket.localAddress
|
|
}
|
|
|
|
get localPort() {
|
|
return this._socket === null ? undefined : this._socket.localPort
|
|
}
|
|
|
|
get localFamily() {
|
|
return this._socket === null ? undefined : this._socket.localFamily
|
|
}
|
|
|
|
get remoteAddress() {
|
|
return this._socket === null ? undefined : this._socket.remoteAddress
|
|
}
|
|
|
|
get remotePort() {
|
|
return this._socket === null ? undefined : this._socket.remotePort
|
|
}
|
|
|
|
get remoteFamily() {
|
|
return this._socket === null ? undefined : this._socket.remoteFamily
|
|
}
|
|
|
|
connect(...args) {
|
|
let opts = {}
|
|
let onconnect
|
|
|
|
if (typeof args[0] === 'string') {
|
|
// connect(path[, onconnect])
|
|
opts.path = args[0]
|
|
onconnect = args[1]
|
|
} else if (typeof args[0] === 'number') {
|
|
// connect(port[, host][, onconnect])
|
|
opts.port = args[0]
|
|
|
|
if (typeof args[1] === 'function') {
|
|
onconnect = args[1]
|
|
} else {
|
|
opts.host = args[1]
|
|
onconnect = args[2]
|
|
}
|
|
} else {
|
|
// connect(opts[, onconnect])
|
|
opts = args[0] || {}
|
|
onconnect = args[1]
|
|
}
|
|
|
|
opts = { ...opts, ...this._opts }
|
|
|
|
if (opts.path) {
|
|
this._attach(constants.type.IPC, pipe.createConnection(opts))
|
|
} else {
|
|
this._attach(constants.type.TCP, tcp.createConnection(opts))
|
|
}
|
|
|
|
if (onconnect) this.once('connect', onconnect)
|
|
|
|
return this
|
|
}
|
|
|
|
setKeepAlive(...args) {
|
|
if (this._socket !== null) this._socket.setKeepAlive(...args)
|
|
return this
|
|
}
|
|
|
|
setNoDelay(...args) {
|
|
if (this._socket !== null) this._socket.setNoDelay(...args)
|
|
return this
|
|
}
|
|
|
|
setTimeout(...args) {
|
|
if (this._socket !== null) this._socket.setTimeout(...args)
|
|
return this
|
|
}
|
|
|
|
ref() {
|
|
this._state &= ~constants.state.UNREFED
|
|
if (this._socket !== null) this._socket.ref()
|
|
return this
|
|
}
|
|
|
|
unref() {
|
|
this._state |= constants.state.UNREFED
|
|
if (this._socket !== null) this._socket.unref()
|
|
return this
|
|
}
|
|
|
|
_attach(type, socket) {
|
|
this._type = type
|
|
this._socket = socket
|
|
|
|
this._socket
|
|
.on('connect', this._onconnect.bind(this))
|
|
.on('timeout', this._ontimeout.bind(this))
|
|
.on('error', this._onerror.bind(this))
|
|
.on('data', this._ondata.bind(this))
|
|
.on('end', this._onend.bind(this))
|
|
.on('finish', this._onfinish.bind(this))
|
|
.on('drain', this._ondrain.bind(this))
|
|
.on('close', this._onclose.bind(this))
|
|
|
|
if (this._state & constants.state.UNREFED) this._socket.unref()
|
|
|
|
this._continueOpen()
|
|
|
|
return this
|
|
}
|
|
|
|
_open(cb) {
|
|
if (this._socket !== null) return cb(null)
|
|
this._pendingOpen = cb
|
|
}
|
|
|
|
_write(data, encoding, cb) {
|
|
if (this._socket.write(data)) return cb(null)
|
|
this._pendingWrite = cb
|
|
}
|
|
|
|
_final(cb) {
|
|
this._socket.end()
|
|
this._pendingFinal = cb
|
|
}
|
|
|
|
_destroy(err, cb) {
|
|
if (this._socket === null || this._socket.destroyed) return cb(null)
|
|
|
|
this._socket.destroy(err)
|
|
this._pendingDestroy = cb
|
|
}
|
|
|
|
_onconnect() {
|
|
this.emit('connect')
|
|
}
|
|
|
|
_ontimeout() {
|
|
this.emit('timeout')
|
|
}
|
|
|
|
_onerror(err) {
|
|
this.destroy(err)
|
|
}
|
|
|
|
_ondata(data) {
|
|
this.push(data)
|
|
}
|
|
|
|
_onend() {
|
|
this.push(null)
|
|
}
|
|
|
|
_onfinish() {
|
|
this._continueFinal()
|
|
}
|
|
|
|
_ondrain() {
|
|
this._continueWrite()
|
|
}
|
|
|
|
_onclose() {
|
|
this._continueWrite()
|
|
this._continueFinal()
|
|
|
|
if (this._pendingDestroy) this._continueDestroy()
|
|
else this.destroy()
|
|
}
|
|
|
|
_continueOpen() {
|
|
if (this._pendingOpen === null) return
|
|
const cb = this._pendingOpen
|
|
this._pendingOpen = null
|
|
cb(null)
|
|
}
|
|
|
|
_continueWrite() {
|
|
if (this._pendingWrite === null) return
|
|
const cb = this._pendingWrite
|
|
this._pendingWrite = null
|
|
cb(null)
|
|
}
|
|
|
|
_continueFinal() {
|
|
if (this._pendingFinal === null) return
|
|
const cb = this._pendingFinal
|
|
this._pendingFinal = null
|
|
cb(null)
|
|
}
|
|
|
|
_continueDestroy() {
|
|
if (this._pendingDestroy === null) return
|
|
const cb = this._pendingDestroy
|
|
this._pendingDestroy = null
|
|
cb(null)
|
|
}
|
|
}
|
|
|
|
exports.Server = class NetServer extends EventEmitter {
|
|
constructor(opts = {}, onconnection) {
|
|
if (typeof opts === 'function') {
|
|
onconnection = opts
|
|
opts = {}
|
|
}
|
|
|
|
super()
|
|
|
|
const {
|
|
readBufferSize = defaultReadBufferSize,
|
|
allowHalfOpen = false,
|
|
pauseOnConnect = false
|
|
} = opts
|
|
|
|
this._type = 0
|
|
this._state = 0
|
|
this._server = null
|
|
|
|
this._opts = { readBufferSize, allowHalfOpen, pauseOnConnect }
|
|
|
|
if (onconnection) this.on('connection', onconnection)
|
|
}
|
|
|
|
get listening() {
|
|
return this._server !== null && this._server.listening
|
|
}
|
|
|
|
address() {
|
|
return this._server === null ? null : this._server.address()
|
|
}
|
|
|
|
listen(...args) {
|
|
let opts = {}
|
|
let onlistening
|
|
|
|
if (typeof args[0] === 'string') {
|
|
// listen(path[, backlog][, onlistening])
|
|
opts.path = args[0]
|
|
|
|
if (typeof args[1] === 'function') {
|
|
onlistening = args[1]
|
|
} else {
|
|
opts.backlog = args[1]
|
|
onlistening = args[2]
|
|
}
|
|
} else {
|
|
// listen([port[, host[, backlog]]][, onlistening])
|
|
if (typeof args[0] === 'function') {
|
|
onlistening = args[0]
|
|
} else {
|
|
opts.port = args[0]
|
|
|
|
if (typeof args[1] === 'function') {
|
|
onlistening = args[1]
|
|
} else {
|
|
opts.host = args[1]
|
|
|
|
if (typeof args[2] === 'function') {
|
|
onlistening = args[2]
|
|
} else {
|
|
opts.backlog = args[2]
|
|
onlistening = args[3]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
opts = { ...opts, ...this._opts }
|
|
|
|
if (opts.path) {
|
|
this._attach(constants.type.IPC, pipe.createServer(opts))
|
|
} else {
|
|
this._attach(constants.type.TCP, tcp.createServer(opts))
|
|
}
|
|
|
|
this._server.listen(opts)
|
|
|
|
if (onlistening) this.once('listening', onlistening)
|
|
|
|
return this
|
|
}
|
|
|
|
close(onclose) {
|
|
if (onclose) this.once('close', onclose)
|
|
this._server.close()
|
|
return this
|
|
}
|
|
|
|
ref() {
|
|
this._state &= ~constants.state.UNREFED
|
|
if (this._server !== null) this._server.ref()
|
|
return this
|
|
}
|
|
|
|
unref() {
|
|
this._state |= constants.state.UNREFED
|
|
if (this._server !== null) this._server.unref()
|
|
return this
|
|
}
|
|
|
|
_attach(type, server) {
|
|
this._type = type
|
|
this._server = server
|
|
|
|
this._server
|
|
.on('listening', this._onlistening.bind(this))
|
|
.on('connection', this._onconnection.bind(this))
|
|
.on('error', this._onerror.bind(this))
|
|
.on('close', this._onclose.bind(this))
|
|
|
|
if (this._state & constants.state.UNREFED) this._server.unref()
|
|
|
|
return this
|
|
}
|
|
|
|
_onlistening() {
|
|
this.emit('listening')
|
|
}
|
|
|
|
_onconnection(socket) {
|
|
this.emit('connection', new exports.Socket(this._opts)._attach(this._type, socket))
|
|
}
|
|
|
|
_onerror(err) {
|
|
this.emit('error', err)
|
|
}
|
|
|
|
_onclose() {
|
|
this.emit('close')
|
|
}
|
|
}
|
|
|
|
exports.constants = constants
|
|
|
|
exports.isIP = tcp.isIP
|
|
exports.isIPv4 = tcp.isIPv4
|
|
exports.isIPv6 = tcp.isIPv6
|
|
|
|
exports.createConnection = function createConnection(...args) {
|
|
let opts = {}
|
|
let onconnect
|
|
|
|
if (typeof args[0] === 'string') {
|
|
// createConnection(path[, onconnect])
|
|
opts.path = args[0]
|
|
onconnect = args[1]
|
|
} else if (typeof args[0] === 'number') {
|
|
// createConnection(port[, host][, onconnect])
|
|
opts.port = args[0]
|
|
|
|
if (typeof args[1] === 'function') {
|
|
onconnect = args[1]
|
|
} else {
|
|
opts.host = args[1]
|
|
onconnect = args[2]
|
|
}
|
|
} else {
|
|
// createConnection(opts[, onconnect])
|
|
opts = args[0] || {}
|
|
onconnect = args[1]
|
|
}
|
|
|
|
return new exports.Socket(opts).connect(opts, onconnect)
|
|
}
|
|
|
|
// For Node.js compatibility
|
|
exports.connect = exports.createConnection
|
|
|
|
exports.createServer = function createServer(opts, onconnection) {
|
|
return new exports.Server(opts, onconnection)
|
|
}
|