MiniWEB項目、程序解耦和耦合關系、區分動態數據和靜態數據、WSGI、WSGI接口中的接口函數中參數的函數回調
3.1 MiniWEB項目
學習目標
? 1. 能夠說出WEB服務器在訪問時的執行過程
? 2. 能夠說出實現框架的意義
? 3. 能夠說出為什么要進行程序的解耦
總結:
? 1. 代碼在開發過程中,應該遵循高內聚低耦合的思想
? 2. 靜態數據是指在訪問時不會發生變化的數據
? 3. 動態數據是指在訪問時會服務的狀態,條件等發生不同的變化,得到的數據不同
? 4. 通過WSGI接口,實現了服務器和框架的功能分離
? 5. 服務器和框架應用的功能分離,使服務器的遷移,維護更加簡單
--------------------------------------------------------------------------------
3.1.1 HTTP 服務器運行原理
之前在實現的程序中,主要代碼都實現在上圖的左半部分。服務器的運行和 WEB 應用的處理,都是在一個文件中實現的。
這幾天的工作,就是把程序解耦,將功能分離,服務器只用來提供WEB服務,WEB應用用來實現數據處理。
大家可以了解一下開發中比較常用的WEB框架,比如 Apache ,Nigix,Tomcat等。
沒有一個服務器框架安裝完成后,就完成了WEB應用的開發的。
因為服務器根本不知道你要完成的功能是什么,所以只提供給你服務,而應用的功能按照服務的接口來完成。然后讓服務器響應處理。
3.1.2 原始服務器回顧分析
在前面的課程中,我們實現過一個 HTTP 服務器,我們就在這個服務器的基礎上,來實現這階段的 MiniWEB 框架。
首先,先來回顧一下這個HTTP服務器的代碼
注意:將代碼復制到工程文件中之后,還需要將資源文件復制到工程目錄中
原始服務器 WebServer.py
#? 代碼實現:
? ? import socket
? ? import re
? ? import multiprocessing
? ? def service_client(new_socket):
? ? ? ? """為客戶端返回數據"""
? ? ? ? # 1. 接收瀏覽器發送過來的請求 ,即http請求相關信息
? ? ? ? # GET / HTTP/1.1
? ? ? ? # .....
? ? ? ? request = new_socket.recv(1024).decode("utf-8")
? ? ? ? #將請求頭信息進行按行分解存到列表中
? ? ? ? request_lines = request.splitlines()
? ? ? ? # GET /index.html HTTP/1.1
? ? ? ? file_name = ""
? ? ? ? #正則:? [^/]+ 不以/開頭的至少一個字符 匹配到/之前
? ? ? ? #? ? ? (/[^ ]*) 以分組來匹配第一個字符是/,然后不以空格開始的0到多個字符,也就是空格之前
? ? ? ? #? ? ? 最后通過匹配可以拿到 請求的路徑名? 比如:index.html
? ? ? ? ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
? ? ? ? #如果匹配結果 不為none,說明請求地址正確
? ? ? ? if ret:
? ? ? ? ? ? #利用分組得到請求地址的文件名,正則的分組從索引1開始
? ? ? ? ? ? file_name = ret.group(1)
? ? ? ? ? ? print('FileName:? ' + file_name)
? ? ? ? ? ? #如果請求地址為 / 將文件名設置為index.html,也就是默認訪問首頁
? ? ? ? ? ? if file_name == "/":
? ? ? ? ? ? ? ? file_name = "/index.html"
? ? ? ? # 2. 返回http格式的數據,給瀏覽器
? ? ? ? try:
? ? ? ? ? ? #拼接路徑,在當前的html目錄下找訪問的路徑對應的文件進行讀取
? ? ? ? ? ? f = open("./html" + file_name, "rb")
? ? ? ? except:
? ? ? ? ? ? #如果沒找到,拼接響應信息并返回信息
? ? ? ? ? ? response = "HTTP/1.1 404 NOT FOUND\r\n"
? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? response += "------file not found-----"
? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? else:
? ? ? ? ? ? #如果找到對應文件就讀取并返回內容
? ? ? ? ? ? html_content = f.read()
? ? ? ? ? ? f.close()
? ? ? ? ? ? # 2.1 準備發送給瀏覽器的數據---header
? ? ? ? ? ? response = "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? #如果想在響應體中直接發送文件內的信息,那么在上面讀取文件時就不能用rb模式,只能使用r模式,所以下面將響應頭和響應體分開發送
? ? ? ? ? ? #response += html_content
? ? ? ? ? ? # 2.2 準備發送給瀏覽器的數據
? ? ? ? ? ? # 將response header發送給瀏覽器
? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? # 將response body發送給瀏覽器
? ? ? ? ? ? new_socket.send(html_content)
? ? ? ? # 關閉套接
? ? ? ? new_socket.close()
? ? def main():
? ? ? ? """用來完成整體的控制"""
? ? ? ? # 1. 創建套接字
? ? ? ? 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. 設置套接字監聽連接數(最大連接數)
? ? ? ? tcp_server_socket.listen(128)
? ? ? ? while True:
? ? ? ? ? ? # 4. 等待新客戶端的鏈接
? ? ? ? ? ? new_socket, client_addr = tcp_server_socket.accept()
? ? ? ? ? ? # 5. 為連接上來的客戶端去創建一個新的進程去運行
? ? ? ? ? ? p = multiprocessing.Process(target=service_client, args=(new_socket,))
? ? ? ? ? ? p.start()
? ? ? ? ? ? #因為新進程在創建過程中會完全復制父進程的運行環境,所以父線程中關閉的只是自己環境中的套接字對象
? ? ? ? ? ? #而新進程中因為被復制的環境中是獨立存在的,所以不會受到影響
? ? ? ? ? ? new_socket.close()
? ? ? ? # 關閉監聽套接字
? ? ? ? tcp_server_socket.close()
? ? if __name__ == "__main__":
? ? ? ? main()
3.1.3 程序解耦
? <1>概念理解 什么是耦合關系?
? ? ? 耦合關系是指某兩個事物之間如果存在一種相互作用、相互影響的關系,那么這種關系就稱"耦合關系"。
? ? ? 在軟件工程中的耦合就是代碼之間的依賴性。
? ? ? 代碼之間的耦合度越高,維護成本越高。
? <2>代碼開發原則之一:高內聚,低耦合。
? ? ? 這句話的意思就是程序的每一個功能都要單獨內聚在一個函數中,讓代碼之間的耦合度達到最小。也就是相互之間的依賴性達到最小。
? 實現面向對象的思想的代碼重構
? ? ? 以面向對象的思想來完成服務器的代碼實現 實現過程:
? ? ? ? ? ■ 1.封裝類
? ? ? ? ? ■ 2.初始化方法中創建socket對象
? ? ? ? ? ■ 3.啟動服務器的方法中進行服務監聽
? ? ? ? ? ■ 4.實現數據處理的方法
? ? ? ? ? ■ 5.對象屬性的相應修改
? ? ? ? ? ■ 6.重新實現main方法,創建WEBServer類對象并啟動服務
WebServer.py
? ? # 面向對象修改數據
? ? import socket
? ? import re
? ? import multiprocessing
? ? class WEBServer(object):
? ? ? ? #在初始化方法中完成服務器Socket對象的創建
? ? ? ? def __init__(self):
? ? ? ? ? ? """用來完成整體的控制"""
? ? ? ? ? ? # 1. 創建套接字
? ? ? ? ? ? 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. 設置套接字監聽連接數(最大連接數)
? ? ? ? ? ? self.tcp_server_socket.listen(128)
? ? ? ? def service_client(self,new_socket):
? ? ? ? ? ? """為這個客戶端返回數據"""
? ? ? ? ? ? # 1. 接收瀏覽器發送過來的請求 ,即http請求相關信息
? ? ? ? ? ? # GET / HTTP/1.1
? ? ? ? ? ? # .....
? ? ? ? ? ? request = new_socket.recv(1024).decode("utf-8")
? ? ? ? ? ? #將請求頭信息進行按行分解存到列表中
? ? ? ? ? ? request_lines = request.splitlines()
? ? ? ? ? ? # GET /index.html HTTP/1.1
? ? ? ? ? ? # get post put del
? ? ? ? ? ? file_name = ""
? ? ? ? ? ? #正則:? [^/]+ 不以/開頭的至少一個字符 匹配到/之前
? ? ? ? ? ? #? ? ? (/[^ ]*) 以分組來匹配第一個字符是/,然后不以空格開始的0到多個字符,也就是空格之前
? ? ? ? ? ? #? ? ? 最后通過匹配可以拿到 請求的路徑名? 比如:index.html
? ? ? ? ? ? ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
? ? ? ? ? ? #如果匹配結果 不為none,說明請求地址正確
? ? ? ? ? ? if ret:
? ? ? ? ? ? ? ? #利用分組得到請求地址的文件名,正則的分組從索引1開始
? ? ? ? ? ? ? ? file_name = ret.group(1)
? ? ? ? ? ? ? ? print('FileName:? ' + file_name)
? ? ? ? ? ? ? ? #如果請求地址為 / 將文件名設置為index.html,也就是默認訪問首頁
? ? ? ? ? ? ? ? if file_name == "/":
? ? ? ? ? ? ? ? ? ? file_name = "/index.html"
? ? ? ? ? ? # 2. 返回http格式的數據,給瀏覽器
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? #拼接路徑,在當前的html目錄下找訪問的路徑對應的文件進行讀取
? ? ? ? ? ? ? ? f = open("./html" + file_name, "rb")
? ? ? ? ? ? except:
? ? ? ? ? ? ? ? #如果沒找到,拼接響應信息并返回信息
? ? ? ? ? ? ? ? response = "HTTP/1.1 404 NOT FOUND\r\n"
? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? response += "------file not found-----"
? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? #如果找到對應文件就讀取并返回內容
? ? ? ? ? ? ? ? html_content = f.read()
? ? ? ? ? ? ? ? f.close()
? ? ? ? ? ? ? ? # 2.1 準備發送給瀏覽器的數據---header
? ? ? ? ? ? ? ? response = "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? #如果想在響應體中直接發送文件內的信息,那么在上面讀取文件時就不能用rb模式,只能使用r模式,所以下面將響應頭和響應體分開發送
? ? ? ? ? ? ? ? #response += html_content
? ? ? ? ? ? ? ? # 2.2 準備發送給瀏覽器的數據
? ? ? ? ? ? ? ? # 將response header發送給瀏覽器
? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? ? ? # 將response body發送給瀏覽器
? ? ? ? ? ? ? ? new_socket.send(html_content)
? ? ? ? ? ? # 關閉套接
? ? ? ? ? ? new_socket.close()
? ? ? ? def run(self):
? ? ? ? ? ? while True:
? ? ? ? ? ? ? ? # 4. 等待新客戶端的鏈接
? ? ? ? ? ? ? ? new_socket, client_addr = self.tcp_server_socket.accept()
? ? ? ? ? ? ? ? # 5. 為這個客戶端服務
? ? ? ? ? ? ? ? p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
? ? ? ? ? ? ? ? p.start()
? ? ? ? ? ? ? ? #因為新線程在創建過程中會完全復制父線程的運行環境,所以父線程中關閉的只是自己環境中的套接字對象
? ? ? ? ? ? ? ? #而新線程中因為被復制的環境中是獨立存在的,所以不會受到影響
? ? ? ? ? ? ? ? new_socket.close()
? ? ? ? ? ? # 關閉監聽套接字
? ? ? ? ? ? self.tcp_server_socket.close()
? ? def main():
? ? ? ? webServer = WEBServer()
? ? ? ? webServer.run()
? ? if __name__ == "__main__":
? ? ? ? main()
通過使用面向對象的思想,將代碼重構后,耦合性降低,但還沒有完全實現功能的分離。 目前還是在一個文件中實現所有的程序功能,也就是說,目前只是完成了在原理圖中,左半側的功能。后面會繼續改進。
3.1.4 區分動態數據和靜態數據
? 靜態數據:是指在頁面進行訪問時,無論何時訪問,得到的內容都是同樣的,不會發生任意變化
? ? ? (比如我們現在實現的API網頁的訪問效果,這些API文件都是保存在本地(或服務器上)的一些固定的文檔說明,無論在何時何地訪問這些數據,都是相同的,不會發生變化)
? 動態數據:是指在頁面進行訪問時,得到的數據是經過服務器進行計算,加工,處理過后的數據,稱為動態數據,哪怕只是加了一個空格
? ? ? 比如:實時新聞,股票信息,購物網站顯示的商品信息等等都動態數據
? 在這部分代碼實現中,先來實現不同形式的頁面訪問,服務器返回不同的數據(數據暫時還是靜態的,假的數據,真正的動態數據會在完成框架后,在數據庫中讀取返回)
? 這里設定: xxx.html 訪問時,返回的是靜態數據 API 文檔中的內容, xxx.py 訪問時,返回的是動態數據(數據先以靜態數據代替)
? 實現過程:
? ? ? 1.先根據訪問頁面地址判斷訪問數據的類型,是py的動態還是html的靜態
? ? ? 2.根據動態請求的路徑名的不同來返回不同的數據,不在使用html獲取數據,而使用py來獲取
WebServer.py
? ? # ...
? ? # 前面的代碼不需要修改
? ? ? ? ? ? if ret:
? ? ? ? ? ? #利用分組得到請求地址的文件名,正則的分組從索引1開始
? ? ? ? ? ? file_name = ret.group(1)
? ? ? ? ? ? print('FileName:? ' + file_name)
? ? ? ? ? ? #如果請求地址為 / 將文件名設置為index.html,也就是默認訪問首頁
? ? ? ? ? ? if file_name == "/":
? ? ? ? ? ? ? ? file_name = "/index.html"
? ? ? ? ? ? # ------------- 這里開始修改代碼------------
? ? ? ? ? ? #判斷訪問路徑的類型
? ? ? ? ? ? if file_name.endswith('.py'):
? ? ? ? ? ? ? ? #根據不同的文件名來確定返回的響應信息
? ? ? ? ? ? ? ? if file_name == '/index.py':? ? ? ? ? ? ? ? #首頁
? ? ? ? ? ? ? ? ? ? header =? "HTTP/1.1 200 OK\r\n"? ? ? ? #響應頭
? ? ? ? ? ? ? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應體
? ? ? ? ? ? ? ? ? ? data = header + '\r\n' + body? ? ? ? ? #拼接響應信息
? ? ? ? ? ? ? ? ? ? new_socket.send(data.encode('utf-8'))? #返回響應信息
? ? ? ? ? ? ? ? elif file_name == '/center.py':? ? ? ? ? ? #個人中心頁面
? ? ? ? ? ? ? ? ? ? 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格式的數據,給瀏覽器
? ? ? ? ? ? ? ? try:
? ? ? ? ? ? ? ? ? ? #拼接路徑,在當前的html目錄下找訪問的路徑對應的文件進行讀取
? ? ? ? ? ? ? ? ? ? f = open("./html" + file_name, "rb")
? ? ? ? ? ? ? ? except:
? ? ? ? ? ? ? ? ? ? #如果沒找到,拼接響應信息并返回信息
? ? ? ? ? ? ? ? ? ? response = "HTTP/1.1 404 NOT FOUND\r\n"
? ? ? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? ? ? response += "------file not found-----"
? ? ? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? ? ? else:
? ? ? ? ? ? ? ? ? ? #如果找到對應文件就讀取并返回內容
? ? ? ? ? ? ? ? ? ? html_content = f.read()
? ? ? ? ? ? ? ? ? ? f.close()
? ? ? ? ? ? ? ? ? ? # 2.1 準備發送給瀏覽器的數據---header
? ? ? ? ? ? ? ? ? ? response = "HTTP/1.1 200 OK\r\n"
? ? ? ? ? ? ? ? ? ? response += "\r\n"
? ? ? ? ? ? ? ? ? ? #如果想在響應體中直接發送文件內的信息,那么在上面讀取文件時就不能用rb模式,只能使用r模式,所以下面將響應頭和響應體分開發送
? ? ? ? ? ? ? ? ? ? #response += html_content
? ? ? ? ? ? ? ? ? ? # 2.2 準備發送給瀏覽器的數據
? ? ? ? ? ? ? ? ? ? # 將response header發送給瀏覽器
? ? ? ? ? ? ? ? ? ? new_socket.send(response.encode("utf-8"))
? ? ? ? ? ? ? ? ? ? # 將response body發送給瀏覽器
? ? ? ? ? ? ? ? ? ? new_socket.send(html_content)
3.1.5 實現動態數據的響應優化
雖然前面的代碼實現了設計需求,但是實現過程太過冗余,不符合代碼開發原則。 一個服務器中提供可以訪問的頁面肯定不止這么幾個,如果每一個都實現一次響應信息的編寫,那冗余代碼就太多了,不符合代碼的開發規范 通過分析我們可以看出,代碼中大部分內容都是相同的,只有在響應信息的響應體部分不同,那么就可以將代碼優化一下。
? 實現過程: 因為所有頁面的響應信息都是相同的,所以讓這些頁面共用一塊代碼
? ? ? 1. 將響應頭和空行代碼放到判斷頁面之前
? ? ? 2. 將發拼接和發送代碼放到判斷之后
? ? ? 3. 頁面判斷中,只根據不同的頁面設計不同的響應體信息
實現代碼: WebServer.py
? ? # ...
? ? # 前面的代碼不需要修改
? ? ? ? #判斷訪問路徑的類型
? ? ? ? # ------------- 這里開始修改代碼------------
? ? ? ? if file_name.endswith('.py'):
? ? ? ? ? ? header = "HTTP/1.1 200 OK\r\n"? # 響應頭
? ? ? ? ? ? #根本不同的文件名來確定返回的響應信息
? ? ? ? ? ? if file_name == '/index.py':
? ? ? ? ? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應體
? ? ? ? ? ? elif file_name == '/center.py':
? ? ? ? ? ? ? ? body = 'Center Page ...'
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? body = 'Other Page ...'
? ? ? ? ? ? data = header + '\r\n' + body? # 拼接響應信息
? ? ? ? ? ? new_socket.send(data.encode('utf-8'))? # 返回響應信息
? ? ? ? # ------------- 這里開始修改代碼結束------------
? ? ? ? else:
? ? ? ? # 后面的代碼不需要修改
3.1.6 實現功能的分離
? 代碼被進一步優化,但是還是存在問題。網絡請求和數據處理還是沒有分開,還是在同一個文件中實現的。
? ? ? 實際開發中WEB服務器有很多種,比如Apache,Nigix等等。
? ? ? 如果在開發過程中,需要對 WEB 服務器進行更換。那么我們現在的做法就要花費很大的精力,因為 WEB 服務和數據處理都在一起。
? ? ? 如果能將程序的功能進行進行分離,提供 WEB 請求響應的服務器只管請求的響應,而響應返回的數據由另外的程序來進行處理。
? ? ? 這樣的話,WEB 服務和數據處理之間的耦合性就降低了,這樣更便于功能的擴展和維護
? ? ? ? ? ■ 比如:
? ? ? ? ? ■ 一臺電腦,如果要是所有的更件都是集成在主板上的,那么只要有一個地方壞了。那整個主板都要換掉。成本很高
? ? ? ? ? ■ 如果所有的硬件都是以卡槽接口的形式插在主板上,那么如果哪一個硬件壞了或要進行升級擴展都會很方便,降低了成本。
? ? ? 在實際開發過程中,代碼的模塊化思想就是來源于生活,讓每個功能各司其職。
? 實現思想: 將原來的服務器文件拆分成兩個文件,一個負責請求響應,一個負責數據處理。 那么這里出現一個新的問題,兩個文件中如何進行通信呢?負責數據處理的文件怎么知道客戶端要請求什么數據呢?
? ? ? 想一下主板和內存之間是如何連接的?
? 實現過程:
? ? ? 1.WebServer 文件只用來提供請求的接收和響應
? ? ? 2.WebFrame 文件只用來提供請求數據的處理和返回
? ? ? 3.文件之間利用一個函數來傳遞請求數據和返回的信息
? 實現代碼 WebServer.py
? ? # ------------- 這里需要修改代碼------------
? ? # 因為在這里需要使用框架文件來處理數據,所以需要進行模塊導入
? ? import WebFrame
? ? #...
? ? # 前面的代碼不需要修改
? ? # ------------- 這里開始修改代碼------------
? ? #判斷訪問路徑的類型
? ? if file_name.endswith('.py'):
? ? ? ? header = "HTTP/1.1 200 OK\r\n"? # 響應頭
? ? ? ? # 根本不同的訪問路徑名來向框架文件獲取對應的數據
? ? ? ? # 通過框架文件中定義的函數將訪問路徑傳遞給框架文件
? ? ? ? body = WebFrame.application(file_name)
? ? ? ? #將返回的數據進行拼接
? ? ? ? data = header + '\r\n' + body? # 拼接響應信息
? ? ? ? new_socket.send(data.encode('utf-8'))? # 返回響應信息
? ? # ------------- 這里開始修改代碼結束------------
? ? else:
? ? ? ? # 后面的代碼不需要修改
? ? ? ? # ...
? WebFrame.py
# 在框架文件中,實現一個函數,做為 Web 服務器和框架文件之間的通信接口
# 在這個接口函數中,根據 Web 服務器傳遞過來的訪問路徑,判斷返回的數據
def application(url_path):
? ? if url_path == '/index.py':
? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應體
? ? elif url_path == '/center.py':
? ? ? ? body = 'Center Page ...'
? ? else:
? ? ? ? body = 'Other Page ...'
? ? return body
代碼實現到這里,基本將功能進行了分離,初步完成了前面原理圖中的功能分離。
但是還沒有真正的完成框架,到這里只是完成了框架中的一小步。
3.1.7 WSGI
? <1>WSGI是什么?
? ? ? WSGI,全稱 Web Server Gateway Interface,
? ? ? 是為 Python 語言定義的 Web 服務器和 Web 應用程序或框架之間的一種簡單而通用的接口。
? ? ? 是用來描述web server如何與web application通信的規范。
? <2>WSGI協議中,定義的接口函數就是 application ,定義如下:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
? 這個函數有兩個參數:
? ? ? 參數一: web服務器向數據處理文件中傳遞請求相關的信息,一般為請求地址,請求方式等,傳入類型約定使用字典
? ? ? 參數二: 傳入一個函數,使用函數回調的形式,將數據處理的狀態結果返回給服務器
? ? ? ? ? ■ 服務器的函數一般用來存儲返回的信息,用來組合響應頭信息,這里只是在框架中調用這個函數的時候傳入定義時的參數(此處參數包括2個,第一個是響應狀態和狀態描述,第二個是響應頭信息),其中描述響應頭信息的參數是以列表裝元組的形式返回,列表中的每一個元素都是以元組形式存放的一條響應頭的信息,元組中有兩個數據,分別對應著響應頭信息中:前后的部分,所以要得到里面的數據應該先遍歷列表,得到的是列表里的數據元組,'%s:%s\r\n' %t是對元組的拆包然后拼接響應頭信息
? ? ? 返回值: 用來返回具體的響應體數據。
? 服務器和框架應用程序在共同遵守了這個協議后,就可以通過 application 函數進行通信。完成請求的轉發和響應數據的處理返回。
? 實現過程:
? ? ? 1.在服務器中調用application函數
? ? ? 2.定義用來儲存返回的響應頭信息的回調函數,函數有兩個參數,一個是狀態,一個是其它信息,以字典形式傳入
? ? ? 3.以字典傳入請求地址名,傳入回調的函數名
? ? ? 4.當處理完數據后,調用傳入的函數并返回數據 5
? ? ? .服務器收到返回的信息后進行響應信息的拼接處理.
? 代碼實現: WebServer.py
? ? ? ? import WebFrame
? ? ? ? #...
? ? ? ? # 前面的代碼不需要修改
? ? ? ? # ------------- 這里開始修改代碼------------
? ? ? ? #判斷訪問路徑的類型
? ? ? ? if file_name.endswith('.py'):
? ? ? ? ? ? #要先調用這個函數,如果不調用,那么回調函數不能執行,下面拼接數據就會出錯
? ? ? ? ? ? #根本不同的文件名來向數據處理文件獲取對應的數據
? ? ? ? ? ? #并將回調函數傳入進去
? ? ? ? ? ? env = {'PATH_INFO':file_name}
? ? ? ? ? ? body = WEBFrame.application(env,self.start_response)
? ? ? ? ? ? #拼接返回的狀態信息
? ? ? ? ? ? header = "HTTP/1.1 %s\r\n"%self.status? # 響應頭
? ? ? ? ? ? #拼接返回的響應頭信息
? ? ? ? ? ? #因為是返回是以列表裝元組的形式返回,所以遍歷列表,得到的是列表里的數據元組,
? #'%s:%s\r\n'%t是對元組的拆包,然后拼接元組里的信息
? ? ? ? ? ? for t in self.params:
? ? ? ? ? ? ? ? header += '%s:%s\r\n'%t
? ? ? ? ? ? data = header + '\r\n' + body? # 拼接響應信息
? ? ? ? ? ? new_socket.send(data.encode('utf-8'))? # 返回響應信息
? ? ? ? # ------------- 這里開始修改代碼結束------------
? ? ? ? else:
? ? ? ? ? ? # 后面的代碼不需要修改
? ? ? ? ? ? # ...
? ? # ------------- 這里需要修改代碼------------
? ? #定義一個成員函數 ,用來回調保存數據使用
def start_response(self,status,params):
#保存返回回來的響應狀態和其它響應信息
self.status = status
self.params = params
? WebFrame.py
# 實現 WSGI 協議中的 application 接口方法
def application(environ, start_response):
? ? # 從服務器傳過來的字典中將訪問路徑取出來
? ? url_path = environ['PATH_INFO']
? ? # 判斷訪問路徑,確定響應數據內容,保存到body中
? ? if url_path == '/index.py':
? ? ? ? body = 'Index Page ...'? ? ? ? ? ? ? ? #響應體
? ? elif url_path == '/center.py':
? ? ? ? body = 'Center Page ...'
? ? else:
? ? ? ? body = 'Other Page ...'
? ? # 回調 start_response 函數,將響應狀態信息回傳給服務器
? ? start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
? ? # 返回響應數據內容
? ? return body
通過代碼的優化,到這里,基本已經將服務器和框架應用的功能分離。
3.1.8 總結:
? 1. 代碼在開發過程中,應該遵循高內聚低耦合的思想
? 2. 靜態數據是指在訪問時不會發生變化的數據
? 3. 動態數據是指在訪問時會服務的狀態,條件等發生不同的變化,得到的數據不同
? 4. 通過WSGI接口,實現了服務器和框架的功能分離
? 5. 服務器和框架應用的功能分離,使服務器的遷移,維護更加簡單