類Flask實現前後端互動之程式碼聊天室
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
前言
這兩天老是做夢,全根Python有關,這不昨晚夢見我把Python做成了類似於JavaScript一樣的功能,在前端混的風生水起。結果是個夢。。。。。。
在第一次接觸了Flask之後,就被它優雅的路由對映給俘獲了。後來我自己又搜尋了相關的知識點,算是勉強做出一個最最簡化的版本。詳細的內容可以檢視我的這篇文章。
http://blog.csdn.net/marksinoberg/article/details/72811360
關於昨晚的夢,早上醒來倒是給了我一個靈感,為什麼不能做出一個程式碼聊天室呢? 說著可能有點讓人摸不著頭腦,其實說白了,就是一個本地的程式碼執行環境。大致的模樣應該是這個樣子的。
“框架”?
專案目錄及各自功能
說到底,這根本不能算是一個框架,充其量也只能是一個工具集吧。專案目錄也比較簡單。如下:
C:\Users\biao\Desktop\筆記\code-chatter>tree /f .資料夾 PATH 列表卷序列號為 E0C6-0 F15C:\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└─
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
流程圖
大致來說,軟體工作的流程如下:
由於作圖工具的問題,原本應該雙向互動的物件只畫出了單個箭頭。不過看到這個圖後,這個軟體的工作流程應該就不難理解了。
後端
基本上來說後端是重中之重啦。接下來一一的介紹一下吧。
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')]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
backend
後臺存在的意義就是路由對映以及監聽客戶端請求,並將與請求對應的處理函式進行轉發處理。
總的來說類似於一個控制器,或者中介軟體。用過Flask的童鞋可能會非常容易的理解下面程式碼的作用了。沒用過的話也應該能見名之意。
# coding: utf8"""監聽客戶端請求,返回相應的執行結果。"""import osfrom server import Applicationfrom executor import runcode# from jinja2 import Templateapp = 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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
通過裝飾器就可以實現非常方便的路由對映,結合server的分發處理,就可以實現針對不同的路徑實現不同的功能了。
exector
臨時程式碼執行這塊稍微有點問題,經過測試,我發現使用subprocess.Popen()並不是一個很好的解決辦法。具體表現在:
臨時檔案清理工作不夠及時,不夠徹底。
有待進一步改進。
目前版本也只是夠用。。。。。。
# coding: utf8"""接受Python指令碼,執行相關程式碼,返回相應結果。"""import subprocessimport osdef 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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
前端
前端我的思路就是利用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(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
頁面更新
這裡頁面更新的觸發時機應該是每次點選完傳送按鈕之後,所以只需要在按鈕的響應函式裡面新增相應的邏輯即可。
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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
演示
下面來看幾個圖片,聊表心意。
簡易“應答”模式
“代理模式”處理外部請求
後臺日誌
總結
已知問題:
- make_server 本身的處理問題。
- temp.py臨時檔案更新問題
- 靜態檔案路徑處理的不是很好(⊙﹏⊙)b
完整程式碼可以到我的GitHub上進行download。
https://github.com/guoruibiao/code-chatter