Web框架正如前文所述, 在整個(gè)項(xiàng)目結(jié)構(gòu)中處于一個(gè)承上啟下的位置, 是整個(gè)項(xiàng)目的核心組件, 所以這次來(lái)聊聊Web框架的一些普適性特性和如何快速的入門(mén).
為什么Web框架需要快速入門(mén)?
Web框架是一組工具的集合, 為你的Web應(yīng)用開(kāi)發(fā)提供了基礎(chǔ)環(huán)境, 從如何獲取參數(shù), 到如何返回結(jié)果, 從如何獲取cookie到如何修改返回的http header. Web框架幫你隱藏了HTTP協(xié)議的細(xì)節(jié), 你作為一個(gè)使用者只需要關(guān)心如何使用, 而毋須去了解細(xì)節(jié)( 當(dāng)然如果你打算自己實(shí)現(xiàn)一個(gè)Web框架除外 ).
第二個(gè)原因是Web框架很多, 不同的語(yǔ)言都有自己實(shí)現(xiàn)的不同的Web框架(Python尤其的多). 每一種都有各自不同的實(shí)現(xiàn)思路, 有不同的開(kāi)發(fā)哲學(xué), 不管是就換工作換開(kāi)發(fā)棧的需要, 還是學(xué)習(xí)本身的需要, 快速的多掌握幾個(gè)框架還是很有必要的.
繼續(xù)閱讀本文需要掌握HTTP協(xié)議的基本知識(shí), 如果不了解請(qǐng)前往?<關(guān)于HTTP協(xié)議,一篇就夠了>
以下是一個(gè)Web框架的通用Guide, 基本上你能涉及到的大多數(shù)Web框架都是遵循下面的模式來(lái)的, 少數(shù)奇葩不在本文描述的范疇, 但是需要理解的知識(shí)點(diǎn)和問(wèn)題領(lǐng)域是大致相同, 你可以參考著來(lái).
一. Web框架的工作方式
現(xiàn)代的Web框架,不管其采用何種設(shè)計(jì)思想, 開(kāi)發(fā)哲學(xué), 根本的工作模式是相似的,均是從接收HTTP請(qǐng)求,處理HTTP的各項(xiàng)參數(shù),路由到相應(yīng)的用戶實(shí)現(xiàn)的處理器上, 再獲取返回的結(jié)果, 生成HTTP的Response.如下圖:

其中, 只有URL路由和控制器部分是由用戶來(lái)定義的, 所以, 如果要快速上手一個(gè)Web框架, 就要先從這兩個(gè)部分入手.
一. URL路由
一般來(lái)說(shuō)現(xiàn)在大部分的Web框架對(duì)于將HTTP請(qǐng)求交給哪一個(gè)邏輯來(lái)處理, 是由URL路由框架的模塊來(lái)決定的. URL路由模塊會(huì)提供一些函數(shù)或者裝飾器等方式, 讓用戶來(lái)寫(xiě)一些代碼, 將URL和控制器的綁定關(guān)系注冊(cè)到框架中.
大多數(shù)的框架都支持在URL路由的定義中使用正則表達(dá)式.有的支持在URL中定義參數(shù),框架會(huì)自動(dòng)在URL中將參數(shù)截取出來(lái)保存在框架的上下文中以供控制器使用.
舉幾個(gè)例子:

Django是通過(guò)定義了一個(gè)數(shù)組, 其中包含了url和控制器的關(guān)系來(lái)定義的URL路由

SpringMVC是通過(guò)在控制器上定義裝飾器來(lái)將控制器和URL的關(guān)系注冊(cè)到框架中的.
以上兩種是最長(zhǎng)用的兩種注冊(cè)的模式
二.控制器
控制器是用戶自己實(shí)現(xiàn)的類(lèi)或者函數(shù), 用來(lái)處理HTTP請(qǐng)求, 確切的說(shuō)是指定URL發(fā)來(lái)的請(qǐng)求, 并且將業(yè)務(wù)邏輯的結(jié)果返回給框架, 有框架去決定如何解析成HTTP的響應(yīng)數(shù)據(jù)輸出.
由于是由框架來(lái)調(diào)用控制器, 所以框架不同, 對(duì)控制器的定義規(guī)范是各不相同的. 有的框架要求控制器是一個(gè)類(lèi), URL將會(huì)Map到類(lèi)上, 類(lèi)中定義了GET, PUT, DELETE等方法, 用來(lái)對(duì)應(yīng)處理HTTP的GET, PUT, DELETE等方法的請(qǐng)求. 有的是將URL的不同方法的請(qǐng)求Map到不同的函數(shù), 或者將URL的不同方法Map到同一個(gè)函數(shù)上執(zhí)行. 這些差異和實(shí)現(xiàn)框架的語(yǔ)言有關(guān)系, 比如Java, C#等語(yǔ)言, 函數(shù)必須以類(lèi)方法的形式定義而不能成為頂級(jí)元素, 那么這些語(yǔ)言的框架大多會(huì)要求將控制器定義成類(lèi), 而Python, Ruby, Javascript這些函數(shù)是頂級(jí)元素的語(yǔ)言, 大多數(shù)的框架都是將控制器用函數(shù)來(lái)定義, 當(dāng)然也有異類(lèi), 非要用類(lèi)來(lái)定義的, 比如說(shuō) Python的Tornado, 有的是兩種方式都支持. 這個(gè)跟框架作者的喜好關(guān)系比較大.
框架需要給控制器提供HTTP請(qǐng)求的完整上下文環(huán)境,那么我們?cè)趯W(xué)習(xí)框架使用的時(shí)候, 首先就要在文檔中整理清楚下面的這些問(wèn)題:
1. 如何獲取Query String參數(shù)?
QueryString是url中?后定義的由&符號(hào)分割的key=value形式的參數(shù), 框架會(huì)在解析HTTP協(xié)議的時(shí)候?qū)⑦@個(gè)部分的數(shù)據(jù)轉(zhuǎn)化成比如字典之類(lèi)的數(shù)據(jù)結(jié)構(gòu)存起來(lái), 然后提供相應(yīng)的API去訪問(wèn).但是QueryString都是只讀的, 有一些框架會(huì)提供一些工具幫助用戶組裝URL, 比如Flask提供了url_for用來(lái)組裝轉(zhuǎn)跳到指定控制器的URL, 方便你在頁(yè)面上渲染相應(yīng)的<a>標(biāo)簽
2.如何獲取Form表單的數(shù)據(jù)(Json body)?
POST和PUT方法是HTTP協(xié)議中主要的接收數(shù)據(jù)的語(yǔ)義, 傳輸數(shù)據(jù)需要相應(yīng)的編碼方式將數(shù)據(jù)編碼后放在HTTP協(xié)議的Body部分. 編碼方式現(xiàn)在主要有兩種, 一種是最古老的用Form表單編制數(shù)據(jù)的方式, 另外一種是將數(shù)據(jù)用json字符串直接放到HTTP協(xié)議的body部分. 網(wǎng)頁(yè)上的應(yīng)用多數(shù)會(huì)采用Form表單, 因?yàn)檫@是瀏覽器對(duì)Form表單的默認(rèn)支持, 而App的后端大多采用JSONString放在body來(lái)傳數(shù)據(jù)的方式, 優(yōu)點(diǎn)是可以傳送復(fù)雜的數(shù)據(jù)結(jié)構(gòu).
Web框架會(huì)提供相應(yīng)的API去按照這兩種方式獲取Body的內(nèi)容, 但是有的框架會(huì)根據(jù)header里的mime type來(lái)判斷是不是可以按照J(rèn)SON去解析body的內(nèi)容.
3. 如何讀取和修改cookie?
cookie是瀏覽器(客戶端)在多次請(qǐng)求之間共享數(shù)據(jù)的一個(gè)數(shù)據(jù)結(jié)構(gòu), 因?yàn)镠TTP協(xié)議是無(wú)狀態(tài)的,所以cookie在每一次請(qǐng)求的時(shí)候都會(huì)從瀏覽器放到HTTP請(qǐng)求中傳到服務(wù)端, 在服務(wù)端生成響應(yīng)的時(shí)候也會(huì)寫(xiě)入HTTP響應(yīng)里傳回瀏覽器. 從而實(shí)現(xiàn)了在兩次請(qǐng)求之間共享了數(shù)據(jù).
Web框架會(huì)提供對(duì)應(yīng)的API去讀取和修改cookie的值, cookie是key-value形式的, 訪問(wèn)的方式應(yīng)該和字典類(lèi)似.
4. 如何獲取headers?
headers對(duì)應(yīng)到HTTP協(xié)議中HTTP請(qǐng)求的頭部, 是一個(gè)key-value形式的數(shù)據(jù)格式.headers是HTTP請(qǐng)求的一些額外的描述信息, 比如客戶端類(lèi)型, 字符編碼方式, 認(rèn)證信息等. headers的key是固定的, 你不能自己隨便定義一些特殊key, 并且headers部分也是只讀的.框架會(huì)提供對(duì)應(yīng)API去讀取headers.
5. 如何實(shí)現(xiàn)頁(yè)面轉(zhuǎn)跳?
有些時(shí)候在處理完數(shù)據(jù)后需要通知瀏覽器轉(zhuǎn)跳到對(duì)應(yīng)的看數(shù)據(jù)的URL上去, 就需要在文檔里找找如何實(shí)現(xiàn)頁(yè)面轉(zhuǎn)跳. 服務(wù)端返回給客戶端一個(gè)HTTP協(xié)議中的301或者302的狀態(tài), 就可以讓瀏覽器去執(zhí)行轉(zhuǎn)跳的動(dòng)作, 作出這樣子的HTTP響應(yīng)在框架中會(huì)有對(duì)應(yīng)的API.
6. 如何輸出Http響應(yīng)?
大多數(shù)的Web框架都是用函數(shù)(方法)的返回值來(lái)作為Http響應(yīng)的body的, 所以你在很多示例中可以看到 return "hello world" 這樣子的寫(xiě)法, 框架會(huì)自動(dòng)把字符串轉(zhuǎn)換成Response的對(duì)象再去做編碼成Http響應(yīng)的操作.
當(dāng)然有一些例外, 比如Python的Tornado就是用控制器繼承的基類(lèi)中提供的write方法, 來(lái)向輸出的流中寫(xiě)入數(shù)據(jù)的.
針對(duì)App的API會(huì)比較方便, 輸出JSON的字符串就行了. 如果是網(wǎng)站的話, 需要輸出HTML頁(yè)面,有一些框架提供了內(nèi)置的模板引擎, 用于渲染輸出html內(nèi)容(其實(shí)不限html, xml的內(nèi)容也可以由模板引擎來(lái)渲染). 有的框架自己不提供模板引擎, 你可以用你喜歡的模板引擎來(lái)渲染.
在框架的文章中找到上述問(wèn)題的答案后, 我們就可以在控制器中獲取請(qǐng)求的上下文和控制輸出了.
搞定上面內(nèi)容我們就可以寫(xiě)邏輯了, 控制器中邏輯的一般性結(jié)構(gòu)如下:

在學(xué)習(xí)完URL路由和控制器部分后, 你就可以嘗試寫(xiě)一些簡(jiǎn)單的東西了, 起碼一些簡(jiǎn)單的demo可以跑起來(lái)了. 接下來(lái)為了讓你的Demo可以跑起來(lái), 還需要繼續(xù)學(xué)習(xí)框架的一些進(jìn)一步的特性
三. 開(kāi)發(fā)環(huán)境
Web框架的開(kāi)發(fā)環(huán)境由其實(shí)現(xiàn)的語(yǔ)言來(lái)決定, 比如用Java的很多是Eclipse, C#是visual studio, 大公司IDE產(chǎn)品很多都內(nèi)置集成了開(kāi)發(fā)運(yùn)行的環(huán)境方便調(diào)試, 而很多開(kāi)源產(chǎn)品就五花八門(mén)了, 所以比如Python, Ruby和nodejs下的web框架大多都內(nèi)置了開(kāi)發(fā)服務(wù)器, 啟動(dòng)開(kāi)發(fā)服務(wù)器就等于啟動(dòng)了一個(gè)小web服務(wù)器, 并且大多數(shù)都提供了檢測(cè)文件改變后自動(dòng)重啟服務(wù)的auto reload功能.
四. 配置或者約定
配置用來(lái)自定義框架的一些特性, 不同的框架配置區(qū)別是蠻大的, 有的基于配置文件, 有的是約定優(yōu)先, 有的代碼本身就是配置, 所以這部分仔細(xì)看文檔即可, 并且可配置的項(xiàng)目可能很多, 在學(xué)習(xí)階段其實(shí)可以將文檔的這部分做成Cheat Sheet方便平時(shí)查閱.
如果是約定優(yōu)先的框架多半會(huì)提供腳手架工具, 熟悉腳手架工具也是很重要的一點(diǎn).
五. 模板引擎
前面在控制器的部分提到了模板引擎, 模板引擎主要負(fù)責(zé)用數(shù)據(jù)替換模板上的占位符生成最終結(jié)果的文本. 工作方式可以簡(jiǎn)化為下圖:

這里詳細(xì)的說(shuō)一下模板引擎部分在學(xué)習(xí)的時(shí)候要注意那一些要點(diǎn):
1. 占位符如何定義
2. 如何定義循環(huán)
3. 如何定義判斷條件
4. 如何對(duì)占位符加過(guò)濾器
5. 如何定義繼承模板
6. 如何定義嵌套模板
在文檔中學(xué)習(xí)完上面六點(diǎn)基本上就能玩轉(zhuǎn)一個(gè)模板引擎了, 當(dāng)然你之后還是可以繼續(xù)深入學(xué)習(xí)的, 但是這六點(diǎn)已經(jīng)可以支撐你做完完整的應(yīng)用了.
六. 中間件
中間件是AOP模式統(tǒng)一在每個(gè)請(qǐng)求的開(kāi)始和結(jié)束部分注入代碼的機(jī)制, 最常見(jiàn)的有兩個(gè)級(jí)別的中間件, 一個(gè)是Application級(jí)別的, 也就是在應(yīng)用啟動(dòng)和結(jié)束的時(shí)候執(zhí)行注入的代碼, 另一個(gè)是Request級(jí)別的, 主要是在每個(gè)請(qǐng)求開(kāi)始和結(jié)束的時(shí)候執(zhí)行, 比如統(tǒng)一的鑒權(quán),數(shù)據(jù)庫(kù)連接管理(打開(kāi),關(guān)閉), 統(tǒng)一的錯(cuò)誤處理 都可以在這里完成.
簡(jiǎn)單的來(lái)說(shuō)如下圖所示:

七. 統(tǒng)一錯(cuò)誤處理
有的框架是提供了統(tǒng)一錯(cuò)誤處理機(jī)制的, 當(dāng)在控制器中發(fā)生了沒(méi)有捕獲或者重新拋出的異常的時(shí)候, 就會(huì)執(zhí)行到統(tǒng)一錯(cuò)誤處理的部分. 這里可以統(tǒng)一完成關(guān)閉數(shù)據(jù)庫(kù)連接等清理工作.一般都是由框架內(nèi)置的中間件來(lái)實(shí)現(xiàn)的.
八. 鑒權(quán)和Session
并不是所有的框架都提供了這兩個(gè)機(jī)制的, 因?yàn)橥ㄟ^(guò)cookie和中間件可以很輕松的實(shí)現(xiàn)鑒權(quán)和Session機(jī)制. 很多框架自己提供的也不怎么好用(比如Django), 所以很多人也會(huì)自己去實(shí)現(xiàn)這兩個(gè)機(jī)制, 如果你不知道怎么實(shí)現(xiàn), 我在后面會(huì)有專門(mén)的話題來(lái)詳細(xì)說(shuō)明.
九. 生產(chǎn)部署的方式
開(kāi)發(fā)的Server是頂不住正式上線后的壓力的, 所以要讓你應(yīng)用能夠正式的發(fā)布, 還需要了解如何部署生產(chǎn)環(huán)境.
不同的框架部署的方式也是大不一樣的, 所以這個(gè)要根據(jù)框架文檔來(lái)實(shí)施.
在完成上述9個(gè)步驟的學(xué)習(xí)后, 你就已經(jīng)可以說(shuō)是初步掌握了一個(gè)Web框架了, 還有很多細(xì)節(jié)的東西可以在開(kāi)發(fā)的過(guò)程中逐步的來(lái)補(bǔ)充. 可以說(shuō)上面的內(nèi)容就是一個(gè)Web框架的最小功能集合了, 如果你想自己實(shí)現(xiàn)一個(gè)Web框架的話也可以參考本文, 特別是寫(xiě)文檔的時(shí)候, 現(xiàn)在很多Web框架的文檔寫(xiě)得亂七八糟語(yǔ)焉不詳, 建議參考本文的結(jié)構(gòu)寫(xiě)一個(gè)Quick Guide, 也好讓更多的人體會(huì)到你的精心設(shè)計(jì)了.
現(xiàn)在你可以讓用戶看到頁(yè)面, 也可以收到用戶的請(qǐng)求內(nèi)容, 那么數(shù)據(jù)要如何保存, 如何查詢, 有那些<知識(shí)點(diǎn)>是需要我們掌握的? 所 以
下一章的內(nèi)容是<數(shù)據(jù)庫(kù)以及數(shù)據(jù)訪問(wèn)>
to be continue...
PS: 根據(jù)本文套路寫(xiě)了一個(gè)番外篇, 用一個(gè)實(shí)際框架來(lái)做了個(gè)入門(mén)指南:?實(shí)戰(zhàn):Flask快速入門(mén)