Corrige VNC remoto: proxy via Next.js e fix de hidratação SSR
--- - Adicionado docker/server.mjs, entry point customizado que monkey-patcha http.createServer antes do Next.js standalone subir, injetando handler de upgrade para fazer pipe TCP de WebSocket (ws://host:3000/websockify?token=...) direto para localhost:6080; - Adicionado src/app/api/novnc/[...path]/route.ts, proxy HTTP dos assets estáticos do noVNC (vnc.html, JS, CSS) para localhost:6080, seguindo o mesmo padrão do proxy HLS; - Corrigido bug de hidratação SSR em src/app/vnc/[id]/page.tsx: URL do iframe agora é computada apenas client-side via useEffect/useState, usando path relativo /api/novnc/ em vez de http://host:6080/; - Atualizado config/supervisord.conf para iniciar Next.js com node /opt/server.mjs; - Atualizado docker/Dockerfile para copiar docker/server.mjs para /opt/server.mjs; ---
This commit is contained in:
@@ -25,7 +25,7 @@ stderr_logfile=/app/data/logs/mediamtx.log
|
|||||||
|
|
||||||
[program:nextjs]
|
[program:nextjs]
|
||||||
directory=/app
|
directory=/app
|
||||||
command=node server.js
|
command=node /opt/server.mjs
|
||||||
environment=PORT=3000,HOSTNAME=0.0.0.0,DATA_DIR=/app/data
|
environment=PORT=3000,HOSTNAME=0.0.0.0,DATA_DIR=/app/data
|
||||||
autorestart=true
|
autorestart=true
|
||||||
priority=2
|
priority=2
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ COPY --from=builder /build/public/ /app/public/
|
|||||||
COPY config/supervisord.conf /etc/supervisor/supervisord.conf
|
COPY config/supervisord.conf /etc/supervisor/supervisord.conf
|
||||||
COPY config/mediamtx.yml /etc/mediamtx.yml
|
COPY config/mediamtx.yml /etc/mediamtx.yml
|
||||||
COPY scripts/ /opt/scripts/
|
COPY scripts/ /opt/scripts/
|
||||||
|
COPY docker/server.mjs /opt/server.mjs
|
||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /opt/scripts/*.sh /entrypoint.sh
|
RUN chmod +x /opt/scripts/*.sh /entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Custom entry point: patches http.createServer before Next.js starts,
|
||||||
|
// so WebSocket upgrade requests to /websockify are proxied to localhost:6080
|
||||||
|
// (noVNC/websockify) without exposing that port publicly.
|
||||||
|
import http from "node:http"
|
||||||
|
import net from "node:net"
|
||||||
|
|
||||||
|
const _createServer = http.createServer.bind(http)
|
||||||
|
|
||||||
|
function attachWebSocketProxy(server) {
|
||||||
|
server.on("upgrade", (req, socket, head) => {
|
||||||
|
const upstream = net.connect({ host: "127.0.0.1", port: 6080 })
|
||||||
|
|
||||||
|
upstream.once("connect", () => {
|
||||||
|
let raw = `${req.method} ${req.url} HTTP/1.1\r\n`
|
||||||
|
for (const [k, v] of Object.entries(req.headers)) {
|
||||||
|
raw += `${k}: ${Array.isArray(v) ? v.join(", ") : v}\r\n`
|
||||||
|
}
|
||||||
|
raw += "\r\n"
|
||||||
|
upstream.write(raw)
|
||||||
|
if (head?.length) upstream.write(head)
|
||||||
|
socket.pipe(upstream)
|
||||||
|
upstream.pipe(socket)
|
||||||
|
})
|
||||||
|
|
||||||
|
upstream.on("error", () => { try { socket.destroy() } catch {} })
|
||||||
|
socket.on("error", () => { try { upstream.destroy() } catch {} })
|
||||||
|
socket.on("close", () => { try { upstream.destroy() } catch {} })
|
||||||
|
upstream.on("close", () => { try { socket.destroy() } catch {} })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
http.createServer = function (...args) {
|
||||||
|
const server = _createServer(...args)
|
||||||
|
attachWebSocketProxy(server)
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
await import("/app/server.js")
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { type NextRequest, NextResponse } from "next/server"
|
||||||
|
|
||||||
|
type Ctx = { params: Promise<{ path: string[] }> }
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest, { params }: Ctx) {
|
||||||
|
const { path } = await params
|
||||||
|
const searchParams = req.nextUrl.searchParams.toString()
|
||||||
|
const query = searchParams ? `?${searchParams}` : ""
|
||||||
|
const upstream = `http://localhost:6080/${path.join("/")}${query}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(upstream, {
|
||||||
|
headers: { Accept: req.headers.get("Accept") ?? "*/*" },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok) return new NextResponse(null, { status: res.status })
|
||||||
|
|
||||||
|
const headers = new Headers()
|
||||||
|
const ct = res.headers.get("content-type")
|
||||||
|
if (ct) headers.set("content-type", ct)
|
||||||
|
headers.set("cache-control", "no-cache")
|
||||||
|
|
||||||
|
return new NextResponse(res.body, { status: 200, headers })
|
||||||
|
} catch {
|
||||||
|
return new NextResponse(null, { status: 502 })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,14 +38,17 @@ function BackButton({ onClick }: { onClick: () => void }) {
|
|||||||
function VncInner() {
|
function VncInner() {
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const host = typeof window !== "undefined" ? window.location.hostname : "localhost"
|
const [vncUrl, setVncUrl] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const token = encodeURIComponent(`token=${id}`)
|
const token = encodeURIComponent(`token=${id}`)
|
||||||
const vncUrl = `http://${host}:6080/vnc.html?autoconnect=true&path=websockify%3F${token}`
|
setVncUrl(`/api/novnc/vnc.html?autoconnect=true&path=websockify%3F${token}`)
|
||||||
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative bg-black w-screen h-screen overflow-hidden">
|
<div className="relative bg-black w-screen h-screen overflow-hidden">
|
||||||
<BackButton onClick={() => router.push("/")} />
|
<BackButton onClick={() => router.push("/")} />
|
||||||
<iframe src={vncUrl} className="w-screen h-screen border-0" allowFullScreen />
|
{vncUrl && <iframe src={vncUrl} className="w-screen h-screen border-0" allowFullScreen />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user