:3000

Oasis Proxy

Routes, HLS + MJPEG, security headers

Mapa de Rutas — Arq. 3

RouteUpstreamContent-TypePropósito
GET /videogo2rtc :1984/api/frame.mjpegmultipart/x-mixed-replaceMJPEG stream (LAN)
GET /video.m3u8go2rtc :1984/api/stream.m3u8application/vnd.apple.mpegurlHLS playlist (remoto)
GET /snapshotgo2rtc :1984/api/frame.jpegimage/jpegFrame único → SSB blob
GET /audioMumble bridge (interno)audio/oggAudio downstream chunked
GET /camtext/htmlVista LAN (template)
GET /remotetext/htmlVista remota HLS (template)
GET /statusgo2rtc :1984/api/streamsapplication/jsonHealth check
Dos vistas: /cam para LAN (MJPEG sin JS) y /remote para peers (HLS, puede necesitar hls.js). Rutas separadas → CSP separadas.

Implementación Proxy — Node.js

GET /video — MJPEG proxy
const GO2RTC = 'http://127.0.0.1:1984'; app.get('/video', (req, res) => { const up = http.get( `${GO2RTC}/api/frame.mjpeg?src=solar-cam`, (mjpeg) => { res.writeHead(200, { 'Content-Type': mjpeg.headers['content-type'], 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' }); mjpeg.pipe(res); } ); up.on('error', () => res.status(502).end('upstream down')); req.on('close', () => up.destroy()); });
GET /video.m3u8 — HLS proxy (para remoto)
app.get('/video.m3u8', (req, res) => { const up = http.get( `${GO2RTC}/api/stream.m3u8?src=solar-cam`, (hls) => { res.writeHead(200, { 'Content-Type': 'application/vnd.apple.mpegurl', 'Cache-Control': 'no-store' }); hls.pipe(res); } ); up.on('error', () => res.status(502).end()); req.on('close', () => up.destroy()); });
GET /snapshot — JPEG
app.get('/snapshot', (req, res) => { const up = http.get( `${GO2RTC}/api/frame.jpeg?src=solar-cam`, (snap) => { res.writeHead(200, { 'Content-Type': 'image/jpeg', 'Cache-Control': 'no-store' }); snap.pipe(res); } ); up.on('error', () => res.status(502).end()); req.on('close', () => up.destroy()); });

Templates HTML

Vista LAN — /cam (cero JS)
<img src="/video" alt="MJPEG stream"> <audio src="/audio" controls autoplay></audio> <p>MJPEG · Mumble OGG · cero JavaScript</p>
Vista remota — /remote (HLS)
<video src="/video.m3u8" controls autoplay></video> <audio src="/audio" controls autoplay></audio> <p>HLS H.264 · Mumble OGG</p>
HLS nativo solo en Safari. Para Firefox/Chrome añadir hls.js en /remote (afecta al CSP de esa ruta).

Security Headers — Doble CSP

CSP para /cam (LAN, cero JS)
Content-Security-Policy: default-src 'self'; img-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'none'
CSP para /remote (si hls.js necesario)
Content-Security-Policy: default-src 'self'; img-src 'self'; media-src 'self' blob:; style-src 'self' 'unsafe-inline'; script-src 'self' ← permite hls.js local
Siempre incluir: X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: no-referrer. → Poster de seguridad canónico

DRY Rules — Proxy Oasis

1. const GO2RTC = URL centralizada. Nunca hardcodear localhost:1984 en cada ruta.
2. req.on('close', () => up.destroy()) — siempre. Evitar socket leaks.
3. Cache-Control: no-store — en todo stream. No cachear datos en vuelo.
4. Un solo puerto público: :3000. go2rtc (:1984) y murmurd (:64738) son internos.
5. Dos CSP: /cam (script-src 'none') y /remote (script-src 'self' si hls.js).
6. up.on('error') — siempre manejar caída del upstream con 502.

Keywords

proxy pipe(res) CSP HLS script-src 'none' SAMEORIGIN :3000 no-store upstream.destroy() MJPEG .m3u8 SSB blob