Flask WebSockets
In modern web applications, real-time communication has become increasingly important. Traditional HTTP requests follow a request-response pattern where the client must initiate all communication. However, many applications require instant updates without refreshing the page or continuously polling the server. This is where WebSockets come in.
What are WebSockets?
WebSockets provide a persistent connection between a client and server, allowing for bidirectional communication. Unlike HTTP, which closes the connection after each request-response cycle, WebSockets keep the connection open for continuous data exchange.
Some common use cases for WebSockets include:
- Chat applications
- Live notifications
- Collaborative editing tools
- Real-time dashboards
- Online gaming
- Live sports updates
Flask-SocketIO
Flask itself doesn't have built-in WebSocket support. Instead, we can use the Flask-SocketIO
extension, which integrates Socket.IO with Flask applications. Socket.IO is a library that enables real-time, bidirectional communication between clients and servers.
Setting Up Flask-SocketIO
First, let's install the necessary packages:
pip install flask flask-socketio
Now, let's create a basic Flask application with Socket.IO:
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
socketio = SocketIO(app)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
socketio.run(app, debug=True)
Creating HTML Templates
Let's create a simple template with Socket.IO client-side integration:
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Flask-SocketIO Example</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', () => {
// Connect to the Socket.IO server
const socket = io();
// Event handlers can go here
});
</script>
</head>
<body>
<h1>Flask-SocketIO Example</h1>
<div id="messages"></div>
</body>
</html>
Handling Socket Events
Server-Side Events
In Flask-SocketIO, we use decorators to define event handlers:
@socketio.on('connect')
def handle_connect():
print('Client connected')
@socketio.on('disconnect')
def handle_disconnect():
print('Client disconnected')
@socketio.on('message')
def handle_message(data):
print('Received message:', data)
# Echo the message back to the client
socketio.emit('message', data)
Client-Side Events
On the client side, we need to update our JavaScript to handle these events:
// Update the script section in index.html
const socket = io();
// Connection events
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('disconnect', () => {
console.log('Disconnected from server');
});
// Custom event handlers
socket.on('message', (data) => {
const messagesDiv = document.getElementById('messages');
const messageElement = document.createElement('p');
messageElement.textContent = data;
messagesDiv.appendChild(messageElement);
});
// Function to send a message
function sendMessage(message) {
socket.emit('message', message);
}
Building a Simple Chat Application
Let's build a complete chat application to demonstrate WebSockets in action:
Updated Server Code
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit, join_room, leave_room
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
socketio = SocketIO(app)
@app.route('/')
def index():
return render_template('chat.html')
@socketio.on('connect')
def handle_connect():
print('Client connected:', request.sid)
@socketio.on('disconnect')
def handle_disconnect():
print('Client disconnected:', request.sid)
@socketio.on('join')
def on_join(data):
username = data['username']
room = data['room']
join_room(room)
emit('message', f'{username} has entered the room.', to=room)
@socketio.on('leave')
def on_leave(data):
username = data['username']
room = data['room']
leave_room(room)
emit('message', f'{username} has left the room.', to=room)
@socketio.on('chat_message')
def handle_message(data):
room = data['room']
emit('message', f"{data['username']}: {data['message']}", to=room)
if __name__ == '__main__':
socketio.run(app, debug=True)
Chat Template
<!-- templates/chat.html -->
<!DOCTYPE html>
<html>
<head>
<title>Flask-SocketIO Chat</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<style>
#chat-container {
width: 600px;
margin: 0 auto;
}
#messages {
height: 400px;
border: 1px solid #ccc;
overflow-y: scroll;
margin-bottom: 10px;
padding: 10px;
}
#user-form, #message-form {
margin-bottom: 10px;
}
input[type="text"] {
width: 70%;
padding: 5px;
}
button {
padding: 5px 10px;
}
</style>
</head>
<body>
<div id="chat-container">
<h1>Flask-SocketIO Chat</h1>
<div id="user-form">
<input type="text" id="username" placeholder="Username">
<input type="text" id="room" placeholder="Room">
<button id="join-btn">Join</button>
<button id="leave-btn" disabled>Leave</button>
</div>
<div id="messages"></div>
<div id="message-form">
<input type="text" id="message" placeholder="Type your message..." disabled>
<button id="send-btn" disabled>Send</button>
</div>
</div>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', () => {
const socket = io();
let activeRoom = '';
let username = '';
// DOM elements
const messagesDiv = document.getElementById('messages');
const usernameInput = document.getElementById('username');
const roomInput = document.getElementById('room');
const messageInput = document.getElementById('message');
const joinBtn = document.getElementById('join-btn');
const leaveBtn = document.getElementById('leave-btn');
const sendBtn = document.getElementById('send-btn');
// Add a message to the chat
function addMessage(message) {
const messageElement = document.createElement('p');
messageElement.textContent = message;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// Socket events
socket.on('connect', () => {
addMessage('Connected to server');
});
socket.on('disconnect', () => {
addMessage('Disconnected from server');
});
socket.on('message', (data) => {
addMessage(data);
});
// Join room
joinBtn.addEventListener('click', () => {
username = usernameInput.value.trim();
activeRoom = roomInput.value.trim();
if (username && activeRoom) {
socket.emit('join', {username: username, room: activeRoom});
addMessage(`You joined room: ${activeRoom}`);
// Update UI
usernameInput.disabled = true;
roomInput.disabled = true;
joinBtn.disabled = true;
leaveBtn.disabled = false;
messageInput.disabled = false;
sendBtn.disabled = false;
}
});
// Leave room
leaveBtn.addEventListener('click', () => {
socket.emit('leave', {username: username, room: activeRoom});
addMessage(`You left room: ${activeRoom}`);
// Update UI
usernameInput.disabled = false;
roomInput.disabled = false;
joinBtn.disabled = false;
leaveBtn.disabled = true;
messageInput.disabled = true;
sendBtn.disabled = true;
activeRoom = '';
});
// Send message
sendBtn.addEventListener('click', () => {
const message = messageInput.value.trim();
if (message) {
socket.emit('chat_message', {
username: username,
room: activeRoom,
message: message
});
messageInput.value = '';
}
});
// Allow Enter key to send messages
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !sendBtn.disabled) {
sendBtn.click();
}
});
});
</script>
</body>
</html>
Running the Application
To run the application, save the files and execute:
python app.py
Open multiple browser windows at http://localhost:5000
to test the chat functionality. You can:
- Enter a username and room name
- Join the room
- Send messages
- See messages from other users in the same room
- Leave the room
Namespaces and Rooms
Socket.IO has two important concepts for organizing connections:
Namespaces
Namespaces allow you to split the logic of your application into separate communication channels:
# Server-side
chat = socketio.namespace('/chat')
@chat.on('connect')
def chat_connect():
print('Client connected to chat namespace')
@chat.on('message')
def chat_message(data):
emit('message', data, namespace='/chat')
// Client-side
const chatSocket = io('/chat');
Rooms
Rooms are used to group connections within a namespace, as we've seen in our chat example. Rooms are useful for broadcasting messages to a subset of clients:
@socketio.on('join')
def on_join(data):
username = data['username']
room = data['room']
join_room(room)
emit('message', f'{username} has entered the room.', to=room)
Error Handling with WebSockets
It's important to handle errors in your WebSocket applications:
@socketio.on_error()
def error_handler(e):
print('An error has occurred:', e)
@socketio.on_error('/chat')
def chat_error_handler(e):
print('An error has occurred in the chat namespace:', e)
@socketio.on_error_default
def default_error_handler(e):
print('Default error handler:', e)
Authentication with WebSockets
For secure applications, you'll often need authentication:
from flask_login import current_user
@socketio.on('connect')
def handle_connect():
if not current_user.is_authenticated:
return False # Reject the connection
else:
print(f'User {current_user.username} connected')
Broadcasting to All Clients
Sometimes you need to send a message to all connected clients:
@socketio.on('broadcast_message')
def handle_broadcast(data):
emit('message', data, broadcast=True)
Deployment Considerations
When deploying WebSocket applications, there are a few considerations:
- Use an asynchronous server like Eventlet or Gevent
- Consider using a message queue like Redis for scaling
- Ensure your reverse proxy (Nginx, etc.) is configured for WebSockets
# For production with Eventlet
import eventlet
eventlet.monkey_patch()
# At the end of your file
if __name__ == '__main__':
socketio.run(app, debug=False, host='0.0.0.0')
Summary
WebSockets provide a powerful way to implement real-time bidirectional communication in Flask applications. With Flask-SocketIO, you can:
- Establish persistent connections between clients and server
- Send and receive messages in real-time
- Organize connections using namespaces and rooms
- Build interactive web applications like chat systems, dashboards, and more
WebSockets are an essential technology for modern web applications that require real-time updates and interactions.
Additional Resources
Exercises
- Enhance the chat application to show a list of active users in each room
- Implement private messaging between users
- Add typing indicators to show when a user is typing a message
- Create a collaborative drawing application using WebSockets
- Build a real-time dashboard that displays system metrics updated via WebSockets
With these skills, you can create powerful interactive web applications that respond instantly to user actions and server events!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)