基於 flask-socketio 的 CRUD 操做初探

Flask 做爲一個全棧架構,若是你只會 python,而不懂 javascript 的前端知識,彷佛是沒法支撐起你的 web 夢想的,好比,一個簡單的頁面 局部刷新 功能,你就須要用到 ajax 的知識,固然,你還能夠使用 HTML5 的新特性 —— websocket功能,好在 flask 還提供了一個 flask-socketio 插件,本文咱們就探討一下這個 flask-scoketio插件的用法。

理解 websocket 協議

  • HTTP 協議只能經過客戶端發起請求來與客戶端進行通信 —— 這是一個缺陷。
  • 經過websocket 協議,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。

websocket 協議特性

  1. 創建在 TCP 協議之上,服務器端的實現比較容易。
  2. 與 HTTP 協議有着良好的兼容性。默認端口也是80和443,而且握手階段採用 HTTP 協議,所以握手時不容易屏蔽,能經過各類 HTTP 代理服務器。
  3. 數據格式比較輕量,性能開銷小,通訊高效。
  4. 能夠發送文本,也能夠發送二進制數據。
  5. 沒有同源限制,客戶端能夠與任意服務器通訊。
  6. 協議標識符是ws(若是加密,則爲wss),服務器網址就是 URL。

使用 flask-socketio

安裝插件

pip install flask-socketio

項目結構

本文是在 《基於 flask 的 CRUD 操做》 的基礎上增長了 webscoket 的功能,使用的是 init_app() 的形式加載 flask-socketio 插件,和網上的大多數教程稍有不一樣javascript

flask-wtf-crud/
|-- env/
    |-- <python虛擬環境>
|-- app/ <項目的模塊名稱>
    |-- crud/ <前端藍圖>
        |-- __init__.py
        |-- views.py <路由和視圖函數文件>
        |-- forms.py <表單類文件, wtforms插件必須項>
        |-- templates <HTML模板>
            |-- static <靜態文件夾>
                |-- js <JavaScript 文件夾>
                    |-- crud.js # 異步請求的程序主要在此添加
    |-- XXXXXX/ <其它藍圖>
    |-- __init__.py
    |-- models.py <數據庫模型文件>
|-- migrations/ <數據庫表關係文件夾,Flask-Migrate遷移數據庫時使用>
|-- config.py <項目的配置文件>
|-- manage.py <用於啓動程序以及其它程序任務>

將 flask-socketio 引入項目

修改 manage.py 內容

# -*- coding:utf-8 -*-
__author__ = '東方鶚'
__blog__ = u'http://www.os373.cn'

import os
from app import create_app, db, socketio
from app.models import User
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand


app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app=app)
migrate = Migrate(app=app, db=db)

def make_shell_context():
    return dict(app=app, db=db, User=User)


manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)
manager.add_command('run', socketio.run(app=app, host='0.0.0.0', port=5001)) # 新加入的內容


if __name__ == '__main__':
    manager.run()

 修改 app/__init__.py 內容

# -*- coding:utf-8 -*-
__author__ = '東方鶚'
__blog__ = u'http://www.os373.cn'

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import config
from flask_socketio import SocketIO # 新加入的內容
db = SQLAlchemy()

async_mode = None
socketio = SocketIO()


def create_app(config_name):
    """ 使用工廠函數初始化程序實例"""
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app=app)

    db.init_app(app=app)

    socketio.init_app(app=app, async_mode=async_mode) # 新加入的內容

    # 註冊藍本crud
    from .crud import crud as crud_blueprint
    app.register_blueprint(crud_blueprint, url_prefix='/crud')

    return app

當前藍圖的 views.py

# -*- coding:utf-8 -*-
__author__ = '東方鶚'
__blog__ = u'http://www.os373.cn'

from flask import render_template, redirect, request, current_app, url_for, flash, json
from . import crud
from ..models import User
from .forms import AddUserForm, DeleteUserForm, EditUserForm
from ..import db
from threading import Lock
from app import socketio # 新加入的內容
from flask_socketio import emit # 新加入的內容

# 新加入的內容-開始
thread = None
thread_lock = Lock()

def background_thread(users_to_json):
    """Example of how to send server generated events to clients."""
    while True:
        socketio.sleep(5) \\ 每五秒發送一次

        socketio.emit('user_response', {'data': users_to_json}, namespace='/websocket/user_refresh')
# 新加入的內容-結束

@crud.route('/', methods=['GET', 'POST'])
def index():

    return render_template('index.html')


@crud.route('/websocket', methods=['GET', 'POST'])
def websocket():
    add_user_form = AddUserForm(prefix='add_user')
    delete_user_form = DeleteUserForm(prefix='delete_user')
    if add_user_form.validate_on_submit():
        if add_user_form.role.data == u'True':
            role = True
        else:
            role = False
        if add_user_form.status.data == u'True':
            status = True
        else:
            status = False
        u = User(username=add_user_form.username.data.strip(), email=add_user_form.email.data.strip(),
                 role=role, status=status)
        db.session.add(u)
        flash({'success': u'添加用戶<%s>成功!' % add_user_form.username.data.strip()})
    if delete_user_form.validate_on_submit():
        u = User.query.get_or_404(int(delete_user_form.user_id.data.strip()))
        db.session.delete(u)
        flash({'success': u'刪除用戶<%s>成功!' % u.username})

    users = User.query.all()

    return render_template('websocket.html', users=users, addUserForm=add_user_form, deleteUserForm=delete_user_form)


@crud.route('/websocket-edit/<user_id>', methods=['GET', 'POST'])
def user_edit(user_id):
    user = User.query.get_or_404(user_id)
    edit_user_form = EditUserForm(prefix='edit_user', obj=user)
    if edit_user_form.validate_on_submit():
        user.username = edit_user_form.username.data.strip()
        user.email = edit_user_form.email.data.strip()
        if edit_user_form.role.data == u'True':
            user.role = True
        else:
            user.role = False
        if edit_user_form.status.data == u'True':
            user.status = True
        else:
            user.status = False
        flash({'success': u'用戶資料已修改爲功!'})
        return redirect(url_for('.basic'))

    return render_template('edit_websocket.html', editUserForm=edit_user_form, user=user)

# 新加入的內容-開始
@socketio.on('connect', namespace='/websocket/user_refresh')
def connect():
    """ 服務端自動發送通訊請求 """
    global thread
    with thread_lock:
        users = User.query.all()
        users_to_json = [user.to_json() for user in users]

        if thread is None:
            thread = socketio.start_background_task(background_thread, (users_to_json, ))
    emit('server_response', {'data': '試圖鏈接客戶端!'})


@socketio.on('connect_event', namespace='/websocket/user_refresh')
def refresh_message(message):
    """ 服務端接受客戶端發送的通訊請求 """

    emit('server_response', {'data': message['data']})
# 新加入的內容-結束

---------- 以上內容是後端的內容,如下內容是將是前段的內容 ----------html

crud.js 內容

$(document).ready(function () {
    namespace='/websocket/user_refresh';
    var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
    $("#url_show").text("websocket URL: " + location.protocol + '//' + document.domain + ':' + location.port + namespace);

    socket.on('connect', function() { // 發送到服務器的通訊內容
        socket.emit('connect_event', {data: '我已鏈接上服務端!'});
    });

    socket.on('server_response', function(msg) {
        \\ 顯示接受到的通訊內容,包括服務器端直接發送的內容和反饋給客戶端的內容
        $('#log').append('<br>' + $('<div/>').text('接收 : ' + msg.data).html());
    });
    socket.on('user_response', function(msg) {
        //console.log(eval(msg.data[0]));
        //$('#users_show').append('<br>' + $('<div/>').text('接收 : ' + msg.data).html());
        var tbody = "";
        var obj = eval(msg.data[0]);
        $.each(obj, function (n, value) {
            var role = "";
            if (value.role===true){
                role = "管理員";
            }else {
                role = "通常用戶";
            }
            var status = "";
            if (value.status===true){
                status = "正常";
            }else {
                status = "註銷";
            }
            edit_url = "<a href=" +  location.protocol + '//' + document.domain + ':' + location.port + "/crud/websocket-edit/" + value.id + "> 修改</a>";
            delete_url = "<a href=\"javascript:delete_user_" + value.id + "()\">刪除</a>";
            var trs = "";
            trs += "<tr><th>" + (n+1) + "</th><td>" + value.username + "</td><td>" + value.email + "</td><td>" + role + "</td><td>" + status + "</td><td>" + edit_url + " | " + delete_url +"</td></tr>";
            tbody += trs;
        })
        $('#users_show').empty();
        $('#users_show').append(tbody);
    });
});

顯示結果

每次打開網頁,會顯示服務端發送的內容——「試圖鏈接客戶端!」,其後,客戶端返回給服務端——「我已鏈接上服務端!」,然後又被服務端返回給客戶端顯示。前端

如下的表格內容顯示數據局裏的內容,每 5 秒局部刷新一次表格內容。java

服務器後端 log 日誌內容以下:python

總結

  1. 因爲 flask 架構具備上下文的限制,在數據庫裏 增長刪改 內容的時候,表格的內容沒有變化——儘管局部已經進行了刷新。要想顯示變化後的數據庫內容,必須得從新啓動一下 flask 服務。
  2. 就總體的部署來講,在 flask 項目裏添加 websocket 協議,顯得項目較重,實現一個局部刷新的功能仍是用 ajax 比較簡單。
  3. 歡迎大俠可以給個人項目提出修改意見,先行感謝!!!

源碼下載git

參考

相關文章
相關標籤/搜索