faster nat operations
Massimo Melina committed
Sep 2, 2023 at 23:38 UTC
1a1f00dba9dc961d76466e43938a284c27bbe3af
2 files changed
+25
-12
frontend/src/BrowseFiles.ts
+1
-1
@@ -18,7 +18,7 @@ import { Checkbox, CustomCode, Spinner } from './components'
18
import { Head } from './Head'
19
import { DirEntry, state, useSnapState } from './state'
20
import { alertDialog } from './dialog'
21
-import useFetchList, { usePath } from './useFetchList'
21
+import useFetchList from './useFetchList'
22
import { useAuthorized } from './login'
23
import { acceptDropFiles, enqueue } from './upload'
24
import _ from 'lodash'
src/api.net.ts
+24
-11
@@ -2,7 +2,7 @@
2
3
import { ApiError, ApiHandlers } from './apiMiddleware'
4
import { Client } from 'nat-upnp'
5
-import { HTTP_FAILED_DEPENDENCY, HTTP_SERVICE_UNAVAILABLE, IS_MAC, IS_WINDOWS } from './const'
5
+import { HTTP_FAILED_DEPENDENCY, HTTP_SERVER_ERROR, HTTP_SERVICE_UNAVAILABLE, IS_MAC, IS_WINDOWS } from './const'
6
import axios from 'axios'
7
import {parse} from 'node-html-parser'
8
import _ from 'lodash'
@@ -10,14 +10,28 @@ import { getIps, getServerStatus } from './listen'
10
import { getProjectInfo } from './github'
11
import { httpString } from './util-http'
12
import { exec } from 'child_process'
13
+import { debounceAsync } from './misc'
14
14
-async function getNatInfo() {
15
- const client = new Client({ timeout: 3000 })
15
+const client = new Client({ timeout: 5_000 })
16
+const original = client.getGateway
17
+client.getGateway = () => {
18
+ const promise = original.apply(client)
19
+ promise.then(() => { // store in cache only if successful
20
+ console.debug('caching gateway')
21
+ // other client methods call getGateway too, so this will ensure they reuse this same result
22
+ client.getGateway = () => promise
23
+ }, ()=>{})
24
+ return promise
25
+}
26
+client.getGateway()
27
+
28
+const getNatInfo = debounceAsync(async () => {
29
+ const gettingIp = getPublicIp() // don't wait, do it in parallel
30
const res = await client.getGateway().catch(() => null)
31
const status = await getServerStatus()
32
const mappings = res && await client.getMappings().catch(() => null)
33
+ console.debug('mappings found', mappings)
34
const externalIp = res && await client.getPublicIp().catch(() => null)
20
- const publicIp = await getPublicIp() || externalIp
35
const gatewayIp = res ? new URL(res.gateway.description).hostname : await getGateway().catch(() => null)
36
const localIp = res?.address || getIps()[0]
37
const internalPort = status?.https?.listening && status.https.port || status?.http?.listening && status.http.port
@@ -27,14 +41,13 @@ async function getNatInfo() {
41
upnp: Boolean(res),
42
localIp,
43
gatewayIp,
30
- publicIp,
44
+ publicIp: await gettingIp || externalIp,
45
externalIp,
46
mapped,
33
- mappings,
47
internalPort,
48
externalPort: mapped?.public.port,
49
}
37
-}
50
+})
51
52
async function getPublicIp() {
53
const prjInfo = await getProjectInfo()
@@ -54,7 +67,7 @@ function getGateway(): Promise<string | undefined> {
67
return new Promise((resolve, reject) =>
68
exec(IS_WINDOWS || IS_MAC ? 'netstat -rn' : 'route -n', (err, out) => {
69
if (err) return reject(err)
57
- const re = IS_WINDOWS ? /(?:0\.0\.0\.0 +){2}([\d\.]+)/ : IS_MAC ? /default +([\d\.]+)/ : /^0\.0\.0\.0 +([\d\.]+)/
70
+ const re = IS_WINDOWS ? /(?:0\.0\.0\.0 +){2}([\d.]+)/ : IS_MAC ? /default +([\d.]+)/ : /^0\.0\.0\.0 +([\d.]+)/
71
resolve(re.exec(out)?.[1])
72
}) )
73
}
@@ -68,9 +81,9 @@ const apis: ApiHandlers = {
81
return new ApiError(HTTP_SERVICE_UNAVAILABLE, 'upnp failed')
82
if (!internalPort)
83
return new ApiError(HTTP_FAILED_DEPENDENCY, 'no internal port')
71
- const client = new Client()
84
if (mapped)
73
- await client.removeMapping({ private: mapped.private.port, public: mapped.public.port, protocol: 'tcp' })
85
+ try { await client.removeMapping({ private: mapped.private.port, public: mapped.public.port, protocol: 'tcp' }) }
86
+ catch (e: any) { return new ApiError(HTTP_SERVER_ERROR, 'removeMapping failed: ' + String(e) ) }
87
if (external)
88
await client.createMapping({ private: internalPort, public: external, description: 'hfs', ttl: 0 })
89
return {}
@@ -102,12 +115,12 @@ const apis: ApiHandlers = {
115
const api = (axios as any)[svc.method]
116
const body = svc.body?.replace('$IP', publicIp).replace('$PORT', String(port)) || ''
117
const res = await api(svc.url, body, {headers: svc.headers})
105
- console.debug(service, 'responded')
118
const parsed = parse(res.data).querySelector(svc.selector)?.innerText
119
if (!parsed) throw console.debug('empty:' + service)
120
const success = new RegExp(svc.regexpSuccess).test(parsed)
121
const failure = new RegExp(svc.regexpFailure).test(parsed)
122
if (success === failure) throw console.debug('inconsistent:' + service) // this result cannot be trusted
123
+ console.debug(service, 'responded', success)
124
return { success, service }
125
}))
126
}