import os
import subprocess
import asyncio
from aiohttp import web
# 配置
TTYD_PORT = 7681
SERVER_PORT = 7860
# 简单的 HTML 前端
HTML_CONTENT = """
HF Space Terminal
Initializing...
"""
async def handle_http(request):
return web.Response(text=HTML_CONTENT, content_type='text/html')
async def handle_ws(request):
ws = web.WebSocketResponse(heartbeat=30) # 添加心跳防止超时断开
await ws.prepare(request)
reader = None
writer = None
try:
# 连接到本地运行的 ttyd
reader, writer = await asyncio.open_connection('127.0.0.1', TTYD_PORT)
print("Connected to ttyd backend")
except Exception as e:
print(f"Failed to connect to ttyd: {e}")
await ws.close()
return ws
async def ws_to_tty():
try:
async for msg in ws:
if msg.type == web.WSMsgType.BINARY:
writer.write(msg.data)
elif msg.type == web.WSMsgType.TEXT:
# ttyd 期望字节流,将文本编码为 bytes
writer.write(msg.data.encode('utf-8'))
await writer.drain()
except Exception as e:
print(f"WS->TTY Error: {e}")
finally:
if writer:
writer.close()
try:
await writer.wait_closed()
except:
pass
async def tty_to_ws():
try:
while True:
# 读取 ttyd 的输出
data = await reader.read(4096)
if not data:
break
# 发送给前端 (作为二进制,保留所有控制字符)
await ws.send_bytes(data)
except Exception as e:
print(f"TTY->WS Error: {e}")
finally:
if writer:
writer.close()
try:
await writer.wait_closed()
except:
pass
# 并行运行两个方向的数据流
await asyncio.gather(ws_to_tty(), tty_to_ws())
if not ws.closed:
await ws.close()
return ws
async def on_startup(app):
# 启动 ttyd 子进程
# 使用 --writable 确保可写 (虽然默认通常是可写的,但显式指定更安全)
# 注意:如果之前的 ttyd 版本不支持某些参数,这里只保留最基础的 -p
cmd = ["/usr/local/bin/ttyd", "-p", str(TTYD_PORT), "/bin/bash"]
print(f"Starting ttyd: {' '.join(cmd)}")
# 启动进程
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
app['ttyd_proc'] = proc
print("ttyd started successfully.")
def main():
app = web.Application()
app.router.add_get('/', handle_http)
app.router.add_get('/ws', handle_ws)
app.on_startup.append(on_startup)
print(f"Starting server on port {SERVER_PORT}...")
# print=None 禁止 aiohttp 自带的日志,避免干扰
web.run_app(app, host='0.0.0.0', port=SERVER_PORT, print=lambda x: None)
if __name__ == '__main__':
main()