File size: 5,348 Bytes
dd07930
76ca3e9
dd07930
 
76ca3e9
 
 
 
 
 
49a61bb
76ca3e9
dd07930
 
 
76ca3e9
49a61bb
76ca3e9
 
49a61bb
76ca3e9
 
dd07930
49a61bb
 
 
 
 
 
dd07930
76ca3e9
 
dd07930
 
 
76ca3e9
dd07930
 
 
 
 
 
 
 
 
 
 
 
 
 
76ca3e9
 
 
 
 
 
 
 
 
 
 
dd07930
 
 
 
 
 
76ca3e9
 
 
 
 
 
 
 
dd07930
 
 
 
 
 
 
 
49a61bb
dd07930
 
 
 
76ca3e9
dd07930
 
76ca3e9
dd07930
76ca3e9
dd07930
76ca3e9
dd07930
 
76ca3e9
 
 
dd07930
 
 
 
76ca3e9
dd07930
 
 
 
 
 
 
 
76ca3e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd07930
 
76ca3e9
 
dd07930
76ca3e9
dd07930
76ca3e9
dd07930
49a61bb
 
 
 
 
 
 
 
 
dd07930
49a61bb
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# 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()