Skip to content

Application

The Flask application factory and the server entry point.

WebATM.app

WebATM.app

Build the Flask web server and Socket.IO application for WebATM.

This web application provides a browser-based interface for BlueSky - The Open Air Traffic Simulator developed by TU Delft (Delft University of Technology).

This module is separated into different focused modules in the server/ package:

  • server/session_manager.py: Session tracking
  • server/routes.py: Basic Flask routes (index, commands, config, health)
  • server/server_status.py: BlueSky server status
  • server/socket_handlers.py: Socket.IO event handlers

create_app

create_app()

Create and configure the Flask application with all routes and handlers.

Sets up logging (including Werkzeug HTTP access logs), the session manager, the Socket.IO server, and the global BlueSky proxy, then registers the Flask routes, server-status routes, and Socket.IO event handlers. When the WEBATM_INTEGRATED environment variable is "1", the optional webatm_integrated extensions are registered as well (best-effort; a failure never breaks the core app).

Returns:

Type Description
tuple

A (app, socketio) pair with the configured :class:flask.Flask application and its :class:flask_socketio.SocketIO instance.

Source code in WebATM/app.py
def create_app():
    """Create and configure the Flask application with all routes and handlers.

    Sets up logging (including Werkzeug HTTP access logs), the session manager,
    the Socket.IO server, and the global BlueSky proxy, then registers the
    Flask routes, server-status routes, and Socket.IO event handlers. When the
    ``WEBATM_INTEGRATED`` environment variable is ``"1"``, the optional
    ``webatm_integrated`` extensions are registered as well (best-effort; a
    failure never breaks the core app).

    Returns:
        tuple: A ``(app, socketio)`` pair with the configured
            :class:`flask.Flask` application and its
            :class:`flask_socketio.SocketIO` instance.
    """

    # Create Flask app
    app = Flask(
        __name__,
        template_folder=Path(__file__).parent / "templates",
        static_folder=Path(__file__).parent / "static",
    )
    app.config["SECRET_KEY"] = "WebATM_ui_secret_key"

    # Configure Flask and Werkzeug logging to use WebATM logger
    logger = get_logger("app")
    app.logger = logger

    # Configure Werkzeug (Flask's web server) to use WebATM logger for HTTP access logs
    werkzeug_logger = logging.getLogger("werkzeug")
    werkzeug_logger.handlers = []  # Clear default handlers
    werkzeug_logger.setLevel(logging.INFO)
    # Copy handlers from WebATM logger to Werkzeug logger
    for handler in logging.getLogger("WebATM").handlers:
        werkzeug_logger.addHandler(handler)

    # Initialize session manager
    session_manager = SessionManager()

    # Create SocketIO instance
    socketio = SocketIO(
        app,
        cors_allowed_origins="*",
        ping_timeout=60,
        ping_interval=25,
        async_mode="threading",
        logger=False,
        engineio_logger=False,
    )

    # Create and configure the BlueSky proxy instance
    bluesky_proxy = BlueSkyProxy()
    bluesky_proxy.socketio = socketio
    set_bluesky_proxy(bluesky_proxy)  # Set it globally for the subscriber callbacks

    # NB: subscribers are NOT registered here. The proxy creates its network
    # client lazily on connect (ZMQ pattern), so at app-creation time
    # bluesky_client is still None and there is nothing to attach to.
    # register_subscribers() is therefore called on connect instead -- by the
    # /api/server/config route (standalone) and by the auto-start hook
    # (integrated), both right after start_client() builds the client.

    # Store proxy reference in app for access in routes
    app.bluesky_proxy = bluesky_proxy

    # === Error Handlers ===
    @app.errorhandler(Exception)
    def handle_exception(e):
        """Return 500 for unexpected errors while preserving HTTP error codes.

        HTTP errors keep their status (404, 405, ...) instead of being masked
        as 500.
        """
        if isinstance(e, HTTPException):
            return e
        return jsonify({"error": "Internal server error"}), 500

    # === Register Routes and Handlers ===

    # Register basic routes (index, commands, server config, health/status)
    register_basic_routes(app, session_manager)

    # Register BlueSky server control routes (start/stop/restart/status/logs)
    register_server_status_routes(app)

    # Register all Socket.IO event handlers
    register_socket_handlers(socketio, session_manager)

    # Expose the session manager so optional extensions can reach it.
    # Harmless and unused in the default build.
    app.session_manager = session_manager

    # Optional integrated extensions: BlueSky server lifecycle control and
    # live log streaming. This is a no-op in the default build -- the
    # WEBATM_INTEGRATED env var is unset and the webatm_integrated package is
    # not installed, so the import is skipped or caught. The core package
    # never imports webatm_integrated; the dependency points the other way.
    if os.environ.get("WEBATM_INTEGRATED") == "1":
        try:
            import webatm_integrated

            webatm_integrated.register(
                app,
                socketio,
                session_manager=session_manager,
                bluesky_proxy=bluesky_proxy,
            )
            logger.info("Integrated extensions registered (webatm_integrated)")
        except Exception as e:  # best-effort: never break the core app
            logger.warning(f"Integrated extensions not loaded: {e}")

    return app, socketio

WebATM.main

WebATM.main

Start the WebATM web server and manage its lifecycle.

start_WebATM

start_WebATM(hostname=None, port=8082, debug=False)

Start the WebATM web server.

Creates the Flask/Socket.IO application, sets the default BlueSky server IP on the proxy (without connecting), and runs the web server until it exits, at which point the proxy client is stopped.

The web server bind address is taken from the WEB_HOST environment variable (default "localhost").

Parameters:

Name Type Description Default
hostname str | None

BlueSky server hostname/IP to use as the default. Falls back to the BLUESKY_SERVER_HOST environment variable, then "localhost".

None
port int

Web server port. The WEB_PORT environment variable, if set, takes precedence.

8082
debug bool

Whether to run the Socket.IO server in debug mode.

False
Source code in WebATM/main.py
def start_WebATM(hostname=None, port=8082, debug=False):
    """Start the WebATM web server.

    Creates the Flask/Socket.IO application, sets the default BlueSky server IP
    on the proxy (without connecting), and runs the web server until it exits,
    at which point the proxy client is stopped.

    The web server bind address is taken from the ``WEB_HOST`` environment
    variable (default ``"localhost"``).

    Args:
        hostname (str | None): BlueSky server hostname/IP to use as the default.
            Falls back to the ``BLUESKY_SERVER_HOST`` environment variable, then
            ``"localhost"``.
        port (int): Web server port. The ``WEB_PORT`` environment variable, if
            set, takes precedence.
        debug (bool): Whether to run the Socket.IO server in debug mode.
    """
    # Get BlueSky server hostname from environment variable or parameter
    bluesky_host = hostname or os.environ.get("BLUESKY_SERVER_HOST", "localhost")
    web_port = int(os.environ.get("WEB_PORT", port))
    web_host = os.environ.get("WEB_HOST", "localhost")

    # create the app
    app, socketio = create_app()

    # Set default server IP on the client but don't connect - wait for user to configure
    app.bluesky_proxy.server_ip = bluesky_host
    logger.info("BlueSky Proxy initialized (not connected to BlueSky server)")
    logger.info(f"Default BlueSky server IP set to: {bluesky_host}")
    logger.info("Ready - Connect to BlueSky server via WebATM")

    try:
        logger.info(f"Starting WebATM on http://{web_host}:{web_port}")
        # Suppress Flask development server warning for local use
        os.environ["FLASK_ENV"] = "production"
        socketio.run(
            app,
            host=web_host,
            port=web_port,
            debug=debug,
            use_reloader=False,
            allow_unsafe_werkzeug=True,
        )
    finally:
        logger.info("Shutting down WebATM...")
        app.bluesky_proxy.stop_client("shutdown")
        logger.info("Shutdown complete")