实时 Django 终于来了 —— Django Channels 入门指南 已翻译 100%

oschina 投递于 2016/03/18 07:20 (共 17 段, 翻译完成于 03-24)
阅读 29977
收藏 152
7
加载中

今天,我们很高兴请到Jacob Kaplan-Moss。Jacob是来自Herokai,也是 Django的长期的核心代码贡献者,他将在这里分享一些他对某些特性的深入研究,他认为这些特性将重新定义框架未来。

当Django刚创建时,那是十多年前,网络还是一个不太复杂的地方。大部分的网页都是静态的。由数据库支撑的模型/视图/ 控制器架构的网络应用还是很新鲜的东西。Ajax刚刚开始被使用,只在较少的场景中。

到现在2016年,网络明显更加强大。过去的几年里已经看到了所谓的“实时”网络应用:在这类应用中客户端和服务器之间、点对点通信交互非常频繁。包含很多服务(又名微服务)的应用也变成是常态。新的web技术允许web应用程序走向十年前我们只敢在梦里想象的方向。这些核心技术之一就是WebSockets:一种新的提供全双工通信的协议——一个持久的,允许任何时间发送数据的客户端和服务器之间的连接。

翻译于 2016/03/18 21:40
2

在这个新的世界,Django显示出了它的老成。在其核心,Django是建立在请求和响应的简单概念之上的:浏览器发出请求,Django调用一个视图,它返回一个响应并发送回浏览器。

这在WebSockets中是行不通的 !视图的生命周期只在一个请求当中,没有一种机制能打开一个连接不断的发送数据到客户端,而不发送相关请求。

因此:Django  Channels就应运而生了。Channels,简而言之,取代了Django中的“guts” ——请求/响应周期发送跨通道的消息。Channels允许Django以非常类似于传统HTTP的方式支持WebSockets。Channels也允许在运行Django的服务器上运行后台任务。HTTP请求表现以前一样,但也通过Channels进行路由。因此,在Channels 支持下Django现在看起来像这样:

翻译于 2016/03/18 22:10
2

如您所见,Django Channels引入了一些新的概念:

Channels基本上就是任务队列:消息被生产商推到通道,然后传递给监听通道的消费者之一。如果你使用Go语言中的渠道,这个概念应该相当熟悉。主要的区别在于,Django Channels通过网络工作,使生产者和消费者透明地运行在多台机器上。这个网络层称为通道层。通道设计时使用Redis作为其首选通道层,虽然也支持其他类型(和API来创建自定义通道层)。有很多整洁和微妙的技术细节,查阅文档可以看到完整的记录。

现在,通道作为一个独立的应用程序搭配使用Django 1.9使用。计划是将通道合并到Django1.10版本,今年夏天将会发布。

我认为Channels将是Django的一个非常重要的插件:它们将支撑Django顺利进入这个新的web开发的时代。虽然这些api还没有成为Django的一部分,他们将很快就会是!所以,现在是一个完美的时间开始学习Channels:你可以了解未来的Django。

翻译于 2016/03/18 22:20
1

开始实践:如何在Django中实现一个实时聊天应用

作为一个例子,我构建了一个简单的实时聊天应用程序——就像一个非常非常轻量级的Slack。有很多的房间,每个人都在同一个房间里可以聊天,彼此实时交互(使用WebSockets)。

你可以访问我在网络上部署的例子,看看在GitHub上的代码,或点击这个按钮来部署自己的。(这需要一个免费的Heroku账户,所以得要先注册):

注意:你需要在点击上面的按钮后,启动工作进程。使用仪表盘或运行heroku ps:scale web=1:free worker=1:free。

如果你想深入了解这个应用程序是如何工作的——包括你为什么需要worker!——那么请继续读下去。我将会一步一步来构建这个应用程序,并突出关键位置和概念。

翻译于 2016/03/18 22:31
1

第一步——从Django开始

虽然在实现上有了很大差异,但是这仍旧是我们使用了十年的Django。所以第一步和其他任何Django应用是一样的(如果你是Django新手,你得看看如何在Heroku上开始使用PythonDjango新手教程)。创建一个工程后,你可以定义模型来表示一个聊天室和其中的消息(chat/models.py):

class Room(models.Model):
    name = models.TextField()
    label = models.SlugField(unique=True)

class Message(models.Model):
    room = models.ForeignKey(Room, related_name='messages')
    handle = models.TextField()
    message = models.TextField()
    timestamp = models.DateTimeField(default=timezone.now, db_index=True)

(在这一步中,包括后面的例子,我已经将代码最简化,希望能将焦点放到重点上,全部代码请看Gitbub。)

然后创建一个聊天室视图以及相应的urls.py模板

def chat_room(request, label):
    # If the room with the given label doesn't exist, automatically create it
    # upon first visit (a la etherpad).
    room, created = Room.objects.get_or_create(label=label)

    # We want to show the last 50 messages, ordered most-recent-last
    messages = reversed(room.messages.order_by('-timestamp')[:50])

    return render(request, "chat/room.html", {
        'room': room,
        'messages': messages,
    })

现在,我们已经已经有了一个可以运行的Django应用。如果你在标准的Django环境中运行它,你可以看到已经存在的聊天室和聊天记录,但是聊天室内无法进行交互操作。实时没有起作用,我们得做工作来处理 WebSockets。

翻译于 2016/03/18 22:47
2

接下来我们做什么

为了搞明白接下来后台需要做些什么,我们得先看下客户端的代码。你可以在 chat.js 中找到,其实也没做多少工作!首先,创建一个 websocket:

var ws_scheme = window.location.protocol == "https:" ? "wss" : "ws";
var chat_socket = new ReconnectingWebSocket(ws_scheme + '://' + window.location.host + "/chat" + window.location.pathname);

注意:

接下来,我们将加入一个回调函数,当表单提交时,我们就通过WebSocket发送数据(而不是 POST数据):

$('#chatform').on('submit', function(event) {
    var message = {
        handle: $('#handle').val(),
        message: $('#message').val(),
    }
    chat_socket.send(JSON.stringify(message));
    return false;
});

我们可以通过WebSocket发送任何想要发送的数据。像众多的API一样, JSON 是最容易的,所以我们将要发送的数据打包成JSON格式。

最后,我们需要将回调函数与WebSocket上的新数据接收事件对接起来:

chatsock.onmessage = function(message) {
    var data = JSON.parse(message.data);
    $('#chat').append('<tr>' 
        + '<td>' + data.timestamp + '</td>' 
        + '<td>' + data.handle + '</td>'
        + '<td>' + data.message + ' </td>'
    + '</tr>');
};

简单提示:从获取的信息中拉取数据,在会话的表上加上一行。如果现在就运行这个代码,他是无法运行的,现在还没有谁监听WebSocket连接呢,只是简单的HTTP。现在,让我们来连接WebSocket。

翻译于 2016/03/18 23:23
2

安装和创建 Channels

要将这个应用“通道化”,我们需要做三件事情:安装Channels,建立通道层,定义通道路由,修改我们的工程使其运行在Channels上(而不是WSGI)。

1. 安装Channels

要安装Channels,只需要执行pip install channels,然后将 "channels”添加到 INSTALLED_APPS配置项中。安装Channels后,允许Django以“通道模式”运行,使用上面描述的通道架构来完成请求/响应的循环。(为了向后兼容,你仍可以以 WSGI模式运行Django ,但是在这种模式下WebSockets和Channel的其他特性就不能工作了。)

2. 选择一个通道层

接下来,我们将定义一个通道层。这是Channels用来在消费者和生产者(消息发送者)之间传递消息的交换机制。 这是一种有特定属性的消息队列(详细信息请查看Channels文档)。

我们将使用Redis作为我们的通道层:它是首选的生产型(可用于工程部署)通道层,是部署在Heroku上显而易见的选择。 当然也有一些驻留内存和基于数据的通道层,但是它们更适合于本地开发或者低流量情况下使用。 (更多细节,再次请查看 文档。)

但是首先:因为Redis通道层是在另外的包中实现的,我们需要运行pip安装 asgi_redis。(我将会在下面稍微介绍点“ASGI”。)然后我们在CHANNEL_LAYERS配置中定义通道层:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "asgi_redis.RedisChannelLayer",
        "CONFIG": {
            "hosts": [os.environ.get('REDIS_URL', 'redis://localhost:6379')],
        },
        "ROUTING": "chat.routing.channel_routing",
    },
}

要注意的是我们把Redis的连接URL放到环境外面,以适应部署到Heroku的情况。

翻译于 2016/03/23 19:35
2

3. 通道路由

在通道层(CHANNEL_LAYERS),我们已经告诉 Channel去哪里找通道路由——chat.routing.channel_routing。通道路由很类似与URL路由的概念:URL路由将URL映射到视图函数;通道路由将通道映射到消费者函数。跟 urls.py类似,按照惯例通道路由应该在routing.py里。现在,我们创建一条空路由:

channel_routing = {}

(我们将在后面看到好几条通道路由信息,当连接WebSocket的时候回用到。)

你会注意到我们的app里有urls.py和routing.py两个文件:我们使用同一个app处理HTTP请求和WebSockets。这是很典型的做法:Channels应用也是Django应用,所以你想用的所有Django的特性——视图,表单,模型等等——都可以在Channels应用里使用。

翻译于 2016/03/22 22:05
2

4. 运行

最后,我们需要替换掉Django的基于HTTP/WSGI的请求处理器,而是使用通道。它是一个基于新兴标准ASGI(异步服务器网关接口)的, 所以我们将在asgi.py文件里定义处理器:

import os
import channels.asgi

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chat.settings")
channel_layer = channels.asgi.get_channel_layer()

(将来,Django会自动生成这个文件,就像现在自动生成wsgi.py文件一样。)

现在,如果一切顺利的话,我们应该能在通道上把这个app运行起来。Channels接口服务叫做Daphne,我们可以运行如下命令运行这个app:

$ daphne chat.asgi:channel_layer --port 8888

** 如果现在访问http://localhost:8888/ 我们会看到……什么事情也没发生。这很让人困惑,直到你想起Channels将 Django分成了两部分:前台接口服务 Daphne,后台消息消费者。所以想要处理HTTP 请求,我们得运行一个worker:

$ python manage.py runworker

现在请求应该能传递过去了。这说明了其中的机制很简洁:Channels 继续处理 HTTP(S)请求,但是是以一个完全不同的方式去处理,这与通过Django运行 Celery 没有太大的不同,那种情况下运行WSGI服务的同时也要运行Celery服务。不过现在,所有的任务——HTTP请求, WebSockets,后台服务都在worker中运行起来了.

(顺便说一句,我们仍然可以通过运行python manage.py runserver命令来做本地测试。当这么做时, Channels只是在同一进程里运行起Daphne和一个worker。)

翻译于 2016/03/22 22:34
1

WebSocket消费者

好了,我们已经完成了安装;让我们开始进入最奇妙的部分吧。

Channels 将WebSocket连接映射到三个通道中:

  • 一个新的客户端 (如浏览器)第一次通过WebSocket连接上时,一条消息被发送到 websocket.connect 通道。当这发生时,我们记录这个客户端当前进入一个已知的聊天室。

  • 每条客户端通过已建立的socket发送的消息都被发送到 websocket.receive通道。(这些都是从浏览器接收到的消息;记住通道都是单向的。我们等一会儿会介绍如何将消息发送给客户端。)当一条消息被接受时,我们将对聊天室里所有其他客户端进行广播。

  • 最后,当客户端断开连接时,一条消息被发送到websocket.disconnect通道。当这发生时,我们将此客户端从聊天室里移除。

首先,我们得在routing.py文件里对这个三个通道进行hook:

from . import consumers

channel_routing = {
    'websocket.connect': consumers.ws_connect,
    'websocket.receive': consumers.ws_receive,
    'websocket.disconnect': consumers.ws_disconnect,
}

其实很简单:就是将每个通道连接到对应的处理函数。现在我们来看看这些函数。按照惯例我们会将这些函数放到一个 consumers.py 文件里(但是像视图一样,其实也可以放在任何地方)。

首先来看看 ws_connect:

from channels import Group
from channels.sessions import channel_session
from .models import Room

@channel_session
def ws_connect(message):
    prefix, label = message['path'].strip('/').split('/')
    room = Room.objects.get(label=label)
    Group('chat-' + label).add(message.reply_channel)
    message.channel_session['room'] = room.label

(为了清晰起见,我将代码中的异常处理和日志去掉了。要看完整版本,请看GitHub上的consumers.py)。

翻译于 2016/03/22 22:51
3
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(13)

hoosir
hoosir

引用来自“kidfruit”的评论

Django没用过,比较喜欢轻量的flask。。。。

引用来自“latyas”的评论

赶紧用用很快就想丢掉flask了

引用来自“kidfruit”的评论

真的么?我用flask做的微信后端,感觉挺好。。。。下次看一下django
复杂应用,构建大型应用平台的时候,你就知道django的好处。。。
kidfruit
kidfruit

引用来自“kidfruit”的评论

Django没用过,比较喜欢轻量的flask。。。。

引用来自“latyas”的评论

赶紧用用很快就想丢掉flask了
真的么?我用flask做的微信后端,感觉挺好。。。。下次看一下django
latyas
latyas

引用来自“kidfruit”的评论

Django没用过,比较喜欢轻量的flask。。。。
赶紧用用很快就想丢掉flask了
wei2011
wei2011

引用来自“wei2011”的评论

websocket用起来好像不如flask+socketio方便

引用来自“Raphael_goh”的评论

WebSocket是协议,socket io是实现,两码事
我说的是,这篇文章展示的django使用websocket的方法,感觉还不如flask+socketio方便
Raphael_goh
Raphael_goh

引用来自“wei2011”的评论

websocket用起来好像不如flask+socketio方便
WebSocket是协议,socket io是实现,两码事
Loveni
Loveni
这个是好东西 可惜不会
crossmix
crossmix
niubility
古水流觞
古水流觞
NBHH…
wei2011
wei2011
websocket用起来好像不如flask+socketio方便
yuzhouliu
yuzhouliu
牛逼
返回顶部
顶部