Victoria Yelenska, Python Web Developer at NIX
Today, applications that allow instant messaging and real-time news tracking have become indispensable for us. WebSockets are one of the tools that developers use to implement such applications.
WebSocket is a bidirectional full-duplex communication protocol between a client and a server. What does this mean? Unlike the HTTP protocol, which works on the principle of "client request - server response," in WebSockets, both the server and the client can send messages to each other. Each communication party is capable of simultaneously receiving and sending data.
In WebSockets, message exchange takes place through a single communication channel. It remains open throughout the entire communication, and if necessary, either party can close it.
The WebSocket protocol exists as an overlay on TCP. The specification defines two URI schemes for websockets: ws:// for unencrypted connections and wss:// for encrypted ones. The protocol consists of an initial handshake and the direct exchange of data.
In the code block below, you can see how the handshake looks from the client's side. The header Connection: Upgrade is present here. It's also visible which upgrade is being proposed — Upgrade: websocket:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket
Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 8
The server confirms the handshake with a status code 101 — Switching Protocols, and also sends details about the new connection:
HTTP/1.1 101 Switching Protocols Upgrade: websocket
Data is sent in the form of frames with specified types. Each message can consist of one or more frames, all of which must be of the same type. These types can be text, binary data, and control frames meant not for data transmission but for protocol-level control signals. For example, signaling that the connection needs to be closed.
They are necessary for separating responsibilities within a single application. Socket.IO allows you to create multiple namespaces that behave as separate communication channels. Under the hood, they will still use the same connection. Organizing into namespaces can be logical based on modules in the application or, for example, based on shared permissions.
This is the second level of hierarchy. Within each namespace, including the default one, you can create individual channels called rooms, to which clients can join and leave. This way, you can broadcast messages to a "room," and all clients that have joined it will receive the message. This can be convenient for simultaneously sending messages to a group of users or collecting messages from multiple devices for a single user.
If we constantly need to monitor updates of information on the server, in some cases, this can be implemented using the HTTP protocol in the form of HTTP Polling.
There are several types of HTTP Polling:
Short polling: This is a simple approach but is considered bad practice. The client constantly sends requests to the server, asking if the requested information is ready. The server processes the requests as soon as they arrive and responds with an empty response if the data is not ready. In this case, a large number of requests can overload the server.
Long polling: The client sends a single request to the server and waits for a response. The server, in turn, holds onto the request until the necessary data becomes available or a specified timeout is reached. Under optimal conditions, we receive a response as soon as the data changes on the server, and we don't create as much traffic as with short polling. However, in practice, it's quite challenging to configure requests so that they are neither too frequent (and often receive no response) nor too slow (resulting in long delays on the server and wasted resources).
As a result, HTTP polling is not a very convenient approach if we need to receive real-time information.
HTTP Streaming & Server-sent Events
A good option for subscribe-only scenarios. For example, subscribing to news feed updates or receiving notifications in a browser. The client makes a single HTTP request to establish a connection, and the server responds with a series of responses as relevant data becomes available. Responses will be sent until the client closes the connection. Thus, there's no need to open and close connections for each request-response pair.
Drawbacks of this approach:
It doesn't guarantee instant message delivery: the connection can be interrupted, and the request might be queued behind other HTTP requests.
The client can't send data to the server, which prevents using this approach in applications that require true interactivity. In other words, in cases where the client and server need to send data to each other without additional intermediate requests.
Since WebSockets support bidirectional communication, they are ideal for situations that require fast two-way data exchange. This includes online games, chats, financial applications, news services, and data exchange with IoT devices.
WebSockets might not be suitable when interactivity isn't needed, there's no constant bidirectional traffic, and there are high security requirements. Prolonged open connections introduce several risks.
Like any network communication, communication using the WebSocket protocol has vulnerabilities.
Potential attack vectors include:
Cross-Site Scripting (XSS) and SQL injections — injecting malicious code into messages.
Man-in-the-Middle attacks — intercepting information from the communication channel.
Denial of Service (DoS) — sending a large number of requests to make the resource unavailable to users.
Unauthorized access to information.
How to protect yourself:
Use encrypted connections (wss://).
Implement rate limiting — restricting the number of messages per unit of time.
Add authentication during the handshake. One approach is to use ticket-based authentication. This is when the client contacts the HTTP server before connection upgrade, which generates a ticket with necessary user information. Then, the client sends this ticket to the WebSocket server, which validates it and only then grants connection permission.
Among the actively developed libraries for WebSockets in Python, the following can be mentioned:
python-socketio — a framework-independent implementation of Socket.IO for Python, offering synchronous and asynchronous variants.
Flask-SocketIO — integration of Socket.IO with Flask.
Django Channels — an extension for the Django framework that adds WebSocket protocol support, HTTP long polling, MQTT, and allows choosing between synchronous and asynchronous implementation.
a WebSocket protocol and WAMP (web application messaging protocol) implementation on Twisted and asyncio.
websockets — a library for creating WebSocket servers and clients based on the WebSocket protocol. The default implementation is built on asyncio but allows optional threading.
websocket-client — a low-level WebSocket client for Python based on the raw WebSocket protocol.
The WebSocket protocol enables interactive communication between the client and server without the need to constantly ping the server. This saves time, speeds up application performance, and allows real-time updates.
As with any technology, WebSockets are invaluable where appropriate, but their application is not always necessary. Sometimes, their use introduces additional risks and requires specific knowledge for proper and secure implementation.