fix: Properly handle timeouts

This commit is contained in:
Myzel394 2024-11-06 14:04:28 +01:00
parent 1390ef247c
commit c60913b796
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
6 changed files with 76 additions and 49 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -5,11 +5,11 @@
}, },
"dependencies": { "dependencies": {
"hono": "^4.6.9", "hono": "^4.6.9",
"id": "^0.0.0", "ip": "^2.0.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"@types/ip": "^1.1.3" "@types/ip": "^1.1.3"
} }
} }

View File

@ -1,19 +1,11 @@
import { Hono } from 'hono' import { Hono } from 'hono'
import {getConnInfo} from "hono/bun" import { portRoute } from './routes/port'
import connectToAddress from './utils/connect-to-address'
const app = new Hono() const app = new Hono()
app.get('/:port', async context => { app.route(
const port = Number(context.req.param("port")) "/",
const info = getConnInfo(context) portRoute,
const ipAddress = info.remote.address! )
const result = await connectToAddress(ipAddress, port)
return context.json({
isOpen: result.isOpen
})
})
export default app export default app

View File

@ -7,8 +7,8 @@ import * as IP from "ip"
export const portRoute = new Hono(); export const portRoute = new Hono();
const schema = z.object({ const schema = z.object({
ip: z.string().refine(ip => !IP.isPublic(ip), "This IP address is not valid"), ip: z.string().refine(ip => IP.isPublic(ip), "This IP address is not valid"),
port: z.coerce.number().min(1).max(2**16 - 1), port: z.string().transform(Number).pipe(z.number().min(1).max(2**16 - 1)),
}); });
portRoute.get("/:port", async context => { portRoute.get("/:port", async context => {
@ -31,3 +31,23 @@ portRoute.get("/:port", async context => {
isOpen: result.isOpen isOpen: result.isOpen
}) })
}); });
portRoute.get("/:ip/:port", async context => {
const rawData = {
ip: context.req.param("ip"),
port: context.req.param("port"),
};
const parsedData = schema.safeParse(rawData);
if (!parsedData.success) {
return context.json({ error: parsedData.error }, 401);
}
const { ip, port } = parsedData.data;
const result = await connectToAddress(ip, port)
return context.json({
isOpen: result.isOpen
})
})

View File

@ -1,43 +1,54 @@
import * as net from "net"
export interface ConnectionResult { export interface ConnectionResult {
isOpen: boolean isOpen: boolean
} }
const TIMEOUT = 5 const TIMEOUT = 5_000
export default function connectToAddress(address: string, port: number): Promise<ConnectionResult> { export default function connectToAddress(address: string, port: number): Promise<ConnectionResult> {
return new Promise<ConnectionResult>(async (resolve) => { return new Promise<ConnectionResult>(async (resolve) => {
const socket = await Bun.connect({ const onIsOpen = () => {
hostname: address, if (!socket.destroyed) {
port: port, socket.end()
socket.destroy()
socket: {
open(socket) {
socket.end()
resolve({
isOpen: true
})
},
data() {
},
connectError() {
resolve({
isOpen: false
})
},
error(socket) {
socket.end()
resolve({
isOpen: false
})
},
timeout() {
resolve({
isOpen: false
})
},
} }
})
socket.timeout(TIMEOUT) resolve({
isOpen: true
})
}
const onIsClosed = () => {
if (!socket.destroyed) {
socket.end()
socket.destroy()
}
resolve({
isOpen: false
})
}
const socket = new net.Socket()
// The `setTimeout` function from a socket does not work when connecting,
// so we need to use a custom timeout function ourselves.
setTimeout(() => {
if (!socket.destroyed) {
socket.end()
socket.destroy()
}
resolve({
isOpen: false
})
}, TIMEOUT)
socket.on("ready", onIsOpen)
socket.on("close", onIsClosed)
socket.on("error", onIsClosed)
socket.on("timeout", onIsClosed)
socket.connect(port, address)
}) })
} }

4
src/utils/timeout.ts Normal file
View File

@ -0,0 +1,4 @@
export default function timeout(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}