Django websocket之web端实时查看日志实践案例
- 2019 年 10 月 3 日
- 筆記
??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,LogoutView from tailf.views import tailf urlpatterns = [ 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.TAILF return 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
- ???routing???????
webapp/routing.py
from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from django.urls import path, re_path from chat.consumers import ChatConsumer from tailf.consumers import TailfConsumer application = 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??
- ??consumer?
tailf/consumers.py
???
import json from channels.generic.websocket import WebsocketConsumer from tailf.tasks import tailf class 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): # ??????Task self.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
???id
?channel_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_task import 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_send
?channel_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??????????????????????????????????????????
?????????