mirror of
https://github.com/django/daphne.git
synced 2025-04-21 17:22:03 +03:00
Rename channels and change message format docs
This commit is contained in:
parent
fc52e3c5a2
commit
70caf7d171
|
@ -11,7 +11,7 @@ from channels import Channel, channel_backends, DEFAULT_CHANNEL_BACKEND
|
|||
class InterfaceProtocol(WebSocketServerProtocol):
|
||||
"""
|
||||
Protocol which supports WebSockets and forwards incoming messages to
|
||||
the django.websocket channels.
|
||||
the websocket channels.
|
||||
"""
|
||||
|
||||
def onConnect(self, request):
|
||||
|
@ -23,22 +23,22 @@ class InterfaceProtocol(WebSocketServerProtocol):
|
|||
|
||||
def onOpen(self):
|
||||
# Make sending channel
|
||||
self.reply_channel = Channel.new_name("!django.websocket.send")
|
||||
self.reply_channel = Channel.new_name("!websocket.send")
|
||||
self.request_info["reply_channel"] = self.reply_channel
|
||||
self.last_keepalive = time.time()
|
||||
self.factory.protocols[self.reply_channel] = self
|
||||
# Send news that this channel is open
|
||||
Channel("django.websocket.connect").send(self.request_info)
|
||||
Channel("websocket.connect").send(self.request_info)
|
||||
|
||||
def onMessage(self, payload, isBinary):
|
||||
if isBinary:
|
||||
Channel("django.websocket.receive").send(dict(
|
||||
Channel("websocket.receive").send(dict(
|
||||
self.request_info,
|
||||
content = payload,
|
||||
binary = True,
|
||||
))
|
||||
else:
|
||||
Channel("django.websocket.receive").send(dict(
|
||||
Channel("websocket.receive").send(dict(
|
||||
self.request_info,
|
||||
content = payload.decode("utf8"),
|
||||
binary = False,
|
||||
|
@ -62,13 +62,13 @@ class InterfaceProtocol(WebSocketServerProtocol):
|
|||
def onClose(self, wasClean, code, reason):
|
||||
if hasattr(self, "reply_channel"):
|
||||
del self.factory.protocols[self.reply_channel]
|
||||
Channel("django.websocket.disconnect").send(self.request_info)
|
||||
Channel("websocket.disconnect").send(self.request_info)
|
||||
|
||||
def sendKeepalive(self):
|
||||
"""
|
||||
Sends a keepalive packet on the keepalive channel.
|
||||
"""
|
||||
Channel("django.websocket.keepalive").send(self.request_info)
|
||||
Channel("websocket.keepalive").send(self.request_info)
|
||||
self.last_keepalive = time.time()
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class WSGIInterface(WSGIHandler):
|
|||
super(WSGIInterface, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_response(self, request):
|
||||
request.reply_channel = Channel.new_name("django.wsgi.response")
|
||||
Channel("django.wsgi.request", channel_backend=self.channel_backend).send(request.channel_encode())
|
||||
request.reply_channel = Channel.new_name("http.response")
|
||||
Channel("http.request", channel_backend=self.channel_backend).send(request.channel_encode())
|
||||
channel, message = self.channel_backend.receive_many_blocking([request.reply_channel])
|
||||
return HttpResponse.channel_decode(message)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.http import HttpResponse
|
||||
from django.http.cookie import SimpleCookie
|
||||
from six import PY3
|
||||
|
||||
|
||||
|
@ -12,7 +11,7 @@ def encode_response(response):
|
|||
"content": response.content,
|
||||
"status_code": response.status_code,
|
||||
"headers": list(response._headers.values()),
|
||||
"cookies": {k: v.output(header="") for k, v in response.cookies.items()}
|
||||
"cookies": [v.output(header="") for _, v in response.cookies.items()]
|
||||
}
|
||||
if PY3:
|
||||
value["content"] = value["content"].decode('utf8')
|
||||
|
@ -29,9 +28,9 @@ def decode_response(value):
|
|||
content_type = value['content_type'],
|
||||
status = value['status_code'],
|
||||
)
|
||||
for cookie in value['cookies'].values():
|
||||
for cookie in value['cookies']:
|
||||
response.cookies.load(cookie)
|
||||
response._headers = {k.lower: (k, v) for k, v in value['headers']}
|
||||
response._headers = {k.lower(): (k, v) for k, v in value['headers']}
|
||||
return response
|
||||
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ and producers running in different processes or on different machines.
|
|||
|
||||
Inside a network, we identify channels uniquely by a name string - you can
|
||||
send to any named channel from any machine connected to the same channel
|
||||
backend. If two different machines both write to the ``django.wsgi.request``
|
||||
backend. If two different machines both write to the ``http.request``
|
||||
channel, they're writing into the same channel.
|
||||
|
||||
How do we use channels?
|
||||
|
@ -102,12 +102,12 @@ slightly more complex abstraction than that presented by Django views.
|
|||
A view takes a request and returns a response; a consumer takes a channel
|
||||
message and can write out zero to many other channel messages.
|
||||
|
||||
Now, let's make a channel for requests (called ``django.wsgi.request``),
|
||||
and a channel per client for responses (e.g. ``django.wsgi.response.o4F2h2Fd``),
|
||||
Now, let's make a channel for requests (called ``http.request``),
|
||||
and a channel per client for responses (e.g. ``http.response.o4F2h2Fd``),
|
||||
with the response channel a property (``reply_channel``) of the request message.
|
||||
Suddenly, a view is merely another example of a consumer::
|
||||
|
||||
# Listens on django.wsgi.request.
|
||||
# Listens on http.request
|
||||
def my_consumer(message):
|
||||
# Decode the request from JSON-compat to a full object
|
||||
django_request = Request.decode(message.content)
|
||||
|
@ -154,7 +154,7 @@ to the channel server they're listening on.
|
|||
|
||||
For this reason, Channels treats these as two different *channel types*, and
|
||||
denotes a *response channel* by having the first character of the channel name
|
||||
be the character ``!`` - e.g. ``!django.wsgi.response.f5G3fE21f``. *Normal
|
||||
be the character ``!`` - e.g. ``!http.response.f5G3fE21f``. *Normal
|
||||
channels* have no special prefix, but along with the rest of the response
|
||||
channel name, they must contain only the characters ``a-z A-Z 0-9 - _``,
|
||||
and be less than 200 characters long.
|
||||
|
@ -186,14 +186,14 @@ set of channels (here, using Redis) to send updates to::
|
|||
content=instance.content,
|
||||
)
|
||||
|
||||
# Connected to django.websocket.connect
|
||||
# Connected to websocket.connect
|
||||
def ws_connect(message):
|
||||
# Add to reader set
|
||||
redis_conn.sadd("readers", message.reply_channel.name)
|
||||
|
||||
While this will work, there's a small problem - we never remove people from
|
||||
the ``readers`` set when they disconnect. We could add a consumer that
|
||||
listens to ``django.websocket.disconnect`` to do that, but we'd also need to
|
||||
listens to ``websocket.disconnect`` to do that, but we'd also need to
|
||||
have some kind of expiry in case an interface server is forced to quit or
|
||||
loses power before it can send disconnect signals - your code will never
|
||||
see any disconnect notification but the response channel is completely
|
||||
|
@ -222,7 +222,7 @@ we don't need to; Channels has it built in, as a feature called Groups::
|
|||
content=instance.content,
|
||||
)
|
||||
|
||||
# Connected to django.websocket.connect and django.websocket.keepalive
|
||||
# Connected to websocket.connect and websocket.keepalive
|
||||
def ws_connect(message):
|
||||
# Add to reader group
|
||||
Group("liveblog").add(message.reply_channel)
|
||||
|
|
|
@ -12,7 +12,7 @@ First Consumers
|
|||
---------------
|
||||
|
||||
Now, by default, Django will run things through Channels but it will also
|
||||
tie in the URL router and view subsystem to the default ``django.wsgi.request``
|
||||
tie in the URL router and view subsystem to the default ``http.request``
|
||||
channel if you don't provide another consumer that listens to it - remember,
|
||||
only one consumer can listen to any given channel.
|
||||
|
||||
|
@ -48,7 +48,7 @@ custom consumer we wrote above. Here's what that looks like::
|
|||
"default": {
|
||||
"BACKEND": "channels.backends.database.DatabaseChannelBackend",
|
||||
"ROUTING": {
|
||||
"django.wsgi.request": "myproject.myapp.consumers.http_consumer",
|
||||
"http.request": "myproject.myapp.consumers.http_consumer",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -74,19 +74,19 @@ serve HTTP requests from now on - and make this WebSocket consumer instead::
|
|||
def ws_add(message):
|
||||
Group("chat").add(message.reply_channel)
|
||||
|
||||
Hook it up to the ``django.websocket.connect`` channel like this::
|
||||
Hook it up to the ``websocket.connect`` channel like this::
|
||||
|
||||
CHANNEL_BACKENDS = {
|
||||
"default": {
|
||||
"BACKEND": "channels.backends.database.DatabaseChannelBackend",
|
||||
"ROUTING": {
|
||||
"django.websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
"websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Now, let's look at what this is doing. It's tied to the
|
||||
``django.websocket.connect`` channel, which means that it'll get a message
|
||||
``websocket.connect`` channel, which means that it'll get a message
|
||||
whenever a new WebSocket connection is opened by a client.
|
||||
|
||||
When it gets that message, it takes the ``reply_channel`` attribute from it, which
|
||||
|
@ -100,12 +100,12 @@ don't keep track of the open/close states of the potentially thousands of
|
|||
connections you have open at any one time.
|
||||
|
||||
The solution to this is that the WebSocket interface servers will send
|
||||
periodic "keepalive" messages on the ``django.websocket.keepalive`` channel,
|
||||
periodic "keepalive" messages on the ``websocket.keepalive`` channel,
|
||||
so we can hook that up to re-add the channel (it's safe to add the channel to
|
||||
a group it's already in - similarly, it's safe to discard a channel from a
|
||||
group it's not in)::
|
||||
|
||||
# Connected to django.websocket.keepalive
|
||||
# Connected to websocket.keepalive
|
||||
def ws_keepalive(message):
|
||||
Group("chat").add(message.reply_channel)
|
||||
|
||||
|
@ -114,8 +114,8 @@ just route both channels to the same consumer::
|
|||
|
||||
...
|
||||
"ROUTING": {
|
||||
"django.websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
"django.websocket.keepalive": "myproject.myapp.consumers.ws_add",
|
||||
"websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
"websocket.keepalive": "myproject.myapp.consumers.ws_add",
|
||||
},
|
||||
...
|
||||
|
||||
|
@ -123,7 +123,7 @@ And, even though channels will expire out, let's add an explicit ``disconnect``
|
|||
handler to clean up as people disconnect (most channels will cleanly disconnect
|
||||
and get this called)::
|
||||
|
||||
# Connected to django.websocket.disconnect
|
||||
# Connected to websocket.disconnect
|
||||
def ws_disconnect(message):
|
||||
Group("chat").discard(message.reply_channel)
|
||||
|
||||
|
@ -134,15 +134,15 @@ any message sent in to all connected clients. Here's all the code::
|
|||
|
||||
from channels import Channel, Group
|
||||
|
||||
# Connected to django.websocket.connect and django.websocket.keepalive
|
||||
# Connected to websocket.connect and websocket.keepalive
|
||||
def ws_add(message):
|
||||
Group("chat").add(message.reply_channel)
|
||||
|
||||
# Connected to django.websocket.receive
|
||||
# Connected to websocket.receive
|
||||
def ws_message(message):
|
||||
Group("chat").send(message.content)
|
||||
|
||||
# Connected to django.websocket.disconnect
|
||||
# Connected to websocket.disconnect
|
||||
def ws_disconnect(message):
|
||||
Group("chat").discard(message.reply_channel)
|
||||
|
||||
|
@ -152,10 +152,10 @@ And what our routing should look like in ``settings.py``::
|
|||
"default": {
|
||||
"BACKEND": "channels.backends.database.DatabaseChannelBackend",
|
||||
"ROUTING": {
|
||||
"django.websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
"django.websocket.keepalive": "myproject.myapp.consumers.ws_add",
|
||||
"django.websocket.receive": "myproject.myapp.consumers.ws_message",
|
||||
"django.websocket.disconnect": "myproject.myapp.consumers.ws_disconnect",
|
||||
"websocket.connect": "myproject.myapp.consumers.ws_add",
|
||||
"websocket.keepalive": "myproject.myapp.consumers.ws_add",
|
||||
"websocket.receive": "myproject.myapp.consumers.ws_message",
|
||||
"websocket.disconnect": "myproject.myapp.consumers.ws_disconnect",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -332,7 +332,7 @@ name in the path of your WebSocket request (we'll ignore auth for now)::
|
|||
from channels import Channel
|
||||
from channels.decorators import channel_session
|
||||
|
||||
# Connected to django.websocket.connect
|
||||
# Connected to websocket.connect
|
||||
@channel_session
|
||||
def ws_connect(message):
|
||||
# Work out room name from path (ignore slashes)
|
||||
|
@ -341,17 +341,17 @@ name in the path of your WebSocket request (we'll ignore auth for now)::
|
|||
message.channel_session['room'] = room
|
||||
Group("chat-%s" % room).add(message.reply_channel)
|
||||
|
||||
# Connected to django.websocket.keepalive
|
||||
# Connected to websocket.keepalive
|
||||
@channel_session
|
||||
def ws_add(message):
|
||||
Group("chat-%s" % message.channel_session['room']).add(message.reply_channel)
|
||||
|
||||
# Connected to django.websocket.receive
|
||||
# Connected to websocket.receive
|
||||
@channel_session
|
||||
def ws_message(message):
|
||||
Group("chat-%s" % message.channel_session['room']).send(content)
|
||||
|
||||
# Connected to django.websocket.disconnect
|
||||
# Connected to websocket.disconnect
|
||||
@channel_session
|
||||
def ws_disconnect(message):
|
||||
Group("chat-%s" % message.channel_session['room']).discard(message.reply_channel)
|
||||
|
@ -400,7 +400,7 @@ have a ChatMessage model with ``message`` and ``room`` fields::
|
|||
"content": message.content['message'],
|
||||
})
|
||||
|
||||
# Connected to django.websocket.connect
|
||||
# Connected to websocket.connect
|
||||
@channel_session
|
||||
def ws_connect(message):
|
||||
# Work out room name from path (ignore slashes)
|
||||
|
@ -409,12 +409,12 @@ have a ChatMessage model with ``message`` and ``room`` fields::
|
|||
message.channel_session['room'] = room
|
||||
Group("chat-%s" % room).add(message.reply_channel)
|
||||
|
||||
# Connected to django.websocket.keepalive
|
||||
# Connected to websocket.keepalive
|
||||
@channel_session
|
||||
def ws_add(message):
|
||||
Group("chat-%s" % message.channel_session['room']).add(message.reply_channel)
|
||||
|
||||
# Connected to django.websocket.receive
|
||||
# Connected to websocket.receive
|
||||
@channel_session
|
||||
def ws_message(message):
|
||||
# Stick the message onto the processing queue
|
||||
|
@ -423,7 +423,7 @@ have a ChatMessage model with ``message`` and ``room`` fields::
|
|||
"message": content,
|
||||
})
|
||||
|
||||
# Connected to django.websocket.disconnect
|
||||
# Connected to websocket.disconnect
|
||||
@channel_session
|
||||
def ws_disconnect(message):
|
||||
Group("chat-%s" % message.channel_session['room']).discard(message.reply_channel)
|
||||
|
|
|
@ -2,13 +2,27 @@ Message Standards
|
|||
=================
|
||||
|
||||
Some standardised message formats are used for common message types - they
|
||||
are detailed below.
|
||||
are detailed below. Message formats are meant to be generic and offload as
|
||||
much protocol-specific processing to the interface server as is reasonable;
|
||||
thus, they should generally represent things at as high a level as makes sense.
|
||||
|
||||
Note: All consumers also receive the channel name as the keyword argument
|
||||
"channel", so there is no need for separate type information to let
|
||||
multi-channel consumers distinguish.
|
||||
In addition to the standards outlined below, each message may contain a
|
||||
``reply_channel``, which details where to send responses. Protocols with
|
||||
separate connection and data receiving messages (like WebSockets) will only
|
||||
contain the connection and detailed client information in the first message;
|
||||
use the ``@channel_session`` decorator to persist this data to consumers of
|
||||
the received data (the decorator will take care of handling persistence and
|
||||
ordering guarantees on messages).
|
||||
|
||||
The length limit on channel names will be 200 characters.
|
||||
All messages must be able to be encoded as JSON; channel backends don't
|
||||
necessarily have to use JSON, but we consider it the lowest common denominator
|
||||
for serialisation format compatability.
|
||||
|
||||
The size limit on messages is 1MB (while channel backends may support larger
|
||||
sizes, all message formats should stay under this limit, which might include
|
||||
multi-part messages where large content must be transferred).
|
||||
|
||||
The length limit on channel names is 200 printable ASCII characters.
|
||||
|
||||
|
||||
HTTP Request
|
||||
|
@ -16,29 +30,45 @@ HTTP Request
|
|||
|
||||
Represents a full-fledged, single HTTP request coming in from a client.
|
||||
|
||||
Standard channel name is ``http.request``.
|
||||
|
||||
Contains the following keys:
|
||||
|
||||
* GET: List of (key, value) tuples of GET variables
|
||||
* POST: List of (key, value) tuples of POST variables
|
||||
* COOKIES: Same as ``request.COOKIES``
|
||||
* META: Same as ``request.META``
|
||||
* path: Same as ``request.path``
|
||||
* path_info: Same as ``request.path_info``
|
||||
* method: Upper-cased HTTP method
|
||||
* response_channel: Channel name to write response to
|
||||
* GET: List of (key, value) tuples of GET variables (keys and values are strings)
|
||||
* POST: List of (key, value) tuples of POST variables (keys and values are strings)
|
||||
* COOKIES: Dict of cookies as {cookie_name: cookie_value} (names and values are strings)
|
||||
* META: Dict of HTTP headers and info as defined in the Django Request docs (names and values are strings)
|
||||
* path: String, full path to the requested page, without query string or domain
|
||||
* path_info: String, like ``path`` but without any script prefix. Often just ``path``.
|
||||
* method: String, upper-cased HTTP method
|
||||
|
||||
Should come with an associated ``reply_channel`` which accepts HTTP Responses.
|
||||
|
||||
|
||||
HTTP Response
|
||||
-------------
|
||||
|
||||
Sends a whole response to a client.
|
||||
Sends either a part of a response or a whole response to a HTTP client - to do
|
||||
streaming responses, several response messages are sent with ``more_content: True``
|
||||
and the final one has the key omitted. Normal, single-shot responses do not
|
||||
need the key at all.
|
||||
|
||||
Contains the following keys:
|
||||
Due to the 1MB size limit on messages, some larger responses will have to be
|
||||
sent multi-part to stay within the limit.
|
||||
|
||||
Only sent on reply channels.
|
||||
|
||||
Keys that must only be in the first message of a set:
|
||||
|
||||
* content_type: String, mimetype of content
|
||||
* status_code: Integer, numerical HTTP status code
|
||||
* cookies: List of cookies to set (as encoded cookie strings suitable for headers)
|
||||
* headers: Dictionary of headers (key is header name, value is value, both strings)
|
||||
|
||||
All messages in a set can the following keys:
|
||||
|
||||
* content: String of content to send
|
||||
* content_type: Mimetype of content
|
||||
* status_code: Numerical HTTP status code
|
||||
* headers: Dictionary of headers (key is header name, value is value)
|
||||
* more_content: Boolean, signals the interface server should wait for another response chunk to stream.
|
||||
|
||||
|
||||
HTTP Disconnect
|
||||
|
@ -47,7 +77,9 @@ HTTP Disconnect
|
|||
Send when a client disconnects early, before the response has been sent.
|
||||
Only sent by long-polling-capable HTTP interface servers.
|
||||
|
||||
Contains the same keys as HTTP Request.
|
||||
Standard channel name is ``http.disconnect``.
|
||||
|
||||
Contains no keys.
|
||||
|
||||
|
||||
WebSocket Connection
|
||||
|
@ -55,14 +87,9 @@ WebSocket Connection
|
|||
|
||||
Sent when a new WebSocket is connected.
|
||||
|
||||
Contains the following keys:
|
||||
Standard channel name is ``websocket.connect``.
|
||||
|
||||
* GET: List of (key, value) tuples of GET variables
|
||||
* COOKIES: Same as ``request.COOKIES``
|
||||
* META: Same as ``request.META``
|
||||
* path: Same as ``request.path``
|
||||
* path_info: Same as ``request.path_info``
|
||||
* reply_channel: Channel name to send responses on
|
||||
Contains the same keys as HTTP Request, without the ``POST`` or ``method`` keys.
|
||||
|
||||
|
||||
WebSocket Receive
|
||||
|
@ -70,10 +97,12 @@ WebSocket Receive
|
|||
|
||||
Sent when a datagram is received on the WebSocket.
|
||||
|
||||
Contains the same keys as WebSocket Connection, plus:
|
||||
Standard channel name is ``websocket.receive``.
|
||||
|
||||
* content: String content of the datagram
|
||||
* binary: If the content is to be interpreted as text or binary
|
||||
Contains the following keys:
|
||||
|
||||
* content: String content of the datagram.
|
||||
* binary: Boolean, saying if the content is binary. If not present or false, content is a UTF8 string.
|
||||
|
||||
|
||||
WebSocket Client Close
|
||||
|
@ -81,25 +110,22 @@ WebSocket Client Close
|
|||
|
||||
Sent when the WebSocket is closed by either the client or the server.
|
||||
|
||||
Contains the same keys as WebSocket Connection, including reply_channel,
|
||||
though nothing should be sent on it.
|
||||
Standard channel name is ``websocket.disconnect``.
|
||||
|
||||
Contains no keys.
|
||||
|
||||
|
||||
WebSocket Send
|
||||
--------------
|
||||
WebSocket Send/Close
|
||||
--------------------
|
||||
|
||||
Sent by a Django consumer to send a message back over the WebSocket to
|
||||
the client.
|
||||
the client or close the client connection. The content is optional if close
|
||||
is set, and close will happen after any content is sent, if some is present.
|
||||
|
||||
Only sent on reply channels.
|
||||
|
||||
Contains the keys:
|
||||
|
||||
* content: String content of the datagram
|
||||
* binary: If the content is to be interpreted as text or binary
|
||||
|
||||
|
||||
WebSocket Server Close
|
||||
----------------------
|
||||
|
||||
Sent by a Django consumer to close the client's WebSocket.
|
||||
|
||||
Contains no keys.
|
||||
* content: String content of the datagram.
|
||||
* binary: If the content is to be interpreted as text or binary.
|
||||
* close: Boolean. If set to True, will close the client connection.
|
||||
|
|
Loading…
Reference in New Issue
Block a user