Skip to content

Commit

Permalink
feat: print resolved address for localhost (#8647)
Browse files Browse the repository at this point in the history
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
  • Loading branch information
sapphi-red and bluwy committed Jun 20, 2022
1 parent 60721ac commit eb52d36
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 37 deletions.
2 changes: 1 addition & 1 deletion docs/config/server-options.md
Expand Up @@ -14,7 +14,7 @@ This can be set via the CLI using `--host 0.0.0.0` or `--host`.

There are cases when other servers might respond instead of Vite.

The first case is when `localhost` is used. Node.js below v17 reorders the result of DNS-resolved address by default. When accessing `localhost`, browsers use DNS to resolve the address and that address might differ from the address which Vite is listening.
The first case is when `localhost` is used. Node.js below v17 reorders the result of DNS-resolved address by default. When accessing `localhost`, browsers use DNS to resolve the address and that address might differ from the address which Vite is listening. Vite prints the resolved address when it differs.

You could set [`dns.setDefaultResultOrder('verbatim')`](https://nodejs.org/api/dns.html#dns_dns_setdefaultresultorder_order) to disable the reordering behavior. Or you could set `server.host` to `127.0.0.1` explicitly.

Expand Down
2 changes: 2 additions & 0 deletions docs/guide/migration.md
Expand Up @@ -134,6 +134,8 @@ Also there are other breaking changes which only affect few users.
- `server.force` option was removed in favor of `optimizeDeps.force` option.
- [[#8550] fix: dont handle sigterm in middleware mode](https://github.com/vitejs/vite/pull/8550)
- When running in middleware mode, Vite no longer kills process on `SIGTERM`.
- [[#8647] feat: print resolved address for localhost](https://github.com/vitejs/vite/pull/8647)
- `server.printUrls` and `previewServer.printUrls` are now async

## Migration from v1

Expand Down
42 changes: 27 additions & 15 deletions packages/vite/src/node/__tests__/utils.spec.ts
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest'
import {
asyncFlatten,
getHash,
getLocalhostAddressIfDiffersFromDNS,
getPotentialTsSrcPaths,
injectQuery,
isWindows,
Expand Down Expand Up @@ -50,38 +51,49 @@ describe('injectQuery', () => {
})

describe('resolveHostname', () => {
test('defaults to localhost', () => {
expect(resolveHostname(undefined)).toEqual({
test('defaults to localhost', async () => {
const resolved = await getLocalhostAddressIfDiffersFromDNS()

expect(await resolveHostname(undefined)).toEqual({
host: 'localhost',
name: 'localhost'
name: resolved ?? 'localhost',
implicit: true
})
})

test('accepts localhost', () => {
expect(resolveHostname('localhost')).toEqual({
test('accepts localhost', async () => {
const resolved = await getLocalhostAddressIfDiffersFromDNS()

expect(await resolveHostname('localhost')).toEqual({
host: 'localhost',
name: 'localhost'
name: resolved ?? 'localhost',
implicit: false
})
})

test('accepts 0.0.0.0', () => {
expect(resolveHostname('0.0.0.0')).toEqual({
test('accepts 0.0.0.0', async () => {
expect(await resolveHostname('0.0.0.0')).toEqual({
host: '0.0.0.0',
name: 'localhost'
name: 'localhost',
implicit: false
})
})

test('accepts ::', () => {
expect(resolveHostname('::')).toEqual({
test('accepts ::', async () => {
expect(await resolveHostname('::')).toEqual({
host: '::',
name: 'localhost'
name: 'localhost',
implicit: false
})
})

test('accepts 0000:0000:0000:0000:0000:0000:0000:0000', () => {
expect(resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000')).toEqual({
test('accepts 0000:0000:0000:0000:0000:0000:0000:0000', async () => {
expect(
await resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000')
).toEqual({
host: '0000:0000:0000:0000:0000:0000:0000:0000',
name: 'localhost'
name: 'localhost',
implicit: false
})
})
})
Expand Down
8 changes: 4 additions & 4 deletions packages/vite/src/node/logger.ts
Expand Up @@ -145,15 +145,15 @@ export function createLogger(
return logger
}

export function printCommonServerUrls(
export async function printCommonServerUrls(
server: Server,
options: CommonServerOptions,
config: ResolvedConfig
): void {
): Promise<void> {
const address = server.address()
const isAddressInfo = (x: any): x is AddressInfo => x?.address
if (isAddressInfo(address)) {
const hostname = resolveHostname(options.host)
const hostname = await resolveHostname(options.host)
const protocol = options.https ? 'https' : 'http'
printServerUrls(
hostname,
Expand Down Expand Up @@ -191,7 +191,7 @@ function printServerUrls(
)
})

if (hostname.name === 'localhost') {
if (hostname.implicit) {
urls.push({
label: 'Network',
url: `use ${colors.white(colors.bold('--host'))} to expose`,
Expand Down
8 changes: 4 additions & 4 deletions packages/vite/src/node/preview.ts
Expand Up @@ -50,7 +50,7 @@ export interface PreviewServer {
/**
* Print server urls
*/
printUrls: () => void
printUrls: () => Promise<void>
}

export type PreviewServerHook = (server: {
Expand Down Expand Up @@ -112,7 +112,7 @@ export async function preview(
postHooks.forEach((fn) => fn && fn())

const options = config.preview
const hostname = resolveHostname(options.host)
const hostname = await resolveHostname(options.host)
const port = options.port ?? 4173
const protocol = options.https ? 'https' : 'http'
const logger = config.logger
Expand All @@ -139,8 +139,8 @@ export async function preview(
return {
config,
httpServer,
printUrls() {
printCommonServerUrls(httpServer, config.preview, config)
async printUrls() {
await printCommonServerUrls(httpServer, config.preview, config)
}
}
}
8 changes: 4 additions & 4 deletions packages/vite/src/node/server/index.ts
Expand Up @@ -223,7 +223,7 @@ export interface ViteDevServer {
/**
* Print server urls
*/
printUrls(): void
printUrls(): Promise<void>
/**
* Restart the server.
*
Expand Down Expand Up @@ -353,9 +353,9 @@ export async function createServer(
closeHttpServer()
])
},
printUrls() {
async printUrls() {
if (httpServer) {
printCommonServerUrls(httpServer, config.server, config)
await printCommonServerUrls(httpServer, config.server, config)
} else {
throw new Error('cannot print server URLs in middleware mode.')
}
Expand Down Expand Up @@ -555,7 +555,7 @@ async function startServer(

const options = server.config.server
const port = inlinePort ?? options.port ?? 5173
const hostname = resolveHostname(options.host)
const hostname = await resolveHostname(options.host)

const protocol = options.https ? 'https' : 'http'
const info = server.config.logger.info
Expand Down
44 changes: 37 additions & 7 deletions packages/vite/src/node/utils.ts
Expand Up @@ -5,6 +5,7 @@ import { createHash } from 'node:crypto'
import { promisify } from 'node:util'
import { URL, URLSearchParams, pathToFileURL } from 'node:url'
import { builtinModules, createRequire } from 'node:module'
import { promises as dns } from 'node:dns'
import { performance } from 'node:perf_hooks'
import resolve from 'resolve'
import type { FSWatcher } from 'chokidar'
Expand Down Expand Up @@ -735,16 +736,38 @@ export function unique<T>(arr: T[]): T[] {
return Array.from(new Set(arr))
}

/**
* Returns resolved localhost address when `dns.lookup` result differs from DNS
*
* `dns.lookup` result is same when defaultResultOrder is `verbatim`.
* Even if defaultResultOrder is `ipv4first`, `dns.lookup` result maybe same.
* For example, when IPv6 is not supported on that machine/network.
*/
export async function getLocalhostAddressIfDiffersFromDNS(): Promise<
string | undefined
> {
const [nodeResult, dnsResult] = await Promise.all([
dns.lookup('localhost'),
dns.lookup('localhost', { verbatim: true })
])
const isSame =
nodeResult.family === dnsResult.family &&
nodeResult.address === dnsResult.address
return isSame ? undefined : nodeResult.address
}

export interface Hostname {
// undefined sets the default behaviour of server.listen
/** undefined sets the default behaviour of server.listen */
host: string | undefined
// resolve to localhost when possible
/** resolve to localhost when possible */
name: string
/** if it is using the default behavior */
implicit: boolean
}

export function resolveHostname(
export async function resolveHostname(
optionsHost: string | boolean | undefined
): Hostname {
): Promise<Hostname> {
let host: string | undefined
if (optionsHost === undefined || optionsHost === false) {
// Use a secure default
Expand All @@ -757,10 +780,17 @@ export function resolveHostname(
}

// Set host name to localhost when possible
const name =
host === undefined || wildcardHosts.has(host) ? 'localhost' : host
let name = host === undefined || wildcardHosts.has(host) ? 'localhost' : host

if (host === 'localhost') {
// See #8647 for more details.
const localhostAddr = await getLocalhostAddressIfDiffersFromDNS()
if (localhostAddr) {
name = localhostAddr
}
}

return { host, name }
return { host, name, implicit: optionsHost === undefined }
}

export function arraify<T>(target: T | T[]): T[] {
Expand Down
4 changes: 3 additions & 1 deletion playground/cli-module/__tests__/serve.ts
Expand Up @@ -119,7 +119,9 @@ async function startedOnPort(serverProcess, port, timeout) {
const str = data.toString()
// hack, console output may contain color code gibberish
// skip gibberish between localhost: and port number
const match = str.match(/(http:\/\/localhost:)(?:.*)(\d{4})/)
const match = str.match(
/(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/
)
if (match) {
const startedPort = parseInt(match[2], 10)
if (startedPort === port) {
Expand Down
4 changes: 3 additions & 1 deletion playground/cli/__tests__/serve.ts
Expand Up @@ -119,7 +119,9 @@ async function startedOnPort(serverProcess, port, timeout) {
const str = data.toString()
// hack, console output may contain color code gibberish
// skip gibberish between localhost: and port number
const match = str.match(/(http:\/\/localhost:)(?:.*)(\d{4})/)
const match = str.match(
/(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/
)
if (match) {
const startedPort = parseInt(match[2], 10)
if (startedPort === port) {
Expand Down

0 comments on commit eb52d36

Please sign in to comment.