这是Django Channels系列文章的第二篇,以web端实现tailf的案例讲解Channels的具体使用以及跟Celery的结合

通过上一篇《Django使用Channels实现WebSocket--上篇》的学习应该对Channels的各种概念有了清晰的认知,可以顺利的将Channels框架集成到自己的Django项目中实现WebSocket了,本篇文章将以一个Channels+Celery实现web端tailf功能的例子更加深入的介绍Channels

先说下我们要实现的目标:所有登录的用户可以查看tailf日志页面,在页面上能够选择日志文件进行监听,多个页面终端同时监听任何日志都互不影响,页面同时提供终止监听的按钮能够终止前端的输出以及后台对日志文件的读取

最终实现的结果见下图

接着我们来看下具体的实现过程

技术实现

所有代码均基于以下软件版本:

  • python==3.6.3
  • django==2.2
  • channels==2.1.7
  • celery==4.3.0

celery4在windows下支持不完善,所以请在linux下运行测试

日志数据定义

我们只希望用户能够查询固定的几个日志文件,就不是用数据库仅借助settings.py文件里写全局变量来实现数据存储

在settings.py里添加一个叫TAILF的变量,类型为字典,key标识文件的编号,value标识文件的路径

TAILF = {1: '/ops/coffee/error.log',2: '/ops/coffee/access.log',
}

基础Web页面搭建

假设你已经创建好了一个叫tailf的app,并添加到了settings.py的INSTALLED_APPS中,app的目录结构大概如下

tailf- migrations- __init__.py- __init__.py- admin.py- apps.py- models.py- tests.py- views.py

依然先构建一个标准的Django页面,相关代码如下

url:

from django.urls import path
from django.contrib.auth.views import LoginView,LogoutViewfrom tailf.views import tailfurlpatterns = [path('tailf', tailf, name='tailf-url'),path('login', LoginView.as_view(template_name='login.html'), name='login-url'),path('logout', LogoutView.as_view(template_name='login.html'), name='logout-url'),
]

因为我们规定只有通过登录的用户才能查看日志,所以引入Django自带的LoginView,logoutView帮助我们快速构建Login,Logout功能

指定了登录模板使用login.html,它就是一个标准的登录页面,post传入username和password两个参数即可,不贴代码了

view:

from django.conf import settings
from django.shortcuts import render
from django.contrib.auth.decorators import login_required# Create your views here.
@login_required(login_url='/login')
def tailf(request):logDict = settings.TAILFreturn render(request, 'tailf/index.html', {"logDict": logDict})

引入了login_required装饰器,来判断用户是否登录,未登录就给跳到/login登录页面

logDict 去setting里取我们定义好的TAILF字典赋值,并传递给前端

template:

{% extends "base.html" %}{% block content %}
<div class="col-sm-8"><select class="form-control" id="file"><option value="">选择要监听的日志</option>{% for k,v in logDict.items %}<option value="{{ k }}">{{ v }}</option>{% endfor %}</select>
</div>
<div class="col-sm-2"><input class="btn btn-success btn-block" type="button" onclick="connect()" value="开始监听"/><br/>
</div>
<div class="col-sm-2"><input class="btn btn-warning btn-block" type="button" onclick="goclose()" value="终止监听"/><br/>
</div>
<div class="col-sm-12"><textarea class="form-control" id="chat-log" disabled rows="20"></textarea>
</div>
{% endblock %}

前端拿到TAILF后通过循环的方式填充到select选择框下,因为数据是字典格式,使用logDict.items的方式可以循环出字典的key和value

这样一个日志监听页面就完成了,但还无法实现日志的监听,继续往下

集成Channels实现WebSocket

日志监听功能主要的设计思路就是页面跟后端服务器建立websocket长连接,后端通过celery异步执行while循环不断的读取日志文件然后发送到websocket的channel里,实现页面上的实时显示

接着我们来集成channels

  1. 先添加routing路由,直接修改webapp/routing.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouterfrom django.urls import path, re_path
from chat.consumers import ChatConsumer
from tailf.consumers import TailfConsumerapplication = ProtocolTypeRouter({'websocket': AuthMiddlewareStack(URLRouter([path('ws/chat/', ChatConsumer),re_path(r'^ws/tailf/(?P<id>\d+)/$', TailfConsumer),]))
})

直接将路由信息写入到了URLRouter里,注意路由信息的外层多了一个list,区别于上一篇中介绍的写路由文件路径的方式

页面需要将监听的日志文件传递给后端,我们使用routing正则P<id>\d+传文件ID给后端程序,后端程序拿到ID之后根据settings中指定的TAILF解析出日志路径

routing的写法跟Django中的url写法完全一致,使用re_path匹配正则routing路由

  1. 添加consumer在tailf/consumers.py文件中
import json
from channels.generic.websocket import WebsocketConsumer
from tailf.tasks import tailfclass TailfConsumer(WebsocketConsumer):def connect(self):self.file_id = self.scope["url_route"]["kwargs"]["id"]self.result = tailf.delay(self.file_id, self.channel_name)print('connect:', self.channel_name, self.result.id)self.accept()def disconnect(self, close_code):# 中止执行中的Taskself.result.revoke(terminate=True)print('disconnect:', self.file_id, self.channel_name)def send_message(self, event):self.send(text_data=json.dumps({"message": event["message"]}))

这里使用Channels的单通道模式,每一个新连接都会启用一个新的channel,彼此互不影响,可以随意终止任何一个监听日志的请求

connect

我们知道self.scope类似于Django中的request,记录了丰富的请求信息,通过self.scope["url_route"]["kwargs"]["id"]取出routing中正则匹配的日志ID

然后将idchannel_name传递给celery的任务函数tailf,tailf根据id取到日志文件的路径,然后循环文件,将新内容根据channel_name写入对应channel

disconnect

当websocket连接断开的时候我们需要终止Celery的Task执行,以清除celery的资源占用

终止Celery任务使用到revoke指令,采用如下代码来实现

self.result.revoke(terminate=True)

注意self.result是一个result对象,而非id

参数terminate=True的意思是是否立即终止Task,为True时无论Task是否正在执行都立即终止,为False(默认)时需要等待Task运行结束之后才会终止,我们使用了While循环不设置为True就永远不会终止了

终止Celery任务的另外一种方法是:

from webapp.celery import app
app.control.revoke(result.id, terminate=True)

send_message

方便我们通过Django的view或者Celery的task调用给channel发送消息,官方也比较推荐这种方式

使用Celery异步循环读取日志

上边已经集成了Channels实现了WebSocket,但connect函数中的celery任务tailf还没有实现,下边来实现它

关于Celery的详细内容可以看这篇文章:《Django配置Celery执行异步任务和定时任务》,本文就不介绍集成使用以及细节原理,只讲一下任务task

task实现代码如下:

from __future__ import absolute_import
from celery import shared_taskimport time
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from django.conf import settings@shared_task
def tailf(id, channel_name):channel_layer = get_channel_layer()filename = settings.TAILF[int(id)]try:with open(filename) as f:f.seek(0, 2)while True:line = f.readline()if line:print(channel_name, line)async_to_sync(channel_layer.send)(channel_name,{"type": "send.message","message": "微信公众号【运维咖啡吧】原创 版权所有 " + str(line)})else:time.sleep(0.5)except Exception as e:print(e)

这里边主要涉及到Channels中另一个非常重要的点:从Channels的外部发送消息给Channel

其实上篇文章中检查通道层是否能够正常工作的时候使用的方法就是从外部给Channel通道发消息的示例,本文的具体代码如下

async_to_sync(channel_layer.send)(channel_name,{"type": "send.message","message": "微信公众号【运维咖啡吧】原创 版权所有 " + str(line)}
)

channel_name 对应于传递给这个任务的channel_name,发送消息给这个名字的channel

type 对应于我们Channels的TailfConsumer类中的send_message方法,将方法中的_换成.即可

message 就是要发送给这个channel的具体信息

上边是发送给单Channel的情况,如果是需要发送到Group的话需要使用如下代码

async_to_sync(channel_layer.group_send)(group_name,{'type': 'chat.message','message': '欢迎关注公众号【运维咖啡吧】'}
)

只需要将发送单channel的send改为group_sendchannel_name改为group_name即可

需要特别注意的是:使用了channel layer之后一定要通过async_to_sync来异步执行

页面添加WebSocket支持

后端功能都已经完成,我们最后需要添加前端页面支持WebSocket

  function connect() {if ( $('#file').val() ) {window.chatSocket = new WebSocket('ws://' + window.location.host + '/ws/tailf/' + $('#file').val() + '/');chatSocket.onmessage = function(e) {var data = JSON.parse(e.data);var message = data['message'];document.querySelector('#chat-log').value += (message);// 跳转到页面底部$('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);};chatSocket.onerror = function(e) {toastr.error('服务端连接异常!')};chatSocket.onclose = function(e) {toastr.error('websocket已关闭!')};} else {toastr.warning('请选择要监听的日志文件')}}

上一篇文章中有详细介绍过websocket的消息类型,这里不多介绍了

至此我们一个日志监听页面完成了,包含了完整的监听功能,但还无法终止,接着看下面的内容

Web页面主动断开WebSocket

web页面上“终止监听”按钮的主要逻辑就是触发WebSocket的onclose方法,从而可以触发Channels后端consumer的disconnect方法,进而终止Celery的循环读取日志任务

前端页面通过.close()可以直接触发WebSocket关闭,当然你如果直接关掉页面的话也会触发WebSocket的onclose消息,所以不用担心Celery任务无法结束的问题

  function goclose() {console.log(window.chatSocket);window.chatSocket.close();window.chatSocket.onclose = function(e) {toastr.success('已终止日志监听!')};}

至此我们包含完善功能的Tailf日志监听、终止页面就全部完成了

写在最后

两篇文章结束不知道你是否对Channels有了更深一步的了解,能够操刀上手将Channels用在自己的项目中,实现理想的功能。个人觉得Channels的重点和难点在于对channel layer的理解和运用,真正的理解了并能熟练运用,相信你一定能够举一反三完美实现更多需求。最后如果对本文的demo源码感兴趣可以关注微信公众号【运维咖啡吧】后台回复小二加我微信向我索取,一定有求必应


相关文章推荐阅读:

  • Django使用Channels实现WebSocket--上篇
  • Django配置Celery执行异步任务和定时任务

转载于:https://www.cnblogs.com/37Y37/p/11248433.html

Django实现web端tailf日志文件相关推荐

  1. python 录制web视频_Python django框架 web端视频加密的实例详解

    视频加密流程图: 后端获取保利威的视频播放授权token,提供接口api给前端 参考文档:http://dev.polyv.net/2019/videoproduct/v-api/v-api-play ...

  2. web在线阅读日志文件,response.getOutputStream().write中文乱码原因

    背景 系统的对接,将接口部署到第三方的企业,有的时候需要看系统的日志信息,每次都要登录服务器上操作太麻烦,如何实现在web页面对日志的观察,将日志文件显示到web页面上,可能实时的显示目前还没做好,但 ...

  3. 使用Python Django开发web应用14 静态文件

    版本声明:转载请注明出处.未经允许,禁止商业用途. 14 使用Python Django开发web应用 静态文件 讲师:刘一凡 第一步: 准备静态文件,制作图片OurBlog 第二步: 缺省配置下.静 ...

  4. 开启Hadoop/Yarn的日志监控功能,配置Spark历史服务,解决web端查看日志时的Java.lang.Exception:Unknown container问题

    解放方法 下来查询官方文档后,才了解到yarn的日志监控功能默认是处于关闭状态的,需要我们进行开启,开启步骤如下: Ps:下面配置的文件的位置在hadoop根目录 etc/haddop文件夹下,比较老 ...

  5. 使用ZipOutputStream实现web端批量下载文件到浏览器

    1.需求场景 平时我们开发web应用时可能会遇到用户在界面选择下载多个报表,点击下载按钮后需要批量下载这些文件到浏览器.但是一般情况下,页面一次点击只能触发一个文件的下载,所以无法实现上述诉求. 2. ...

  6. 【webrtc】web端打开日志及调试

    参考gist Chrome Browser debug logs sawbuck webrtc-org/native-code/logging 取日志 C:\Users\zhangbin\AppDat ...

  7. 【环信IM集成指南】Web端常见问题整理

    1. 掉线之后,会有回调或通知吗? sdk有提供连接状态的回调监听 初始化 [IM开发文档] 2. 怎么转发多条聊天记录? 挑选你要转发的消息内容然后以自定义消息 或者ext扩展字段携带过去实现 3. ...

  8. 记录-分模块输出到不同的日志文件

    文章目录 原因剖析 解决1 测试 解决二 测试 今天碰到一个业务场景: 之前做好了一个web系统,接到任务需要将一个独立的Java系统嵌入到web系统中,Java系统嵌入顺利完成,此时出现了一个问题由 ...

  9. web站点服务器日志管理及分析

    管理Web网站不只是监视Web的速度和Web的内容传送.它不仅要关注服务器每天的吞吐量,还要了解这些Web网站的外来访问,了解网站各页面的访问情况.根据各页面的点击频率来改善网页的内容和质量,提高内容 ...

  10. 从Apache的日志文件收集和提供统计数据(一个Python插件架构的简单实现)

    从Apache的日志文件收集和提供统计数据 这一章我们将介绍基于插件程序的架构和实现.作为例子,我们将构建一个分析Apache服务器log文件的框架.这一次我们不再使用单片机的方式来创建,而是改为采用 ...

最新文章

  1. EL:谁说N素含量高就不固氮了(本研究反而“多多益善”)
  2. 51单片机的中断系统
  3. 福禄克宣布推出 FiberLert,口袋大小的实时光纤探测器
  4. ASP.NET MVC 4 过滤器(Authorize)
  5. Bluetooth LE(低功耗蓝牙) - 第一部分
  6. JavaScript-鼠标事件(鼠标点击松开移动效果)
  7. POJ 1986:Distance Queries(倍增求LCA)
  8. Gitlab 服务器搭建,maven安装与jdk安装,linux下安装git
  9. MPLS virtual private network Internet接入
  10. 虚拟机同步器用易语言怎么写_安装虚拟机的好处或用处是什么/如有效地防止病毒...
  11. 走进WPF之开发类似Visio软件
  12. Keras入门(八)K折交叉验证
  13. DB2新建用户及数据库过程
  14. STM32固件库点亮LED灯
  15. 分享一个matplotlib柱状图
  16. 【阅读笔记】联邦学习实战——联邦个性化推荐案例
  17. ipad/iphone内存管理二
  18. Ankylosauridae OS
  19. 反问疑问_反问疑问句的语法
  20. 5-TDengine集成SpringBoot,MyBatis,MyBatisPlus

热门文章

  1. 【STM32F429的DSP教程】第14章 DSP统计函数-最大值,最小值,平均值和功率
  2. 计算机的音量打不开,电脑的音量控制打不开怎么办
  3. InDesign CS3完全自学视频教程
  4. 信息安全行业含金量较高的2个认证(建议都看一下)
  5. Xcode8 官方下载地址
  6. 【全网最全】Python Package Installer (pip)镜像源使用
  7. php获取input file路径,input上传文件获取路径为C:\fakepath\文件名
  8. AppFuse 3.0
  9. 绕过cdn探测真实ip方法大全
  10. python整除符号_python 整除