2010-12-06 6 views
24

Der lustige Teil von websockets sendet im Wesentlichen nicht angeforderten Inhalt vom Server zum Browser richtig?Machen bewegt sich mit websockets und python/django (/ twisted?)

Nun, ich benutze django-websocket von Gregor Müllegger. Es ist ein wirklich wunderbarer früher Sprung, wenn es darum geht, Websockets in Django zu arbeiten.

Ich habe "Hallo Welt" erreicht. Das funktioniert folgendermaßen: Wenn eine Anfrage ein Websocket ist, wird ein Objekt, Websocket, an das Anfrageobjekt angehängt. So kann ich, in der Ansicht, die Websocket interpretieren, etwas tun wie:

Das funktioniert gut. Ich bekomme die Nachricht wie ein Zauber im Browser zurück.

Aber was, wenn ich das tun möchte, ohne überhaupt eine Anfrage vom Browser zu stellen?

OK, also zuerst ich spare den websocket in der Sitzung Wörterbuch:

request.session['websocket'] = request.websocket 

Dann in einer Schale, gehe ich und die Sitzung mit Sitzungsschlüssel greifen. Sicher, es gibt ein Websocket-Objekt im Sitzungswörterbuch. Glücklich!

Allerdings, wenn ich versuche zu tun:

>>> session.get_decoded()['websocket'].send('With a herring!') 

ich:

Traceback (most recent call last): 
File "<console>", line 1, in <module> 
error: [Errno 9] Bad file descriptor 

Sad. :-(

OK, also weiß ich nicht viel über Sockets, aber ich weiß genug, um in einem Debugger herumzuschnüffeln, und siehe da, ich sehe, dass der Socket in meinem Debugger (der gebunden ist die echte websocket von der Anfrage) hat = 6 fd, während die, die ich aus der Sitzung gespeicherten websocket gepackt hat fd = -1.

eine Socket-orientierte Person mir diese Sachen aussortieren helfen?

+0

@EliCourtwright Sie haben das Kopfgeld nicht vergeben .. – pradyunsg

+0

@paddila: Ja, es lässt Sie 24 Stunden warten, auch wenn der Grund für die Eröffnung der Bounty ist "Eine oder mehrere der Antworten ist vorbildlich und würdig zusätzliches Kopfgeld. " Ich werde das Kopfgeld vergeben, sobald das System mich lässt. –

Antwort

63

Ich bin der Autor von Django-Websocket. Ich bin kein echter Experte in Sachen Websockets und Networking, aber ich denke, ich habe ein gutes Verständnis davon, was los ist. Tut mir leid, dass ich ins Detail gegangen bin. Auch wenn der Großteil der Antwort nicht spezifisch für Ihre Frage ist, könnte es Ihnen an einem anderen Punkt helfen. :-)


Wie WebSockets

arbeiten

mich kurz Lassen Sie erklären, was ein websocket ist. Ein Websocket startet als etwas, das wirklich wie eine einfache HTTP-Anfrage aussieht, die vom Browser aus erstellt wird. Es zeigt über einen HTTP-Header an, dass es das Protokoll als Websocket "upgraden" will, statt als HTTP-Anfrage. Wenn der Server Websockets unterstützt, stimmt er dem Handshake zu und beide - Server und Client - wissen nun, dass sie den etablierten tcp-Socket, der früher für die HTTP-Anfrage verwendet wurde, als Verbindung zum Austausch von Websocket-Nachrichten verwenden.

Neben dem Senden und Warten auf Nachrichten haben sie natürlich auch die Möglichkeit, die Verbindung jederzeit zu schließen.

Wie django-websocket Mißbräuche der wsgi Anfrage Umgebung Python die Buchse

Jetzt bekommen in die Details, wie django-websocket implementiert die "Aufwertung" der HTTP-Anforderung in einer django Request-Response cylce lässt kapern.

Django verwendet normalerweise die WSGI-Spezifikation, um mit dem Webserver wie Apache oder Gunicorn usw. zu kommunizieren. Diese Spezifikation wurde nur mit dem sehr eingeschränkten Kommunikationsmodell von HTTP entworfen. Es wird davon ausgegangen, dass es eine HTTP-Anforderung (nur eingehende Daten) erhält und die Antwort (nur ausgehende Daten) zurückgibt. Dies macht es schwierig, Django in das Konzept eines Websockets zu zwingen, wo bidirektionale Kommunikation erlaubt ist.

Was ich in django-websocket tun, um dies zu erreichen, ist, dass ich sehr tief in die Interna von WSGI und django Request-Objekt, um den Underlaying-Socket abrufen. Dieser tcp-Socket wird dann verwendet, um die HTTP-Anfrage direkt an eine Websocket-Instanz zu übertragen.

Nun zu Ihrer ursprünglichen Frage ...

ich hoffe, die oben macht es offensichtlich, dass, wenn ein websocket hergestellt ist, hat es keinen Sinn ein Httpresponse bei der Rückkehr. Aus diesem Grund geben Sie normalerweise in einer Ansicht, die von django-websocket gehandhabt wird, nichts zurück.

Allerdings wollte ich nah an dem Konzept einer Sicht halten, die die Logik hält und Daten basierend auf der Eingabe zurückgibt. Aus diesem Grund sollten Sie nur den Code in Ihrer Sicht verwenden, um den Websocket zu handhaben.

Nachdem Sie von der Ansicht zurückgekehrt sind, wird der Websocket automatisch geschlossen. Dies geschieht aus einem bestimmten Grund: Wir möchten den Socket nicht für eine unbestimmte Zeit offen halten und verlassen uns auf den Client (den Browser), um ihn zu schließen.

Aus diesem Grund können Sie auf einen Websocket mit django-websocket außerhalb Ihrer Sicht nicht zugreifen. Der Dateideskriptor wird dann natürlich auf -1 gesetzt, um anzuzeigen, dass er bereits geschlossen ist. in einer sehr hackish Weise - - Zugriff auf die darunter liegende Buchse irgendwie zu bekommen

Haftungsausschluss

ich darüber erklärte ich in der Umgebung von django zu graben. Dies ist sehr fragil und sollte auch nicht funktionieren, da WSGI nicht dafür gedacht ist! Ich habe auch oben erklärt, dass der Websocket geschlossen wird, nachdem die Ansicht endet - aber nachdem die WebSocket geschlossen wurde (UND den TCP-Socket geschlossen hat), versucht djangos WSGI-Implementierung eine HTTP-Antwort zu senden - sie weiß nichts über WebSockets und denkt, dass sie da ist ein normaler HTTP-Anfrage-Antwort-Zyklus. Aber der Socket ist bereits geschlossen und das Senden wird fehlschlagen. Dies verursacht normalerweise eine Ausnahme im Django.

Das hat meine Tests mit dem Entwicklungsserver nicht beeinflusst. Der Browser wird es nie bemerken (Sie wissen, dass der Socket bereits geschlossen ist ;-)), aber ein unbehandelter Fehler in jeder Anfrage anzuwerfen ist kein sehr gutes Konzept und kann Speicher verlieren, nicht korrektes Herunterfahren der Datenbankverbindung und viele andere Dinge dass wird brechen an einem bestimmten Punkt, wenn Sie Django-Websocket für mehr als das Experimentieren verwenden. Diese

Deshalb würde ich Sie wirklich raten nicht WebSockets mit django noch zu verwenden. Es funktioniert nicht mit Absicht. Django und speziell WSGI würden eine Totalüberholung benötigen, um diese Probleme zu lösen (siehe this discussion for websockets and WSGI). Seitdem würde ich vorschlagen, etwas wie eventlet zu verwenden.Eventlet hat eine funktionierende Websocket-Implementierung (ich habe etwas Code von eventlet für die erste Version von django-websocket ausgeliehen) und seit seinem einfachen Python-Code können Sie Ihre Modelle und alles andere von django importieren. Der einzige Nachteil ist, dass Sie einen zweiten Webserver benötigen, der nur für den Umgang mit Websockets läuft.

+0

Danke für diese erstaunliche Antwort. Ich warte darauf, es zu akzeptieren, bis ich dir dafür eine Prämie geben kann. :-) – jMyles

+0

bedeutet das, dass, um die Websocket zu bedienen, man einfach die Anfrage offen halten sollte? Wie würden Sie unerwünschte Nachrichten senden, wenn der Nachrichteniterator beim Warten auf eingehende Nachrichten blockiert? – Thomas

+0

@Thomas ja du solltest die Anfrage offen halten (; da ist der Socket für den Websocket - sinnvoll?). Sie müssen nicht blockieren, während Sie auf Nachrichten warten. Mit den Methoden 'read()' und 'has_messages()' können Sie regelmäßig nach neuen Dingen suchen. Lesen Sie die README hier: https://github.com/gregmuellegger/django-websocket Sie können dann natürlich jederzeit das Versenden einer Nachricht auslösen. –

0

request.websocket wird wahrscheinlich geschlossen, wenn Sie vom Anforderungshandler (Ansicht) zurückkehren Die einfache Lösung besteht darin, den Handler am Leben zu halten (indem Sie nicht aus der Ansicht zurückkehren) Wenn Ihr Server nicht Multi-Threading ist, können Sie nicht Akzeptiere alle anderen simultanen Anfragen .

+0

Aber was soll das dann? Wenn ich die Anfrage in der Ansicht lebendig halten muss, bin ich gleich wieder bei Comet. Der springende Punkt von websockets ist es, Inhalte in den Browser zu pushen, ohne die Anfrage am Leben zu halten, oder? – jMyles

+1

@Justin Myles Holmes: Lesen Sie den Haftungsausschluss unter http: //pypi.python.org/pypi/django-websocket – jfs

+0

Ja, das habe ich gesehen. Das ist kein Problem, aus zwei Gründen: 1) Ich benutze den Multithread-Runserver, der mit django-websocket geliefert wird, 2) Ich bin vollständig darauf vorbereitet, eine Websocket-fähige Serverlösung zu verwenden; entweder Apache/mod_python mit pywebsocket (bledh) oder Twisted (yay) - aber im Moment kann ich es nicht mal testen. Ich muss mehr darüber verstehen, warum dieser Sockel mir aus der Shell nicht zur Verfügung steht. – jMyles

2

Sie könnten Stargate eine Bash geben: http://boothead.github.com/stargate/ und http://pypi.python.org/pypi/stargate/.

Es ist auf der Spitze der Pyramide und Eventlet gebaut (ich habe auch ein gutes Stück der Websocket Unterstützung und Tests zu Eventlet beigetragen). Der große Vorteil der Pyramide für diese Art von Sache ist, dass sie das Konzept einer Ressource hat, auf die die URL verweist, und nicht nur das Ergebnis einer aufrufbaren Ressource. Sie erhalten also ein Diagramm mit persistenten Ressourcen, die Ihrer URL-Struktur zugeordnet sind, und Websocket-Verbindungen werden einfach weitergeleitet und mit diesen Ressourcen verbunden.

So Sie am Ende nur zwei Dinge zu tun, um:

class YourView(WebSocketView): 

    def handler(self, websocket): 
     self.request.context.add_listener(websocket) 
     while True: 
      msg = websocket.wait() 
      # Do something with message 

Um Nachrichten zu empfangen und

resource.send(some_other_message) 

Hier Ressource ist eine Instanz einer stargate.resource.WebSocketAwareContext (als selbst .request.context), und die send-Methode sendet die Nachricht an alle Clients, die mit der Methode add_listener verbunden sind.

Um eine Nachricht an alle angeschlossenen Clients veröffentlichen Sie rufen Sie einfach node.send(message)

ich hoffentlich ein wenig Beispiel-App in der nächsten Woche oder zwei dieser ein wenig besser zu demonstrieren, schreiben gehen.

Fühlen Sie sich frei, mich auf Github anzumelden, wenn Sie Hilfe damit haben möchten.

+0

Ben - das sieht nach Optimismus aus. Du hast meine Aufmerksamkeit! :-) – jMyles

5

Wie Gregor Müllegger darauf hingewiesen hat, kann Websockets von WSGI nicht ordnungsgemäß verarbeitet werden, da dieses Protokoll niemals für eine solche Funktion entwickelt wurde. uWSGI, seit Version 1.9.11, kann Websockets out of the box handhaben. Hier kommuniziert uWSGI mit dem Anwendungsserver unter Verwendung von Roh-HTTP anstelle des WSGI-Protokolls. Ein so geschriebener Server kann daher die Protokolleinbauten handhaben und die Verbindung über einen langen Zeitraum offen halten. Es ist auch keine gute Idee, langlebige Verbindungen zu haben, die von einer Django-Sicht behandelt werden, weil sie dann einen Worker-Thread blockieren würden, der eine begrenzte Ressource ist.

Der Hauptzweck von Websockets besteht darin, dass der Server Nachrichten asynchron an den Client sendet. Dies kann eine Django-Ansicht sein, die von anderen Browsern (z. B. Chat-Clients, Multiplayer-Spielen) ausgelöst wird, oder ein Ereignis, das beispielsweise von Django-Sellerie ausgelöst wird (z. B. Sportergebnisse). Daher ist es für diese Django-Dienste von grundlegender Bedeutung, eine Nachrichtenwarteschlange zum Weiterleiten von Nachrichten an den Client zu verwenden.

Um dies in einer skalierbaren Weise zu behandeln, schrieb ich django-websocket-redis, ein Django-Modul, das alle diese langlebigen Websocket-Verbindungen in einem einzigen Thread/Prozess mit Redis als Backend-Nachrichtenwarteschlange offen halten kann.

+0

Vielen Dank für die Einsicht in diese alternde Frage! – jMyles

+0

+1 für 'django-websocket-redis', sehr einfach zu installieren/zu verwenden, wenn Sie mit der vorgeschlagenen' nginx' & 'uWSGI' Kombination vertraut sind –