SSE -Schnittstelle für Python-Prog.

Django, Flask, Bottle, WSGI, CGI…
Antworten
erima
User
Beiträge: 3
Registriert: Mittwoch 31. Januar 2024, 05:47

Hallo,
ich möchte auf einem Raspberry (VERSION="11 (bullseye)") und Apache2 (Apache/2.4.56 (Raspbian)) mit Python3 eine Protokolldatei (im JSON-Format) dynamisch auf einer Webseite ausgeben.
d.h. wenn ein neuer Satz in die Protokolldatei geschrieben wird, soll er autom. (analog: 'tail -f') auf der Webseite erscheinen.
Zum Übertragen auf die Webseite würde sich Server-Sent-Events (SSE) anbieten,
was auf einem ESP32 mit Arduino + C++ auch gut funktioniert.
Aber mit Python fehlt mir etwas das KnowHow. Es sollte aber auch nicht unbedingt sowas wie 'Flask' oder 'Django' notwendig sein.
Hat jemand einen guten Tip oder vielleicht schon etwas SourceCode ?
Danke im Voraus
Gruß erima
__deets__
User
Beiträge: 14544
Registriert: Mittwoch 14. Oktober 2015, 14:29

Warum kein Flask? Damit geht es einfach, und was kostet es dich, so ein Paket einzusetzen? Das du das selbst korrekt in akzeptabler Zeit hinbekommst, ist nicht zu erwarten.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Zum Übertragen auf die Webseite würde sich Server-Sent-Events (SSE) anbieten,
Oder Websockets. Dafür findet man ziemlich sicher mehr Beispiele als zu SSE.

Gruß, noisefloor
erima
User
Beiträge: 3
Registriert: Mittwoch 31. Januar 2024, 05:47

Hallo,
um das Thema abzuschließen:
es läuft jetzt, wie gewünscht. Nach HTML-Start wird die Logdatei angezeigt. Anschl. bleibt die Webseite per SSE aktiv und zeigt neue Protokolle sofort, ohne Refresh, an.
(und ohne Flask + Websockets)
Gruß erima
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

da Foren wie dieses stark von sozial Interaktion leben wäre es super, wenn du deinen Code noch zeigen würdest. Für die Leute, die in der Zukunft eine ähnlich gelagerte Problemstellung haben und dann deinen funktionierenden Code als Startpunkt für ihre eigene Problemlösung nehmen können.

Gruß, noisefloor
erima
User
Beiträge: 3
Registriert: Mittwoch 31. Januar 2024, 05:47

Hallo,
hier meine Scripts, die SSE ganz gut demonstrieren. (s.u.)
Die Scripts laufen auf einem Raspberry mit Apache2.
'sse_py.html' startet 'sse.py' und öffnet die Verbindung mit:

const sse = new EventSource("/cgi-bin/sse.py");

sse.py muss mit: print("Content-Type: text/event-stream") + 2x LF antworten.

Das Ergebnis der Verbindung kann in sse.onopen oder sse.onerror abgefragt werden.

Wenn alles ok,
können in sse.py, in einer Endlosschleife, die 'events' ausgegeben werden.
Ein 'event' wird immer mit Key: 'event: <wert>' und dem Key: 'data: <wert>' + 2x LF übergeben.
Ein event-wert wird im Browser in der entspr. 'addEventListener'-Funktion verarbeitet,
oder landet in der onmessage-Funktion.

z.B. "event: myFunc" --> sse.addEventListener("myFunc",....)

Mit data-wert wird ein beliebiger String übergeben. (2x LF nicht vergessen)

Sollte die SSE-Verbindung unterbrochen werden, wird sie vom Browser autom. wieder geöffnet.
Wenn nach ca. 5 Minuten keine events mehr empfangen werden, beendet der Browser von
sich aus die Verbindung, versucht aber direkt wieder eine Neue zu eröffnen.
Will man das verhindern, kann man z.B. timergesteuert jede Minute ein dummy-event abschicken.
print("event: message");
print("data: Ping \n");

Hier mal meine 'event: daten' Verarbeitung:
vom Server wird mit 'data:' eine Protokollzeile als JSON-String gesendet.

im Javascript:
const arr1 = [ "DATE", "TIME", "espName", "mcc", "msg" ];
const arr2 = [ "Datum", "Uhrzeit", "Espname", "MAC-Adr", "Info" ];

sse.addEventListener("daten", (e) => {
console.log("daten: " + e.data);

let obj = JSON.parse(e.data); // = array mit objecten
var row = tbo.insertRow(-1);
for (let i=0; i < arr1.length; i++) {
var cell = row.insertCell(-1);
cell.setAttribute("data-title", arr2);
cell.innerHTML = obj[arr1];
}
});

'tbo' ist die <tbody>-ID. Hier entstehen die Tabellen-Zeilen.
Mit 'arr1' gebe ich an, welche JSON-Daten ich sehen will.
Mit 'arr2' gebe ich an, wie die Tabelle im 'Handy-Modus' aussehen soll.
Wenn die Protokolldatei angezeigt ist, bleibt das Programm in einer Endlosschleife und wartet auf einen neuen Satz.
Sobald einer eingetragen wird, wird er per 'event' zum Browser gesendet und direkt angezeigt.

Code: Alles auswählen

<!DOCTYPE HTML> 
<!--
    P:\www\html\sse_py.html
-->
<html>  
   <head> 
        <meta charset="UTF-8">
        <title>Server-sent events demo</title>
   </head> 
  
   <body> 
      <div id = "sse"> 
          Server-sent events demo <br>
          (Ausgabe in Browser-Konsole)
      </div> 
      <br>
       <button>Close the connection</button>
      <br>
   
<script type = "text/javascript"> 
//---------------------------------------------------------------------
function getDateTime() {
    var currentdate = new Date();
    var dateTime = currentdate.getFullYear() + "." +
    ("00" + (currentdate.getMonth() + 1)).slice(-2) + "." +
    ("00" + currentdate.getDate()).slice(-2) + " /  " +
    ("00" + currentdate.getHours()).slice(-2) + ":" +
    ("00" + currentdate.getMinutes()).slice(-2) + ":" +
    ("00" + currentdate.getSeconds()).slice(-2);
    return dateTime;
};
//---------------------------------------------------------------------
//     if (!!window.EventSource) {

        const button = document.querySelector('button');
        const sse = new EventSource("/cgi-bin/sse.py");

        console.log(sse.withCredentials);
        console.log(sse.readyState);
        console.log(sse.url);

        sse.onopen = function() {
            console.log("Connection to server opened.");
            console.log(getDateTime());
        };
        sse.onmessage = function(e) {
            console.log("onmessage: " + e.data);
        };
        sse.onerror = function() {
            console.log("EventSource failed.");
            console.log(getDateTime());
        };

        sse.addEventListener("myFunc", (e) => {
            console.log("myFunc:    " + e.data);
            // console.log(e.origin);     =  http://<IP>
        });
        sse.addEventListener("ende", (e) => {
            console.log("ENDE: " + e.data);
            sse.close();
        });
        button.onclick = function() {
            console.log('Connection closed');
            sse.close();
        };
      </script> 
   </body> 
</html>

##################################################################

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# P:\www\cgi-bin\sse.py

import time
from datetime import datetime

print("Content-Type: text/event-stream")  
print("Cache-Control: no-cache\n")      # 2x LF
count = 0

while True: 
    count += 1
    if count > 5:
        # print("event: ende") 
        # print(f"data: *** sse-Ende ***\n",flush=True)
        break

    datum   = datetime.now().strftime("%Y.%m.%d")
    uhrzeit = datetime.now().strftime("%H:%M:%S")

    print("event: myFunc") 
    print(f"data: ---{uhrzeit}--- count: {count}\n",flush=True)
    # abschluss mit 2x LF, sonst wird das nicht als eigener event
    # gesehen und mit dem naechsten verknuepft

    print("event: message") 
    print(f"data: +++{uhrzeit}+++\n",flush=True)
    # abschluss mit 2x LF
    time.sleep(5); 

while True: 
    pass

-------------------------------------------------------------------------------------------
Consol-Ausgabe
--------------------------
myFunc: ---17:07:41--- count: 5 --> und anschl. in Endlosschleife
onmessage: +++17:07:41+++

---> nach ca. 5 Minuten ohne 'event' beendet der Browser die Verbindung

EventSource failed.
2024.02.28 / 17:12:47

---> und eröffnet eine neue Verbindung

Connection to server opened.
2024.02.28 / 17:12:52
myFunc: ---17:12:52--- count: 1
...

Ich hoffe, ich konnte etwas zum Thema: SSE beitragen.

Gruß erima
Antworten