settings: Make the websocket settings clearer.

It is now using `WEBSOCKET_BACK_HOST`, `WEBSOCKET_BACK_PORT` and
`WEBSOCKET_FRONT_URI`.

We need to take in consideration that the "front" WebSocket address
(that clients will connect to) might be different than the "back" ip and
port which are bound in the host.

This happens for instance for reverse proxies, or when running inside
a container.

We considered using a `WEBSOCKET_TLS` setting, to try guessing the
"front" address based on `WEBSOCKET_HOST`, `WEBSOCKET_PORT` and
`WEBSOCKET_TLS`, but as the back and front address can differ, this
would need to introduce a `WEBSOCKET_URI` in any case, so we went with
just using it, and not adding an extra `WEBSOCKET_TLS`.
This commit is contained in:
Alexis Métaireau 2024-05-31 11:16:37 +02:00
parent 90780fbf6d
commit 940ae77602
5 changed files with 49 additions and 29 deletions

View file

@ -271,12 +271,15 @@ Otherwise, use any valid [python-social-auth configuration](https://python-socia
#### WEBSOCKET_ENABLED #### WEBSOCKET_ENABLED
A websocket server is packaged with uMap, and can be turned-on to activate "real-time collaboration". A WebSocket server is packaged with uMap, and can be turned-on to activate
In practice, you would need to run the websocket server and specify a set of settings. "real-time collaboration". In practice, in order to enable it, a few settings
are exposed.
Turning this setting to `True` **will make a switch available** on each map, to enable "real-time collaboration". Setting `WEBSOCKET_ENABLED` to `True` will **not** enable real-time
collaboration on all the maps served by the server. Instead, a switch will be
available in the "advanced properties" of the map.
You can run the websocket server with this command: The websocket server can be run with the following command:
```bash ```bash
python -m umap.ws python -m umap.ws
@ -286,19 +289,31 @@ Configuration example:
```python ```python
WEBSOCKET_ENABLED = True WEBSOCKET_ENABLED = True
WEBSOCKET_HOST = "localhost" WEBSOCKET_BACK_HOST = "localhost"
WEBSOCKET_PORT = 8002 WEBSOCKET_BACK_PORT = 8002
WEBSOCKET_URI = "ws://localhost:8002" WEBSOCKET_FRONT_URI = "ws://localhost:8002"
``` ```
These settings can also be set with the (same names) environment variables. These settings can also be set with the (same names) environment variables.
#### WEBSOCKET_HOST #### WEBSOCKET_BACK_HOST
#### WEBSOCKET_PORT #### WEBSOCKET_BACK_PORT
The host and port for the websocket server. The internal host and port the websocket server will connect to.
#### WEBSOCKET_URI #### WEBSOCKET_FRONT_URI
The connection string that will be used by the client to connect to the websocket server. The connection string that will be used by the client to connect to the
Use `wss://host:port` if the server is behind TLS, and `ws://host:port` otherwise. websocket server. In practice, as it's useful to put the WebSocket server behind
TLS encryption, the values defined by `WEBSOCKET_FRONT_URI` are different than
the values defined by `WEBSOCKET_BACK_PORT` and `WEBSOCKET_BACK_HOST`.
This value is comprised of three parts:
```
protocol://host:port
```
- `protocol`: can either be `ws` for plain unencrypted WebSockets, or `wss` when using TLS encryption.
- `host`: is the address where the connection will be sent. It should be public facing.
- `port`: is the port that is open on the host.

View file

@ -1,11 +1,14 @@
"""Base settings shared by all environments""" """Base settings shared by all environments"""
# Import global settings to make it easier to extend settings. # Import global settings to make it easier to extend settings.
import os
from email.utils import parseaddr from email.utils import parseaddr
import environ import environ
from django.conf.locale import LANG_INFO from django.conf.locale import LANG_INFO
import umap as project_module
env = environ.Env() env = environ.Env()
# ============================================================================= # =============================================================================
@ -137,9 +140,6 @@ FORM_RENDERER = "django.forms.renderers.DjangoTemplates"
# Calculation of directories relative to the project module location # Calculation of directories relative to the project module location
# ============================================================================= # =============================================================================
import os
import umap as project_module
PROJECT_DIR = os.path.dirname(os.path.realpath(project_module.__file__)) PROJECT_DIR = os.path.dirname(os.path.realpath(project_module.__file__))
@ -310,6 +310,6 @@ LOGGING = {
# WebSocket configuration # WebSocket configuration
WEBSOCKET_ENABLED = env.bool("WEBSOCKET_ENABLED", default=False) WEBSOCKET_ENABLED = env.bool("WEBSOCKET_ENABLED", default=False)
WEBSOCKET_HOST = env("WEBSOCKET_HOST", default="localhost") WEBSOCKET_BACK_HOST = env("WEBSOCKET_BACK_HOST", default="localhost")
WEBSOCKET_PORT = env.int("WEBSOCKET_PORT", default=8001) WEBSOCKET_BACK_PORT = env.int("WEBSOCKET_BACK_PORT", default=8001)
WEBSOCKET_URI = env("WEBSOCKET_URI", default="ws://localhost:8001") WEBSOCKET_FRONT_URI = env("WEBSOCKET_FRONT_URI", default="ws://localhost:8001")

View file

@ -27,5 +27,5 @@ PASSWORD_HASHERS = [
WEBSOCKET_ENABLED = True WEBSOCKET_ENABLED = True
WEBSOCKET_PORT = "8010" WEBSOCKET_BACK_PORT = "8010"
WEBSOCKET_URI = "ws://localhost:8010" WEBSOCKET_FRONT_URI = "ws://localhost:8010"

View file

@ -504,7 +504,7 @@ class MapDetailMixin:
"umap_version": VERSION, "umap_version": VERSION,
"featuresHaveOwner": settings.UMAP_DEFAULT_FEATURES_HAVE_OWNERS, "featuresHaveOwner": settings.UMAP_DEFAULT_FEATURES_HAVE_OWNERS,
"websocketEnabled": settings.WEBSOCKET_ENABLED, "websocketEnabled": settings.WEBSOCKET_ENABLED,
"websocketURI": settings.WEBSOCKET_URI, "websocketURI": settings.WEBSOCKET_FRONT_URI,
} }
created = bool(getattr(self, "object", None)) created = bool(getattr(self, "object", None))
if (created and self.object.owner) or (not created and not user.is_anonymous): if (created and self.object.owner) or (not created and not user.is_anonymous):

View file

@ -83,15 +83,20 @@ async def handler(websocket):
async def main(): async def main():
if not settings.WEBSOCKET_ENABLED: if not settings.WEBSOCKET_ENABLED:
print("WEBSOCKET_ENABLED should be set to True to run the WebSocket Server") msg = (
"WEBSOCKET_ENABLED should be set to True to run the WebSocket Server. "
"See the documentation at "
"https://docs.umap-project.org/en/stable/config/settings/#websocket_enabled "
"for more information."
)
print(msg)
exit(1) exit(1)
async with serve(handler, settings.WEBSOCKET_HOST, settings.WEBSOCKET_PORT): host = settings.WEBSOCKET_BACK_HOST
print( port = settings.WEBSOCKET_BACK_PORT
(
f"Waiting for connections on {settings.WEBSOCKET_HOST}:{settings.WEBSOCKET_PORT}" async with serve(handler, host, port):
) print(f"Waiting for connections on {host}:{port}")
)
await asyncio.Future() # run forever await asyncio.Future() # run forever