# Hosting a WebSocket server An in-memory `Server` "hosts" all `perspective.Table` and `perspective.View` instances created by its connected `Client`s. Hosted tables/views can have their methods called from other sources than the Python server, i.e. by a `perspective-viewer` running in a JavaScript client over the network, interfacing with `perspective-python` through the websocket API. The server has full control of all hosted `Table` and `View` instances, and can call any public API method on hosted instances. This makes it extremely easy to stream data to a hosted `Table` using `.update()`: ```python server = perspective.Server() client = server.new_local_client() table = client.table(data, name="data_source") for i in range(10): # updates continue to propagate automatically table.update(new_data) ``` The `name` provided is important, as it enables Perspective in JavaScript to look up a `Table` and get a handle to it over the network. Otherwise, `name` will be assigned randomly and the `Client` must look this up with `Client.get_hosted_table_names()` ## Client/Server Replicated Mode Using Tornado and [`PerspectiveTornadoHandler`](python.md#perspectivetornadohandler), as well as `Perspective`'s JavaScript library, we can set up "distributed" Perspective instances that allows multiple browser `perspective-viewer` clients to read from a common `perspective-python` server, as in the [Tornado Example Project](https://github.com/perspective-dev/perspective/tree/master/examples/python-tornado). This architecture works by maintaining two `Tables`—one on the server, and one on the client that mirrors the server's `Table` automatically using `on_update`. All updates to the table on the server are automatically applied to each client, which makes this architecture a natural fit for streaming dashboards and other distributed use-cases. In conjunction with [multithreading](#multi-threading), distributed Perspective offers consistently high performance over large numbers of clients and large datasets. _*server.py*_ ```python from perspective import Server from perspective.handlers.tornado import PerspectiveTornadoHandler # Create an instance of Server, and host a Table SERVER = Server() CLIENT = SERVER.new_local_client() # The Table is exposed at `localhost:8888/websocket` with the name `data_source` client.table(data, name = "data_source") app = tornado.web.Application([ # create a websocket endpoint that the client JavaScript can access (r"/websocket", PerspectiveTornadoHandler, {"perspective_server": SERVER}) ]) # Start the Tornado server app.listen(8888) loop = tornado.ioloop.IOLoop.current() loop.start() ``` Instead of calling `load(server_table)`, create a `View` using `server_table` and pass that into `viewer.load()`. This will automatically register an `on_update` callback that synchronizes state between the server and the client. _*index.html*_ ```html ``` For a more complex example that offers distributed editing of the server dataset, see [client_server_editing.html](https://github.com/perspective-dev/perspective/blob/master/examples/python-tornado/client_server_editing.html). We also provide examples for Starlette/FastAPI and AIOHTTP: - [Starlette Example Project](https://github.com/perspective-dev/perspective/tree/master/examples/python-starlette). - [AIOHTTP Example Project](https://github.com/perspective-dev/perspective/tree/master/examples/python-aiohttp). ## Server-only Mode The server setup is identical to [Client/Server Replicated Mode](#client-server-replicated-mode) above, but instead of creating a `View`, the client calls `load(server_table)`: In Python, use `Server` and `PerspectiveTornadoHandler` to create a websocket server that exposes a `Table`. In this example, `table` is a proxy for the `Table` we created on the server. All API methods are available on _proxies_, e.g. calling `view()`, `schema()`, `update()` on `table` will pass those operations to the Python `Table`, execute the commands, and return the result back to Javascript. ```html ``` ```javascript const websocket = perspective.websocket("ws://localhost:8888/websocket"); const table = websocket.open_table("data_source"); document.getElementById("viewer").load(table); ```