1. 程式人生 > >類Flask實現前後端交互之代碼聊天室

類Flask實現前後端交互之代碼聊天室

運行 知識 後來 python腳本 零基礎 wsgi headers 發現 機器

    • 前言
    • 框架
      • 項目目錄及各自功能
      • 流程圖
    • 後端
      • server
      • backend
      • exector
    • 前端
      • ajax
      • 頁面更新
    • 演示
      • 簡易應答模式
      • 代理模式處理外部請求
      • 後臺日誌
    • 總結

前言

這兩天老是做夢,全根Python有關,這不昨晚夢見我把Python做成了類似於JavaScript一樣的功能,在前端混的風生水起。結果是個夢。。。。。。

在第一次接觸了Flask之後,就被它優雅的路由映射給俘獲了。後來我自己又搜索了相關的知識點,算是勉強做出一個最最簡化的版本。詳細的內容可以查看我的這篇文章。
http://blog.csdn.net/marksinoberg/article/details/72811360

關於昨晚的夢,早上醒來倒是給了我一個靈感,為什麽不能做出一個代碼聊天室呢? 說著可能有點讓人摸不著頭腦,其實說白了,就是一個本地的代碼執行環境。大致的模樣應該是這個樣子的。

技術分享圖片

“框架”?

項目目錄及各自功能

說到底,這根本不能算是一個框架,充其量也只能是一個工具集吧。項目目錄也比較簡單。如下:

C:\Users\biao\Desktop\筆記\code-chatter>tree /f .
文件夾 PATH 列表
卷序列號為 E0C6-0F15
C:\USERS\BIAO\DESKTOP\筆記\CODE-CHATTER
│  .gitignore
│  backend.py              # 服務後臺
│ executor.py # 客戶端代碼執行工具 │ server.py # 後臺web應用處理器 │ temp.py # 客戶端臨時代碼存放 │ test.py # 測試相關文件 │ ├─templates │ index.css │ index.html │ index.js │ jquery-2.2.4.min.js └─

流程圖

大致來說,軟件工作的流程如下:
技術分享圖片

由於作圖工具的問題,原本應該雙向交互的對象只畫出了單個箭頭。不過看到這個圖後,這個軟件的工作流程應該就不難理解了。

後端

基本上來說後端是重中之重啦。接下來一一的介紹一下吧。

server

按照WSGI標準, 一個WEB應用程序或者框架應該滿足如下條件:
- 本身為一個對象(函數,類init,對象call)
- 有env, start_response兩個參數(當然名字可以不固定)
- 返回對象可叠代

我這裏借助了對象的形式來實現,在__call__方法中添加了處理邏輯。

def __call__(self, env, start_response):
        """
        根據WSGI標準,web應用程序需要包含兩個參數:
        @param env : 一個包含了請求內容的字典
        @param start_response : 開始處理來自客戶端的請求
        """
        path = env["PATH_INFO"]
        if path in self.routes:
            # 路由映射函數已知
            status = ‘200 OK‘
            headers = [(‘Content-Type‘, ‘text/html;charset=UTF-8‘)]
            # print(env)
            # 對來自客戶端的請求做封裝處理
            request_method = env.get("REQUEST_METHOD", "")
            print("***"*28, request_method)
            if request_method == "POST":
                content_length = int(env.get(‘CONTENT_LENGTH‘, 0))
                form_data = parse_qs(env.get(‘wsgi.input‘, ‘‘).read(content_length))
                self.request.add(key=‘method‘, value="POST")
                # TODO 或許在這裏處理一下來自用戶請求的數據,比如escape防止腳本攻擊
                self.request.add(key=‘post_data‘, value=form_data)
            elif request_method == "GET":
                self.request.add(key=‘method‘, value=request_method)
                query_string = env.get(‘QUERY_STRING‘, ‘‘)
                self.request.add(key=‘query_string‘, value=query_string)
            start_response(status, headers)
            return self.routes[path](self.request)
        else:
            # 處理函數不包含在路由控制器內
            status = ‘404 Not Found‘
            headers = [(‘Content-Type‘, ‘text/html;charset=UTF-8‘)]
            start_response(status, headers)
            return ["No handler match for `{}`".format(path).encode(‘utf8‘)]

backend

後臺存在的意義就是路由映射以及監聽客戶端請求,並將與請求對應的處理函數進行轉發處理。
總的來說類似於一個控制器,或者中間件。用過Flask的童鞋可能會非常容易的理解下面代碼的作用了。沒用過的話也應該能見名之意。

# coding: utf8
"""
監聽客戶端請求,返回相應的執行結果。
"""
import os
from server import Application
from executor import runcode
# from jinja2 import Template

app = Application(__name__)


# 下面開始對於前臺的請求做路由控制
@app.route(‘/‘)
def index(request):
    """
    可以適當的對首頁做下簡介。
    """
    print("handler方面:", request)
    # 也可以嘗試使用模板
    with open(‘./templates/index.html‘, ‘r‘, encoding="utf8") as f:
        html = f.read()
        f.close()
    yield html.encode(‘utf8‘)

@app.route(‘/api/user‘)
def user(request):
    print(request.params)
    if request.params.get(‘method‘, ‘‘) == "POST" or request.params.get(‘method‘, ‘‘)== "GET":
        data = request.params.get(‘post_data‘)
        print(data[b‘code‘][0].decode(‘utf8‘))
        # yield "接口處理相關".encode(‘utf8‘)
        code = data[b‘code‘][0].decode(‘utf8‘)
        result = runcode(code)
        yield result
    else:
        yield "Nothing".encode(‘utf8‘)


if __name__ == "__main__":
    app.run(host=‘127.0.0.1‘, port=8888)

通過裝飾器就可以實現非常方便的路由映射,結合server的分發處理,就可以實現針對不同的路徑實現不同的功能了。

exector

臨時代碼執行這塊稍微有點問題,經過測試,我發現使用subprocess.Popen()並不是一個很好的解決辦法。具體表現在:

臨時文件清理工作不夠及時,不夠徹底。

有待進一步改進。

目前版本也只是夠用。。。。。。

# coding: utf8
"""
接受Python腳本,執行相關代碼,返回相應結果。
"""

import subprocess
import os


def runcode(data):
    """
    運行前臺傳過來的Python代碼
    """
    # 刪除臨時文件, 防止上次產生的結果產生影響。
    if os.path.exists(‘temp.py‘):
        os.remove(‘temp.py‘)
    with open(‘temp.py‘, ‘w‘, encoding=‘utf8‘, buffering=1) as f:
        f.write(data)
        f.close()
    # 開啟管道,獲取執行結果
    process = subprocess.Popen(‘python temp.py‘, stdout=subprocess.PIPE)
    data = process.stdout.read()
    del process
    return data

前端

前端我的思路就是利用ajax實現前後端的分離邏輯。讓頁面和數據處理分離開來,更高效的處理各自的事物。

ajax

為了驗證方便性,我用原生的JavaScript和JQuery分別作了實現,發現還是JQuery好用啊,讓我們可以更專註於事物的處理而不是糾結於控制結構上。(⊙﹏⊙)b

function send() {
        // 先獲取文本域內的代碼值
        var sourcecode = $("#sourcecode").val();
        // var sourcecode = document.getElementById("sourcecode").value;
        // 借助ajax實現功能獲取
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                console.log(xhr.responseText)
            }
        }
        xhr.open(‘post‘, ‘/api/user‘)
        xhr.send({ ‘code‘: sourcecode })
    }

    function send2(){
        // 更新聊天頁面
        update_chat();
        // 請求代碼執行結果
        $.ajax({
            url: ‘/api/user‘,
            type: "POST",
            dataType: "json",
            async: true,
            data: {‘code‘: $("#sourcecode").val()},
            success: function(response){
                console.log("Success.")
                console.log(response)
                // console.log(response.responseText);
                // eval(‘var data = ‘+ response.responseText)
                // result = response.responseText
                update_robot(response.content);
            },
            error: function(msg){
                console.log("Error.")
                console.log(msg.responseText);
                result = msg.responseText;
                update_robot(result);
            }
        })
        // document.getElementById("sourcecode").value = "";
        // 更新滾動條,以便於自動上劃。
        scroll_top();
    }

頁面更新

這裏頁面更新的觸發時機應該是每次點擊完發送按鈕之後,所以只需要在按鈕的響應函數裏面添加相應的邏輯即可。

function update_chat(){
        console.log("Ready to append mywords.")
        // 先創建本人說話的內容節點
        var source = $("#sourcecode").val();
        // http://avatar.csdn.net/0/8/F/3_marksinoberg.jpg
        child_node = "<div class=‘mywords‘><img src=‘http://avatar.csdn.net/0/8/F/3_marksinoberg.jpg‘><span>"+source+"</span><br /><br /></div>"
        var mywords = $(child_node);
        $("#lefttop").append(mywords);
    }


    function update_robot(result){
        console.log(‘更新聊天機器人代碼執行結果。‘)
        // 創建代碼返回結果的節點
        child_node = "<br /><br /><div class=‘robot‘><span>"+result+"</span><img src=‘http://avatar.csdn.net/0/B/4/1_yangwei19680827.jpg‘></div>"
        var robot_words = $(child_node);
        $("#lefttop").append(robot_words);
    }

    // 頁面自動上劃
    function scroll_top(){
        var messagebox = document.getElementById("lefttop");
        messagebox.scrollTop = messagebox.scrollHeight-messagebox.style.height;
    }

演示

下面來看幾個圖片,聊表心意。

簡易“應答”模式

技術分享圖片

“代理模式”處理外部請求

技術分享圖片

後臺日誌

技術分享圖片


總結

已知問題:

  • make_server 本身的處理問題。
  • temp.py臨時文件更新問題
  • 靜態文件路徑處理的不是很好(⊙﹏⊙)b

完整代碼可以到我的GitHub上進行download。

https://github.com/guoruibiao/code-chatter

再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow

類Flask實現前後端交互之代碼聊天室