mirror of
				https://github.com/django/daphne.git
				synced 2025-10-31 15:57:25 +03:00 
			
		
		
		
	Merge pull request #26 from maikhoepfel/patch-1
Wording changes to docs
This commit is contained in:
		
						commit
						17e42a85fb
					
				|  | @ -5,13 +5,13 @@ Django's traditional view of the world revolves around requests and responses; | ||||||
| a request comes in, Django is fired up to serve it, generates a response to | a request comes in, Django is fired up to serve it, generates a response to | ||||||
| send, and then Django goes away and waits for the next request. | send, and then Django goes away and waits for the next request. | ||||||
| 
 | 
 | ||||||
| That was fine when the internet was all driven by simple browser interactions, | That was fine when the internet was driven by simple browser interactions, | ||||||
| but the modern Web includes things like WebSockets and HTTP2 server push, | but the modern Web includes things like WebSockets and HTTP2 server push, | ||||||
| which allow websites to communicate outside of this traditional cycle. | which allow websites to communicate outside of this traditional cycle. | ||||||
| 
 | 
 | ||||||
| And, beyond that, there are plenty of non-critical tasks that applications | And, beyond that, there are plenty of non-critical tasks that applications | ||||||
| could easily offload until after a response as been sent - like saving things | could easily offload until after a response as been sent - like saving things | ||||||
| into a cache, or thumbnailing newly-uploaded images. | into a cache or thumbnailing newly-uploaded images. | ||||||
| 
 | 
 | ||||||
| Channels changes the way Django runs to be "event oriented" - rather than  | Channels changes the way Django runs to be "event oriented" - rather than  | ||||||
| just responding to requests, instead Django responses to a wide array of events | just responding to requests, instead Django responses to a wide array of events | ||||||
|  | @ -38,7 +38,7 @@ alternative is *at-least-once*, where normally one consumer gets the message | ||||||
| but when things crash it's sent to more than one, which is not the trade-off | but when things crash it's sent to more than one, which is not the trade-off | ||||||
| we want. | we want. | ||||||
| 
 | 
 | ||||||
| There are a couple of other limitations - messages must be JSON-serialisable, | There are a couple of other limitations - messages must be JSON serializable, | ||||||
| and not be more than 1MB in size - but these are to make the whole thing | and not be more than 1MB in size - but these are to make the whole thing | ||||||
| practical, and not too important to think about up front. | practical, and not too important to think about up front. | ||||||
| 
 | 
 | ||||||
|  | @ -59,13 +59,13 @@ channel, they're writing into the same channel. | ||||||
| How do we use channels? | How do we use channels? | ||||||
| ----------------------- | ----------------------- | ||||||
| 
 | 
 | ||||||
| That's what a channel is, but how is Django using them? Well, inside Django | So how is Django using those channels? Inside Django | ||||||
| you can write a function to consume a channel, like so:: | you can write a function to consume a channel:: | ||||||
| 
 | 
 | ||||||
|     def my_consumer(message): |     def my_consumer(message): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
| And then assign a channel to it like this in the channel routing:: | And then assign a channel to it in the channel routing:: | ||||||
| 
 | 
 | ||||||
|     channel_routing = { |     channel_routing = { | ||||||
|         "some-channel": "myapp.consumers.my_consumer", |         "some-channel": "myapp.consumers.my_consumer", | ||||||
|  | @ -76,14 +76,11 @@ consumer function with a message object (message objects have a "content" | ||||||
| attribute which is always a dict of data, and a "channel" attribute which | attribute which is always a dict of data, and a "channel" attribute which | ||||||
| is the channel it came from, as well as some others). | is the channel it came from, as well as some others). | ||||||
| 
 | 
 | ||||||
| Django can do this as rather than run in a request-response mode, Channels | Instead of having Django run in the traditional request-response mode,  | ||||||
| changes Django so that it runs in a worker mode - it listens on all channels | Channels changes Django so that it runs in a worker mode - it listens on  | ||||||
| that have consumers assigned, and when a message arrives on one, runs the | all channels that have consumers assigned, and when a message arrives on | ||||||
| relevant consumer. | one, it runs the relevant consumer. So rather than running in just a  | ||||||
| 
 | single process tied to a WSGI server, Django runs in three separate layers: | ||||||
| In fact, this is illustrative of the new way Django runs to enable Channels to |  | ||||||
| work. Rather than running in just a single process tied to a WSGI server, |  | ||||||
| Django runs in three separate layers: |  | ||||||
| 
 | 
 | ||||||
| * Interface servers, which communicate between Django and the outside world. | * Interface servers, which communicate between Django and the outside world. | ||||||
|   This includes a WSGI adapter as well as a separate WebSocket server - we'll |   This includes a WSGI adapter as well as a separate WebSocket server - we'll | ||||||
|  | @ -104,8 +101,8 @@ message and can write out zero to many other channel messages. | ||||||
| 
 | 
 | ||||||
| Now, let's make a channel for requests (called ``http.request``), | Now, let's make a channel for requests (called ``http.request``), | ||||||
| and a channel per client for responses (e.g. ``http.response.o4F2h2Fd``), | and a channel per client for responses (e.g. ``http.response.o4F2h2Fd``), | ||||||
| with the response channel a property (``reply_channel``) of the request message. | where the response channel is a property (``reply_channel``) of the request | ||||||
| Suddenly, a view is merely another example of a consumer:: | message. Suddenly, a view is merely another example of a consumer:: | ||||||
| 
 | 
 | ||||||
|     # Listens on http.request |     # Listens on http.request | ||||||
|     def my_consumer(message): |     def my_consumer(message): | ||||||
|  | @ -120,22 +117,20 @@ In fact, this is how Channels works. The interface servers transform connections | ||||||
| from the outside world (HTTP, WebSockets, etc.) into messages on channels, | from the outside world (HTTP, WebSockets, etc.) into messages on channels, | ||||||
| and then you write workers to handle these messages. | and then you write workers to handle these messages. | ||||||
| 
 | 
 | ||||||
| This may seem like it's still not very well designed to handle push-style |  | ||||||
| code - where you use HTTP2's server-sent events or a WebSocket to notify |  | ||||||
| clients of changes in real time (messages in a chat, perhaps, or live updates |  | ||||||
| in an admin as another user edits something). |  | ||||||
| 
 |  | ||||||
| However, the key here is that you can run code (and so send on channels) in | However, the key here is that you can run code (and so send on channels) in | ||||||
| response to any event - and that includes ones you create. You can trigger | response to any event - and that includes ones you create. You can trigger | ||||||
| on model saves, on other incoming messages, or from code paths inside views | on model saves, on other incoming messages, or from code paths inside views | ||||||
| and forms. | and forms. That approach comes in handy for push-style | ||||||
|  | code - where you use HTML5's server-sent events or a WebSocket to notify | ||||||
|  | clients of changes in real time (messages in a chat, perhaps, or live updates | ||||||
|  | in an admin as another user edits something). | ||||||
| 
 | 
 | ||||||
| .. _channel-types: | .. _channel-types: | ||||||
| 
 | 
 | ||||||
| Channel Types | Channel Types | ||||||
| ------------- | ------------- | ||||||
| 
 | 
 | ||||||
| Now, if you think about it, there are actually two major uses for channels in | There are actually two major uses for channels in | ||||||
| this model. The first, and more obvious one, is the dispatching of work to | this model. The first, and more obvious one, is the dispatching of work to | ||||||
| consumers - a message gets added to a channel, and then any one of the workers | consumers - a message gets added to a channel, and then any one of the workers | ||||||
| can pick it up and run the consumer. | can pick it up and run the consumer. | ||||||
|  | @ -240,15 +235,15 @@ Next Steps | ||||||
| ---------- | ---------- | ||||||
| 
 | 
 | ||||||
| That's the high-level overview of channels and groups, and how you should | That's the high-level overview of channels and groups, and how you should | ||||||
| starting thinking about them - remember, Django provides some channels | start thinking about them. Remember, Django provides some channels | ||||||
| but you're free to make and consume your own, and all channels are | but you're free to make and consume your own, and all channels are | ||||||
| network-transparent. | network-transparent. | ||||||
| 
 | 
 | ||||||
| One thing channels are not, however, is guaranteed delivery. If you want tasks | One thing channels do not, however, is guarantee delivery. If you need | ||||||
| you're sure will complete, use a system designed for this with retries and | certainty that tasks will complete, use a system designed for this with  | ||||||
| persistence like Celery, or you'll need to make a management command that | retries and persistence (e.g. Celery), or alternatively make a management | ||||||
| checks for completion and re-submits a message to the channel if nothing | command that checks for completion and re-submits a message to the channel | ||||||
| is completed (rolling your own retry logic, essentially). | if nothing is completed (rolling your own retry logic, essentially). | ||||||
| 
 | 
 | ||||||
| We'll cover more about what kind of tasks fit well into Channels in the rest | We'll cover more about what kind of tasks fit well into Channels in the rest | ||||||
| of the documentation, but for now, let's progress to :doc:`getting-started` | of the documentation, but for now, let's progress to :doc:`getting-started` | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ Make a new project, a new app, and put this in a ``consumers.py`` file in the ap | ||||||
|         message.reply_channel.send(response.channel_encode()) |         message.reply_channel.send(response.channel_encode()) | ||||||
| 
 | 
 | ||||||
| The most important thing to note here is that, because things we send in | The most important thing to note here is that, because things we send in | ||||||
| messages must be JSON-serialisable, the request and response messages | messages must be JSON serializable, the request and response messages | ||||||
| are in a key-value format. There are ``channel_decode()`` and | are in a key-value format. There are ``channel_decode()`` and | ||||||
| ``channel_encode()`` methods on both Django's request and response classes, | ``channel_encode()`` methods on both Django's request and response classes, | ||||||
| but here we just use the message's ``content`` attribute directly for simplicity | but here we just use the message's ``content`` attribute directly for simplicity | ||||||
|  | @ -69,12 +69,14 @@ If you start up ``python manage.py runserver`` and go to | ||||||
| you get the Hello World response, so things are working. If you don't see | you get the Hello World response, so things are working. If you don't see | ||||||
| a response, check you :doc:`installed Channels correctly <installation>`. | a response, check you :doc:`installed Channels correctly <installation>`. | ||||||
| 
 | 
 | ||||||
| Now, that's not very exciting - raw HTTP responses are something Django can | Now, that's not very exciting - raw HTTP responses are something Django has | ||||||
| do any time. Let's try some WebSockets, and make a basic chat server! | been able to do for a long time. Let's try some WebSockets, and make a basic | ||||||
|  | chat server! | ||||||
| 
 | 
 | ||||||
| Delete that consumer and its routing - we'll want the normal Django view layer to | Delete that consumer and its routing - we'll want the normal Django view layer to | ||||||
| serve HTTP requests from now on - and make this WebSocket consumer instead:: | serve HTTP requests from now on - and make this WebSocket consumer instead:: | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Group |     from channels import Group | ||||||
| 
 | 
 | ||||||
|     def ws_add(message): |     def ws_add(message): | ||||||
|  | @ -82,6 +84,7 @@ serve HTTP requests from now on - and make this WebSocket consumer instead:: | ||||||
| 
 | 
 | ||||||
| Hook it up to the ``websocket.connect`` channel like this:: | Hook it up to the ``websocket.connect`` channel like this:: | ||||||
| 
 | 
 | ||||||
|  |     # In routing.py | ||||||
|     channel_routing = { |     channel_routing = { | ||||||
|         "websocket.connect": "myproject.myapp.consumers.ws_add", |         "websocket.connect": "myproject.myapp.consumers.ws_add", | ||||||
|     } |     } | ||||||
|  | @ -102,19 +105,21 @@ connections you have open at any one time. | ||||||
| 
 | 
 | ||||||
| The solution to this is that the WebSocket interface servers will send | The solution to this is that the WebSocket interface servers will send | ||||||
| periodic "keepalive" messages on the ``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 | so we can hook that up to re-add the channel:: | ||||||
| a group it's already in - similarly, it's safe to discard a channel from a |  | ||||||
| group it's not in):: |  | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Group |     from channels import Group | ||||||
| 
 | 
 | ||||||
|     # Connected to websocket.keepalive |     # Connected to websocket.keepalive | ||||||
|     def ws_keepalive(message): |     def ws_keepalive(message): | ||||||
|         Group("chat").add(message.reply_channel) |         Group("chat").add(message.reply_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. | ||||||
| Of course, this is exactly the same code as the ``connect`` handler, so let's | Of course, this is exactly the same code as the ``connect`` handler, so let's | ||||||
| just route both channels to the same consumer:: | just route both channels to the same consumer:: | ||||||
| 
 | 
 | ||||||
|  |     # In routing.py | ||||||
|     channel_routing = { |     channel_routing = { | ||||||
|         "websocket.connect": "myproject.myapp.consumers.ws_add", |         "websocket.connect": "myproject.myapp.consumers.ws_add", | ||||||
|         "websocket.keepalive": "myproject.myapp.consumers.ws_add", |         "websocket.keepalive": "myproject.myapp.consumers.ws_add", | ||||||
|  | @ -124,6 +129,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 | handler to clean up as people disconnect (most channels will cleanly disconnect | ||||||
| and get this called):: | and get this called):: | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Group |     from channels import Group | ||||||
| 
 | 
 | ||||||
|     # Connected to websocket.disconnect |     # Connected to websocket.disconnect | ||||||
|  | @ -135,6 +141,7 @@ Now, that's taken care of adding and removing WebSocket send channels for the | ||||||
| we're not going to store a history of messages or anything and just replay | we're not going to store a history of messages or anything and just replay | ||||||
| any message sent in to all connected clients. Here's all the code:: | any message sent in to all connected clients. Here's all the code:: | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Group |     from channels import Group | ||||||
| 
 | 
 | ||||||
|     # Connected to websocket.connect and websocket.keepalive |     # Connected to websocket.connect and websocket.keepalive | ||||||
|  | @ -158,9 +165,9 @@ And what our routing should look like in ``routing.py``:: | ||||||
|         "websocket.disconnect": "myproject.myapp.consumers.ws_disconnect", |         "websocket.disconnect": "myproject.myapp.consumers.ws_disconnect", | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| With all that code in your ``consumers.py`` file, you now have a working | With all that code, you now have a working set of a logic for a chat server. | ||||||
| set of a logic for a chat server. All you need to do now is get it deployed, | All you need to do now is get it deployed, and as we'll see, that's not too | ||||||
| and as we'll see, that's not too hard. | hard. | ||||||
| 
 | 
 | ||||||
| Running with Channels | Running with Channels | ||||||
| --------------------- | --------------------- | ||||||
|  | @ -168,7 +175,7 @@ Running with Channels | ||||||
| Because Channels takes Django into a multi-process model, you can no longer | Because Channels takes Django into a multi-process model, you can no longer | ||||||
| just run one process if you want to serve more than one protocol type. | just run one process if you want to serve more than one protocol type. | ||||||
| 
 | 
 | ||||||
| There are multiple kinds of "interface server", and each one will service a | There are multiple kinds of "interface servers", and each one will service a | ||||||
| different type of request - one might do WSGI requests, one might handle | different type of request - one might do WSGI requests, one might handle | ||||||
| WebSockets, or you might have one that handles both. | WebSockets, or you might have one that handles both. | ||||||
| 
 | 
 | ||||||
|  | @ -233,7 +240,7 @@ Persisting Data | ||||||
| Echoing messages is a nice simple example, but it's | Echoing messages is a nice simple example, but it's | ||||||
| skirting around the real design pattern - persistent state for connections. | skirting around the real design pattern - persistent state for connections. | ||||||
| Let's consider a basic chat site where a user requests a chat room upon initial | Let's consider a basic chat site where a user requests a chat room upon initial | ||||||
| connection, as part of the query string (e.g. ``http://host/websocket?room=abc``). | connection, as part of the query string (e.g. ``wss://host/websocket?room=abc``). | ||||||
| 
 | 
 | ||||||
| The ``reply_channel`` attribute you've seen before is our unique pointer to the | The ``reply_channel`` attribute you've seen before is our unique pointer to the | ||||||
| open WebSocket - because it varies between different clients, it's how we can | open WebSocket - because it varies between different clients, it's how we can | ||||||
|  | @ -253,6 +260,7 @@ just like a normal Django session. | ||||||
| Let's use it now to build a chat server that expects you to pass a chatroom | Let's use it now to build a chat server that expects you to pass a chatroom | ||||||
| name in the path of your WebSocket request (we'll ignore auth for now - that's next):: | name in the path of your WebSocket request (we'll ignore auth for now - that's next):: | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Group |     from channels import Group | ||||||
|     from channels.decorators import channel_session |     from channels.decorators import channel_session | ||||||
| 
 | 
 | ||||||
|  | @ -282,7 +290,7 @@ name in the path of your WebSocket request (we'll ignore auth for now - that's n | ||||||
| 
 | 
 | ||||||
| If you play around with it from the console (or start building a simple | If you play around with it from the console (or start building a simple | ||||||
| JavaScript chat client that appends received messages to a div), you'll see | JavaScript chat client that appends received messages to a div), you'll see | ||||||
| that you can now request which chat room you want in the initial request. | that you can set a chat room with the initial request. | ||||||
| 
 | 
 | ||||||
| Authentication | Authentication | ||||||
| -------------- | -------------- | ||||||
|  | @ -336,9 +344,10 @@ loads the user from the *channel* session rather than the *HTTP* session, | ||||||
| and a function called ``transfer_user`` which replicates a user from one session | and a function called ``transfer_user`` which replicates a user from one session | ||||||
| to another. | to another. | ||||||
| 
 | 
 | ||||||
| Bringing that all together, let's make a chat server one where users can only | Bringing that all together, let's make a chat server where users can only | ||||||
| chat to people with the same first letter of their username:: | chat to people with the same first letter of their username:: | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Channel, Group |     from channels import Channel, Group | ||||||
|     from channels.decorators import channel_session |     from channels.decorators import channel_session | ||||||
|     from channels.auth import http_session_user, channel_session_user, transfer_user |     from channels.auth import http_session_user, channel_session_user, transfer_user | ||||||
|  | @ -375,7 +384,7 @@ Django session ID as part of the URL, like this:: | ||||||
| 
 | 
 | ||||||
| You can get the current session key in a template with ``{{ request.session.session_key }}``. | You can get the current session key in a template with ``{{ request.session.session_key }}``. | ||||||
| Note that Channels can't work with signed cookie sessions - since only HTTP | Note that Channels can't work with signed cookie sessions - since only HTTP | ||||||
| responses can set cookies, it needs a backend it can write to separately to | responses can set cookies, it needs a backend it can write to to separately to | ||||||
| store state. | store state. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -393,7 +402,7 @@ easily integrate the send into the save flow of the model, rather than the | ||||||
| message receive - that way, any new message saved will be broadcast to all | message receive - that way, any new message saved will be broadcast to all | ||||||
| the appropriate clients, no matter where it's saved from. | the appropriate clients, no matter where it's saved from. | ||||||
| 
 | 
 | ||||||
| We'll even take some performance considerations into account - We'll make our | We'll even take some performance considerations into account: We'll make our | ||||||
| own custom channel for new chat messages and move the model save and the chat | own custom channel for new chat messages and move the model save and the chat | ||||||
| broadcast into that, meaning the sending process/consumer can move on | broadcast into that, meaning the sending process/consumer can move on | ||||||
| immediately and not spend time waiting for the database save and the | immediately and not spend time waiting for the database save and the | ||||||
|  | @ -402,10 +411,12 @@ immediately and not spend time waiting for the database save and the | ||||||
| Let's see what that looks like, assuming we | Let's see what that looks like, assuming we | ||||||
| have a ChatMessage model with ``message`` and ``room`` fields:: | have a ChatMessage model with ``message`` and ``room`` fields:: | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Channel |     from channels import Channel | ||||||
|     from channels.decorators import channel_session |     from channels.decorators import channel_session | ||||||
|     from .models import ChatMessage |     from .models import ChatMessage | ||||||
| 
 | 
 | ||||||
|  |     # Connected to chat-messages | ||||||
|     def msg_consumer(message): |     def msg_consumer(message): | ||||||
|         # Save to model |         # Save to model | ||||||
|         ChatMessage.objects.create( |         ChatMessage.objects.create( | ||||||
|  | @ -482,6 +493,7 @@ decorator, but generally you'll want to use it for most session-based WebSocket | ||||||
| and other "continuous protocol" things. Here's an example, improving our | and other "continuous protocol" things. Here's an example, improving our | ||||||
| first-letter-of-username chat from earlier:: | first-letter-of-username chat from earlier:: | ||||||
| 
 | 
 | ||||||
|  |     # In consumers.py | ||||||
|     from channels import Channel, Group |     from channels import Channel, Group | ||||||
|     from channels.decorators import channel_session, linearize |     from channels.decorators import channel_session, linearize | ||||||
|     from channels.auth import http_session_user, channel_session_user, transfer_user |     from channels.auth import http_session_user, channel_session_user, transfer_user | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user