django + nginx + uwsgi + websocket
最近使用django框架做了一个简单的聊天机器人demo, 开发的过程中使用了django自带的websocket模块,当使用django框架自带的wsgi服务去启动的话,没有什么问题。如果要使用uwsgi启动的话,会报错:handshake的返回400,也就是客户端不合法。针对这边些问题,我去查询了官方文档,发现了我问题:
1. django-websocket 是旧版本的,现在已经没有人维护了。dwebsocket是新版的,推荐使用dwebsocket;
2.使用uwsgi启动的话,需要原有uwsgi.ini 配置信息下添加两行配置信息
2.1 websocket要使用uwsgi自带的websocket模块
2.2 # 加载项目配置
DJANGO_SETTINGS_MODULE=py_webserver.settings
WEBSOCKET_FACTORY_CLASS="dwebsocket.backends.uwsgi.factory.uWsgiWebSocketFactory"
2.3 启动脚本如下:uwsgi --ini ./conf/uwsgi.ini --http-websockets
3.在nginx.conf配置文件中添加几行配置文件即可解决问题
整个demo以及配置信息如下:
1.项目结构:
2.pywebserever项目代码:
conf 模块下的代码:
mysql.ini
[mysqlsettings]
dbname = webSocket_chatbot
host = 127.0.0.1
port = 3306
uname = root
passwd = mysql
pyweb_log.ini
[logs]
mail_level = ERROR
tofile_level = DEBUG
loggers_level = INFO
uwsgi.ini (uwsgi启动项目的时候需要的配置文件)
[uwsgi]
#使用nginx连接时使用
socket=127.0.0.1:8080
#直接做web服务器使用
#http=127.0.0.1:8080
# 启动主进程
master=True
pidfile=uwsgi.pid
processes=4
threads=2
#项目目录
chdir=/home/python/Desktop/test_code/py_webserver
#项目中wsgi.py文件的目录,相对于项目目录
wsgi-file=py_webserver/wsgi.py
# 设置日志目录
daemonize=logs/uwsgi.log
# 设置缓存
buffer-size=32768
# 当服务器退出的时候自动删除unix socket 文件和pid 文件
vacuum = true
# 加载项目配置(django + websocket时需要配置的信息)
DJANGO_SETTINGS_MODULE=py_webserver.settings
WEBSOCKET_FACTORY_CLASS="dwebsocket.backends.uwsgi.factory.uWsgiWebSocketFactory"
pywebserver模块下的代码:
urls.py:
from django.urls import path, include from django.contrib import admin urlpatterns = [ path('admin/', admin.site.urls), path('', include('webwork.urls')), ]
settings.py:
import os import configparser # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) conf = configparser.ConfigParser() # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'webwork', # 此处注册应用 ] # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases conf.read(BASE_DIR + "/conf/mysql.ini") DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': conf.get("mysqlsettings", "dbname"), "Host":conf.get("mysqlsettings", "host"), "PORT":conf.get("mysqlsettings", "port"), "USER":conf.get("mysqlsettings", "uname"), "PASSWORD":conf.get("mysqlsettings", "passwd"), "CONN_MAX_AGE":600 # 增加连接池,保持数据 长连接600秒 } } # Logs配置 conf.read(BASE_DIR + "/conf/pyweb_log.ini") LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' }, }, 'filters': { }, 'handlers': { 'mail_admins': { 'level': conf.get("logs", "mail_level"), 'class': 'django.utils.log.AdminEmailHandler', 'formatter': 'standard', }, 'tofile': { 'level': conf.get("logs", "tofile_level"), 'class': 'logging.FileHandler', 'formatter': 'standard', 'filename': os.path.join(BASE_DIR, 'logs/py_web.log'), }, }, 'loggers': { 'django': { 'handlers': ['tofile'], 'level': conf.get("logs", "loggers_level"), 'propagate': True, }, } }
__inint__.py
import pymysql pymysql.install_as_MySQLdb()
template模块下的代码:
chat.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Test Django Channels</title> </head> <body> <div style="text-align: center;margin-top: 50px"> <input id="message" type="text" style="width: 300px" placeholder="输入消息"> <button id="send-message" style="width:80px;margin-left:20px;">发送</button> <button id="close-message" style="width:80px;margin-left:20px;">关闭</button> <button id="connect-message" style="...">登录连接</button> <button id="test" style="...">测试长连接</button> </div> <table id="show-message" style="width: 410px;margin: 0 auto;margin-top: 10px"> <tr> <td style="text-align: center; border-bottom:1px dashed #000;"><strong>聊天记录</strong></td> </tr> </table> </body> <script src="/static/js/jquery-3.2.1.min.js"></script> <script> var socket = new WebSocket('ws://' + window.location.host + '/users/'); socket.onmessage = function (message) { updateLog("机器人", message.data); $("#message").val(""); $("#message").focus(); }; socket.onopen = function () { }; $("#connect-message").click(function () { socket = new WebSocket('ws://' + window.location.host + '/users/'); socket.onmessage = function (message) { updateLog("机器人", message); $("#message").val(""); $("#message").focus(); }; socket.onopen = function () { }; }); $("#close-message").click(function () { updateLog("机器人", "你好,此次服务结束"); socket.close() }); $("#send-message").click(function () { var inputText = $("#message").val(); if (typeof(inputText) == "undefined" || inputText.length < 1) { alert("没有输入信息"); } else { var msg = {"text": inputText}; socket.send(JSON.stringify(msg)); updateLog("你", inputText); } }); function updateLog(name, message) { var chat = $("#show-message"); var ele = "<tr><td>" + name + ": " + message + "</td></tr>"; chat.append(ele); } $("#test").click(function () { alert(socket.readyState); }) </script> </html>
webwork模块下的代码:
models.py
1 from django.db import models 2 3 # Create your models here. 4 5 class MysqlModel(models.Model): 6 info = models.CharField(max_length=100) 7 save_time = models.DateTimeField() 8 9 class Meta: 10 db_table = "t_chatbot_test"
urls.py
1 from django.urls import include, path 2 from webwork.views import ChatView, user 3 4 urlpatterns = [ 5 path('', ChatView.as_view(), name='chat'), 6 path("users/", user, name="users") 7 8 ]
view.py
1 from django.shortcuts import render 2 from django.views.generic.base import View 3 from .models import MysqlModel 4 import time 5 import uwsgi 6 import json 7 import logging 8 9 10 logger = logging.getLogger("django") 11 # Create your views here. 12 13 class ChatView(View): 14 """加载前端页面""" 15 @staticmethod 16 def get(request): 17 return render(request, "webwork/chat.html") 18 19 20 def user(request): 21 """接受websocket传递过来的信息""" 22 uwsgi.websocket_handshake() 23 uwsgi.websocket_send("你还,很高心为你服务") 24 while True: 25 msg = uwsgi.websocket_recv() 26 msg = msg.decode() 27 data = json.loads(msg) 28 data_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) 29 talk_words = MysqlModel() 30 talk_words.info = data["text"] 31 talk_words.save_time = data_time 32 try: 33 talk_words.save() 34 talks = MysqlModel.objects.all() 35 except Exception as ret: 36 logger.info("save_data:" + str(ret)) 37 else: 38 for talk in talks: 39 logger.info("talk_info:" + talk.info) 40 uwsgi.websocket_send(data["text"])
requirements.txt 中的依赖包
gi-redis==1.0.0 asgiref==1.0.0 attrs==16.3.0 autobahn==0.17.1 Automat==0.5.0 channels==1.0.3 constantly==15.1.0 daphne==1.0.3 Django==2.0.3 incremental==16.10.1 msgpack-python==0.4.8 PyMySQL==0.8.0 pytz==2018.3 redis==2.10.5 six==1.10.0 Twisted==17.1.0 txaio==2.6.1 txredisapi==1.4.4 uWSGI==2.0.17 zope.interface==4.3.3
3.uwsgi的配置:
3.1 安装uwsgi pip install uwsgi 3.2 在项目下的conf文件下创建uwsgi.ini文件,具体配置信息如上边的uwsgi.ini文件所示 3.3 启动uwsgi:uwsgi --ini ./conf/uwsgi.ini --http-websockets 3.4 查看uwsgi启动状态:ps -ef|grep uwsgi 3.5 启动之后在浏览器输入:127.0.0.1;8080 查看uwsgi服务器的状态 3.6 停止启动状态 uwsgi -- stop uwsgi.pid
# 安装nginx并验证是否安装正确
4.1 下载nginx后放到桌面上,解压缩 tar zxvf nginx-1.6.3.tar.gz4.2 进入nginx-1.6.3目录,依次执行以下命令进行安装 ./configure (--prefix=<path>,nginx 指定安装根目录) make sudo make install 4.3 默认安装到/usr/local/nginx/目录,进入此目录 4.4 启动: sudo sbin/nginx
4.5.查看进程 ps -ef|grep nginx
4.6 浏览器检测ngnix是否启动:http://127.0.0.1/ 4.7 关闭ngnix服务器:sudo sbin/nginx -s stop
# nginx指向uwsgi
- http:表示所有的http请求的处理
- server:监听端口,绑定服务器
- location:匹配请求路径,转到相应的处理
server {
listen 80;
server_name localhost;
location / { #将所有的参数转到uwsgi下 include uwsgi_params;; #uwsgi的ip与端口 uwsgi_pass 127.0.0.1:8080; # websocket的匹配 proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
# 注释掉nginx server下的这几行配置
#location / {
# root html;
# index index.html index.htm;
# }
STATIC_ROOT="/home/python/Desktop/test_code/django_static/websocket_static"
STATIC_URL = '/static/'
# 将STATICFILES_DIRS的参数注释掉
4.9.3 收集静态文件到 STATIC_ROOT 中
4.10 在nginx.conf 的server下增加静态文件配置
location /static {
alias /home/python/Desktop/test_code/django_static/websocket_static;
}