mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
113 lines
3.7 KiB
Python
113 lines
3.7 KiB
Python
"""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
|
|
)
|
|
|
|
@app.get('/')
|
|
async def root() -> dict[str, str]:
|
|
"""Root endpoint to check if the server is running."""
|
|
return {'status': 'File viewer server is running'}
|
|
|
|
@app.get('/view')
|
|
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')
|