fix: no warning was given if ipv4 port is busy while v6 is available

Massimo Melina committed May 31, 2022 at 23:04 UTC 225a1eaefa1cd6003ec93fc25b97f566e16fb9d7
2 files changed +59 -42
server/src/listen.ts
+58 -42
@@ -8,7 +8,7 @@ import { watchLoad } from './watchLoad'
8 import { networkInterfaces } from 'os';
9 import { newConnection } from './connections'
10 import open from 'open'
11 -import { onlyTruthy, prefix, wait } from './misc'
11 +import { debounceAsync, onlyTruthy, prefix, wait } from './misc'
12 import { ADMIN_URI, DEV } from './const'
13 import findProcess from 'find-process'
14
@@ -32,33 +32,7 @@ portCfg.sub(async port => {
32 open('http://localhost' + (port === 80 ? '' : ':' + port) + ADMIN_URI).then()
33 })
34
35 -const cert = defineConfig<string>('cert')
36 -const privateKey = defineConfig<string>('private_key')
37 -const httpsNeeds = [cert, privateKey]
38 -const httpsNeedsNames = { cert: 'certificate', private_key: 'private key' }
39 -const httpsOptions = { key: '', cert: '' }
40 -for (const cfg of httpsNeeds) {
41 - let unwatch: ReturnType<typeof watchLoad>['unwatch']
42 - cfg.sub(async v => {
43 - unwatch?.()
44 - const k = cfg.key() === 'private_key' ? 'key' : 'cert'
45 - httpsOptions[k] = v
46 - if (!v || v.includes('\n'))
47 - return considerHttps()
48 - // v is a path
49 - httpsOptions[k] = ''
50 - unwatch = watchLoad(v, data => {
51 - httpsOptions[k] = data
52 - considerHttps()
53 - }).unwatch
54 - await considerHttps()
55 - })
56 -}
57 -
58 -export const httpsPortCfg = defineConfig('https_port', -1)
59 -httpsPortCfg.sub(considerHttps)
60 -
61 -async function considerHttps() {
35 +const considerHttps = debounceAsync(async () => {
36 stopServer(httpsSrv).then()
37 let port = httpsPortCfg.get()
38 try {
@@ -84,15 +58,62 @@ async function considerHttps() {
58 httpsSrv.on('connection', socket =>
59 newConnection(socket, true))
60 printUrls(port, 'https')
61 +})
62 +
63 +
64 +const cert = defineConfig<string>('cert')
65 +const privateKey = defineConfig<string>('private_key')
66 +const httpsNeeds = [cert, privateKey]
67 +const httpsNeedsNames = { cert: 'certificate', private_key: 'private key' }
68 +const httpsOptions = { key: '', cert: '' }
69 +for (const cfg of httpsNeeds) {
70 + let unwatch: ReturnType<typeof watchLoad>['unwatch']
71 + cfg.sub(async v => {
72 + unwatch?.()
73 + const k = cfg.key() === 'private_key' ? 'key' : 'cert'
74 + httpsOptions[k] = v
75 + if (!v || v.includes('\n'))
76 + return considerHttps()
77 + // v is a path
78 + httpsOptions[k] = ''
79 + unwatch = watchLoad(v, data => {
80 + httpsOptions[k] = data
81 + considerHttps()
82 + }).unwatch
83 + await considerHttps()
84 + })
85 }
86
89 -interface StartServer { port: number, net?:string }
90 -function startServer(srv: typeof httpSrv, { port, net }: StartServer) {
91 - return new Promise<number>((resolve, reject) => {
87 +export const httpsPortCfg = defineConfig('https_port', -1)
88 +httpsPortCfg.sub(considerHttps)
89 +
90 +interface StartServer { port: number, host?:string }
91 +function startServer(srv: typeof httpSrv, { port, host }: StartServer) {
92 + return new Promise<number>(async resolve => {
93 try {
93 - if (port < 0)
94 + if (port < 0 || !host && !await testIpV4()) // !host means ipV4+6, and if v4 port alone is busy we won't be notified of the failure, so we'll first test it on its own
95 return resolve(0)
95 - srv.listen(port, net, () => {
96 + port = await listen(host)
97 + if (port)
98 + console.log(srv.name, "serving on", host||"any network", ':', port)
99 + resolve(port)
100 + }
101 + catch(e) {
102 + srv.error = String(e)
103 + console.error(srv.name, "couldn't listen on port", port, srv.error)
104 + resolve(0)
105 + }
106 + })
107 +
108 + async function testIpV4() {
109 + const res = await listen('0.0.0.0')
110 + await new Promise(res => srv.close(res))
111 + return res > 0
112 + }
113 +
114 + function listen(host?: string) {
115 + return new Promise<number>(async (resolve, reject) => {
116 + srv.listen({ port, host }, () => {
117 const ad = srv.address()
118 if (!ad)
119 return reject('no address')
@@ -100,7 +121,6 @@ function startServer(srv: typeof httpSrv, { port, net }: StartServer) {
121 srv.close()
122 return reject('type of socket not supported')
123 }
103 - console.log(srv.name, "serving on", net||"any network", ':', ad.port)
124 resolve(ad.port)
125 }).on('error', async e => {
126 srv.error = String(e)
@@ -111,16 +131,12 @@ function startServer(srv: typeof httpSrv, { port, net }: StartServer) {
131 srv.error = `couldn't listen on port ${port} used by ${srv.busy}`
132 }
133 console.error(srv.name, srv.error)
114 - console.log(" >> try specifying a different port like: --port 8011")
134 + const k = (srv === httpSrv? portCfg : httpsPortCfg).key()
135 + console.log(` >> try specifying a different port like: --${k} 8011`)
136 resolve(0)
137 })
117 - }
118 - catch(e) {
119 - srv.error = String(e)
120 - console.error(srv.name, "couldn't listen on port", port, srv.error)
121 - resolve(0)
122 - }
123 - })
138 + })
139 + }
140 }
141
142 function stopServer(srv: http.Server) {
todo.md
+1
@@ -1,4 +1,5 @@
1 # To do
2 +- fix: exe is not using native crc32 lib
3 - plugin api to read hfs config
4 - plugin: config to be able to declare a file field (real-path) https://github.com/rejetto/hfs/issues/46
5 - fix: root without can_download breaks