kaiobash-server / server.py
Silicon Valley - Admin
Enhance server.py with CORS support, versioning, and health check endpoints
49a61bb
raw
history blame
5.35 kB
# server.py
from dataclasses import dataclass, asdict
import secrets
import logging
import asyncio
import json
from typing import Tuple
from quart import Quart, websocket, request
from quart_schema import QuartSchema, validate_request, validate_response
from quart_cors import cors
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from broker import SessionBroker, SessionDoesNotExist, ClientRequest, ClientResponse, ClientError
# Configuración
VERSION = "1.0.0" # Versión de la API
TIMEOUT: int = 40
LOG_LEVEL: int = logging.DEBUG
TRUSTED_HOSTS: list[str] = ["127.0.0.1", "10.16.38.136", "10.16.3.13", "10.16.13.73"]
# Create app
app = Quart(__name__)
app = cors(app,
allow_origin=["https://*.hf.space", "https://*.huggingface.co"],
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["Content-Type"],
max_age=3600
)
QuartSchema(app)
app.asgi_app = ProxyHeadersMiddleware(app.asgi_app, trusted_hosts=TRUSTED_HOSTS)
app.logger.setLevel(LOG_LEVEL)
broker = SessionBroker()
# Modelos de datos
@dataclass
class Status:
status: str
version: str
@dataclass
class Session:
session_id: str
@dataclass
class Command:
session_id: str
command: str
@dataclass
class Read:
session_id: str
path: str
@dataclass
class Write:
session_id: str
path: str
content: str
@dataclass
class CommandResponse:
return_code: int
stdout: str
stderr: str
@dataclass
class ReadResponse:
content: str
@dataclass
class WriteResponse:
size: int
@dataclass
class ErrorResponse:
error: str
# Rutas API
@app.get("/status")
@validate_response(Status)
async def status() -> Status:
return Status(status="OK", version=VERSION)
@app.websocket('/session')
async def session_handler():
session_id = secrets.token_hex()
app.logger.info(f"{websocket.remote_addr} - NEW SESSION - {session_id}")
await websocket.send_as(Session(session_id=session_id), Session)
task = None
try:
task = asyncio.ensure_future(_receive(session_id))
async for request in broker.subscribe(session_id):
app.logger.info(f"{websocket.remote_addr} - REQUEST - {session_id} - {json.dumps(asdict(request))}")
await websocket.send_as(request, ClientRequest)
finally:
if task is not None:
task.cancel()
await task
async def _receive(session_id: str) -> None:
while True:
response = await websocket.receive_as(ClientResponse)
app.logger.info(f"{websocket.remote_addr} - RESPONSE - {session_id} - {json.dumps(asdict(response))}")
await broker.receive_response(session_id, response)
@app.post('/command')
@validate_request(Command)
@validate_response(CommandResponse, 200)
@validate_response(ErrorResponse, 500)
async def command(data: Command) -> Tuple[CommandResponse | ErrorResponse, int]:
try:
response = CommandResponse(**await broker.send_request(
data.session_id,
{'action': 'command', 'command': data.command},
timeout=TIMEOUT))
return response, 200
except SessionDoesNotExist:
app.logger.warning(f"{request.remote_addr} - INVALID SESSION ID - {repr(data.session_id)}")
return ErrorResponse('Session does not exist.'), 500
except ClientError as e:
return ErrorResponse(e.message), 500
except asyncio.TimeoutError:
return ErrorResponse('Timeout when waiting for client.'), 500
@app.post('/read')
@validate_request(Read)
@validate_response(ReadResponse, 200)
@validate_response(ErrorResponse, 500)
async def read(data: Read) -> Tuple[ReadResponse | ErrorResponse, int]:
try:
response = ReadResponse(**await broker.send_request(
data.session_id,
{'action': 'read', 'path': data.path},
timeout=TIMEOUT))
return response, 200
except SessionDoesNotExist:
app.logger.warning(f"{request.remote_addr} - INVALID SESSION ID - {repr(data.session_id)}")
return ErrorResponse('Session does not exist.'), 500
except ClientError as e:
return ErrorResponse(e.message), 500
except asyncio.TimeoutError:
return ErrorResponse('Timeout when waiting for client.'), 500
@app.post('/write')
@validate_request(Write)
@validate_response(WriteResponse, 200)
@validate_response(ErrorResponse, 500)
async def write(data: Write) -> Tuple[WriteResponse | ErrorResponse, int]:
try:
response = WriteResponse(**await broker.send_request(
data.session_id,
{'action': 'write', 'path': data.path, 'content': data.content},
timeout=TIMEOUT))
return response, 200
except SessionDoesNotExist:
app.logger.warning(f"{request.remote_addr} - INVALID SESSION ID - {repr(data.session_id)}")
return ErrorResponse('Session does not exist.'), 500
except ClientError as e:
return ErrorResponse(e.message), 500
except asyncio.TimeoutError:
return ErrorResponse('Timeout when waiting for client.'), 500
# Agregar un endpoint de health check y root
@app.route("/")
async def root():
return {"message": "Kaio API Server", "version": VERSION}
@app.route("/health")
async def health_check():
return {"status": "healthy"}
def run():
app.run(host='0.0.0.0', port=7860)
if __name__ == "__main__":
run()