聲明:本文轉(zhuǎn)載自http://cizixs.com/2015/09/21/what-is-a-web-framework
Web 應(yīng)用框架艾扮,簡(jiǎn)稱為 web 框架谒主,是編寫 web 應(yīng)用程序的基石掉盅。不管簡(jiǎn)單的博客系統(tǒng)也拜,還是 Ajax 為主的應(yīng)用,網(wǎng)絡(luò)上所有的頁(yè)面都是代碼構(gòu)成的趾痘。進(jìn)來(lái)我發(fā)現(xiàn)慢哈,很多想學(xué)習(xí)諸如 Flask 或者 Django 等 web 框架的開(kāi)發(fā)者,并不很了解 web 框架是什么永票,它們的作用和工作原理卵贱。這篇文章,我將會(huì)講一下這個(gè)通常會(huì)被忽略的話題侣集。希望讀完這篇文章键俱,你能比較深刻地理解 web 框架到底是什么,還有為什么會(huì)有 web 框架世分。這些知識(shí)將有利于你學(xué)習(xí)新的 web 框架编振,而且在選擇 web 框架的時(shí)候有法可依。
WEB 是什么工作的臭埋?
在討論框架之前踪央,我們要先了解一下網(wǎng)頁(yè)是怎么工作。我們就從你在瀏覽器輸入一個(gè)網(wǎng)址瓢阴,摁下 enter 鍵說(shuō)起畅蹂。打開(kāi)你的瀏覽器,輸入 http://jeffknupp.com(譯者注:原作者的個(gè)人網(wǎng)站首頁(yè))炫掐,我們來(lái)看看你的瀏覽器做了那些事情(DNS 查詢的 buffer 就略過(guò))魁莉,才能顯示你看到的網(wǎng)頁(yè)。
web servers 和 web … servers …
瀏覽器接接收到的網(wǎng)頁(yè)都是 HTML 文件募胃,HTML 是一種描述網(wǎng)頁(yè)內(nèi)容和結(jié)構(gòu)的語(yǔ)言旗唁。負(fù)責(zé)給瀏覽器發(fā)送 HTML 的程序稱為 web server,容易混淆的是痹束,這個(gè)應(yīng)用程序所在的機(jī)器通常也被稱為 web server检疫。
最重要的一點(diǎn)是,所有的 web 應(yīng)用做的事情就是把 HTML 內(nèi)容發(fā)送給瀏覽器祷嘶。不論這個(gè) web 應(yīng)用有多么復(fù)雜屎媳,最終的任務(wù)都是把 HTML(我故意忽略掉其他格式的內(nèi)容夺溢,比如 JSON,CSS 文件烛谊,因?yàn)樵矶际且粯拥模┌l(fā)送給瀏覽器风响。
問(wèn)題來(lái)了:web 應(yīng)用如何知道要發(fā)送什么內(nèi)容給瀏覽器呢?答案:它會(huì)發(fā)送瀏覽器請(qǐng)求的內(nèi)容丹禀。
HTTP
瀏覽器從 web server 下載內(nèi)容所用的是 HTTP 協(xié)議(協(xié)議在計(jì)算機(jī)科學(xué)中状勤,指的是雙方通信所共同遵循的數(shù)據(jù)格式和通信步驟)。HTTP 協(xié)議的基礎(chǔ)是 請(qǐng)求-應(yīng)答 (request-response) 模型双泪〕炙眩客戶端(你的瀏覽器)請(qǐng)求 某臺(tái)物理機(jī)上 web 應(yīng)用的數(shù)據(jù),web server 則負(fù)責(zé) 應(yīng)答請(qǐng)求的數(shù)據(jù)焙矛。
有個(gè)重要的事情是:所有的通信都是客戶端(你的瀏覽器)發(fā)起的葫盼。服務(wù)端(web server)是不可能主動(dòng)連接你,發(fā)送沒(méi)有請(qǐng)求的數(shù)據(jù)的村斟。如果你收到了數(shù)據(jù)贫导,只是因?yàn)槟愕臑g覽器主動(dòng)請(qǐng)求了這些數(shù)據(jù)。
HTTP 方法
HTTP 協(xié)議的每條消息都有對(duì)應(yīng)的方法(method)蟆盹,不同的方法對(duì)應(yīng)了客戶端能發(fā)起的不同請(qǐng)求脱盲,也對(duì)應(yīng)了客戶端不同的意圖。比如日缨,請(qǐng)求 網(wǎng)頁(yè)的 HTML 和提交一個(gè)表格在邏輯上是不同的,所以這兩種方法需要兩種不同的方法掖看。
HTTP GET
顧名思義匣距,GET 方法就是從 web server 獲取(get)數(shù)據(jù)哎壳,GET 請(qǐng)求也是目前最常用的 HTTP 請(qǐng)求毅待。 處理 GET 請(qǐng)求的過(guò)程中,web 應(yīng)用只需要返回請(qǐng)求的數(shù)據(jù)归榕,無(wú)需其他操作尸红。尤其是,不應(yīng)該修改應(yīng)用的狀態(tài)(比如刹泄, GET 請(qǐng)求不應(yīng)該導(dǎo)致一個(gè)新用戶被創(chuàng)建)外里。因?yàn)檫@個(gè)原因,GET 請(qǐng)求通常被看做是 安全 的特石。
HTTP POST
和網(wǎng)站的交互盅蝗,明顯不只是查看網(wǎng)頁(yè)的。我們還會(huì)通過(guò)表格等形式發(fā)送數(shù)據(jù)給 web 應(yīng)用姆蘸,這些操作需要用到另外一種請(qǐng)求:POST墩莫。POST 請(qǐng)求通常會(huì)傳遞用戶創(chuàng)建的信息芙委,導(dǎo)致 web 應(yīng)用執(zhí)行某些動(dòng)作。輸入自己的信息狂秦,來(lái)注冊(cè)某個(gè)網(wǎng)站就會(huì)用到 POST 請(qǐng)求灌侣,請(qǐng)求中會(huì)包含你輸入的數(shù)據(jù)。
和 GET 請(qǐng)求不同的是裂问, POST 請(qǐng)求通常會(huì)導(dǎo)致 web 應(yīng)用狀態(tài)的改變侧啼。上面提及的例子中,表單被提交后愕秫,一個(gè)新的用戶會(huì)被創(chuàng)建慨菱。還有一點(diǎn)不同,POST 請(qǐng)求的結(jié)果可能不會(huì)返回 HTML 數(shù)據(jù)給客戶端戴甩,客戶端需要通過(guò) response code 來(lái)判斷操作是否成功符喝。
HTTP response code
正常情況下,web server 會(huì)返回 200 的 response code甜孤,意思是:我已經(jīng)完成了你要我做的事情协饲,并且一切都沒(méi)有問(wèn)題。response code 是三位的數(shù)字缴川,每次應(yīng)答都要包含一個(gè) response code茉稠,來(lái)標(biāo)識(shí)請(qǐng)求的結(jié)果。200 表示 OK把夸,是 GET 方法常見(jiàn)的返回值而线。POST 請(qǐng)求經(jīng)常會(huì)返還 204(No contnet),表示:一切正常恋日,但是我沒(méi)有數(shù)據(jù)可以展示給你膀篮。
還需要注意的是:POST 請(qǐng)求發(fā)送給的 url,可能和數(shù)據(jù)發(fā)送出去的 url 不同岂膳。繼續(xù)以我們的注冊(cè)頁(yè)面為例誓竿,注冊(cè)表可能位于 http://foo.com/signup,點(diǎn)擊 submit 之后谈截,包含著注冊(cè)數(shù)據(jù)的 POST 請(qǐng)求可能被發(fā)送到 http://foo.com/process_signup筷屡。POST 請(qǐng)求要發(fā)送到的地址,一般在注冊(cè)表格的 HTML 源碼里指定簸喂。
Web 應(yīng)用
掌握 GET 和 POST 方法就能做很多事情毙死,因?yàn)樗鼈兪?web 上最常用的兩個(gè)方法闯参√猎遥總結(jié)一下,web 應(yīng)用就是接收 HTTP 請(qǐng)求乃沙,然后返回 HTTP 應(yīng)答诽表,一般是包含請(qǐng)求數(shù)據(jù)的 HTML唉锌。POST 方法會(huì)導(dǎo)致 web 應(yīng)用執(zhí)行某些動(dòng)作隅肥,例如在數(shù)據(jù)庫(kù)添加一條記錄。當(dāng)然還有其他的 HTTP 方法袄简,但目前我們只需要關(guān)心 GET 和 POST 就足夠啦腥放。
最簡(jiǎn)單的 web 應(yīng)用長(zhǎng)什么樣呢?我們就來(lái)寫一個(gè)監(jiān)聽(tīng)在 80 端口的 web 應(yīng)用绿语,一旦和客戶端建立連接秃症,就等待客戶端發(fā)起請(qǐng)求,并返回非常簡(jiǎn)單的 HTML吕粹。
這個(gè)程序是這樣的:
import socket
HOST = ''
PORT = 80
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
connection, address = listen_socket.accept()
request = connection.recv(1024)
connection.sendall("""HTTP/1.1 200 OK
Content-type: text/html
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>""")
connection.close()
(如果上面的程序報(bào)端口錯(cuò)誤种柑,可以把 PORT 的值修改成其他值,比如 8080匹耕。)
上面的代碼只會(huì)接收一個(gè)連接和一個(gè)請(qǐng)求聚请,不管請(qǐng)求的 URL 是什么,都會(huì)返回同樣的 HTTP 內(nèi)容稳其,response code 是 200驶赏。(很明顯,這不算真正的 web server)既鞠。在這個(gè)例子煤傍,我們告訴客戶端,返回的數(shù)據(jù)格式為 HTML嘱蛋,而不是其他的格式蚯姆,比如 JSON。
request 請(qǐng)求解析
如果看一下上面例子中發(fā)送的 HTTP 請(qǐng)求(譯者注:可以使用 chrome 的 inspect elements -> Network洒敏,或者抓包工具 tcpdump 等工具查看發(fā)送的 HTTP 請(qǐng)求)蒋失,就會(huì)發(fā)現(xiàn)它和應(yīng)答很相似。請(qǐng)求的第一行是:
<HTTP Method> <URL> <HTTP version>
在這個(gè)例子里就是 GET / HTTP/1.1桐玻。第一行后面跟著的是請(qǐng)求的頭部(headers),比如 Accept: /(表示可以接收任何格式的內(nèi)容作為應(yīng)答)荆萤∧餮ィ基本上就這么多。
我們發(fā)送的應(yīng)答也是類似的格式链韭,第一行是:
<HTTP version> <HTTP Status-Code> <Status-Code Reason-Phrase>
這個(gè)例子中就是 HTTP/1.1 200 OK偏竟。然后是頭部,和請(qǐng)求的頭部一樣敞峭。最后是應(yīng)答的實(shí)際內(nèi)容踊谋。注意:應(yīng)答也可能是字符串或者二進(jìn)制對(duì)象,頭部的 Content-typ 就是來(lái)標(biāo)識(shí)應(yīng)答內(nèi)容旋讹,讓客戶端來(lái)解析的殖蚕。
web server 更多瑣碎的細(xì)節(jié)
要在上面的例子基礎(chǔ)上繼續(xù)擴(kuò)展的話轿衔,還有很多需要我們來(lái)解決的問(wèn)題:
- 怎么查看請(qǐng)求的 URL,然后返回不同的頁(yè)面睦疫?
- 除了 GET 請(qǐng)求害驹,怎么處理 POST 請(qǐng)求?
- 怎么處理很復(fù)雜的概念蛤育,比如 sessions 和 cookies宛官?
- 如何擴(kuò)展這個(gè)應(yīng)用,讓它可以同時(shí)處理數(shù)千條連接瓦糕?
可以想象底洗,沒(méi)人會(huì)愿意每次編寫 web 應(yīng)用的時(shí)候都要自己處理這些問(wèn)題。為了解決這個(gè)難題咕娄,就會(huì)存在很多的軟件包幫你處理這些煩人的細(xì)節(jié)亥揖,讓開(kāi)發(fā)者可以把心思放到業(yè)務(wù)邏輯上。記住谭胚,不管 web 框架多么負(fù)責(zé)徐块,其最核心的功能和我們上面的例子是一樣的:監(jiān)聽(tīng)客戶端請(qǐng)求,然后返回 HTML 給客戶端灾而。
NOTE:客戶端的框架和上面的內(nèi)容迥然不同胡控。
解決兩個(gè)難題:路由(routing)和模板(templates)
在構(gòu)建 web 應(yīng)用的所有的問(wèn)題中,有兩個(gè)比較突出:
- 怎么把請(qǐng)求 URL 和處理它的那部分代碼對(duì)應(yīng)起來(lái)旁趟?
- 怎么動(dòng)態(tài)地生產(chǎn)請(qǐng)求內(nèi)容昼激?包括所有要計(jì)算的值,和從數(shù)據(jù)庫(kù)獲取的信息锡搜?
每個(gè) web 框架解決這兩個(gè)問(wèn)題的方法都不太相同橙困,我們就舉 Flask 和 Django 的例子來(lái)說(shuō)明這個(gè)問(wèn)題。首先耕餐,我們還要來(lái)說(shuō)一下 MVC 模式凡傅。
Django 中的 MVC
Django 采用 MVC 模式, 所以要求使用這個(gè)框架的代碼都遵循這個(gè)模式肠缔。MVC夏跷,是 Model-View-Controller 的縮寫,用來(lái)分離應(yīng)用的不同責(zé)任明未。數(shù)據(jù)庫(kù)表所代表的資源用 models 來(lái)表示槽华,controllers 負(fù)責(zé)應(yīng)用的業(yè)務(wù)邏輯和操作 models。Views 則負(fù)責(zé)動(dòng)態(tài)生成代表頁(yè)面的 HTML趟妥。
不過(guò)容易讓人混淆的是猫态,django 中 controllers 被稱作 views,而 views 被稱為 templates。除了命名外亲雪,django 算是比較直接的 MVC 架構(gòu)勇凭。
Django 的路由(routing)機(jī)制
這里說(shuō)的路由(routing)就是把請(qǐng)求的 URL 對(duì)應(yīng)到處理生成相關(guān) HTML 的代碼。最簡(jiǎn)單的例子匆光,所有的請(qǐng)求都是相同的代碼處理(就是我們之前編寫的代碼)套像。復(fù)雜一點(diǎn)呢,每個(gè)的 URL 都對(duì)應(yīng)一個(gè)不同的 view function终息。比如夺巩,有個(gè)地方的邏輯是接收到 www.foo.com/bar 請(qǐng)求,就把它交給 handle_bar() 函數(shù)處理周崭。我們可以這樣依次編寫出所有 url 對(duì)應(yīng)的處理函數(shù)柳譬。
不過(guò),這個(gè)方法有個(gè)致命傷:沒(méi)有辦法處理帶有動(dòng)態(tài)數(shù)據(jù)的 URL续镇,比如說(shuō)資源的 ID(例如 http://www.foo.com/users/3)美澳。我們?cè)趺窗堰@個(gè) URL 映射到函數(shù),同時(shí)能傳過(guò)去用戶 ID 信息呢摸航?
Django 采用的方法是利用正則表達(dá)式:用正則表達(dá)式匹配 URL制跟,然后把匹配的數(shù)據(jù)作為參數(shù)傳遞給處理函數(shù)。比如酱虎,我可以說(shuō)匹配 ^/users/(?P<id>\d+)/$ 的 URL 會(huì)調(diào)用 display_user(id) 函數(shù)雨膨,其中 id 就是正則表達(dá)式括號(hào)里匹配的內(nèi)容。利用這種方式读串,任何 /users/<some number> 類型的 URL 都能對(duì)應(yīng)到 display_user 函數(shù)聊记,并且正則表達(dá)式可以無(wú)限復(fù)雜,包含任意的關(guān)鍵字和未知參數(shù)恢暖。
Flask 的 路由機(jī)制
Flask 采用的是另外一種方法排监。把 url 對(duì)應(yīng)到函數(shù)參照的是 route() 裝飾器。下面的 Flask 代碼和上面提到的正則表達(dá)式代碼功能相同:
@app.route('/users/<id:int>/')
def display_user(id):
# ...
如你所見(jiàn)杰捂,裝飾器使用的是簡(jiǎn)化版的正則表達(dá)式來(lái)傳遞參數(shù)舆床,參數(shù)被 route 參數(shù)中 <name:type> 的指令捕獲。要路由 /info/about.html 這樣的頁(yè)面嫁佳,就需要 @app.route('/info/about_us.html')挨队。
根據(jù)模板生成 HTML
繼續(xù)上面的例子,一旦我們知道怎么把 URL 對(duì)應(yīng)到邏輯代碼脱拼,那么要怎么動(dòng)態(tài)地生成 HTML,并且方便開(kāi)發(fā)者手動(dòng)編輯呢坷备?Django 和 Flask 兩者這次方法一樣熄浓,那就是 —— HTML 模板。
HTML 模板 有點(diǎn)像 string.format():預(yù)期的輸出首先要用站位標(biāo)識(shí),然后再填入動(dòng)態(tài)的數(shù)據(jù)赌蔑「┰冢可以把這個(gè)網(wǎng)頁(yè)想象成一個(gè)字符串,里面用括號(hào)標(biāo)識(shí)動(dòng)態(tài)的數(shù)據(jù)娃惯,最后調(diào)用 str.format() 生成最終的結(jié)果跷乐。Django 的 模板引擎和 Flask 采用的 jinja2 都是這個(gè)原理。
不過(guò)趾浅,并不是所有的模板引擎地位都一樣愕提。Django 的模板只支持簡(jiǎn)單的變成,而 Jinja2 卻能讓你執(zhí)行任意的代碼(當(dāng)然并發(fā)完全可以皿哨,不過(guò)已經(jīng)很近似)浅侨。Jinja2 很會(huì) cache 渲染的結(jié)果,下次有同樣的參數(shù)傳過(guò)來(lái)的時(shí)候证膨,就會(huì)直接從 cache 獲取結(jié)果如输,而不需要重新渲染。
數(shù)據(jù)庫(kù)集成
Django央勒,宣稱“自帶電池”(batteries included)不见,然后也會(huì)包含 ORM(Object Relational Mapper)。ORM 的目的有兩個(gè):把 python 的類映射到數(shù)據(jù)的表結(jié)構(gòu)崔步,和通過(guò)封裝隱藏不同數(shù)據(jù)庫(kù)之間的差異(第一點(diǎn)是它更主要的功能)稳吮。沒(méi)有人喜歡 ORM(因?yàn)椴煌蛑g的 mapping 從不完美),不過(guò)這些缺點(diǎn)都是可以接受的刷晋。 Django 功能比較全面盖高,F(xiàn)lask 作為一個(gè)微框架,并不自帶 ORM(不過(guò)它很好兼容 SQLAlchemy眼虱,Django ORM 最大的競(jìng)爭(zhēng)者)喻奥。
因?yàn)榘?ORM,Django 能夠創(chuàng)建功能齊全的 CRUD 應(yīng)用捏悬。 CRUD(Create Read Update Delete)是 web 框架(服務(wù)器端)最美好的地方撞蚕,Django 和 Flask-SQLAlchemy 使得 CRUD 操作很直接。
Web 框架總結(jié)
寫到這过牙,web 框架出現(xiàn)的目的也比較明確了:隱藏基礎(chǔ)而又煩人的處理 HTTP 請(qǐng)求和應(yīng)答的代碼甥厦。至于要隱藏多少內(nèi)容,就要看框架啦寇钉。Django 和 Flask 代表了兩個(gè)極端刀疙。Django 每種情況都有涉及,而 Flask 標(biāo)榜自己是“微框架“扫倡,只處理 web 程序最核心的功能谦秧,依賴其他三方插件來(lái)完成其他不常用的工作。
寫了這么多,記住疚鲤,所有的 python web 框架功能方式都一樣:它們接收 HTTP 請(qǐng)求锥累,然后分發(fā)任務(wù),并生成 HTML集歇,然后返回包含 HTML 的 HTTP 應(yīng)答桶略。事實(shí)上,所有的 server 端框架(除了 Javascript 框架)都是這么工作的诲宇。希望际歼,看完這篇文章,你已經(jīng)知道 web 框架的目的焕窝,也知道怎么去選擇 web 框架啦蹬挺。