Python asyncio port of the PeerJS signalling server
Project description
python-peerjs-server
An asyncio-based Python port of the PeerJS signalling server, the WebSocket
broker that PeerJS clients use to discover each other and exchange WebRTC offers/answers/candidates.
It's feature-complete and wire-compatible with the original: WebSocket session handling, reconnect, and the HTTP API are all implemented, plus a few deployment conveniences, like rate limiting, proxied-IP support, and bounded queues.
Features
- WebSocket signalling at
{path}/peerjswithid/token/keyquery-param auth - Reconnect support (same
id+tokenre-attaches the socket and drains queued messages) - Per-peer message queueing for offline destinations, with periodic
EXPIREsweeps - Periodic dead-connection reaping based on heartbeat pings
- HTTP API:
GET {path}{key}/id(fresh peer id) andGET {path}{key}/peers(peer list, opt-in viaallow_discovery) - CORS header support
- Fixed-window per-IP rate limiting on the HTTP API
- Optional
X-Forwarded-Fortrust for real client IPs when running behind a reverse proxy
Requirements
- Python 3.13+
websockets>=14(the only runtime dependency, installed automatically below)
Installation
pip install -e .
Usage
Run as a console script:
peerjs-server --port 9000 --key peerjs
or as a module:
python -m python_peerjs_server --port 9000 --key peerjs
CLI options
| Flag | Env var | Default | Description |
|---|---|---|---|
--host |
PEERJS_HOST |
:: |
Bind host |
--port |
PEERJS_PORT |
9000 |
Bind port |
--key |
PEERJS_KEY |
peerjs |
Access key clients must supply |
--path |
PEERJS_PATH |
/ |
Path prefix for HTTP/WS routes |
--allow-discovery |
PEERJS_ALLOW_DISCOVERY |
off | Expose GET /peers |
--cors-origin |
PEERJS_CORS_ORIGIN |
unset | CORS origin for HTTP API responses |
--log-level |
PEERJS_LOG_LEVEL |
INFO |
Logging level |
Other server behaviour (timeouts, rate limits, queue caps, proxy trust) is configured via
python_peerjs_server.config.Config when embedding the server programmatically; see
src/python_peerjs_server/config.py for the full set of fields and their
defaults.
Embedding in your own asyncio app
import asyncio
from python_peerjs_server.config import Config
from python_peerjs_server.main import peerjs_serve
asyncio.run(peerjs_serve(Config(port=9000, key="peerjs")))
Logging
If you're running via the CLI, --log-level/PEERJS_LOG_LEVEL already configures this for you (it calls
logging.basicConfig); the rest of this section is for embedding python_peerjs_server in your own app.
Every module logs under the python_peerjs_server namespace (logging.getLogger(__name__)), so that one logger name
is the knob to turn:
import logging
# See python_peerjs_server logs at INFO+ on stderr
logging.getLogger("python_peerjs_server").setLevel(logging.INFO)
logging.getLogger("python_peerjs_server").addHandler(logging.StreamHandler())
# Or, if your app already configured the root logger, just set the level;
# propagation does the rest
logging.getLogger("python_peerjs_server").setLevel(logging.DEBUG)
# Silence a noisy sub-logger specifically
logging.getLogger("python_peerjs_server.services.web_socket_session").setLevel(logging.WARNING)
A NullHandler is attached by default, so the library stays silent unless logging is configured (either on
python_peerjs_server
or higher up).
Connecting a PeerJS client
This server only implements the signalling protocol; clients connect using the PeerJS client library (docs, GitHub):
const peer = new Peer("some-id", {
host: "localhost",
port: 9000,
path: "/",
key: "peerjs",
});
Runnable versions of the standalone and FastAPI embedding patterns live in examples/:
standalone.py and fastapi_app.py.
CORS
The PeerJS client calls the HTTP API (GET {path}{key}/id) before it opens the WebSocket. If your page is served
from a different origin than the broker (e.g. a Vite/CRA dev server on localhost:5173 talking to a broker on
localhost:9000), the browser will block that request with a CORS error unless you set --cors-origin /
PEERJS_CORS_ORIGIN / Config.cors_origin to the exact origin (scheme + host + port) serving the page:
peerjs-server --port 9000 --key peerjs --cors-origin http://localhost:5173
It's unset by default (no Access-Control-Allow-Origin header sent), which is fine for same-origin deployments.
Running alongside another web framework
peerjs_serve() is just a coroutine; you can run it inside whatever process already hosts your app instead of standing
up a separate service. Pass handle_signals=False so it doesn't fight your app for SIGINT/SIGTERM handling, and
pick a port distinct from your main app's.
Runnable versions of both patterns below live in examples/: tornado_app.py
and flask_app.py.
Tornado
Tornado runs on the standard asyncio event loop, so the broker can simply be scheduled as another task on it:
import asyncio
import tornado.ioloop
import tornado.web
from python_peerjs_server.config import Config
from python_peerjs_server.main import peerjs_serve
class MainHandler(tornado.web.RequestHandler):
def get(self) -> None:
self.write("hello from tornado")
async def main() -> None:
app = tornado.web.Application([(r"/", MainHandler)])
app.listen(8888)
asyncio.create_task(peerjs_serve(Config(port=9000, key="peerjs"), handle_signals=False))
await asyncio.Event().wait() # run forever
if __name__ == "__main__":
asyncio.run(main())
Flask
Flask's dev server is sync/WSGI and blocks its own thread, so the broker has to run on an event loop in a separate thread:
import asyncio
import threading
from flask import Flask
from python_peerjs_server.config import Config
from python_peerjs_server.main import peerjs_serve
app = Flask(__name__)
@app.get("/")
def index() -> str:
return "hello from flask"
def run_python_peerjs_server() -> None:
asyncio.run(peerjs_serve(Config(port=9000, key="peerjs"), handle_signals=False))
if __name__ == "__main__":
threading.Thread(target=run_python_peerjs_server, daemon=True).start()
app.run(port=8888)
For an ASGI app (FastAPI, Starlette) you can instead start it from a lifespan/startup hook with asyncio.create_task,
the same way as the Tornado example: both share a loop with no extra thread needed.
Development
pip install -e ".[dev]" # install with dev/test/lint dependencies
pytest # run tests
ruff check src # lint
ruff format src # format
mypy src # type check
bandit -r src # security scan
License
MIT, see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_peerjs_server-1.0.0b1.tar.gz.
File metadata
- Download URL: python_peerjs_server-1.0.0b1.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11a94903643ae9340f062b47d558d325f59a3b35ae853b35e0968af6d57643bd
|
|
| MD5 |
2fceef79bf14f30cea4bc011adb52115
|
|
| BLAKE2b-256 |
1228c79df117049b3f8d8ded7ef1b06dcae3f0c2e653e19e886a80d8c8a2cd4a
|
File details
Details for the file python_peerjs_server-1.0.0b1-py3-none-any.whl.
File metadata
- Download URL: python_peerjs_server-1.0.0b1-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c9d99c7f8687d51b56b01f0652393ccca3dd33decaa3650ff0bc837542a5e8f1
|
|
| MD5 |
78ae5cb99f9944fb5a5245beb4c3c7b5
|
|
| BLAKE2b-256 |
75e7118533cb00b0c1ff76c8fca934dd18b4214010e5cfd0b5bfbe67930fb674
|