pythonflasklong-pollingflask-socketio

How to update all clients when a webhook is recieved in Python Flask


I have a Flask server with a webhook endpoint for Meraki Alerts ("host_url/endpoint").
The server also has a page which is supposed to display all recieved alerts ("host_url/view").

What I would love to happen is that when Meraki sends a webhook to my endpoint all the clients on "host_url/view" update to show the latest alerts.

I have tried simply polling every few seconds, but even though it works I would like to avoid this solution.

I also tried using websockets, but the standard flask_socketio implementation requires messages send from the server to be within functions decorated with an @socketio.on("...") decorator.

E.g.

@socketio.on("connect")
def send_message():
    emit("msg")

But since the event that triggers such a message isn't a websocket event in itself I need to be able to send messages outside of a socketio function.

E.g.

def send_message():
    emit("msg")

My goal is to have a function send_to_all_clients(data) that I can use from everywhere in my code and that sends an event that can be recieved by a Javascript event handler.


Solution

  • To send an event to all clients, I recommend taking a look at the “Broadcasting” section of the Flask-SocketIO documentation. Here it is described how you can use socketio.emit(eventName, data) to send an event, for example from a normal route, so that it can be received by all connected clients.

    In the following example, at the push of a button an example event is sent to the webhook, which is then passed on to all clients. Using Postman would certainly be more advisable.

    from flask import (
        Flask, 
        render_template, 
        request
    )
    from flask_socketio import SocketIO
    
    app = Flask(__name__)
    sock = SocketIO(app)
    
    @app.route('/')
    def index():
        return render_template('index.html')
    
    def send_to_all_clients(data):
        sock.emit('details', data)
    
    @app.post('/webhook')
    def webhook():
        data = request.get_json(force=True)
        send_to_all_clients(data)
        return '', 200
    
    if __name__ == '__main__':
        sock.run(app)
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Index</title>
    </head>
    <body>
        <button id="snd">Call Webhook</button>
        <pre><output id="out"></output></pre>
    
        <script 
            src="https://cdn.socket.io/4.7.2/socket.io.min.js" 
            integrity="sha384-mZLF4UVrpi/QTWPA7BjNPEnkIfRFn4ZEO3Qt/HFklTJBj/gBOV8G3HcKn4NfQblz" 
            crossorigin="anonymous"></script>
    
        <script>
            (function() {
                const sock = io();
                sock.on('details', (...args) => {
                    const elem = document.getElementById('out');
                    elem.innerText += JSON.stringify(args) + '\n';
                });
    
                document.getElementById('snd').addEventListener('click', () => {
                    const data = {
                        "version": "0.1",
                        "sharedSecret": "secret",
                        "sentAt": "2021-10-07T08:38:40.522804Z",
                        "organizationId": "2930418",
                        "organizationName": "My organization",
                        "organizationUrl": "https://dashboard.meraki.com/o/VjjsAd/manage/organization/overview",
                        "networkId": "N_24329156",
                        "networkName": "Main Office",
                        "networkUrl": "https://n1.meraki.com//n//manage/nodes/list",
                        "networkTags": [],
                        "deviceSerial": "Q234-ABCD-5678",
                        "deviceMac": "00:11:22:33:44:55",
                        "deviceName": "My access point",
                        "deviceUrl": "https://n1.meraki.com//n//manage/nodes/new_list/000000000000",
                        "deviceTags": [
                            "tag1",
                            "tag2"
                        ],
                        "deviceModel": "MR",
                        "alertId": "0000000000000000",
                        "alertType": "APs came up",
                        "alertTypeId": "started_reporting",
                        "alertLevel": "informational",
                        "occurredAt": "2018-02-11T00:00:00.123450Z",
                        "alertData": {}
                    };
                    fetch('/webhook', {
                        method: 'post', 
                        body: JSON.stringify(data)
                    });
                })
            })();
        </script>
    </body>
    </html>