/
listener.js
128 lines (110 loc) · 3.21 KB
/
listener.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import http from 'http'
import https from 'https'
import enableDestroy from 'server-destroy'
import ip from 'ip'
import consola from 'consola'
import pify from 'pify'
let RANDOM_PORT = '0'
export default class Listener {
constructor ({ port, host, socket, https, app, dev, baseURL }) {
// Options
this.port = port
this.host = host
this.socket = socket
this.https = https
this.app = app
this.dev = dev
this.baseURL = baseURL
// After listen
this.listening = false
this._server = null
this.server = null
this.address = null
this.url = null
}
async close () {
// Destroy server by forcing every connection to be closed
if (this.server && this.server.listening) {
await this.server.destroy()
consola.debug('server closed')
}
// Delete references
this.listening = false
this._server = null
this.server = null
this.address = null
this.url = null
}
computeURL () {
const address = this.server.address()
if (!this.socket) {
switch (address.address) {
case '127.0.0.1': this.host = 'localhost'; break
case '0.0.0.0': this.host = ip.address(); break
}
this.port = address.port
this.url = `http${this.https ? 's' : ''}://${this.host}:${this.port}${this.baseURL}`
this.url = decodeURI(this.url)
return
}
this.url = `unix+http://${address}`
}
async listen () {
// Prevent multi calls
if (this.listening) {
return
}
// Initialize underlying http(s) server
const protocol = this.https ? https : http
const protocolOpts = this.https ? [this.https] : []
this._server = protocol.createServer.apply(protocol, protocolOpts.concat(this.app))
// Call server.listen
// Prepare listenArgs
const listenArgs = this.socket ? { path: this.socket } : { host: this.host, port: this.port }
listenArgs.exclusive = false
// Call server.listen
try {
this.server = await new Promise((resolve, reject) => {
this._server.on('error', reject)
const s = this._server.listen(listenArgs, (error) => {
s.removeListener(reject)
if (error) {
reject(error)
} else {
resolve(s)
}
})
})
} catch (error) {
return this.serverErrorHandler(error)
}
// Enable destroy support
enableDestroy(this.server)
pify(this.server.destroy)
// Compute listen URL
this.computeURL()
// Set this.listening to true
this.listening = true
}
async serverErrorHandler (error) {
// Detect if port is not available
const addressInUse = error.code === 'EADDRINUSE'
// Use better error message
if (addressInUse) {
const address = this.socket || `${this.host}:${this.port}`
error.message = `Address \`${address}\` is already in use.`
// Listen to a random port on dev as a fallback
if (this.dev && !this.socket && this.port !== RANDOM_PORT) {
consola.warn(error.message)
consola.info('Trying a random port...')
this.port = RANDOM_PORT
await this.close()
await this.listen()
RANDOM_PORT = this.port
return
}
}
// Throw error
throw error
}
}