Skip to main content

High-performance WebSockets for Flask apps powered by uWSGI and gevent

Project description

Flask-GWS

A high-performance WebSocket extension for Flask applications powered by uWSGI and gevent. Compatible with Python 3.7 and newer versions. Production-ready with focus on reliability and performance.

Example Usage

from flask import Flask
from flask_gws import WebSocket

app = Flask(__name__)
ws = WebSocket(app)

@ws.route('/echo')
def echo(ws):
    while True:
        msg = ws.receive()
        if msg is None:
            break
        ws.send(msg)

if __name__ == '__main__':
    app.run(debug=True, gevent=100)

Installation

Flask-GWS requires both gevent and uWSGI with WebSocket support. While gevent is automatically installed as a dependency, uWSGI requires special handling:

⚠️ IMPORTANT NOTE: Installing uWSGI with WebSocket support requires special flags for OpenSSL. The traditional method via requirements.txt (pip install uwsgi) will not work as it lacks SSL support needed for WebSockets. You must use the commands below with the specific compilation flags.

For Ubuntu/Debian:

CFLAGS="-I/usr/include/openssl" LDFLAGS="-L/usr/lib/x86_64-linux-gnu" UWSGI_PROFILE_OVERRIDE=ssl=true pip install --no-cache-dir uwsgi --no-binary :all:

For macOS (Apple Silicon):

CFLAGS="-I/opt/homebrew/opt/openssl@3/include" \
LDFLAGS="-L/opt/homebrew/opt/openssl@3/lib" \
UWSGI_PROFILE_OVERRIDE=ssl=true pip install --no-cache-dir uwsgi --no-binary :all:

After installing uWSGI with the proper SSL support, you can install Flask-GWS:

pip install flask-gws

Advanced Usage: Client Tracking and Broadcasting

Flask-GWS can be used to track connected clients and implement broadcasting functionality:

from flask import Flask
from flask_gws import WebSocket
import time

app = Flask(__name__)
ws = WebSocket(app)

# Store connected clients
clients = {}

@ws.route('/echo')
def echo(ws):
    # Store client using unique ID
    client_id = ws.id
    clients[client_id] = {
        'ws': ws,
        'connected_at': time.time()
    }
    print(f"New client connected: {client_id}, Total clients: {len(clients)}")
    
    try:
        while ws.connected:
            msg = ws.receive()
            if msg:
                ws.send(msg)
            time.sleep(0.1)
    except Exception as e:
        print(f"Error in websocket: {e}")
    finally:
        # Clean up when client disconnects
        if client_id in clients:
            print(f"Client disconnected: {client_id}")
            clients.pop(client_id, None)
        print(f"Total remaining clients: {len(clients)}")

# Broadcasting utility function
def broadcast(msg):
    disconnected = []
    
    for client_id, client_info in clients.items():
        try: 
            client_info['ws'].send(msg)
        except Exception as e:
            print(f"Error broadcasting to client {client_id}: {e}")
            disconnected.append(client_id)

    # Clean up any disconnected clients
    for client_id in disconnected:
        clients.pop(client_id, None)

# Example route that triggers a broadcast
@app.route('/notify')
def notify():
    broadcast("New notification for all clients")
    return "Notification sent to all connected clients"

Client-Side Implementation

Here's a simple but robust client implementation using ReconnectingWebSocket:

<!-- Include the ReconnectingWebSocket library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.min.js"></script>

<div>
    <div id="status">Connecting...</div>
    <div id="messages"></div>
</div>

<script>
    // WebSocket client with automatic reconnection
    const messagesEl = document.getElementById('messages');
    const statusEl = document.getElementById('status');
    
    function addMessage(text) {
        const messageEl = document.createElement('div');
        messageEl.textContent = text;
        messagesEl.appendChild(messageEl);
        messagesEl.scrollTop = messagesEl.scrollHeight;
    }
    
    function connect() {
        // Convert HTTP URL to WebSocket URL
        const url_root = window.location.origin;
        const server = url_root.replace('http', 'ws') + '/echo';
        
        // Create a reconnecting WebSocket
        window.socket = new ReconnectingWebSocket(server, [], {
            connectionTimeout: 2000,
            maxRetries: 15
        });
        
        socket.onopen = function() {
            statusEl.textContent = 'Connected';
            statusEl.style.color = 'green';
            addMessage('Connection established');
        };
        
        socket.onmessage = function(message) {
            addMessage('Received: ' + message.data);
            
            // Special command handling
            if (message.data === 'reload') {
                window.location.reload();
            }
        };
        
        socket.onclose = function() {
            statusEl.textContent = 'Disconnected (reconnecting...)';
            statusEl.style.color = 'orange';
        };
        
        socket.onerror = function() {
            statusEl.textContent = 'Connection error';
            statusEl.style.color = 'red';
        };
    }
    
    // Initialize the connection
    connect();
    
    // Example function to send a message
    function sendMessage(text) {
        if (socket && socket.readyState === WebSocket.OPEN) {
            socket.send(text);
            addMessage('Sent: ' + text);
        } else {
            addMessage('Cannot send: connection not open');
        }
    }
</script>

Deployment

You can use uWSGI's built-in HTTP router to get up and running quickly:

$ uwsgi --master --http :8080 --http-websockets --gevent 100 --wsgi-file app.py

...or call app.run, passing uwsgi any arguments you like:

app.run(debug=True, host='localhost', port=8080, master=True, gevent=100)

Development

To use Flask's interactive debugger, install werkzeug's DebuggedApplication middleware:

from werkzeug.debug import DebuggedApplication
app.wsgi_app = DebuggedApplication(app.wsgi_app, True)

Then run uWSGI with a single worker:

$ uwsgi --master --http :8080 --http-websockets --gevent 100 --workers 1 app.py

If you use app.run(debug=True), Flask-GWS will do this automatically.

WebSocket API

Flask-GWS handles the WebSocket handshake and provides a websocket client with the following methods:

  • websocket.recv() - Receive a message (blocking)
  • websocket.receive() - Alias for recv()
  • websocket.send(msg) - Send a message
  • websocket.send_binary(msg) - Send a binary message
  • websocket.recv_nb() - Non-blocking receive
  • websocket.id - Unique identifier for the connection
  • websocket.connected - Boolean indicating if the connection is still active

Credits

This project is based on code from the Flask-uWSGI-WebSocket repository, with enhancements for improved performance and additional features. Flask-GWS provides a simplified and modernized implementation focused on gevent support and contemporary Python versions.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flask_gws-0.1.0.tar.gz (9.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flask_gws-0.1.0-py3-none-any.whl (8.9 kB view details)

Uploaded Python 3

File details

Details for the file flask_gws-0.1.0.tar.gz.

File metadata

  • Download URL: flask_gws-0.1.0.tar.gz
  • Upload date:
  • Size: 9.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.32.0

File hashes

Hashes for flask_gws-0.1.0.tar.gz
Algorithm Hash digest
SHA256 85b9f9b46f0d9fa57a070060d50fa9bd290f02c0b12d40f3eb02ae11c128739a
MD5 48977aae128913804bc308e2d12dd111
BLAKE2b-256 4fda072831e7cf0976d2f8bc6c9b1f28f1e4a783b8b9c5d2d6f037be74b2b9f2

See more details on using hashes here.

File details

Details for the file flask_gws-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: flask_gws-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.32.0

File hashes

Hashes for flask_gws-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6c605dfaba8b964d4f9447dbdc77a425273832069f6f9ebee22b014a6979d8f8
MD5 93d44222718badbc6868cb50a21d29a9
BLAKE2b-256 1175d99baa34d73054c831ebb5888477f0a7a7fed18d318aa84a3ed7ca898254

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page