Define the HTTP Server launcher plugin#

This example shows the launcher plugin that is used to start the Python HTTP server. The full source is available in the examples/example_httpserver_plugin directory on GitHub.

While this example explains some aspects of the code, for information on how to create a plugin for the Local Product Launcher, see Launcher plugin creation.

Launcher code#

The LauncherConfig class determines which options are available to the user when configuring the launcher. It exposes a single option directory, which determines which directory the server is to serve files from.


@dataclass
class LauncherConfig:
    """Defines the configuration options for the HTTP server launcher."""

    directory: str = field(default=os.getcwd())


The Launcher class actually starts the server. It needs to fulfill the interface defined by the LauncherProtocol class. Namely, this interface consists of these endpoints:

  • The start and stop methods for starting and stopping the server.

  • The check method for checking if the server is running.

  • The urls property for getting the URLs that the the server is serving requests on.


class Launcher(LauncherProtocol[LauncherConfig]):
    """Implements launching an HTTP server."""

    CONFIG_MODEL = LauncherConfig
    SERVER_SPEC = {"main": ServerType.GENERIC}

    def __init__(self, *, config: LauncherConfig):
        """Instantiate the HTTP server launcher."""
        self._config = config

    def start(self) -> None:
        """Start the HTTP server."""
        port = find_free_ports()[0]
        self._url = f"localhost:{port}"
        self._process = subprocess.Popen(
            [
                sys.executable,
                "-m",
                "http.server",
                "--directory",
                self._config.directory,
                str(port),
            ],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            text=True,
        )

    def stop(self, *, timeout: float | None = None) -> None:
        """Stop the HTTP server."""
        self._process.terminate()
        try:
            self._process.wait(timeout=timeout)
        except subprocess.TimeoutExpired:
            self._process.kill()
            self._process.wait()

    def check(self, timeout: float | None = None) -> bool:
        """Check if the server is running."""
        try:
            # As a simple check, we try to get the main page from the
            # server. If it is accessible, we the server is running.
            # If not, we assume it is not running.
            requests.get(f"http://{self._url}")
            return True
        except requests.RequestException:
            return False

    @property
    def urls(self) -> dict[str, str]:
        """Addresses on which the server is serving content."""
        return {"main": self._url}


The local product launcher uses this minimal interface to construct a ProductInstance class, which adds some additional functionality on top. For example, the ProductInstance class automatically stops the server when the Python process terminates.

Entrypoint configuration#

Besides the launcher code, the plugin must be registered by adding an entrypoint to the pyproject.toml file, as described in Register entrypoint. In this example, flit is used as a build tool. Thus, the pyproject.toml file looks like this:

[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "example_httpserver_plugin"
authors = [{name = "ANSYS, Inc.", email = "pyansys.core@ansys.com"}]
dynamic = ["version", "description"]
dependencies = ["ansys-tools-local-product-launcher", "requests"]

[project.entry-points."ansys.tools.local_product_launcher.launcher"]
"example_httpserver.direct" = "example_httpserver_plugin:Launcher"
"example_httpserver.__fallback__" = "example_httpserver_plugin:Launcher"

Two entrypoints for the local product launcher are defined:

  • direct: A launch mode that users can configure.

  • __fallback__: A fallback mode that is used if no configuration is available.

Both entrypoints use the same launcher class.