Python 18?MiniWEB項(xiàng)目

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ù)更加簡單

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末截歉,一起剝皮案震驚了整個(gè)濱河市胖腾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖咸作,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锨阿,死亡現(xiàn)場離奇詭異,居然都是意外死亡记罚,警方通過查閱死者的電腦和手機(jī)墅诡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桐智,“玉大人末早,你說我怎么就攤上這事∷低ィ” “怎么了然磷?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刊驴。 經(jīng)常有香客問我姿搜,道長,這世上最難降的妖魔是什么缺脉? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任痪欲,我火速辦了婚禮,結(jié)果婚禮上攻礼,老公的妹妹穿的比我還像新娘业踢。我一直安慰自己,他們只是感情好礁扮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布知举。 她就那樣靜靜地躺著,像睡著了一般太伊。 火紅的嫁衣襯著肌膚如雪雇锡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天僚焦,我揣著相機(jī)與錄音锰提,去河邊找鬼。 笑死芳悲,一個(gè)胖子當(dāng)著我的面吹牛立肘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播名扛,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谅年,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肮韧?” 一聲冷哼從身側(cè)響起融蹂,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旺订,失蹤者是張志新(化名)和其女友劉穎岛宦,沒想到半個(gè)月后认境,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藕帜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年意乓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了劳闹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洽瞬,死狀恐怖本涕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伙窃,我是刑警寧澤菩颖,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站为障,受9級特大地震影響晦闰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳍怨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一呻右、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鞋喇,春花似錦声滥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至罐韩,卻和暖如春憾赁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背散吵。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工龙考, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矾睦。 一個(gè)月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓晦款,卻偏偏與公主長得像,于是被迫代替她去往敵國和親顷锰。 傳聞我的和親對象是個(gè)殘疾皇子柬赐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容