Python 18?MiniWEB項目

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. 服務器和框架應用的功能分離,使服務器的遷移,維護更加簡單

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容