I'm making a second attempt at learning web socket. May the CORS gods be merciful this time.
seen from China
seen from Germany
seen from Spain

seen from United States
seen from United States

seen from United States
seen from China
seen from Australia
seen from Maldives
seen from China
seen from Russia
seen from China
seen from Türkiye

seen from China
seen from Mexico
seen from Mexico
seen from China
seen from Canada
seen from China
seen from Sweden
I'm making a second attempt at learning web socket. May the CORS gods be merciful this time.
ohhhhh fuck just realized I'm gonna have to write websocket code in fucking kotlin (or I could use java, but I think that's worse) eventually. websockets in rust were gross enough, I don't even wanna think about how awful they're going to be in kotlin
Build a Chat App with Node.js and Socket.io ☞ https://morioh.com/p/82f77b634fd7
#morioh #NodeJS #Socketio #WebSockets
weighted too long (3/3)
Switchonyms is not the first of these games I've made. Ever since Diamond Checkers, I've used a 4-character game id as a join mechanism:
With 26 letters and 10 numbers, that's 36 ** 4 possible combinations, all but guaranteeing no overlap between active games.
In my database, or in this case, the local memory's database object, these game ids become parameters that reference the entire game object. In the url, this route becomes a database lookup.
My friend Max told me all the way back with Diamond Checkers that I should stick to letters, to avoid questions about Os and 0s, Is and 1s, etc. - especially with the fancy fonts I find. But I was sure I needed that extra space just in case I got a lot of users...
Listen. ... Okay? Okay, I might have made a mistake. So... for the obvious user experience improvement, I'll go back to my old apps and update them - so enjoy those letters next time you play!
tl;dr: the experience of actual users is more important than the functionality for potential ones; CoColors' 8-character <canvas> ids were even worse; but in general, this Jackbox-esque game id idea is awesome
project: Switchonyms
La vida después de Flash: multimedia para la Web abierta
Esta es una traducción del artículo original publicado en el blog de Mozilla Hacks. Traducción por juliabis. Flash hizo llegar vídeo, animación, sitios interactivos y, sí, anuncios a miles de millones de usuarios durante más de una década, pero ahora se se está marchando. Adobe dejará de soportar Flash para el año 2020. Firefox ya no lo soporta “fuera de la caja”, y Chrome tampoco. ¿Qué es lo…
View On WordPress
nengi instance transfers are doneee
Oh and there’s a hook for player authentication as well. It is up to the dev how/if they want to authenticate though. I need another day to clean/up test before release.
There is no formal TLS / SSH / WSS support, but it is easily added if desired.
It came out pretty interesting in the end. An instance can transfer a player to another address and transfer some data along with the player. The instances actually speak to each other before the player gets transferred. The instances do this via the same websockets that the players use, they just identify themselves with a password. The source instance generates a transferRequest, and the target instance responds with a transferResponse (or fails under a variety of circumstances). If the transfer is accepted, then the source instance sends a transfer message to the client which goes ahead and connects to the target instance, bearing a code that identifies the client. The target instance recognizes the client upon arrival, and gains access to the data that was initially transferred from the other server. Thus nothing from the client is ever trusted -- its the servers who have a conversation with each other, and they merely give the client a receipt (a reference to the conversation) which does not divulge any details to the client. This follows the security model in nengi of making the servers the authority on the game state.
Transfers are pretty quick, taking slightly longer than the round-trip latency between the machines. Transfers are asynchronous.
This enables some really basic but scalable functionality -- such as having a entrance to a dungeon, building, city, portal, etc. where the end point may reside on another server... be that another physical machine or just another instance running in another thread on the same machine. It also offers a way to secure instances, because the game logic can define and enforce rules about who can enter which instance. Lots of features rely on controlling player access (pvp arenas, party-based instances, games that require membership, etc.)
I’d like to keep adding to this demo over time... perhaps it can become a full-featured nengi example eventually. For now the demo just shows a Tiled Map Editor map, an animated character, and inter-instance travel which involves the game client resetting itself and loading up a new map. It hits a lot of the features which were requested, but isn’t much of a game...all you can do is walk around.
switching the switch (1/3)
This is a big one: a real-time, in-person, multiplayer mobile word game. I call it Switchonyms.
There's a lot to talk about, more than I can put in these three posts, so I'm going to focus on the core components that make this project unique. But for those interested: this code is raw nodeJS, with web socket(s) powering the real-time back-and-forth of a fast-paced party game about guessing words.
Let's say your friends are trying to guess the word on your screen. You can't say it, but you can give clues that hopefully spur someone else to say it... but what if you get stuck? Well, check out this switch <button> (top-right):
This triggers a socket.send() that requests a new word from the server, where I've got a big dictionary of nouns, verbs, and adjectives - for rounds 1, 2, and 3, of course.
Since each list is just a giant array, I can write a function to select a random word out of it:
Then update the global game object (making sure that word isn't already there)...
...and send that word back to the player...
...which updates the view, by both changing what word is visible and setting the style of the switch <button>:
Originally, switching meant swapping words between players - but that just meant someone else was stuck with it! After lots of play-testing, this is much, much better. Code-wise, a similar process happens when a word is successfully guessed... but more on that in part 2!
tl;dr: websockets allow for instant two-way communication between client and server; build helper functions, like chooseRandom(), for things you do often; use arbitrary attributes as CSS selectors
project: Switchonyms
FastAPI WebSockets: Async Connections, Scaling, The Multi-Worker Nightmare (2026)
FastAPI WebSockets: Navigating State, Authentication, and Multi-Worker Scaling
FastAPI's WebSocket implementation often appears straightforward, mirroring the ease of building standard HTTP endpoints. This apparent simplicity, however, frequently conceals the underlying complexities of developing robust, scalable real-time applications. A common pitfall involves a WebSocket service functioning perfectly in a single-worker development environment, only to exhibit silent failures—like messages failing to broadcast—when deployed across multiple worker processes in production. This article explores critical architectural considerations to move beyond basic WebSocket examples and build truly production-ready, distributed real-time systems.
The Deceptive Simplicity of Basic WebSocket Implementations
FastAPI's WebSocket capabilities, leveraging Starlette, offer a clean, async/await syntax that feels familiar to anyone building HTTP APIs. This ease of use, however, can be misleading. Unlike the stateless nature of HTTP, where each request is independent, WebSockets maintain a persistent, stateful TCP connection. Failing to actively manage this long-lived connection's lifecycle can lead to resource leaks, event loop blockages, and unexpected server crashes. Many introductory examples overlook the critical exception handling necessary to gracefully manage client disconnections, such as when a user closes their browser tab or loses network connectivity.
The core misunderstanding often lies in treating WebSockets as merely extended HTTP requests. Production-grade WebSocket services demand meticulous state management, comprehensive error handling, and a solid grasp of the Python asyncio event loop. A single blocking operation within a WebSocket's message processing loop can halt all other concurrent connections on that worker process.
Consider an HTTP request as a quick transaction: you send a query, get a response, and the interaction concludes. A WebSocket, by contrast, is an ongoing conversation. The server must continuously monitor the connection. If the client abruptly ends the conversation without proper signaling, the server needs mechanisms to detect this and release the associated resources, preventing a 'phantom' connection from consuming memory indefinitely.
from fastapi import FastAPI, WebSocket, WebSocketDisconnect import logging logger = logging.getLogger(__name__) app = FastAPI() # NEVER skip the try/except block. A dropped connection WILL crash the route. @app.websocket("/ws/echo") async def websocket_endpoint(websocket: WebSocket): await websocket.accept() client_id = f"{websocket.client.host}:{websocket.client.port}" logger.info(f"Client {client_id} connected.") try: while True: # This awaits indefinitely until a message arrives data = await websocket.receive_text() await websocket.send_text(f"Server Echo: {data}") except WebSocketDisconnect as e: # This is expected behavior when a client leaves. Handle it cleanly. logger.info(f"Client {client_id} disconnected gracefully. Code: {e.code}") except Exception as e: # Catch everything else to prevent the worker thread from dying logger.error(f"Unexpected error with client {client_id}: {e}") finally: # Ensure cleanup happens even if the loop breaks unexpectedly logger.debug(f"Cleanup complete for {client_id}.")
Securing WebSocket Connections: Beyond Standard HTTP Headers
A common hurdle for backend engineers transitioning to WebSockets is authentication. The familiar pattern of using an Authorization: Bearer header for HTTP requests doesn't directly translate. Browser-based WebSocket APIs explicitly prevent custom headers during the initial handshake. This means attempting to pass a bearer token in the header of a client-initiated WebSocket request will fail, necessitating alternative, secure authentication strategies.
Avoid workarounds that compromise security. Embedding long-lived JSON Web Tokens (JWTs) directly in URL query parameters is highly insecure, as URLs are frequently logged by proxies, web servers, and browser history. If query parameters are unavoidable, implement a 'ticket' system: issue a short-lived, single-use token via a secure HTTP endpoint, then immediately consume it to establish the WebSocket connection. For browser-based single-page applications, HttpOnly cookies offer a robust solution, as the browser automatically includes domain-scoped cookies during the WebSocket handshake (which starts as an HTTP Upgrade request). For public APIs or mobile clients where cookies are less practical, the "First-Message Authentication" pattern provides a secure and flexible alternative.
Picture a private club: anyone can approach the entrance (connect the socket), but access to the main area is granted only after a valid password is whispered to the bouncer (sending an authentication payload as the very first message). Failure to provide the correct credentials, or a delay in doing so, results in immediate denial of entry (socket closure).
import asyncio from fastapi import status async def verify_token(token: str) -> bool: # Implementation details... return token == "valid-secret-token" @app.websocket("/ws/secure") async def secure_endpoint(websocket: WebSocket): await websocket.accept() try: # CRITICAL: Do not wait forever. If they don't auth fast, kill it. auth_msg = await asyncio.wait_for( websocket.receive_json(), timeout=5.0 ) token = auth_msg.get("token") if not token or not await verify_token(token): # Custom 4000+ close codes signify application-level errors await websocket.close(code=4001, reason="Unauthorized: Invalid Token") return except asyncio.TimeoutError: # They connected but didn't send the password fast enough await websocket.close(code=4002, reason="Auth Timeout") return except Exception: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) return # If we reach here, the connection is authenticated. # We can now enter the main message loop. await websocket.send_json({"status": "authenticated"}) try: while True: data = await websocket.receive_text() # Process secure messages... except WebSocketDisconnect: pass
Scaling WebSockets: The Challenge of Distributed State
The most critical lesson for scalable WebSocket applications is this: in-memory connection managers are fundamentally incompatible with distributed deployments. While a simple ConnectionManager class storing active WebSocket objects works perfectly with a single Uvicorn process, production environments rarely operate this way. Deployments often involve multiple Uvicorn worker processes managed by Gunicorn, or numerous pods orchestrated by Kubernetes. These processes operate in isolation; they do not share memory. Consequently, if client A connects to worker 1 and client B connects to worker 3, worker 1 has no record of client B. Any attempt by client A to send a message intended for client B will fail silently, as worker 1 cannot route the message to a connection it doesn't manage.
FastAPI provides the transport layer for WebSockets, but it doesn't inherently offer a publish/subscribe (pub/sub) system. As soon as you scale beyond a single worker process or deploy across multiple server nodes, your WebSocket architecture transitions from a purely Python-centric challenge to a distributed systems problem. An external message broker becomes essential for synchronizing state and messages across all workers. Redis, with its robust Pub/Sub capabilities, is a widely adopted and practical solution for this.
Consider a network of independent call centers (your workers). If a customer calls center A and needs to relay information to another customer who called center C, center A cannot directly connect them. A central communication hub is required. Redis acts as this hub: when center A receives a message for a customer, it broadcasts it to the central hub. The hub then relays this message to all call centers. Only center C, which manages the target customer's connection, will pick up the message and deliver it.
import redis.asyncio as redis import json import asyncio from typing import Dict from fastapi import WebSocket class RedisPubSubManager: def __init__(self, redis_url: str = "redis://localhost:6379"): self.redis = redis.from_url(redis_url) self.pubsub = self.redis.pubsub() # Local state for THIS specific worker process only self.active_connections: Dict[str, WebSocket] = {} async def connect(self, websocket: WebSocket, user_id: str): await websocket.accept() self.active_connections[user_id] = websocket # Worker subscribes to a global channel upon first connection await self.pubsub.subscribe("global_chat") def disconnect(self, user_id: str): if user_id in self.active_connections: del self.active_connections[user_id] async def publish_message(self, message: dict): # PUSH message to Redis. We don't send to local clients directly here. await self.redis.publish("global_chat", json.dumps(message)) async def listen_to_redis(self): # Background task that listens to Redis and broadcasts to LOCAL clients async for message in self.pubsub.listen(): if message["type"] == "message": payload = json.loads(message["data"].decode()) # Broadcast to all connections managed by THIS worker dead_connections = [] for uid, conn in self.active_connections.items(): try: await conn.send_json(payload) except Exception: # Catch dead sockets during broadcast to prevent loop crashing dead_connections.append(uid) # Cleanup dead connections for uid in dead_connections: self.disconnect(uid) manager = RedisPubSubManager() # You MUST start the Redis listener task when the app starts @app.on_event("startup") async def startup_event(): asyncio.create_task(manager.listen_to_redis())
This architecture ensures that each worker publishes messages to a shared message bus (Redis) and simultaneously subscribes to that same bus. When a message arrives on the bus, every worker receives it and then forwards it to any relevant clients connected to that specific worker. This design enables seamless horizontal scaling across numerous processes and nodes, preventing message loss in distributed environments.
Read the full technical breakdown on my blog