mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
feat(compute): add SPICE WebSocket console support
Add WebSocket-based console support for SPICE protocol, enabling
browser-based graphical console access with enhanced features
like clipboard sharing, USB redirection, and audio streaming.
Changes:
- Add start_spice_websocket_console() method in BaseNode for SPICE
WebSocket to TCP bridging with bidirectional binary forwarding
- Add /console/spice WebSocket endpoints in QEMU and Docker compute APIs
- Add /console/spice WebSocket proxy endpoint in controller API
- Add comprehensive API documentation in docs/features/
Supported console types:
- spice: Basic SPICE protocol support
- spice+agent: SPICE with spice-vdagent for enhanced features
Architecture:
- Browser WebSocket → Controller (JWT + RBAC) → Compute (Basic Auth)
- No external websockify processes required
- Consistent with existing VNC WebSocket implementation
This commit is contained in:
1 parent
ba92b405da
commit
c98f802362
5 files changed
+1046
No files matched your search
@@ -611,6 +611,76 @@ class BaseNode:
|
||||
vnc_writer.close()
|
||||
await vnc_writer.wait_closed()
|
||||
|
||||
async def start_spice_websocket_console(self, websocket):
|
||||
"""
|
||||
Connect to SPICE console using WebSocket.
|
||||
|
||||
:param websocket: FastAPI WebSocket object
|
||||
"""
|
||||
|
||||
log.info(
|
||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to compute "
|
||||
f"SPICE console WebSocket"
|
||||
)
|
||||
|
||||
if self.status != "started":
|
||||
await websocket.close(code=1000)
|
||||
raise NodeError(f"Node {self.name} is not started")
|
||||
if self._console_type not in ("spice", "spice+agent"):
|
||||
await websocket.close(code=1000)
|
||||
raise NodeError(f"Node {self.name} console type is not SPICE")
|
||||
|
||||
try:
|
||||
spice_reader, spice_writer = await asyncio.open_connection(
|
||||
self._manager.port_manager.console_host,
|
||||
self.console # SPICE port
|
||||
)
|
||||
log.info(f"Connected to SPICE server {self._manager.port_manager.console_host}:{self.console}")
|
||||
except ConnectionError as e:
|
||||
await websocket.close(code=1000)
|
||||
raise NodeError(f"Cannot connect to node {self.name} SPICE server: {e}")
|
||||
|
||||
async def ws_forward(spice_writer):
|
||||
# Browser → SPICE: Forward binary WebSocket data to SPICE server
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_bytes()
|
||||
if data:
|
||||
spice_writer.write(data)
|
||||
await spice_writer.drain()
|
||||
except WebSocketDisconnect:
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute "
|
||||
f"SPICE console WebSocket"
|
||||
)
|
||||
|
||||
async def spice_forward(spice_reader):
|
||||
# SPICE → Browser: Forward SPICE data to WebSocket
|
||||
try:
|
||||
while not spice_reader.at_eof():
|
||||
data = await spice_reader.read(65536) # Larger buffer for SPICE data
|
||||
if data:
|
||||
await websocket.send_bytes(data)
|
||||
except Exception as e:
|
||||
log.warning(f"Exception while forwarding SPICE data to WebSocket: {e}")
|
||||
|
||||
# Keep forwarding WebSocket data in both directions
|
||||
if sys.version_info >= (3, 11, 0):
|
||||
# Starting with Python 3.11, passing coroutine objects to wait() directly is forbidden.
|
||||
aws = [asyncio.create_task(ws_forward(spice_writer)), asyncio.create_task(spice_forward(spice_reader))]
|
||||
else:
|
||||
aws = [ws_forward(spice_writer), spice_forward(spice_reader)]
|
||||
|
||||
done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED)
|
||||
for task in done:
|
||||
if task.exception():
|
||||
log.warning(f"Exception while forwarding WebSocket data to SPICE server: {task.exception()}")
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
|
||||
spice_writer.close()
|
||||
await spice_writer.wait_closed()
|
||||
|
||||
@property
|
||||
def aux(self):
|
||||
"""
|
||||
|
||||
Reference in new issue
Block a user