Spaces:
Build error
Build error
""" | |
A tiny, isolated server that provides only the /view endpoint from the action execution server. | |
This server has no authentication and only listens to localhost traffic. | |
""" | |
import os | |
import threading | |
from fastapi import FastAPI, Request | |
from fastapi.responses import HTMLResponse | |
from uvicorn import Config, Server | |
from openhands.core.logger import openhands_logger as logger | |
from openhands.runtime.utils.file_viewer import generate_file_viewer_html | |
def create_app() -> FastAPI: | |
"""Create the FastAPI application.""" | |
app = FastAPI( | |
title='File Viewer Server', openapi_url=None, docs_url=None, redoc_url=None | |
) | |
async def root() -> dict[str, str]: | |
"""Root endpoint to check if the server is running.""" | |
return {'status': 'File viewer server is running'} | |
async def view_file(path: str, request: Request) -> HTMLResponse: | |
"""View a file using an embedded viewer. | |
Args: | |
path (str): The absolute path of the file to view. | |
request (Request): The FastAPI request object. | |
Returns: | |
HTMLResponse: An HTML page with an appropriate viewer for the file. | |
""" | |
# Security check: Only allow requests from localhost | |
client_host = request.client.host if request.client else None | |
if client_host not in ['127.0.0.1', 'localhost', '::1']: | |
return HTMLResponse( | |
content='<h1>Access Denied</h1><p>This endpoint is only accessible from localhost</p>', | |
status_code=403, | |
) | |
if not os.path.isabs(path): | |
return HTMLResponse( | |
content=f'<h1>Error: Path must be absolute</h1><p>{path}</p>', | |
status_code=400, | |
) | |
if not os.path.exists(path): | |
return HTMLResponse( | |
content=f'<h1>Error: File not found</h1><p>{path}</p>', status_code=404 | |
) | |
if os.path.isdir(path): | |
return HTMLResponse( | |
content=f'<h1>Error: Path is a directory</h1><p>{path}</p>', | |
status_code=400, | |
) | |
try: | |
html_content = generate_file_viewer_html(path) | |
return HTMLResponse(content=html_content) | |
except Exception as e: | |
return HTMLResponse( | |
content=f'<h1>Error viewing file</h1><p>{path}</p><p>{str(e)}</p>', | |
status_code=500, | |
) | |
return app | |
def start_file_viewer_server(port: int) -> tuple[str, threading.Thread]: | |
"""Start the file viewer server on the specified port or find an available one. | |
Args: | |
port (int, optional): The port to bind to. If None, an available port will be found. | |
Returns: | |
Tuple[str, threading.Thread]: The server URL and the thread object. | |
""" | |
# Save the server URL to a file | |
server_url = f'http://localhost:{port}' | |
port_path = '/tmp/oh-server-url' | |
os.makedirs(os.path.dirname(port_path), exist_ok=True) | |
with open(port_path, 'w') as f: | |
f.write(server_url) | |
logger.info(f'File viewer server URL saved to /tmp/oh-server-url: {server_url}') | |
logger.info(f'Starting file viewer server on port {port}') | |
app = create_app() | |
config = Config(app=app, host='127.0.0.1', port=port, log_level='error') | |
server = Server(config=config) | |
# Run the server in a new thread | |
thread = threading.Thread(target=server.run, daemon=True) | |
thread.start() | |
return server_url, thread | |
if __name__ == '__main__': | |
url, thread = start_file_viewer_server(port=8000) | |
# Keep the main thread running | |
try: | |
thread.join() | |
except KeyboardInterrupt: | |
logger.info('Server stopped') | |