tornado与vue.js解决web一对一实时聊天(IM)

整个小项目最核心的通讯功能能够实现,效果如图:
demo图片

记录一下这个阶段遇到的一些问题。

1
2
3
4
5
解决方案:
前端vue.js+ajax
后端django rest framework + tornado
用户鉴权用JWT
redis做系统缓存

一.前后端鉴权问题

1.django api鉴权

django通过鉴定http请求头的Authorization头部,进行鉴权。
AZ9Y5E6OHP_HC_O0ZYNF_HS.png

当收到请求时,主要是以下方面的问题:

  • 该token是否真实有效
  • 该token所代表的用户是否为唯一登录
  • 该token还有多久将会失效,若即将失效,则刷新token,同时要防止并发时,多次重复刷新

解决思路

对token发放的后端接口进行拦截,将/new_token得到的新token存入redis中,将/refresh_token得到的刷新token也存入redis中。 当得到一个带有Authorizaion请求头的请求时,首先通过decode该token,得到username和user_id(注意这里无论token是否失效都可以解码出来基本信息),然后在redis中查询only_key,这里实际上就解决了1,2两个问题。 刷新token的问题,没有找到特别好的解决方案,采用的是,在redis中存储一个refresh_token,如果已经刷新,则不再刷新(防止重复刷新)。

2 . tornado api鉴权

tornado中没有django中中间件的概念,通过python的修饰器来进行鉴权,与django鉴权相同。

3.vue.js鉴权

采用router.beforeEach进行全局验证,需要验证的页面在meta中添加requiresAuth字段。

4.axios鉴权

新建一个api.jscreate一个axios实例,在axios的拦截器中添加请求头Authorization。 所以api请求同一存放在api.js中,在组件中使用时,调用相关promise函数。由于axios是异步的,所以在long pool时,容易出现超时中断的情况。这里我不得不在vue中引入了jquery,用ajax做长轮询。(有好的解决方案也可以交流)

5.由于浏览器的限制,ajax请求容易出现cors,跨域请求失败。 在django中可以直接安装第三方组件来解决,在tornado中不行。 当请求方式为get和部分post时是不会出现cors错误的,但是当post,dataType为json时,这时为非简单请求中的一种,浏览器会自动发送一个options进行嗅探,查看server是否支持该ip的请求,在tornado中设置:

1
2
3
4
5
self.set_header('Access-Control-Allow-Origin', 'http://172.20.10.3:8080')
self.set_header('Access-Control-Allow-Credentials', True)
self.set_header('Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, athorization')
self.set_header('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE')

Access-Control-Allow-Origin中为前端ip+端口号,表示同意该ip的非简单请求。

二. tornado处理实时聊天消息

处理实时的信息,django自己的一套并不好用,django-channels加websocket虽然能实现简单的实时通信,但是效果并不好。tornado是一个异步web框架,处理实时消息,作为django的一个组件(这里说的并不准确,因为tornado的服务器是生产级服务器,可以直接生产部署,再加nginx反向代理,性能十分优越)。参考tornado中ajax聊天室demo(facebook工程师的demo,很值得读,编码规范也可以学习),使用redis做缓存在处理单对单的消息收发。

三。django与tornado的结合部署

官网文档有相关的例子,wsgi的结合部署,使用的是tornado的服务器。主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
wsgi_app = wsgi.WSGIContainer(
django.core.handlers.wsgi.WSGIHandler())
tornado_app = tornado.web.Application(
[
('/api/v1/all-message/', ChatCacheApi.IndexMessageHandler),
('/api/v1/new-message/', ChatCacheApi.NewMessageHandler),
('/api/v1/update-message/', ChatCacheApi.UpdateMessageHandler),
('.*', tornado.web.FallbackHandler, dict(fallback=wsgi_app)),
])
server = tornado.httpserver.HTTPServer(tornado_app)
server.listen(options.options.port, '0.0.0.0')
tornado.ioloop.IOLoop.current().start()

13_6AV_3_X1H_M5F82D2_P5.png

响应时,将会使用tornado server。


主要时记录一下思路,具有实现有点麻烦,有需要再记录。