MiniWEB項(xiàng)目、程序解耦和耦合關(guān)系抒倚、區(qū)分動(dòng)態(tài)數(shù)據(jù)和靜態(tài)數(shù)據(jù)、WSGI、WSGI接口中的接口函數(shù)中參數(shù)的函數(shù)回調(diào)
3.1 MiniWEB項(xiàng)目
學(xué)習(xí)目標(biāo)
? 1. 能夠說出WEB服務(wù)器在訪問時(shí)的執(zhí)行過程
? 2. 能夠說出實(shí)現(xiàn)框架的意義
? 3. 能夠說出為什么要進(jìn)行程序的解耦
總結(jié):
? 1. 代碼在開發(fā)過程中乱顾,應(yīng)該遵循高內(nèi)聚低耦合的思想
? 2. 靜態(tài)數(shù)據(jù)是指在訪問時(shí)不會(huì)發(fā)生變化的數(shù)據(jù)
? 3. 動(dòng)態(tài)數(shù)據(jù)是指在訪問時(shí)會(huì)服務(wù)的狀態(tài),條件等發(fā)生不同的變化宫静,得到的數(shù)據(jù)不同
? 4. 通過WSGI接口走净,實(shí)現(xiàn)了服務(wù)器和框架的功能分離
? 5. 服務(wù)器和框架應(yīng)用的功能分離,使服務(wù)器的遷移孤里,維護(hù)更加簡單
--------------------------------------------------------------------------------
3.1.1 HTTP 服務(wù)器運(yùn)行原理
之前在實(shí)現(xiàn)的程序中伏伯,主要代碼都實(shí)現(xiàn)在上圖的左半部分。服務(wù)器的運(yùn)行和 WEB 應(yīng)用的處理捌袜,都是在一個(gè)文件中實(shí)現(xiàn)的说搅。
這幾天的工作,就是把程序解耦虏等,將功能分離弄唧,服務(wù)器只用來提供WEB服務(wù),WEB應(yīng)用用來實(shí)現(xiàn)數(shù)據(jù)處理霍衫。
大家可以了解一下開發(fā)中比較常用的WEB框架候引,比如 Apache ,Nigix,Tomcat等敦跌。
沒有一個(gè)服務(wù)器框架安裝完成后澄干,就完成了WEB應(yīng)用的開發(fā)的。
因?yàn)榉?wù)器根本不知道你要完成的功能是什么柠傍,所以只提供給你服務(wù)麸俘,而應(yīng)用的功能按照服務(wù)的接口來完成。然后讓服務(wù)器響應(yīng)處理惧笛。
3.1.2 原始服務(wù)器回顧分析
在前面的課程中从媚,我們實(shí)現(xiàn)過一個(gè) HTTP 服務(wù)器,我們就在這個(gè)服務(wù)器的基礎(chǔ)上患整,來實(shí)現(xiàn)這階段的 MiniWEB 框架静檬。
首先,先來回顧一下這個(gè)HTTP服務(wù)器的代碼
注意:將代碼復(fù)制到工程文件中之后并级,還需要將資源文件復(fù)制到工程目錄中
原始服務(wù)器 WebServer.py
#? 代碼實(shí)現(xiàn):
? ? import socket
? ? import re
? ? import multiprocessing
? ? def service_client(new_socket):
? ? ? ? """為客戶端返回?cái)?shù)據(jù)"""
? ? ? ? # 1. 接收瀏覽器發(fā)送過來的請求 拂檩,即http請求相關(guān)信息
? ? ? ? # GET / HTTP/1.1
? ? ? ? # .....
? ? ? ? request = new_socket.recv(1024).decode("utf-8")
? ? ? ? #將請求頭信息進(jìn)行按行分解存到列表中
? ? ? ? request_lines = request.splitlines()
? ? ? ? # GET /index.html HTTP/1.1
? ? ? ? file_name = ""
? ? ? ? #正則:? [^/]+ 不以/開頭的至少一個(gè)字符 匹配到/之前
? ? ? ? #? ? ? (/[^ ]*) 以分組來匹配第一個(gè)字符是/,然后不以空格開始的0到多個(gè)字符,也就是空格之前
? ? ? ? #? ? ? 最后通過匹配可以拿到 請求的路徑名? 比如:index.html
? ? ? ? ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
? ? ? ? #如果匹配結(jié)果 不為none,說明請求地址正確
? ? ? ? if ret:
? ? ? ? ? ? #利用分組得到請求地址的文件名,正則的分組從索引1開始
? ? ? ? ? ? file_name = ret.group(1)
? ? ? ? ? ? print('FileName:? ' + file_name)
? ? ? ? ? ? #如果請求地址為 / 將文件名設(shè)置為index.html,也就是默認(rèn)訪問首頁
? ? ? ? ? ? if file_name == "/":
? ? ? ? ? ? ? ? file_name = "/index.html"
? ? ? ? # 2. 返回http格式的數(shù)據(jù),給瀏覽器
? ? ? ? try:
? ? ? ? ? ? #拼接路徑,在當(dāng)前的html目錄下找訪問的路徑對應(yīng)的文件進(jìn)行讀取
? ? ? ? ? ? f = open("./html" + file_name, "rb")
? ? ? ? except:
? ? ? ? ? ? #如果沒找到,拼接響應(yīng)信息并返回信息
? ? ? ? ? ? response = "HTTP/1.1 404 NOT FOUND\r\n"
? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? response += "------file not found-----"
? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? else:
? ? ? ? ? ? #如果找到對應(yīng)文件就讀取并返回內(nèi)容
? ? ? ? ? ? html_content = f.read()
? ? ? ? ? ? f.close()
? ? ? ? ? ? # 2.1 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)---header
? ? ? ? ? ? response = "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? #如果想在響應(yīng)體中直接發(fā)送文件內(nèi)的信息,那么在上面讀取文件時(shí)就不能用rb模式,只能使用r模式,所以下面將響應(yīng)頭和響應(yīng)體分開發(fā)送
? ? ? ? ? ? #response += html_content
? ? ? ? ? ? # 2.2 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)
? ? ? ? ? ? # 將response header發(fā)送給瀏覽器
? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? # 將response body發(fā)送給瀏覽器
? ? ? ? ? ? new_socket.send(html_content)
? ? ? ? # 關(guān)閉套接
? ? ? ? new_socket.close()
? ? def main():
? ? ? ? """用來完成整體的控制"""
? ? ? ? # 1. 創(chuàng)建套接字
? ? ? ? tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
? ? ? ? #用來重新啟用占用的端口
? ? ? ? tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
? ? ? ? # 2. 綁定IP和端口號
? ? ? ? tcp_server_socket.bind(("", 7890))
? ? ? ? # 3. 設(shè)置套接字監(jiān)聽連接數(shù)(最大連接數(shù))
? ? ? ? tcp_server_socket.listen(128)
? ? ? ? while True:
? ? ? ? ? ? # 4. 等待新客戶端的鏈接
? ? ? ? ? ? new_socket, client_addr = tcp_server_socket.accept()
? ? ? ? ? ? # 5. 為連接上來的客戶端去創(chuàng)建一個(gè)新的進(jìn)程去運(yùn)行
? ? ? ? ? ? p = multiprocessing.Process(target=service_client, args=(new_socket,))
? ? ? ? ? ? p.start()
? ? ? ? ? ? #因?yàn)樾逻M(jìn)程在創(chuàng)建過程中會(huì)完全復(fù)制父進(jìn)程的運(yùn)行環(huán)境,所以父線程中關(guān)閉的只是自己環(huán)境中的套接字對象
? ? ? ? ? ? #而新進(jìn)程中因?yàn)楸粡?fù)制的環(huán)境中是獨(dú)立存在的,所以不會(huì)受到影響
? ? ? ? ? ? new_socket.close()
? ? ? ? # 關(guān)閉監(jiān)聽套接字
? ? ? ? tcp_server_socket.close()
? ? if __name__ == "__main__":
? ? ? ? main()
3.1.3 程序解耦
? <1>概念理解 什么是耦合關(guān)系?
? ? ? 耦合關(guān)系是指某兩個(gè)事物之間如果存在一種相互作用嘲碧、相互影響的關(guān)系,那么這種關(guān)系就稱"耦合關(guān)系"稻励。
? ? ? 在軟件工程中的耦合就是代碼之間的依賴性。
? ? ? 代碼之間的耦合度越高,維護(hù)成本越高望抽。
? <2>代碼開發(fā)原則之一:高內(nèi)聚加矛,低耦合。
? ? ? 這句話的意思就是程序的每一個(gè)功能都要單獨(dú)內(nèi)聚在一個(gè)函數(shù)中煤篙,讓代碼之間的耦合度達(dá)到最小斟览。也就是相互之間的依賴性達(dá)到最小。
? 實(shí)現(xiàn)面向?qū)ο蟮乃枷氲拇a重構(gòu)
? ? ? 以面向?qū)ο蟮乃枷雭硗瓿煞?wù)器的代碼實(shí)現(xiàn) 實(shí)現(xiàn)過程:
? ? ? ? ? ■ 1.封裝類
? ? ? ? ? ■ 2.初始化方法中創(chuàng)建socket對象
? ? ? ? ? ■ 3.啟動(dòng)服務(wù)器的方法中進(jìn)行服務(wù)監(jiān)聽
? ? ? ? ? ■ 4.實(shí)現(xiàn)數(shù)據(jù)處理的方法
? ? ? ? ? ■ 5.對象屬性的相應(yīng)修改
? ? ? ? ? ■ 6.重新實(shí)現(xiàn)main方法,創(chuàng)建WEBServer類對象并啟動(dòng)服務(wù)
WebServer.py
? ? # 面向?qū)ο笮薷臄?shù)據(jù)
? ? import socket
? ? import re
? ? import multiprocessing
? ? class WEBServer(object):
? ? ? ? #在初始化方法中完成服務(wù)器Socket對象的創(chuàng)建
? ? ? ? def __init__(self):
? ? ? ? ? ? """用來完成整體的控制"""
? ? ? ? ? ? # 1. 創(chuàng)建套接字
? ? ? ? ? ? self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
? ? ? ? ? ? # 用來重新啟用占用的端口
? ? ? ? ? ? self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
? ? ? ? ? ? # 2. 綁定IP和端口號
? ? ? ? ? ? self.tcp_server_socket.bind(("", 7890))
? ? ? ? ? ? # 3. 設(shè)置套接字監(jiān)聽連接數(shù)(最大連接數(shù))
? ? ? ? ? ? self.tcp_server_socket.listen(128)
? ? ? ? def service_client(self,new_socket):
? ? ? ? ? ? """為這個(gè)客戶端返回?cái)?shù)據(jù)"""
? ? ? ? ? ? # 1. 接收瀏覽器發(fā)送過來的請求 辑奈,即http請求相關(guān)信息
? ? ? ? ? ? # GET / HTTP/1.1
? ? ? ? ? ? # .....
? ? ? ? ? ? request = new_socket.recv(1024).decode("utf-8")
? ? ? ? ? ? #將請求頭信息進(jìn)行按行分解存到列表中
? ? ? ? ? ? request_lines = request.splitlines()
? ? ? ? ? ? # GET /index.html HTTP/1.1
? ? ? ? ? ? # get post put del
? ? ? ? ? ? file_name = ""
? ? ? ? ? ? #正則:? [^/]+ 不以/開頭的至少一個(gè)字符 匹配到/之前
? ? ? ? ? ? #? ? ? (/[^ ]*) 以分組來匹配第一個(gè)字符是/,然后不以空格開始的0到多個(gè)字符,也就是空格之前
? ? ? ? ? ? #? ? ? 最后通過匹配可以拿到 請求的路徑名? 比如:index.html
? ? ? ? ? ? ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
? ? ? ? ? ? #如果匹配結(jié)果 不為none,說明請求地址正確
? ? ? ? ? ? if ret:
? ? ? ? ? ? ? ? #利用分組得到請求地址的文件名,正則的分組從索引1開始
? ? ? ? ? ? ? ? file_name = ret.group(1)
? ? ? ? ? ? ? ? print('FileName:? ' + file_name)
? ? ? ? ? ? ? ? #如果請求地址為 / 將文件名設(shè)置為index.html,也就是默認(rèn)訪問首頁
? ? ? ? ? ? ? ? if file_name == "/":
? ? ? ? ? ? ? ? ? ? file_name = "/index.html"
? ? ? ? ? ? # 2. 返回http格式的數(shù)據(jù)苛茂,給瀏覽器
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? #拼接路徑,在當(dāng)前的html目錄下找訪問的路徑對應(yīng)的文件進(jìn)行讀取
? ? ? ? ? ? ? ? f = open("./html" + file_name, "rb")
? ? ? ? ? ? except:
? ? ? ? ? ? ? ? #如果沒找到,拼接響應(yīng)信息并返回信息
? ? ? ? ? ? ? ? response = "HTTP/1.1 404 NOT FOUND\r\n"
? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? response += "------file not found-----"
? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? #如果找到對應(yīng)文件就讀取并返回內(nèi)容
? ? ? ? ? ? ? ? html_content = f.read()
? ? ? ? ? ? ? ? f.close()
? ? ? ? ? ? ? ? # 2.1 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)---header
? ? ? ? ? ? ? ? response = "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? #如果想在響應(yīng)體中直接發(fā)送文件內(nèi)的信息,那么在上面讀取文件時(shí)就不能用rb模式,只能使用r模式,所以下面將響應(yīng)頭和響應(yīng)體分開發(fā)送
? ? ? ? ? ? ? ? #response += html_content
? ? ? ? ? ? ? ? # 2.2 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)
? ? ? ? ? ? ? ? # 將response header發(fā)送給瀏覽器
? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? ? ? # 將response body發(fā)送給瀏覽器
? ? ? ? ? ? ? ? new_socket.send(html_content)
? ? ? ? ? ? # 關(guān)閉套接
? ? ? ? ? ? new_socket.close()
? ? ? ? def run(self):
? ? ? ? ? ? while True:
? ? ? ? ? ? ? ? # 4. 等待新客戶端的鏈接
? ? ? ? ? ? ? ? new_socket, client_addr = self.tcp_server_socket.accept()
? ? ? ? ? ? ? ? # 5. 為這個(gè)客戶端服務(wù)
? ? ? ? ? ? ? ? p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
? ? ? ? ? ? ? ? p.start()
? ? ? ? ? ? ? ? #因?yàn)樾戮€程在創(chuàng)建過程中會(huì)完全復(fù)制父線程的運(yùn)行環(huán)境,所以父線程中關(guān)閉的只是自己環(huán)境中的套接字對象
? ? ? ? ? ? ? ? #而新線程中因?yàn)楸粡?fù)制的環(huán)境中是獨(dú)立存在的,所以不會(huì)受到影響
? ? ? ? ? ? ? ? new_socket.close()
? ? ? ? ? ? # 關(guān)閉監(jiān)聽套接字
? ? ? ? ? ? self.tcp_server_socket.close()
? ? def main():
? ? ? ? webServer = WEBServer()
? ? ? ? webServer.run()
? ? if __name__ == "__main__":
? ? ? ? main()
通過使用面向?qū)ο蟮乃枷耄瑢⒋a重構(gòu)后鸠窗,耦合性降低妓羊,但還沒有完全實(shí)現(xiàn)功能的分離。 目前還是在一個(gè)文件中實(shí)現(xiàn)所有的程序功能稍计,也就是說躁绸,目前只是完成了在原理圖中,左半側(cè)的功能臣嚣。后面會(huì)繼續(xù)改進(jìn)净刮。
3.1.4 區(qū)分動(dòng)態(tài)數(shù)據(jù)和靜態(tài)數(shù)據(jù)
? 靜態(tài)數(shù)據(jù):是指在頁面進(jìn)行訪問時(shí),無論何時(shí)訪問,得到的內(nèi)容都是同樣的,不會(huì)發(fā)生任意變化
? ? ? (比如我們現(xiàn)在實(shí)現(xiàn)的API網(wǎng)頁的訪問效果,這些API文件都是保存在本地(或服務(wù)器上)的一些固定的文檔說明硅则,無論在何時(shí)何地訪問這些數(shù)據(jù)淹父,都是相同的,不會(huì)發(fā)生變化)
? 動(dòng)態(tài)數(shù)據(jù):是指在頁面進(jìn)行訪問時(shí),得到的數(shù)據(jù)是經(jīng)過服務(wù)器進(jìn)行計(jì)算,加工,處理過后的數(shù)據(jù),稱為動(dòng)態(tài)數(shù)據(jù),哪怕只是加了一個(gè)空格
? ? ? 比如:實(shí)時(shí)新聞,股票信息抢埋,購物網(wǎng)站顯示的商品信息等等都動(dòng)態(tài)數(shù)據(jù)
? 在這部分代碼實(shí)現(xiàn)中,先來實(shí)現(xiàn)不同形式的頁面訪問督暂,服務(wù)器返回不同的數(shù)據(jù)(數(shù)據(jù)暫時(shí)還是靜態(tài)的揪垄,假的數(shù)據(jù),真正的動(dòng)態(tài)數(shù)據(jù)會(huì)在完成框架后逻翁,在數(shù)據(jù)庫中讀取返回)
? 這里設(shè)定: xxx.html 訪問時(shí)饥努,返回的是靜態(tài)數(shù)據(jù) API 文檔中的內(nèi)容, xxx.py 訪問時(shí)八回,返回的是動(dòng)態(tài)數(shù)據(jù)(數(shù)據(jù)先以靜態(tài)數(shù)據(jù)代替)
? 實(shí)現(xiàn)過程:
? ? ? 1.先根據(jù)訪問頁面地址判斷訪問數(shù)據(jù)的類型,是py的動(dòng)態(tài)還是html的靜態(tài)
? ? ? 2.根據(jù)動(dòng)態(tài)請求的路徑名的不同來返回不同的數(shù)據(jù),不在使用html獲取數(shù)據(jù),而使用py來獲取
WebServer.py
? ? # ...
? ? # 前面的代碼不需要修改
? ? ? ? ? ? if ret:
? ? ? ? ? ? #利用分組得到請求地址的文件名,正則的分組從索引1開始
? ? ? ? ? ? file_name = ret.group(1)
? ? ? ? ? ? print('FileName:? ' + file_name)
? ? ? ? ? ? #如果請求地址為 / 將文件名設(shè)置為index.html,也就是默認(rèn)訪問首頁
? ? ? ? ? ? if file_name == "/":
? ? ? ? ? ? ? ? file_name = "/index.html"
? ? ? ? ? ? # ------------- 這里開始修改代碼------------
? ? ? ? ? ? #判斷訪問路徑的類型
? ? ? ? ? ? if file_name.endswith('.py'):
? ? ? ? ? ? ? ? #根據(jù)不同的文件名來確定返回的響應(yīng)信息
? ? ? ? ? ? ? ? if file_name == '/index.py':? ? ? ? ? ? ? ? #首頁
? ? ? ? ? ? ? ? ? ? header =? "HTTP/1.1 200 OK\r\n"? ? ? ? #響應(yīng)頭
? ? ? ? ? ? ? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應(yīng)體
? ? ? ? ? ? ? ? ? ? data = header + '\r\n' + body? ? ? ? ? #拼接響應(yīng)信息
? ? ? ? ? ? ? ? ? ? new_socket.send(data.encode('utf-8'))? #返回響應(yīng)信息
? ? ? ? ? ? ? ? elif file_name == '/center.py':? ? ? ? ? ? #個(gè)人中心頁面
? ? ? ? ? ? ? ? ? ? header =? "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? ? ? ? ? body = 'Center Page ...'
? ? ? ? ? ? ? ? ? ? data = header + '\r\n' + body
? ? ? ? ? ? ? ? ? ? new_socket.send(data.encode('utf-8'))
? ? ? ? ? ? ? ? else:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #其它頁面
? ? ? ? ? ? ? ? ? ? header =? "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? ? ? ? ? body = 'Other Page ...'
? ? ? ? ? ? ? ? ? ? data = header + '\r\n' + body
? ? ? ? ? ? ? ? ? ? new_socket.send(data.encode('utf-8'))
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? # 2. 返回http格式的數(shù)據(jù)酷愧,給瀏覽器
? ? ? ? ? ? ? ? try:
? ? ? ? ? ? ? ? ? ? #拼接路徑,在當(dāng)前的html目錄下找訪問的路徑對應(yīng)的文件進(jìn)行讀取
? ? ? ? ? ? ? ? ? ? f = open("./html" + file_name, "rb")
? ? ? ? ? ? ? ? except:
? ? ? ? ? ? ? ? ? ? #如果沒找到,拼接響應(yīng)信息并返回信息
? ? ? ? ? ? ? ? ? ? response = "HTTP/1.1 404 NOT FOUND\r\n"
? ? ? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? ? ? response += "------file not found-----"
? ? ? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? ? ? else:
? ? ? ? ? ? ? ? ? ? #如果找到對應(yīng)文件就讀取并返回內(nèi)容
? ? ? ? ? ? ? ? ? ? html_content = f.read()
? ? ? ? ? ? ? ? ? ? f.close()
? ? ? ? ? ? ? ? ? ? # 2.1 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)---header
? ? ? ? ? ? ? ? ? ? response = "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? ? ? #如果想在響應(yīng)體中直接發(fā)送文件內(nèi)的信息,那么在上面讀取文件時(shí)就不能用rb模式,只能使用r模式,所以下面將響應(yīng)頭和響應(yīng)體分開發(fā)送
? ? ? ? ? ? ? ? ? ? #response += html_content
? ? ? ? ? ? ? ? ? ? # 2.2 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)
? ? ? ? ? ? ? ? ? ? # 將response header發(fā)送給瀏覽器
? ? ? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? ? ? ? ? # 將response body發(fā)送給瀏覽器
? ? ? ? ? ? ? ? ? ? new_socket.send(html_content)
3.1.5 實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)的響應(yīng)優(yōu)化
雖然前面的代碼實(shí)現(xiàn)了設(shè)計(jì)需求,但是實(shí)現(xiàn)過程太過冗余,不符合代碼開發(fā)原則缠诅。 一個(gè)服務(wù)器中提供可以訪問的頁面肯定不止這么幾個(gè),如果每一個(gè)都實(shí)現(xiàn)一次響應(yīng)信息的編寫溶浴,那冗余代碼就太多了,不符合代碼的開發(fā)規(guī)范 通過分析我們可以看出,代碼中大部分內(nèi)容都是相同的管引,只有在響應(yīng)信息的響應(yīng)體部分不同士败,那么就可以將代碼優(yōu)化一下。
? 實(shí)現(xiàn)過程: 因?yàn)樗许撁娴捻憫?yīng)信息都是相同的,所以讓這些頁面共用一塊代碼
? ? ? 1. 將響應(yīng)頭和空行代碼放到判斷頁面之前
? ? ? 2. 將發(fā)拼接和發(fā)送代碼放到判斷之后
? ? ? 3. 頁面判斷中谅将,只根據(jù)不同的頁面設(shè)計(jì)不同的響應(yīng)體信息
實(shí)現(xiàn)代碼: WebServer.py
? ? # ...
? ? # 前面的代碼不需要修改
? ? ? ? #判斷訪問路徑的類型
? ? ? ? # ------------- 這里開始修改代碼------------
? ? ? ? if file_name.endswith('.py'):
? ? ? ? ? ? header = "HTTP/1.1 200 OK\r\n"? # 響應(yīng)頭
? ? ? ? ? ? #根本不同的文件名來確定返回的響應(yīng)信息
? ? ? ? ? ? if file_name == '/index.py':
? ? ? ? ? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應(yīng)體
? ? ? ? ? ? elif file_name == '/center.py':
? ? ? ? ? ? ? ? body = 'Center Page ...'
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? body = 'Other Page ...'
? ? ? ? ? ? data = header + '\r\n' + body? # 拼接響應(yīng)信息
? ? ? ? ? ? new_socket.send(data.encode('utf-8'))? # 返回響應(yīng)信息
? ? ? ? # ------------- 這里開始修改代碼結(jié)束------------
? ? ? ? else:
? ? ? ? # 后面的代碼不需要修改
3.1.6 實(shí)現(xiàn)功能的分離
? 代碼被進(jìn)一步優(yōu)化漾狼,但是還是存在問題。網(wǎng)絡(luò)請求和數(shù)據(jù)處理還是沒有分開饥臂,還是在同一個(gè)文件中實(shí)現(xiàn)的逊躁。
? ? ? 實(shí)際開發(fā)中WEB服務(wù)器有很多種,比如Apache,Nigix等等。
? ? ? 如果在開發(fā)過程中,需要對 WEB 服務(wù)器進(jìn)行更換隅熙。那么我們現(xiàn)在的做法就要花費(fèi)很大的精力稽煤,因?yàn)?WEB 服務(wù)和數(shù)據(jù)處理都在一起。
? ? ? 如果能將程序的功能進(jìn)行進(jìn)行分離猛们,提供 WEB 請求響應(yīng)的服務(wù)器只管請求的響應(yīng),而響應(yīng)返回的數(shù)據(jù)由另外的程序來進(jìn)行處理念脯。
? ? ? 這樣的話,WEB 服務(wù)和數(shù)據(jù)處理之間的耦合性就降低了,這樣更便于功能的擴(kuò)展和維護(hù)
? ? ? ? ? ■ 比如:
? ? ? ? ? ■ 一臺(tái)電腦,如果要是所有的更件都是集成在主板上的弯淘,那么只要有一個(gè)地方壞了绿店。那整個(gè)主板都要換掉。成本很高
? ? ? ? ? ■ 如果所有的硬件都是以卡槽接口的形式插在主板上庐橙,那么如果哪一個(gè)硬件壞了或要進(jìn)行升級擴(kuò)展都會(huì)很方便假勿,降低了成本。
? ? ? 在實(shí)際開發(fā)過程中态鳖,代碼的模塊化思想就是來源于生活转培,讓每個(gè)功能各司其職。
? 實(shí)現(xiàn)思想: 將原來的服務(wù)器文件拆分成兩個(gè)文件浆竭,一個(gè)負(fù)責(zé)請求響應(yīng)浸须,一個(gè)負(fù)責(zé)數(shù)據(jù)處理。 那么這里出現(xiàn)一個(gè)新的問題邦泄,兩個(gè)文件中如何進(jìn)行通信呢删窒?負(fù)責(zé)數(shù)據(jù)處理的文件怎么知道客戶端要請求什么數(shù)據(jù)呢?
? ? ? 想一下主板和內(nèi)存之間是如何連接的顺囊?
? 實(shí)現(xiàn)過程:
? ? ? 1.WebServer 文件只用來提供請求的接收和響應(yīng)
? ? ? 2.WebFrame 文件只用來提供請求數(shù)據(jù)的處理和返回
? ? ? 3.文件之間利用一個(gè)函數(shù)來傳遞請求數(shù)據(jù)和返回的信息
? 實(shí)現(xiàn)代碼 WebServer.py
? ? # ------------- 這里需要修改代碼------------
? ? # 因?yàn)樵谶@里需要使用框架文件來處理數(shù)據(jù)肌索,所以需要進(jìn)行模塊導(dǎo)入
? ? import WebFrame
? ? #...
? ? # 前面的代碼不需要修改
? ? # ------------- 這里開始修改代碼------------
? ? #判斷訪問路徑的類型
? ? if file_name.endswith('.py'):
? ? ? ? header = "HTTP/1.1 200 OK\r\n"? # 響應(yīng)頭
? ? ? ? # 根本不同的訪問路徑名來向框架文件獲取對應(yīng)的數(shù)據(jù)
? ? ? ? # 通過框架文件中定義的函數(shù)將訪問路徑傳遞給框架文件
? ? ? ? body = WebFrame.application(file_name)
? ? ? ? #將返回的數(shù)據(jù)進(jìn)行拼接
? ? ? ? data = header + '\r\n' + body? # 拼接響應(yīng)信息
? ? ? ? new_socket.send(data.encode('utf-8'))? # 返回響應(yīng)信息
? ? # ------------- 這里開始修改代碼結(jié)束------------
? ? else:
? ? ? ? # 后面的代碼不需要修改
? ? ? ? # ...
? WebFrame.py
# 在框架文件中,實(shí)現(xiàn)一個(gè)函數(shù)特碳,做為 Web 服務(wù)器和框架文件之間的通信接口
# 在這個(gè)接口函數(shù)中诚亚,根據(jù) Web 服務(wù)器傳遞過來的訪問路徑,判斷返回的數(shù)據(jù)
def application(url_path):
? ? if url_path == '/index.py':
? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應(yīng)體
? ? elif url_path == '/center.py':
? ? ? ? body = 'Center Page ...'
? ? else:
? ? ? ? body = 'Other Page ...'
? ? return body
代碼實(shí)現(xiàn)到這里午乓,基本將功能進(jìn)行了分離站宗,初步完成了前面原理圖中的功能分離。
但是還沒有真正的完成框架益愈,到這里只是完成了框架中的一小步份乒。
3.1.7 WSGI
? <1>WSGI是什么?
? ? ? WSGI,全稱 Web Server Gateway Interface或辖,
? ? ? 是為 Python 語言定義的 Web 服務(wù)器和 Web 應(yīng)用程序或框架之間的一種簡單而通用的接口瘾英。
? ? ? 是用來描述web server如何與web application通信的規(guī)范。
? <2>WSGI協(xié)議中颂暇,定義的接口函數(shù)就是 application 缺谴,定義如下:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
? 這個(gè)函數(shù)有兩個(gè)參數(shù):
? ? ? 參數(shù)一: web服務(wù)器向數(shù)據(jù)處理文件中傳遞請求相關(guān)的信息,一般為請求地址,請求方式等,傳入類型約定使用字典
? ? ? 參數(shù)二: 傳入一個(gè)函數(shù),使用函數(shù)回調(diào)的形式,將數(shù)據(jù)處理的狀態(tài)結(jié)果返回給服務(wù)器
? ? ? ? ? ■ 服務(wù)器的函數(shù)一般用來存儲(chǔ)返回的信息,用來組合響應(yīng)頭信息,這里只是在框架中調(diào)用這個(gè)函數(shù)的時(shí)候傳入定義時(shí)的參數(shù)(此處參數(shù)包括2個(gè)耳鸯,第一個(gè)是響應(yīng)狀態(tài)和狀態(tài)描述湿蛔,第二個(gè)是響應(yīng)頭信息),其中描述響應(yīng)頭信息的參數(shù)是以列表裝元組的形式返回,列表中的每一個(gè)元素都是以元組形式存放的一條響應(yīng)頭的信息县爬,元組中有兩個(gè)數(shù)據(jù)阳啥,分別對應(yīng)著響應(yīng)頭信息中:前后的部分,所以要得到里面的數(shù)據(jù)應(yīng)該先遍歷列表,得到的是列表里的數(shù)據(jù)元組财喳,'%s:%s\r\n' %t是對元組的拆包然后拼接響應(yīng)頭信息
? ? ? 返回值: 用來返回具體的響應(yīng)體數(shù)據(jù)察迟。
? 服務(wù)器和框架應(yīng)用程序在共同遵守了這個(gè)協(xié)議后,就可以通過 application 函數(shù)進(jìn)行通信耳高。完成請求的轉(zhuǎn)發(fā)和響應(yīng)數(shù)據(jù)的處理返回扎瓶。
? 實(shí)現(xiàn)過程:
? ? ? 1.在服務(wù)器中調(diào)用application函數(shù)
? ? ? 2.定義用來儲(chǔ)存返回的響應(yīng)頭信息的回調(diào)函數(shù),函數(shù)有兩個(gè)參數(shù)泌枪,一個(gè)是狀態(tài)概荷,一個(gè)是其它信息,以字典形式傳入
? ? ? 3.以字典傳入請求地址名,傳入回調(diào)的函數(shù)名
? ? ? 4.當(dāng)處理完數(shù)據(jù)后,調(diào)用傳入的函數(shù)并返回?cái)?shù)據(jù) 5
? ? ? .服務(wù)器收到返回的信息后進(jìn)行響應(yīng)信息的拼接處理.
? 代碼實(shí)現(xiàn): WebServer.py
? ? ? ? import WebFrame
? ? ? ? #...
? ? ? ? # 前面的代碼不需要修改
? ? ? ? # ------------- 這里開始修改代碼------------
? ? ? ? #判斷訪問路徑的類型
? ? ? ? if file_name.endswith('.py'):
? ? ? ? ? ? #要先調(diào)用這個(gè)函數(shù),如果不調(diào)用,那么回調(diào)函數(shù)不能執(zhí)行,下面拼接數(shù)據(jù)就會(huì)出錯(cuò)
? ? ? ? ? ? #根本不同的文件名來向數(shù)據(jù)處理文件獲取對應(yīng)的數(shù)據(jù)
? ? ? ? ? ? #并將回調(diào)函數(shù)傳入進(jìn)去
? ? ? ? ? ? env = {'PATH_INFO':file_name}
? ? ? ? ? ? body = WEBFrame.application(env,self.start_response)
? ? ? ? ? ? #拼接返回的狀態(tài)信息
? ? ? ? ? ? header = "HTTP/1.1 %s\r\n"%self.status? # 響應(yīng)頭
? ? ? ? ? ? #拼接返回的響應(yīng)頭信息
? ? ? ? ? ? #因?yàn)槭欠祷厥且粤斜硌b元組的形式返回,所以遍歷列表,得到的是列表里的數(shù)據(jù)元組碌燕,
? #'%s:%s\r\n'%t是對元組的拆包误证,然后拼接元組里的信息
? ? ? ? ? ? for t in self.params:
? ? ? ? ? ? ? ? header += '%s:%s\r\n'%t
? ? ? ? ? ? data = header + '\r\n' + body? # 拼接響應(yīng)信息
? ? ? ? ? ? new_socket.send(data.encode('utf-8'))? # 返回響應(yīng)信息
? ? ? ? # ------------- 這里開始修改代碼結(jié)束------------
? ? ? ? else:
? ? ? ? ? ? # 后面的代碼不需要修改
? ? ? ? ? ? # ...
? ? # ------------- 這里需要修改代碼------------
? ? #定義一個(gè)成員函數(shù) ,用來回調(diào)保存數(shù)據(jù)使用
def start_response(self,status,params):
#保存返回回來的響應(yīng)狀態(tài)和其它響應(yīng)信息
self.status = status
self.params = params
? WebFrame.py
# 實(shí)現(xiàn) WSGI 協(xié)議中的 application 接口方法
def application(environ, start_response):
? ? # 從服務(wù)器傳過來的字典中將訪問路徑取出來
? ? url_path = environ['PATH_INFO']
? ? # 判斷訪問路徑,確定響應(yīng)數(shù)據(jù)內(nèi)容修壕,保存到body中
? ? if url_path == '/index.py':
? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應(yīng)體
? ? elif url_path == '/center.py':
? ? ? ? body = 'Center Page ...'
? ? else:
? ? ? ? body = 'Other Page ...'
? ? # 回調(diào) start_response 函數(shù)愈捅,將響應(yīng)狀態(tài)信息回傳給服務(wù)器
? ? start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
? ? # 返回響應(yīng)數(shù)據(jù)內(nèi)容
? ? return body
通過代碼的優(yōu)化,到這里叠殷,基本已經(jīng)將服務(wù)器和框架應(yīng)用的功能分離改鲫。
3.1.8 總結(jié):
? 1. 代碼在開發(fā)過程中诈皿,應(yīng)該遵循高內(nèi)聚低耦合的思想
? 2. 靜態(tài)數(shù)據(jù)是指在訪問時(shí)不會(huì)發(fā)生變化的數(shù)據(jù)
? 3. 動(dòng)態(tài)數(shù)據(jù)是指在訪問時(shí)會(huì)服務(wù)的狀態(tài)林束,條件等發(fā)生不同的變化,得到的數(shù)據(jù)不同
? 4. 通過WSGI接口稽亏,實(shí)現(xiàn)了服務(wù)器和框架的功能分離
? 5. 服務(wù)器和框架應(yīng)用的功能分離壶冒,使服務(wù)器的遷移,維護(hù)更加簡單