refactor(server): porta singola, /display e /controller come percorsi
Unifica i due server Express (display :3000, controller :3001) in un unico processo su PORT (default 3000). Le route /display e /controller servono rispettivamente index.html e controller.html. In sviluppo elimina il server proxy su :3001; il plugin Vite riscrive /display → / e /controller → /controller.html internamente. printServerInfo aggiornata alla firma a porta singola.
This commit is contained in:
@@ -9,32 +9,26 @@ import { printServerInfo } from './src/server-utils.js'
|
|||||||
const __filename = fileURLToPath(import.meta.url)
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
const __dirname = dirname(__filename)
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
// --- Configurazione del server ---
|
const PORT = process.env.PORT || 3000
|
||||||
|
const distDir = join(__dirname, 'dist')
|
||||||
|
|
||||||
const DISPLAY_PORT = process.env.PORT || 3000
|
const app = express()
|
||||||
const CONTROLLER_PORT = process.env.CONTROLLER_PORT || 3001
|
|
||||||
|
|
||||||
// ========================================
|
app.use(express.static(distDir, { index: false }))
|
||||||
// Server Display (porta principale)
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
const displayApp = express()
|
app.get(['/', '/display', '/display/*splat'], (_req, res) => {
|
||||||
|
res.sendFile(join(distDir, 'index.html'))
|
||||||
// Espone i file generati dalla build di Vite.
|
|
||||||
displayApp.use(express.static(join(__dirname, 'dist')))
|
|
||||||
|
|
||||||
// Fallback per SPA: restituisce `index.html` per tutte le route.
|
|
||||||
displayApp.get(/.*/, (_req, res) => {
|
|
||||||
res.sendFile(join(__dirname, 'dist', 'index.html'))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const displayServer = createServer(displayApp)
|
app.get(['/controller', '/controller/*splat'], (_req, res) => {
|
||||||
|
res.sendFile(join(distDir, 'controller.html'))
|
||||||
|
})
|
||||||
|
|
||||||
// Inizializza il server WebSocket condiviso.
|
const server = createServer(app)
|
||||||
const wss = new WebSocketServer({ noServer: true })
|
const wss = new WebSocketServer({ noServer: true })
|
||||||
setupWebSocketHandler(wss)
|
setupWebSocketHandler(wss)
|
||||||
|
|
||||||
displayServer.on('upgrade', (request, socket, head) => {
|
server.on('upgrade', (request, socket, head) => {
|
||||||
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
|
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
|
||||||
if (pathname === '/ws') {
|
if (pathname === '/ws') {
|
||||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||||
@@ -45,39 +39,6 @@ displayServer.on('upgrade', (request, socket, head) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
displayServer.listen(DISPLAY_PORT, '0.0.0.0', () => {
|
server.listen(PORT, '0.0.0.0', () => {
|
||||||
console.log(`[Display] Server running on port ${DISPLAY_PORT}`)
|
printServerInfo(PORT)
|
||||||
})
|
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// Server Controller (porta separata)
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
const controllerApp = express()
|
|
||||||
|
|
||||||
// Espone gli stessi file statici della build.
|
|
||||||
// IMPORTANTE: { index: false } impedisce di servire index.html (il display) sulla root.
|
|
||||||
controllerApp.use(express.static(join(__dirname, 'dist'), { index: false }))
|
|
||||||
|
|
||||||
// Fallback: restituisce `controller.html` per tutte le route.
|
|
||||||
controllerApp.get(/.*/, (_req, res) => {
|
|
||||||
res.sendFile(join(__dirname, 'dist', 'controller.html'))
|
|
||||||
})
|
|
||||||
|
|
||||||
const controllerServer = createServer(controllerApp)
|
|
||||||
|
|
||||||
// Gestisce l'upgrade WebSocket anche sulla porta del controller.
|
|
||||||
controllerServer.on('upgrade', (request, socket, head) => {
|
|
||||||
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
|
|
||||||
if (pathname === '/ws') {
|
|
||||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
||||||
wss.emit('connection', ws, request)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
socket.destroy()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
controllerServer.listen(CONTROLLER_PORT, '0.0.0.0', () => {
|
|
||||||
printServerInfo(DISPLAY_PORT, CONTROLLER_PORT)
|
|
||||||
})
|
})
|
||||||
|
|||||||
+4
-14
@@ -1,16 +1,11 @@
|
|||||||
import { networkInterfaces } from 'os'
|
import { networkInterfaces } from 'os'
|
||||||
|
|
||||||
/**
|
|
||||||
* Restituisce gli indirizzi IP di rete del sistema, escludendo loopback e bridge Docker.
|
|
||||||
* @returns {string[]} Elenco degli indirizzi IP disponibili.
|
|
||||||
*/
|
|
||||||
export function getNetworkIPs() {
|
export function getNetworkIPs() {
|
||||||
const nets = networkInterfaces()
|
const nets = networkInterfaces()
|
||||||
const networkIPs = []
|
const networkIPs = []
|
||||||
|
|
||||||
for (const name of Object.keys(nets)) {
|
for (const name of Object.keys(nets)) {
|
||||||
for (const net of nets[name]) {
|
for (const net of nets[name]) {
|
||||||
// Esclude loopback (127.0.0.1), indirizzi non IPv4 e bridge Docker (172.17.x.x, 172.18.x.x).
|
|
||||||
if (net.family === 'IPv4' &&
|
if (net.family === 'IPv4' &&
|
||||||
!net.internal &&
|
!net.internal &&
|
||||||
!net.address.startsWith('172.17.') &&
|
!net.address.startsWith('172.17.') &&
|
||||||
@@ -23,22 +18,17 @@ export function getNetworkIPs() {
|
|||||||
return networkIPs
|
return networkIPs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function printServerInfo(port = 3000) {
|
||||||
* Stampa il riepilogo di avvio del server con gli URL di accesso.
|
|
||||||
* @param {number} displayPort - Porta del display.
|
|
||||||
* @param {number} controllerPort - Porta del controller.
|
|
||||||
*/
|
|
||||||
export function printServerInfo(displayPort = 5173, controllerPort = 3001) {
|
|
||||||
const networkIPs = getNetworkIPs()
|
const networkIPs = getNetworkIPs()
|
||||||
|
|
||||||
console.log(`\nSegnapunti Server`)
|
console.log(`\nSegnapunti Server`)
|
||||||
console.log(` Display: http://127.0.0.1:${displayPort}/`)
|
console.log(` Display: http://127.0.0.1:${port}/display`)
|
||||||
console.log(` Controller: http://127.0.0.1:${controllerPort}/`)
|
console.log(` Controller: http://127.0.0.1:${port}/controller`)
|
||||||
|
|
||||||
if (networkIPs.length > 0) {
|
if (networkIPs.length > 0) {
|
||||||
console.log(`\n Controller da dispositivi remoti:`)
|
console.log(`\n Controller da dispositivi remoti:`)
|
||||||
networkIPs.forEach(ip => {
|
networkIPs.forEach(ip => {
|
||||||
console.log(` http://${ip}:${controllerPort}/`)
|
console.log(` http://${ip}:${port}/controller`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,23 +102,23 @@ describe('Server Utils', () => {
|
|||||||
// printServerInfo
|
// printServerInfo
|
||||||
// =============================================
|
// =============================================
|
||||||
describe('printServerInfo', () => {
|
describe('printServerInfo', () => {
|
||||||
it('dovrebbe stampare le porte corrette (default)', () => {
|
it('dovrebbe stampare la porta di default (3000)', () => {
|
||||||
os.networkInterfaces.mockReturnValue({})
|
os.networkInterfaces.mockReturnValue({})
|
||||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||||
printServerInfo()
|
printServerInfo()
|
||||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||||
expect(allLogs).toContain('5173')
|
expect(allLogs).toContain('3000')
|
||||||
expect(allLogs).toContain('3001')
|
expect(allLogs).toContain('/display')
|
||||||
|
expect(allLogs).toContain('/controller')
|
||||||
consoleSpy.mockRestore()
|
consoleSpy.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('dovrebbe stampare le porte personalizzate', () => {
|
it('dovrebbe stampare la porta personalizzata', () => {
|
||||||
os.networkInterfaces.mockReturnValue({})
|
os.networkInterfaces.mockReturnValue({})
|
||||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||||
printServerInfo(3000, 4000)
|
printServerInfo(8080)
|
||||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||||
expect(allLogs).toContain('3000')
|
expect(allLogs).toContain('8080')
|
||||||
expect(allLogs).toContain('4000')
|
|
||||||
consoleSpy.mockRestore()
|
consoleSpy.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ describe('Server Utils', () => {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||||
printServerInfo(3000, 3001)
|
printServerInfo(3000)
|
||||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||||
expect(allLogs).toContain('192.168.1.50')
|
expect(allLogs).toContain('192.168.1.50')
|
||||||
expect(allLogs).toContain('remoti')
|
expect(allLogs).toContain('remoti')
|
||||||
@@ -139,7 +139,7 @@ describe('Server Utils', () => {
|
|||||||
it('non dovrebbe mostrare sezione remoti se nessun IP di rete', () => {
|
it('non dovrebbe mostrare sezione remoti se nessun IP di rete', () => {
|
||||||
os.networkInterfaces.mockReturnValue({})
|
os.networkInterfaces.mockReturnValue({})
|
||||||
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||||
printServerInfo(3000, 3001)
|
printServerInfo(3000)
|
||||||
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
const allLogs = consoleSpy.mock.calls.map(c => c[0]).join('\n')
|
||||||
expect(allLogs).not.toContain('remoti')
|
expect(allLogs).not.toContain('remoti')
|
||||||
consoleSpy.mockRestore()
|
consoleSpy.mockRestore()
|
||||||
|
|||||||
+9
-106
@@ -1,30 +1,23 @@
|
|||||||
import { WebSocketServer } from 'ws'
|
import { WebSocketServer } from 'ws'
|
||||||
import { createServer as createHttpServer, request as httpRequest } from 'http'
|
|
||||||
import { setupWebSocketHandler } from './src/websocket-handler.js'
|
import { setupWebSocketHandler } from './src/websocket-handler.js'
|
||||||
import { printServerInfo } from './src/server-utils.js'
|
import { printServerInfo } from './src/server-utils.js'
|
||||||
|
|
||||||
const CONTROLLER_PORT = 3001
|
|
||||||
const DEV_PROXY_HOST = process.env.DEV_PROXY_HOST || '127.0.0.1'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin Vite che integra un server WebSocket per la gestione dello stato di gioco
|
|
||||||
* e un server separato sulla porta 3001 per il controller.
|
|
||||||
* @returns {import('vite').Plugin}
|
|
||||||
*/
|
|
||||||
export default function websocketPlugin() {
|
export default function websocketPlugin() {
|
||||||
return {
|
return {
|
||||||
name: 'vite-plugin-websocket',
|
name: 'vite-plugin-websocket',
|
||||||
configureServer(server) {
|
configureServer(server) {
|
||||||
// Inizializza un server WebSocket collegato al server HTTP di Vite.
|
|
||||||
const wss = new WebSocketServer({ noServer: true })
|
const wss = new WebSocketServer({ noServer: true })
|
||||||
|
|
||||||
// Registra i gestori WebSocket con la logica di gioco.
|
|
||||||
setupWebSocketHandler(wss)
|
setupWebSocketHandler(wss)
|
||||||
|
|
||||||
// Intercetta le richieste di upgrade WebSocket solo sul path /ws.
|
// Rewrite /display → / (index.html) e /controller → /controller.html
|
||||||
|
server.middlewares.use((req, _res, next) => {
|
||||||
|
if (req.url === '/display' || req.url === '/display/') req.url = '/'
|
||||||
|
else if (req.url === '/controller' || req.url === '/controller/') req.url = '/controller.html'
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
server.httpServer.on('upgrade', (request, socket, head) => {
|
server.httpServer.on('upgrade', (request, socket, head) => {
|
||||||
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
|
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
|
||||||
|
|
||||||
if (pathname === '/ws') {
|
if (pathname === '/ws') {
|
||||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||||
wss.emit('connection', ws, request)
|
wss.emit('connection', ws, request)
|
||||||
@@ -32,100 +25,10 @@ export default function websocketPlugin() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Avvia un server separato per il controller sulla porta 3001.
|
|
||||||
server.httpServer.once('listening', () => {
|
server.httpServer.once('listening', () => {
|
||||||
const viteAddr = server.httpServer.address()
|
const { port } = server.httpServer.address()
|
||||||
const vitePort = viteAddr.port
|
setTimeout(() => printServerInfo(port), 100)
|
||||||
|
|
||||||
startControllerDevServer(vitePort, wss)
|
|
||||||
|
|
||||||
setTimeout(() => printServerInfo(vitePort, CONTROLLER_PORT), 100)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Avvia il server di sviluppo per il controller.
|
|
||||||
* Fa da proxy verso il dev server di Vite per moduli ES, HMR, e asset.
|
|
||||||
*/
|
|
||||||
function startControllerDevServer(vitePort, wss) {
|
|
||||||
const controllerServer = createHttpServer((req, res) => {
|
|
||||||
// Se richiesta alla root, riscrive verso controller.html
|
|
||||||
let targetPath = req.url
|
|
||||||
if (targetPath === '/' || targetPath === '') {
|
|
||||||
targetPath = '/controller.html'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy verso il dev server di Vite
|
|
||||||
const proxyReq = httpRequest(
|
|
||||||
{
|
|
||||||
hostname: DEV_PROXY_HOST,
|
|
||||||
port: vitePort,
|
|
||||||
path: targetPath,
|
|
||||||
method: req.method,
|
|
||||||
headers: {
|
|
||||||
...req.headers,
|
|
||||||
host: `${DEV_PROXY_HOST}:${vitePort}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(proxyRes) => {
|
|
||||||
res.writeHead(proxyRes.statusCode, proxyRes.headers)
|
|
||||||
proxyRes.pipe(res, { end: true })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
proxyReq.on('error', (err) => {
|
|
||||||
console.error('[Controller Proxy] Error:', err.message)
|
|
||||||
if (!res.headersSent) {
|
|
||||||
res.writeHead(502)
|
|
||||||
res.end('Proxy error')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
req.pipe(proxyReq, { end: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
// Gestisce l'upgrade WebSocket anche sulla porta del controller
|
|
||||||
controllerServer.on('upgrade', (request, socket, head) => {
|
|
||||||
const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
|
|
||||||
|
|
||||||
if (pathname === '/ws') {
|
|
||||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
||||||
wss.emit('connection', ws, request)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Per l'HMR di Vite, proxare l'upgrade WebSocket verso Vite
|
|
||||||
const proxyReq = httpRequest({
|
|
||||||
hostname: DEV_PROXY_HOST,
|
|
||||||
port: vitePort,
|
|
||||||
path: request.url,
|
|
||||||
method: 'GET',
|
|
||||||
headers: request.headers,
|
|
||||||
})
|
|
||||||
|
|
||||||
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
|
|
||||||
socket.write(
|
|
||||||
`HTTP/1.1 101 Switching Protocols\r\n` +
|
|
||||||
Object.entries(proxyRes.headers)
|
|
||||||
.map(([k, v]) => `${k}: ${v}`)
|
|
||||||
.join('\r\n') +
|
|
||||||
'\r\n\r\n'
|
|
||||||
)
|
|
||||||
proxySocket.pipe(socket)
|
|
||||||
socket.pipe(proxySocket)
|
|
||||||
})
|
|
||||||
|
|
||||||
proxyReq.on('error', (err) => {
|
|
||||||
console.error('[Controller Proxy] WS upgrade error:', err.message)
|
|
||||||
socket.destroy()
|
|
||||||
})
|
|
||||||
|
|
||||||
proxyReq.end()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
controllerServer.listen(CONTROLLER_PORT, '0.0.0.0', () => {
|
|
||||||
console.log(`[Controller] Dev server running on port ${CONTROLLER_PORT}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user