Inhalte in Echtzeit anzeigen

Django, Flask, Bottle, WSGI, CGI…
Antworten
Freumel
User
Beiträge: 69
Registriert: Donnerstag 25. Januar 2018, 13:47

Guten Abend zusammen,

ich möchte Inhalte der Datenbank nach dem Einfügen in Echtzeit anzeigen lassen.
Aktuell schicke ich an meine Datenbank alle 0,5 Sekunden ein paar Daten von einer Desktop Anwendung.

Nun möchte ich, dass einige der geschickten Daten auf einer Webseite automatisch angezeigt werden ohne sie neu laden zu müssen.
Das ist aktuell leider nicht möglich.
(Etwa wie ein Livechat, oder bei Fußballspielen der Liveticker).

Ich vermute mal (eventuell haltlos) irgendein JS-Framework in Richtung Vue, React oder Angular.
Mir fehlt nur leider das richtige Schlagwort zur Umsetzung und schön wäre eine möglichst angenehme Implimentierung in Django.

Hat jemand eventuell einen Tipp?

Vielen Dank :)
Bolitho
User
Beiträge: 219
Registriert: Donnerstag 21. Juli 2011, 07:01
Wohnort: Stade / Hamburg
Kontaktdaten:

Ich habe damit noch nicht gearbeitet, aber Django Channels könnte passen wenn ich alles richtig verstanden habe:
https://channels.readthedocs.io/en/latest/#

Tutorials gibt es einige auf Youtube.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

der "klassischen" Ansatz ist in der Tat clientseitig JavaScript. Entweder fragt das JS im Browser periodisch den Server nach Daten oder - in diesem Fall wohl besser - du nutzt eine Verbindung wie WebSocket. Dann kann der Server die Daten zum Client pushen.

Das kannst du dann mit oder ohne Framework umsetzen, liegt bei dir und wie komplex das ganze wird. Django Channels ist ein Möglichkeit, das Python Webframework Sanic unterstützt auch Websockets (Beispiel).

Gruß, noisefloor
khz
User
Beiträge: 38
Registriert: Freitag 3. August 2012, 22:47
Kontaktdaten:

Hallo,

bisschen spät aber noch kurz zur Ergänzung.
Du kannst auch einfach gutes altes AJAX verwenden.

Ablauf:
1. die Clients senden einen Javascript/AJAX request an den Server (HTTP/GET)
2. der Server merkt sich den client in einer "Callback" Liste.
...
3. Bei neuen Inhalten in der DB (Bsp.-Weise ein insert/update) sendet der Server eine HTTP/response + Bsp.: JSON Daten
an die Clients in der Callbackliste.
4. die Clients zeigen die neuen Daten per Javascript auf der Seite an (Bsp.: <div> hinzufügen)
5. und lösen sofort wieder einen "gibts-neue-daten"-AJAX request aus.

und damit bist du wieder bei 2. .... und das geht immer so weiter.

Der AJAX Ansatz ist m.E. ganz gut, weil:
1. man kein periodisches polling hat. Der Client fragt einmal, der Server legt das auf Halde und das wars.
2. man Standard HTTP verwendet. Websockets ginge eben auch, ist aber ein anderes Protokol .. da muss man dann auch

Hier ein Beispiel wo ich das mal genauso mit Realtime updates von Tweets gemacht habe.

Bild

Die Server Applikation liest im twitter stream (tweepy) und trägt neue tweets mit #python in die DB ein. Die Clients (Browser) fragen der Server per AJAX und warten. Der Server schickt updates
an alle wartenden Clients wenn neue Tweets da sind.
Die Clients fügen die dann als <div> in der linken Column hinzu. ...
Die Dash Componenten aktualisieren sich auch mit den neuen Daten (Top twitter user, top hashtags usw..)

Als Beispiel der verwendete Javascript code
. In diesem Fall jquery getJSON() https://api.jquery.com/jQuery.getJSON/

Code: Alles auswählen

    var num_tweets = 0;
    var max_tweets = 2;
    function get_messages() {
      $.getJSON( "/messages", function( data, status, xhr ) {
        console.log("got it ;)")
        var items = [];
        console.log(data);

        $(".tweets").prepend( "<div>" + data["html"] + "</div>" );
        $("#num_tweets").text(data["num_tweets"]);
        if (num_tweets >= max_tweets){
          $('.tweets').children().last().remove();
        }
        num_tweets += 1
        setTimeout(get_messages,0);
      });
    }

Und die Server Seite
(in diesem Fall Tornado / PythonOnWheels) .. geht aber so auch mit Tornado pur (dann sind die URLs nur nicht so schön ;)

Code: Alles auswählen

@app.add_route('/messages', dispatch={"get" : "get_messages"})
class TweetHandler(PowHandler):
    #
    # on HTTP GET this method will be called. See dispatch parameter.
    #
    
    callbacks=set()

    @web.asynchronous
    def get_messages(self):
        """
            long polling handler method registering the callbacks
        """
        print("Adding callback...")
        self.__class__.callbacks.add(self._callback)
        #print(self.__class__.callbacks)


    def _callback(self, tweet_json):
        print("sending callback...")
        self.success(message="new tweet", data=tweet_json, pure=True)
        self.finish()

    async def fire_callbacks(self, tweet_json):
        print("fire all callbacks!")
        print(50*"-")
        tweet_json["num_tweets"] = len(tweet_cache)
        for c in self.__class__.callbacks:
            c(tweet_json)
        
        self.__class__.callbacks = set()

Die fire_callbacks methode wird genau dann aufgerufen, wenn neue Tweets (in der Datenbank) sind.


Der Ansatz kann nur Problematisch werder, wenn man tausende von clients hat, da dann recht viele offene Verbindungen bestehen.+
Bei einer "normalen" Anzahl ist das aber kein Problem. Ansonsten kann man die Webserver Prozesse erhöhen.
http://www.pythononwheels.org
11to1 sparetime development and all pm.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@khz: Man hat kein periodisches Polling? Der Client macht das doch *nur* und *ständig* – nicht einmal mit einer kleinen Wartezeit‽
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@khz: genau das Polling-Problem hast Du bei AJAX im Gegensatz zu websockets. Denn wie Du in Deinem eigenen Code zeigst, wird quasi ständig ein GET-Request vom Client aus initiiert. Bei websockets dagegen meldet sich der Server von alleine, wenn es neue Daten gibt.
Die ganzen __class__-Referenzierungen in Deinem Code sind übrigens überflüssig. Sogar der letzte, wenn man ein set.clear() macht.
khz
User
Beiträge: 38
Registriert: Freitag 3. August 2012, 22:47
Kontaktdaten:

Implementiert ist sogenanntes: HTTP Long polling.

Ich weiss das da "polling" im Namen des Verfahrens vorkommt, aber
dabei pollt der client eben gerade nicht ständig und dauernd. Sondern führt _einen_ Request aus und wartet dann auf einen response.
Der Server sendet aber keinen (eben erst wenn neue Daten da sind.)

Wenn Bsp.-Weise 10 sekunden keine neue Daten in der DB sind passiert folgendes:

1. Client sendet request
2. Server erhältz request, sendet jedoch kein finish sondern merkt sich den client.
.. dann passiert aber eben 10 sekunden nichts. ..(Insbesondere fragt der client auch nicht wieder an!)
3. Server sendet Antwort und finish
4. Client fragt neu an.

Das implementiert effektiv einen "server push". Es werden auch keine weiteren Ressource für ständiges "Nachfragen" verbraucht.
Der Nachteil sind eben nur die Anzahl der offenen Verbindungen, was aber bei kleinen/mittleren Anwendungen und Userzahlen kein Problem darstellt.

Was nicht passiert ist dauerndes polling. Im Sinne von busy-waiting
1. Client fragt
2. server hat nix
3. client fragt
server hat nix ..

Das websockets ohne den _einen_ request from client auskommt ist klar. (und im Grunde noch einen Tick besser)
aber man erkauft sich das m.E. mit sehr hohem Mehraufwand. Für AJAX / Long polling ist eben alles schon da und in ein
paar lines of code umzusetzen.
http://www.pythononwheels.org
11to1 sparetime development and all pm.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@khz: Also in dem Code den Du gezeigt hast wird das so gar nicht ersichtlich denn der verbrennt CPU-Zeit und haut so schnell er kann Tweetdaten raus. Hättest Da vielleicht ein `sleep()` einbauen können, damit man das sieht. Die Frage bei diesem Vorgehen ist, wann der Browser aufgibt zu warten, denn das macht der ja nicht ewig. Und wenn Du das effektiv einen ”server push” nennst, dann ist es ständiges nachfragen auch. Server-Push wäre da dann doch wohl eher wenn der eine Multipart-Antwort sendet, und auch ab und zu einen „keep alive“-Part, damit der Browser nicht denkt da kommt nix mehr.

Ist der Aufwand für Websockets echt so viel höher? Also ich meine natürlich wenn man die entsprechenden Bibliotheken verwendet.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Diese Comet-Technik des Long polling benötigt definitiv eine vernünftige Fehlerbehandlung, denn irgendwann kommt es zu einem timeout. Und früher zumindest waren die Anzahl der parallel offen gehaltenen Ajax calls beschränkt. Man kann das also nicht beliebig oft machen. Und beeinflusst das ladeverhalten der restlichen Seite. Alles in allem war das ein (durchaus guter) Notbehelf VOR der Verbreitung von websockets. Aber die haben wir ja jetzt. Und wo deren “hoher Mehraufwand” sein soll - das würde ich gerne genauer wissen.
khz
User
Beiträge: 38
Registriert: Freitag 3. August 2012, 22:47
Kontaktdaten:

Hi, __blackjack__, __deets__

mit dem Mehraufwand meine ich im wesentlichen, das bei Websockets die Kommunikationslogik (Protokoll) fehlt. Das sind im Grunde "blanko" TCP Sockets.

Wenn man (wie in diesem Fall) nur Daten vom server an die clients pushen will und sonst keine Meldungen versendet (Bestätigungen oder Formatwechsel o.ä.) ist der Aufwand nahezu identisch. Man braucht einen anderen Handler (Websocket statt HTTP) und man muss auf clientseite im Javascript eben eine WS Verbindung aufbauen... Aber das ist wirklich fast identisch. Weder komplexer noch viel länger ...

Wenn man etwas mehr machen möchte, also irgendwie eine API betreitstellen die z.B. auch Messages from client verarbeiter (wie löschen, updates oder anfrage an XML statt JSON ) dann hat man mit Websockets mehr Aufwand da eben kein so praktisches Protokoll wie HTTP existiert mit dem super simpel eine API abbilden kann.
Denn mit REST oder einer angelehnten Variante ist das auf Client und Server Seite sofort vorhanden.

API wie man sie für CRUD / REST kennt.
* enpoint / GET => client will Liste
* endpoint / DELETE => client will was löschen.
* .. usw.

Man hat die definierten Header mit super Zugriff auf Infos zu Accept, content-type.
Client will anderes format. Also einfach wieder:

Code: Alles auswählen

enpoint / GET + Header: "Accept: application/json"
Man kann sehr leicht binärdaten senden und zugreifen (Files u.ä.)
usw. und so fort..

Es gibt bei den Webframeworks und Javascript libraries auch sofort eingebaute methoden das alles leicht zu senden, zu verarbeiten, zuzugreifen.


Bei API auf Websockets kann das natürlich in den JSON daten die man hin und her sendet einbauen

Code: Alles auswählen

{  format : "JSON", data: [ {"id" : 21223, ... ], action="delete" ....  usw}
Aber man muss das dann eben auch selber "parsen" und Verarbeiten. Das ist der Mehraufwand in der Entwicklung.

Bleibt aber auch, das das mit Websockets am Ende die schnellste Implementierung sein dürfte.

Ich meine nur immer das man, gerade bei eher kleinen Anwendungen manchmal abwägen muss ob die simple Lösung nicht auch reicht und man dann
insgesamt schneller zum "Ziel" kommt. Wenn das nicht reicht kann man ja immer noch Teile ändern.

Deshalb mein Beispiel mit HTTP / AJAX .. ist halt schnell entwickelt, sehr bewährt, läuft überall und ist auch nicht grad langsam.
http://www.pythononwheels.org
11to1 sparetime development and all pm.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@khz: Websocket ist kein einfacher TCP-Socket. Websocket ist ein Message-Stream, das heißt es können bidirektional Nachrichten geschickt werden und verarbeitet werden. Und das parsen von einem JSON-String ist nun wirklich nicht komplizierter als das Parsen einer URL oder einer POST-Message, die dann oft auch JSON-formatiert ist, wenn man etwas Struktur will.
khz
User
Beiträge: 38
Registriert: Freitag 3. August 2012, 22:47
Kontaktdaten:

Ich weiss das das kein reiner TCP Socket ist ... ich beziehe mich (von der Aussage) auf das hier:
Wikipedia:
..." WebSocket enables streams of messages on top of TCP. TCP alone deals with streams of bytes with no inherent concept of a message." ...

Was ich meine ist eben das man bei HTTP/AJAX/REST das Protokoll inklusive Header und libraries auf client und server hat.
Bei Websockets sind die Messages eben undefiniert. (Das soll j auch so sein.)

Bei HTTP API:
URLs z.B. muss man ja eben nicht parsen sondern das macht ja gerade das Framework.
Und die JSON bodies muss man eben auch nicht nach Action oder Typ parsen, da die z.B bei einer REST API ja gerade voll definiert sind.

Bsp. HTTPDelete auf einen /meinarticle/uuid endpoint:

* endpoint einmal als route definiert: /meinarticle/uuid DELETE => action => meinarticle.delete(uuid)
** Alles folgende muss man nicht selbst machen:
* das framework sorgt automatisch dafür, das die article_delete(uuid) Methode aufgerufen wird.
* auch die uuid als paramter muss ich nicht parsen oder sonstwie ermitteln, die bekommen ich auch automatisch übergebeben.
* auch das die action die der client fordert in diesem Fall delete ist, ist automatisch klar, da die delete() methode aufgerufen wurde.
** nur das eigentliche löschen des Datensatzes wird implementiert.

Bsp Websockets:
* man hat einen bidirektionale Websocket "auf" (client öffnet ws: , server connected)
** All das muss man selbst machen:
* jetzt muss jede message "geparsed werden.
* client sendet an server ein JSON Paket (nur als Bsp:) { "uuid" : 21344...., "action": "delete" }
* server empfängt alle Messages in der on_message() methode
* muss jetzt erstmal die action parsen
* dann im delete zweig die uuid rausholen
* dann löschen.
...

Das ist für update, insert, read zu machen und dann noch komplexer. Bei HHTP/REST ist action und content immer schon klar.
Das meine ich mit "Mehraufwand".

Dafür sind Websockets bidirektional, noch schlanker und effizienter. Habe ich ja auch vorher schon geschrieben. Man hat aber eben mehr zu tun wenn man
eine API für typisches Create, Read, Update, Delete voll über Websockets implementieren möchte...
Die sind super! (versteht mich nicht falsch) das geht auch alles mit Tornado und deshalb auch out of the box mit PythonOnWheels. Kein Thema, machen wenn man will.

Man sollte nur beides kennen und dann die präferierte Lösung nehmen. Manchmal möchte ich schneller ans Ziel mit voller REST/API, dann vielleicht erstmal klassisch starten , manchmal geht es bei Einzelanforderungen um Performance-Performance-Performance bis aufs letzte, dann los mit Websockets und so ein Ding wie vom OP in einer Methode umsetzen.
Aufwand dafür ist beinahe gleich dem mit AJAX und HTTP (wie auch vorher schon geschrieben)
http://www.pythononwheels.org
11to1 sparetime development and all pm.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Du redest dir deine Lösung selbst schön. Das Parsen der Message und die entsprechende Aktion sind 2 Zeilen. Das schaffst du auch mit einem magischen REST-Framwork nicht schneller.
Die Nachteile, die ja offensichtlich überwiegen, sind dir ja ausführlich genannt worden. Aber offensichtlich siehst du ein Dauerpollen des Servers als perferkte Lösung und läuft irgendwann in den Timeout. Erscheint dir vielleicht richtig, ist es aber eben jenen Gründen nicht.
khz
User
Beiträge: 38
Registriert: Freitag 3. August 2012, 22:47
Kontaktdaten:

@sparrow:

vielleicht nochmal explizit:

Ich rede mir "meine Lösung" nicht schön.
Das ist nämlich nicht meine Lösung sonder eine die eine der am weitesten im Web verbreiteten überhaupt ist.
Die funktioniert und ist einfach umzusetzen.

Dafür braucht man auch kein "magisches REST framework" sondern kann eigentlich jedes Framework
und jede Javascript library nehmen. Das ist ja gerade das gute.

Es geht in diesem Thread auch nicht darum die Beste client/Server Web Kommunikationslösung der Welt abschliessend
zu klären, sondern darum, das der OP eine Frage gestellt hat und ich geantwortet habe um zu helfen, mit einer Lösung die funktioniert.

Ich habe auch mehrfach geschrieben das beides geht und Websockets am Ende Performanter sind.
Sogar das das in diesem Einzelfall vergleichbarer Aufwand unf komplexität ist. Nur das sich das bei Bereitstellung einer vollen
API eben ändert, da man gerade das Protokoll selbst implementieren muss.

Ich weiss auch nicht was daran so schlimm sein soll. So sind Websockets ja auch gemacht. Ich behaupte da ja nichts böses.
Und wenn man sein Kommunikationsprotokoll einfach umsetzen kann dann los (habe ich ja auch im vorherigen post so geschrieben)
http://www.pythononwheels.org
11to1 sparetime development and all pm.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@khz: Der ”Mehraufwand” ist aber wirklich klein und bei HTTP hast Du den Nachteil das Du da noch die ganze Komplexität und Semantik von HTTP und was der Browser (und ggf. auch der Server!) da alles machen kann und darf. Zum Beispiel musst Du bei GET immer noch einen passenden Expires- oder Cache-Control-Header vom Server mitsenden, damit das nächste GET auf die gleiche URL vom Browser nicht einfach mit lokal gecacheten Daten beantwortet wird, statt tatsächlich die Live-Daten vom Server abzufragen. Das Long-Polling wird beim Client in dem Fall zu einer heissen Schleife weil der Client das gecachete Ergebnis *sofort* liefert. Auf Serverseite können hier so Sachen wie mod-pagespeed dazwischen funken, die man entsprechend konfigurieren muss.

Auch wenn man sich nicht selbst darum kümmern muss, sind die Daten auch recht verstreut – die HTTP-Methode, die URL (Argumente im Pfadteil und im Query-Teil), Cookies im Header, Daten die in verschiedenen Formaten im Body der Anfrage stecken können – *ein* JSON-Objekt pro Nachricht per Websocket ist deutlich simpler. An der Stelle kann man auch sehr einfach Unittests ansetzen, in dem man den entsprechenden Funktionen/Methoden einfach dekodiertes JSON übergibt, ohne das man an der Stelle irgendetwas von HTTP und Webanfragen- und Antworten wissen muss.

Man ist auch nicht auf das HTTP-Repertoire an Methoden beschränkt und muss deshalb das was man will auch nicht auf mehrere Stellen verteilen. Im einfachsten Fall hat man einfach so etwas als Dispatcher:

Code: Alles auswählen

    while True:
        data = websocket.receive()
        if data is None:
            break
        message = json.loads(data)
        result = method_name_to_callable[message.pop("method_name")](**message)
        if result is not NO_RESULT:
            websocket.send(json.dumps(result))
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
khz
User
Beiträge: 38
Registriert: Freitag 3. August 2012, 22:47
Kontaktdaten:

@__blackjack__

jep, genauso sehe ich das auch.

Bei der HTTP/AJAX Variante hat man den Standard, das Protokoll und alles im Boot.
Man muss wenig machen, es wird überall unterstützt, man muss sich aber auch daran halten.

Bei Websockets ist man frei, schnell und direkt, muss sich aber um ein paar Sachen mehr selbst kümmern.
Das kann auch, wie im Fall der Frage des OP oder wie in deinem Dispatching Beispiel sehr einfach sein.
http://www.pythononwheels.org
11to1 sparetime development and all pm.
Antworten