mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-27 08:02:01 +00:00
fix: offer a new port and listen if already used, use consola on server error (#4428)
* Use consola on server error * fix style * ignore coverage * use `consola.error(e)` * formatting server error * fix style * offer a new port and listen * fix style * simplify return * Revert "fix style" This reverts commit770347adb9
. * Revert "simplify return" This reverts commit26f2588b2f
. * simplified tests * remove dependency `get-port` * using port `0` to assign a random free port * update `this.port` value with `address.port` * For production, use `consola.fatal` * pass options.dev from server to listener constructor * add dev on constructor * improve serverErrorHandler and close * Update listener.js * improve serverErrorHandler * improve the way to handle listen errors * fix missed line * fully close old server before listening on a random port * update listen.test
This commit is contained in:
parent
71136fc9b6
commit
1d78027e2b
@ -6,13 +6,14 @@ import consola from 'consola'
|
||||
import pify from 'pify'
|
||||
|
||||
export default class Listener {
|
||||
constructor({ port, host, socket, https, app }) {
|
||||
constructor({ port, host, socket, https, app, dev }) {
|
||||
// Options
|
||||
this.port = port
|
||||
this.host = host
|
||||
this.socket = socket
|
||||
this.https = https
|
||||
this.app = app
|
||||
this.dev = dev
|
||||
|
||||
// After listen
|
||||
this.listening = false
|
||||
@ -24,10 +25,17 @@ export default class Listener {
|
||||
|
||||
async close() {
|
||||
// Destroy server by forcing every connection to be closed
|
||||
if (this.server.listening) {
|
||||
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() {
|
||||
@ -37,6 +45,7 @@ export default class Listener {
|
||||
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}`
|
||||
return
|
||||
}
|
||||
@ -54,22 +63,50 @@ export default class Listener {
|
||||
const protocolOpts = typeof this.https === 'object' ? [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', error => reject(error))
|
||||
const s = this._server.listen(listenArgs, error => error ? reject(error) : 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
|
||||
}
|
||||
|
||||
serverErrorHandler(error) {
|
||||
// Detect if port is not available
|
||||
const addressInUse = error.code === 'EADDRINUSE'
|
||||
|
||||
// Use better error message
|
||||
if (addressInUse) {
|
||||
error.message = `Address \`${this.host}:${this.port}\` is already in use.`
|
||||
}
|
||||
|
||||
// Listen to a random port on dev as a fallback
|
||||
if (addressInUse && this.dev && this.port !== '0') {
|
||||
consola.warn(error.message)
|
||||
consola.info('Trying a random port...')
|
||||
this.port = '0'
|
||||
return this.close().then(() => this.listen())
|
||||
}
|
||||
|
||||
// Throw error
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +219,8 @@ export default class Server {
|
||||
host: host || this.options.server.host,
|
||||
socket: socket || this.options.server.socket,
|
||||
https: this.options.server.https,
|
||||
app: this.app
|
||||
app: this.app,
|
||||
dev: this.options.dev
|
||||
})
|
||||
|
||||
// Listen
|
||||
|
44
test/unit/server.listen.test.js
Normal file
44
test/unit/server.listen.test.js
Normal file
@ -0,0 +1,44 @@
|
||||
import consola from 'consola'
|
||||
|
||||
import { loadFixture, getPort, Nuxt } from '../utils'
|
||||
|
||||
let config
|
||||
|
||||
describe('server listen', () => {
|
||||
beforeAll(async () => {
|
||||
config = await loadFixture('empty')
|
||||
})
|
||||
|
||||
test('should throw error when listening on same port (prod)', async () => {
|
||||
const nuxt = new Nuxt(config)
|
||||
const port = await getPort()
|
||||
const listen = () => nuxt.server.listen(port, 'localhost')
|
||||
|
||||
// Listen for first time
|
||||
await listen()
|
||||
expect(nuxt.server.listeners[0].port).toBe(port)
|
||||
|
||||
// Listen for second time
|
||||
await expect(listen()).rejects.toThrow(`Address \`localhost:${port}\` is already in use.`)
|
||||
|
||||
await nuxt.close()
|
||||
})
|
||||
|
||||
test('should assign a random port when listening on same port (dev)', async () => {
|
||||
const nuxt = new Nuxt({ ...config, dev: true })
|
||||
const port = await getPort()
|
||||
const listen = () => nuxt.server.listen(port, 'localhost')
|
||||
|
||||
// Listen for first time
|
||||
await listen()
|
||||
expect(nuxt.server.listeners[0].port).toBe(port)
|
||||
|
||||
// Listen for second time
|
||||
await listen()
|
||||
expect(nuxt.server.listeners[1].port).not.toBe(nuxt.server.listeners[0].port)
|
||||
expect(consola.warn).toHaveBeenCalledTimes(1)
|
||||
expect(consola.warn).toHaveBeenCalledWith(`Address \`localhost:${port}\` is already in use.`)
|
||||
|
||||
await nuxt.close()
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user