[PYTHON] Echtzeit-Web mit Django-Kanälen

Einführung

Dieser Artikel ist der 17. Tagesartikel von Django Adventskalender 2016.

Was sind Django-Kanäle?

Django Channels — Channels 0.17.2 documentation

Channels is a project to make Django able to handle more than just plain HTTP requests, including WebSockets and HTTP2, as well as the ability to run code after a response has been sent for things like thumbnailing or background calculation.

Channels ist ein Projekt, mit dem Django nach dem Senden einer Antwort Code ausführen kann, z. B. Miniaturansichten und Hintergrundberechnungen sowie einfache HTTP-Anforderungen wie WebSocket und HTTP2.

The core of the system is, unsurprisingly, a datastructure called a channel. What is a channel? It is an ordered, first-in first-out queue with message expiry and at-most-once delivery to only one listener at a time.

If you’ve used channels in Go: Go channels are reasonably similar to Django ones. The key difference is that Django channels are network-transparent; the implementations of channels we provide are all accessible across a network to consumers and producers running in different processes or on different machines.

Modelldiagramm

Traditionelles Anforderungs- / Antwortmodell

1473343845-django-asgi-websockets.png

Arbeitsmodell nach Kanälen

1473343845-django-wsgi.png

Was ist ASGI?

ASGI (Asynchronous Server Gateway Interface) Draft Spec — Channels 0.17.2 documentation

This document proposes a standard interface between network protocol servers (particularly webservers) and Python applications, intended to allow handling of multiple common protocol styles (including HTTP, HTTP2, and WebSocket).

Standardschnittstelle zwischen Netzwerkprotokollservern (insbesondere Webservern) und Python-Anwendungen, um die Verarbeitung mehrerer gängiger Protokollstile (einschließlich HTTP, HTTP2, WebSocket) zu ermöglichen.

Installation

Installieren Sie es einfach mit pip und fügen Sie es zu INSTALLED_APPS hinzu.

$ pip install -U channels
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    ...
    'channels',
)

Versuchen Sie es zum ersten Mal

Sehen wir uns zunächst Erste Schritte mit Kanälen in der Dokumentation an.

$ pip install django channels
$ django-admin startproject myapp
$ tree
.
├── db.sqlite3
├── manage.py
└── myapp
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

First Consumers

Lassen Sie uns zunächst die integrierte Anforderungsbehandlung überschreiben.

consumers.py


from django.http import HttpResponse
from channels.handler import AsgiHandler


def http_consumer(message):
    # Make standard HTTP response - access ASGI path attribute directly
    response = HttpResponse("Hello world! You asked for %s" % message.content['path'])
    # Encode that response into message format (ASGI)
    for chunk in AsgiHandler.encode_response(response):
        message.reply_channel.send(chunk)

settings.py


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
    'channels',
]
...
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "asgiref.inmemory.ChannelLayer",
        "ROUTING": "myproject.routing.channel_routing",
    },
}

routing.py


from channels.routing import route

channel_routing = [
    route("http.request", "myapp.consumers.http_consumer"),
]

Die Einstellung ist jetzt abgeschlossen.

$ tree
.
├── db.sqlite3
├── manage.py
└── myapp
    ├── __init__.py
    ├── consumers.py
    ├── routing.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
$ python manage.py runserver

Überprüfen Sie http://127.0.0.1:8000/ und wenn der Text "Hallo Welt! Sie haben nach / gefragt" angezeigt wird, ist dies in Ordnung. Dies ist jedoch langweilig. Erstellen wir also einen einfachen Chat-Server mit WebSockets.

Es ist überhaupt nicht praktisch, aber zuerst erstellen wir einen Server, der die Nachricht an den Client zurücksendet, der die Nachricht gesendet hat.

consumers.py


def ws_message(message):
    # ASGI WebSocket packet-received and send-packet message types
    # both have a "text" key for their textual data.
    message.reply_channel.send({
        "text": message.content['text'],
    })

routing.py


from channels.routing import route
from myapp.consumers import ws_message

channel_routing = [
    route("websocket.receive", ws_message),
]
$ python manage.py runserver

Gehen Sie zu http://127.0.0.1:8000/ und geben Sie die js-Konsole wie folgt ein:

// Note that the path doesn't matter for routing; any WebSocket
// connection gets bumped over to WebSocket consumers
socket = new WebSocket("ws://" + window.location.host + "/chat/");
socket.onmessage = function(e) {
    alert(e.data);
}
socket.onopen = function() {
    socket.send("hello world");
}
// Call onopen directly if socket is already open
if (socket.readyState == WebSocket.OPEN) socket.onopen();

Es ist in Ordnung, wenn die Warnung "Hallo Welt" angezeigt wird.

Groups

Verwenden Sie dann Gruppen, um einen echten Chat zu implementieren, in dem Sie miteinander sprechen können.

cousumers.py


from channels import Group


# Connected to websocket.connect
def ws_add(message):
    message.reply_channel.send({"accept": True})
    Group("chat").add(message.reply_channel)


# Connected to websocket.receive
def ws_message(message):
    Group("chat").send({
        "text": "[user] %s" % message.content['text'],
    })


# Connected to websocket.disconnect
def ws_disconnect(message):
    Group("chat").discard(message.reply_channel)

routing.py


from channels.routing import route
from myapp.consumers import ws_add, ws_message, ws_disconnect

channel_routing = [
    route("websocket.connect", ws_add),
    route("websocket.receive", ws_message),
    route("websocket.disconnect", ws_disconnect),
]
$ python manage.py runserver

Öffnen Sie http://127.0.0.1:8000/ auf mehreren Registerkarten und geben Sie denselben JS-Code in die Konsole ein. Es ist in Ordnung, wenn auf jeder Registerkarte die Warnung "[Benutzer] Hallo Welt" angezeigt wird.

Running with Channels

Als nächstes wechseln wir die Kanalebene. Früher habe ich "asgiref.inmemory.ChannelLayer" verwendet, aber dies funktioniert nur im selben Prozess. Verwenden Sie in einer Produktionsumgebung ein Backend wie "asgi_redis".

$ pip install asgi_redis

setting.py


CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "asgi_redis.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("localhost", 6379)],
        },
        "ROUTING": "myapp.routing.channel_routing",
    },
}
$ python manage.py runserver --noworker
$ python manage.py runworker

Sie können jetzt den Befehl runworker ausführen.

Persisting Data

Das Attribut "replay_channel", das wir bisher gesehen haben, ist ein eindeutiger Punkt für verbundene WebSockets. Jetzt können Sie nachverfolgen, von wem die Nachricht stammt.

Verwenden Sie in einer Produktionsumgebung "channel_session", um die Sitzung wie ein Cookie in der HTTP-Kommunikation dauerhaft zu machen.

consumers.py


from channels import Group
from channels.sessions import channel_session


# Connected to websocket.connect
@channel_session
def ws_connect(message):
    # Accept connection
    message.reply_channel.send({"accept": True})
    # Work out room name from path (ignore slashes)
    room = message.content['path'].strip("/")
    # Save room in session and add us to the group
    message.channel_session['room'] = room
    Group("chat-%s" % room).add(message.reply_channel)


# Connected to websocket.receive
@channel_session
def ws_message(message):
    Group("chat-%s" % message.channel_session['room']).send({
        "text": message['text'],
    })


# Connected to websocket.disconnect
@channel_session
def ws_disconnect(message):
    Group("chat-%s" % message.channel_session['room']).discard(message.reply_channel)

routing.py


from channels.routing import route
from myapp.consumers import ws_connect, ws_message, ws_disconnect

channel_routing = [
    route("websocket.connect", ws_connect),
    route("websocket.receive", ws_message),
    route("websocket.disconnect", ws_disconnect),
]

Authentication

Kanäle können die für die Benutzerauthentifizierung erforderliche Django-Sitzung auf zwei Arten abrufen.

Die Authentifizierung mithilfe einer Django-Sitzung erfolgt durch Angabe eines Dekorateurs.

consumers.py


from channels import Channel, Group
from channels.sessions import channel_session
from channels.auth import http_session_user, channel_session_user, channel_session_user_from_http


# Connected to websocket.connect
@channel_session_user_from_http
def ws_add(message):
    # Accept connection
    message.reply_channel.send({"accept": True})
    # Add them to the right group
    Group("chat-%s" % message.user.username[0]).add(message.reply_channel)


# Connected to websocket.receive
@channel_session_user
def ws_message(message):
    Group("chat-%s" % message.user.username[0]).send({
        "text": message['text'],
    })


# Connected to websocket.disconnect
@channel_session_user
def ws_disconnect(message):
    Group("chat-%s" % message.user.username[0]).discard(message.reply_channel)

Routing

Wie bei Djangos urls.py können Sierouting.py mithilfe regulärer Ausdrücke flexibel einstellen.

routing.py


http_routing = [
    route("http.request", poll_consumer, path=r"^/poll/$", method=r"^POST$"),
]

chat_routing = [
    route("websocket.connect", chat_connect, path=r"^/(?P<room>[a-zA-Z0-9_]+)/$"),
    route("websocket.disconnect", chat_disconnect),
]

routing = [
    # You can use a string import path as the first argument as well.
    include(chat_routing, path=r"^/chat"),
    include(http_routing),
]

Models

Djangos ORM erleichtert die Integration der Nachrichtenpersistenz.

consumers.py


from channels import Channel
from channels.sessions import channel_session
from .models import ChatMessage


# Connected to chat-messages
def msg_consumer(message):
    # Save to model
    room = message.content['room']
    ChatMessage.objects.create(
        room=room,
        message=message.content['message'],
    )
    # Broadcast to listening sockets
    Group("chat-%s" % room).send({
        "text": message.content['message'],
    })


# Connected to websocket.connect
@channel_session
def ws_connect(message):
    # Work out room name from path (ignore slashes)
    room = message.content['path'].strip("/")
    # Save room in session and add us to the group
    message.channel_session['room'] = room
    Group("chat-%s" % room).add(message.reply_channel)


# Connected to websocket.receive
@channel_session
def ws_message(message):
    # Stick the message onto the processing queue
    Channel("chat-messages").send({
        "room": message.channel_session['room'],
        "message": message['text'],
    })


# Connected to websocket.disconnect
@channel_session
def ws_disconnect(message):
    Group("chat-%s" % message.channel_session['room']).discard(message.reply_channel)

Enforcing Ordering

Indem Sie einen @ @ enforce_ordering (geringfügig = True) -Dekorator angeben, können Sie die Reihenfolge ändern, in der der websocket.connect zuerst erstellt wurde, und nicht die Reihenfolge, in der sie in die Warteschlange gestellt wurden.

consumers.py


from channels import Channel, Group
from channels.sessions import channel_session, enforce_ordering
from channels.auth import http_session_user, channel_session_user, channel_session_user_from_http


# Connected to websocket.connect
@enforce_ordering(slight=True)
@channel_session_user_from_http
def ws_add(message):
    # Add them to the right group
    Group("chat-%s" % message.user.username[0]).add(message.reply_channel)


# Connected to websocket.receive
@enforce_ordering(slight=True)
@channel_session_user
def ws_message(message):
    Group("chat-%s" % message.user.username[0]).send({
        "text": message['text'],
    })


# Connected to websocket.disconnect
@enforce_ordering(slight=True)
@channel_session_user
def ws_disconnect(message):
    Group("chat-%s" % message.user.username[0]).discard(message.reply_channel

Lernprogramm

Ein Chat-App-Tutorial wie das hier gezeigte ist in Heroku verfügbar. Finally, Real-Time Django Is Here: Get Started with Django Channels

Wenn Sie es versuchen, bis Sie es tatsächlich auf Heroku bereitstellen, erhalten Sie ein besseres Gefühl.

Django Channels Example

Referenz

abschließend

Wie Volunteeras in "Server Push mit WebSocket in Django" geschrieben hat, sollten Sie dies in einer anderen Sprache tun, wenn Sie Leistung wünschen.

Einfache Verwendung für Django-Anwendungen, ohne dass SwampDragon oder Tornado separat gestartet werden müssen. Die größte Attraktion ist, dass es eingebaut werden kann.

Die Kanäle werden auch für die Aufnahme in Django 2.0 in Betracht gezogen, die im kommenden Dezember erscheinen soll. Lass es uns benutzen und die Kanäle beleben!

Recommended Posts

Echtzeit-Web mit Django-Kanälen
Erstellen Sie eine Webanwendung mit Django
Erstellen Sie eine Webanwendung mit Django
Internationalisierung mit Django
CRUD mit Django
Ich habe eine WEB-Bewerbung bei Django gemacht
Django 1.11 wurde mit Python3.6 gestartet
Entwicklungsverdauung mit Django
Django Python Web Framework
PDF mit Django ausgeben
Markdown-Ausgabe mit Django
Verwenden Sie Gentelella mit Django
Twitter OAuth mit Django
Erste Schritte mit Django 1
Mail mit Django senden
Echtzeitzeichnung mit Matplotlib
Pooling mechanisieren mit Django
Verwenden Sie MySQL mit Django
Django ab heute
Erste Schritte mit Django 2
Rückblick auf die Erstellung eines Webdienstes mit Django 1
Rückblick auf die Erstellung eines Webdienstes mit Django 2
Webanwendung erstellt mit Python3.4 + Django (Teil.1 Umgebungskonstruktion)
Stellen Sie Echtzeit-Webanwendungen mit swampdragon x apache bereit
Mach Django mit CodeStar (Python3.6.8, Django2.2.9)
Web Scraping mit Python + JupyterLab
Fangen Sie mit Django an! ~ Tutorial ⑤ ~
Minimale Konfigurations-Website-Umgebung mit Django
Erstellen Sie eine API mit Django
Einfache Erstellung und Bereitstellung von Webservern mit EB CLI + git + Django
Mach Django mit CodeStar (Python3.8, Django2.1.15)
Stellen Sie Django serverlos mit Lambda bereit
Python3 + Django ~ Mac ~ mit Apache
Speichern Sie Bilder mit Web Scraping
Entwicklung von Webanwendungen mit Flask
Erstellen Sie eine Homepage mit Django
Richten Sie einen Webserver mit CentOS7 + Anaconda + Django + Apache ein
Fangen Sie mit Django an! ~ Tutorial ④ ~
Erste Schritte mit Python Django (4)
Erste Schritte mit Python Django (3)
Einfaches Web-Scraping mit Scrapy
Kombinieren Sie Fast API mit Django ORM
Fangen Sie mit Django an! ~ Tutorial ⑥ ~
Web-API mit Python + Falcon
Verwenden Sie Django, um Tweet-Daten zu speichern
Erstellen Sie eine Web-API, die Bilder mit Django liefern kann
Kombiniere zwei Bilder mit Django
Erste Schritte mit Django mit PyCharm
(Für Anfänger) Versuchen Sie, mit Django eine einfache Web-API zu erstellen
Web Scraping Anfänger mit Python
Doppelte Submit-Unterdrückung mit Django
Django REST Framework mit Vue.js
Verwenden Sie prefetch_related bequem mit Django
Erste Schritte mit Python Django (5)
Optimieren Sie die Websuche mit Python
Melden Sie sich mit dem Django Rest Framework an
Webanwendung mit Python + Flask ④
Qiita API Oauth mit Django
Entwicklungspraxis für Webanwendungen: Erstellen Sie mit Django eine Seite zum Erstellen von Schichten! (Schichterstellungsseite)
Startete eine Webanwendung auf AWS mit Django und wechselte Jobs