1. 程式人生 > >【譯】什麼是 web 框架?

【譯】什麼是 web 框架?

Web 應用框架,或者簡單的說是“Web 框架”,其實是建立 web 應用的一種方式。從簡單的部落格系統到複雜的富 AJAX 應用,web 上每個頁面都是通過寫程式碼來生成的。我發現很多人都熱衷於學習 web 框架技術,例如 Flask 或這 Django 之類的,但是很多人並不理解什麼是 web 框架,或者它們是如何工作的。這篇文章中,我將探索反覆被忽略的 web 框架基礎的話題。閱讀完這篇文章,你應該首先對什麼是 web 框架以及它們為什麼會存在有更深的認識。這會讓你學習一個新的 web 框架變得簡單的多,還會讓你在使用不同的框架的時候做個明知的選擇。

Web 如何工作的?

在我們討論框架之前,我們需要理解 Web 如何“工作”的。為此,我們將深入挖掘你在瀏覽器裡輸入一個 URL 按下 Enter 之後都發生了什麼。在你的瀏覽器中開啟一個新的標籤,瀏覽http://www.jeffknupp.com

 。我們討論為了顯示這個頁面,瀏覽器都做了什麼事情(不關心 DNS 查詢)。

Web 伺服器

每個頁面都以 HTML 的形式傳送到你的瀏覽器中,HTML 是一種瀏覽器用來描述頁面內容和結構的語言。那些負責傳送 HTML 到瀏覽器的應用稱之為“Web 伺服器”,會讓你迷惑的是,這些應用執行的機器通常也叫做 web 伺服器。

然而,最重要的是要理解,到最後所有的 web 應用要做的事情就是傳送 HTML 到瀏覽器。不管應用的邏輯多麼複雜,最終的結果總是將 HTML 傳送到瀏覽器(我故意將應用可以響應像 JSON 或者 CSS 等不同型別的資料忽略掉,因為在概念上是相同的)。

web 應用如何知道傳送什麼到瀏覽器呢?它傳送瀏覽器請求的任何東西。

HTTP

瀏覽器從 web 伺服器(或者叫應用伺服器)上使用 HTTP 協議下載網站,HTTP 協議是基於一種 請求-響應(request-response)模型的。客戶端(你的瀏覽器)從執行在物理機器上的 web 應用請求資料,web 應用反過來對你的瀏覽器請求進行響應。

重要的一點是,要記住通訊總是由客戶端(你的瀏覽器)發起的,伺服器(也就是 web 伺服器)沒有辦法建立一個連結,傳送沒有經過請求的資料給你的瀏覽器。如果你從 web 伺服器上接收到資料,一定是因為你的瀏覽器顯示地傳送了請求。

HTTP Methods

在 HTTP 協議中,每條報文都關聯方法(method 或者 verb),不同的 HTTP 方法對應客戶端可以傳送的邏輯上不同型別的請求,反過來也代表了客戶端的不同意圖。例如,請求一個 web 頁面的 HTML,與提交一個表單在邏輯上是不同的,所以這兩種行為就需要使用不同的方法。

HTTP GET

GET 方法就像其聽起來的那樣,從 web 伺服器上 get(請求)資料。GET 請求是到目前位置最常見的一種 HTTP 請求,在一次 GET 請求過程中,web 應用對請求頁面的 HTML 進行響應之外,就不需要做任何事情了。特別的,web 應用在 GET 請求的結果中,不應該改變應用的狀態(比如,不能基於 GET 請求建立一個新帳號)。正是因為這個原因,GET 請求通常認為是“安全”的,因為他們不會導致應用的改變。

HTTP POST

顯然,除了簡單的檢視頁面之外,應該還有更多與網站進行互動的操作。我們也能夠嚮應用傳送資料,例如通過表單。為了達到這樣的目的,就需要一種不同型別的請求方法:POST。POST 請求通常攜帶由使用者輸入的資料,web 應用收到之後會產生一些行為。通過在表單裡輸入你的資訊登入一個網站,就是 POST 表單的資料給 web 應用的。

不同於 GET 請求,POST 請求通常會導致應用狀態的改變。在我們的例子中,當表單 POST 之後,一個新的賬戶被建立。不同於 GET 請求,POST 請求不總是生成一個新的 HTML 頁面傳送到客戶端,而是客戶端使用響應的響應碼(response code)來決定對應用的操作是否成功。

HTTTP Response Codes

通常來說,web 伺服器返回 200 的響應碼,意思是,“我已經完成了你要求我做的事情,一切都正常”。響應碼總是一個三位數字的代號,web 應用在每個響應的同時都發送一個這樣的代號,表明給定的請求的結果。響應碼 200 字面意思是“OK”,是響應一個 GET 請求大多情況下都使用的代號。然而對於 POST 請求, 可能會有 204(“No Content”)傳送回來,意思是“一切都正常,但是我不準備向你顯示任何東西”。

POST 請求仍然會發送一個特殊的 URL,這個 URL 可能和提交資料的頁面不同,意識這一點是至關重要的。還是以我們的登入為例,表單可能是在 www.foo.com/signup 頁面,然而點選 submit,可能會導致帶有表單資料的 POST 請求傳送到 www.foo.com/process_sigup 上。POST 請求要傳送的位置在表單的 HTML 中有特別標明。

Web 應用

你可以僅僅使用 HTTP GET 和 POST 做很多事情。一個應用程式負責去接收一個 HTTP 請求,同時給以 HTTP 響應,通常包含了請求頁面的 HTML。POST 請求會引起 web 應用做出一些行為,可能是往資料庫中新增一條記錄這樣的。還有很多其它的 HTTP 方法,但是我們目前只關注 GET 和 POST。

那麼最簡單的 web 應用是什麼樣的呢?我們可以寫一個應用,讓它一直監聽 80 埠(著名的 HTTP 埠,幾乎所有 HTTP 都發送到這個埠上)。一旦它接收到等待的客戶端傳送的請求連線,然後它就會回覆一些簡單的 HTML。

下面是程式的程式碼:

 
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(b"""HTTP/1.1 200 OK
Content-type: text/html

<html>
    <body>
        <h1>Hello, World!</h1>
    </body>
</html>""")
connection.close()

(如果上面的程式碼不工作,試著將 PORT 改為類似 8080 這樣的埠。)

這個程式碼接收簡單的連結和簡單的請求,不管請求的 URL 是什麼,它都會響應 HTTP 200(所以,這不是一個真正意義上的 web 伺服器)。Content-type:text/html 行程式碼的是 header 欄位,header 用來提供請求或者響應的元資訊。這樣,我們就告訴了客戶端接下來的資料是 HTML。

請求的剖析

如果看一下測試上面程式的 HTTP 請求,你會發現它和 HTTP 響應非常類似。第一行<HTTP Method> <URL> <HTTP version>,在這個例子中是 GET / HTTP/1.0。第一行之後就是一些類似Accept: */* 這樣的頭(意思是我們希望在響應中接收任何內容)。

我們響應和請求有著類似的第一行,格式是<HTTP version> <HTTP Status-code> <Status-code Reason Phrase>,在外面的例子中是HTTP/1.1 200 OK 。接下來是頭部,與請求的頭部有著相同的格式。最後是響應的實際包含的內容。注意,這會被解釋為一個字串或者二進位制檔案, Content-type 頭告訴客戶端怎樣去解釋響應。

Web 伺服器之殤

如果我們繼續以上面的例子為基礎建立 web 應用,我們還需要解決很多問題:

  1. 我們怎樣檢測請求的 URL 以及返回正確的頁面?
  2. 除了簡單的 GET 請求之外我們如何處理 POST 請求?
  3. 我們如何理解更高階的概念,如 session 和 cookie?
  4. 我們如何擴充套件程式以使其處理上千個併發連線?

就像你想的那樣,沒有人願意每次建立一個 web 應用都要解決這些問題。正是這個原因,就有處理 HTTP 協議本身和有效解決上面問題的辦法的包存在。然而,記住了,它們的核心功能和我們的例子是相同的:監聽請求,帶有一些 HTML 發回 HTTP 響應。

解決兩大問題:路由和模板

圍繞建立 web 應用的所有問題中,兩個問題尤其突出:

  1. 我們如何將請求的 URL 對映到處理它的程式碼上?
  2. 我們怎樣動態地構造請求的 HTML 返回給客戶端,HTML 中帶有計算得到的值或者從資料庫中取出來的資訊?

每個 web 框架都以某種方法來解決這些問題,也有很多不同的解決方案。用例子來說明更容易理解,所以我將針對這些問題討論 Django 和 Flask 的解決方案。但是,首先我們還需要簡單討論一下 MVC 。

Django 中的 MVC

Django 充分利用 MVC 設計模式。 MVC,也就是 Model-View-Controller (模型-檢視-控制器),是一種將應用的不同功能從邏輯上劃分開。models 代表的是類似資料庫表的資源(與 Python 中用 class 來對真實世界目標建模使用的方法大體相同)。controls 包括應用的業務邏輯,對 models 進行操作。為了動態生成代表頁面的 HTML,需要 views 給出所有要動態生成頁面的 HTML 的資訊。

在 Django 中有點讓人困惑的是,controllers 被稱做 views,而 views 被稱為 templates。除了名字上的有點奇怪,Django 很好地實現了 MVC 的體系架構。

Django 中的路由

路由是處理請求 URL 到負責生成相關的 HTML 的程式碼之間對映的過程。在簡單的情形下,所有的請求都是有相同的程式碼來處理(就像我們之前的例子那樣)。變得稍微複雜一點,每個 URL 對應一個 view function 。舉例來說,如果請求 www.foo.com/bar 這樣的 URL,呼叫 handler_bar() 這樣的函式來產生響應。我們可以建立這樣的對映表,枚舉出我們應用支援的所有 URL 與它們相關的函式。

然而,當 URL 中包含有用的資料,例如資源的 ID(像這樣 www.foo.com/users/3/) ,那麼這種方法將變得非常臃腫。我們如何將 URL 對映到一個 view 函式,同時如何利用我們想顯示 ID 為 3 的使用者?

Django 的答案是,將 URL 正則表示式對映到可以帶引數的 view 函式。例如,我假設匹配^/users/(?P<id>\d+)/$ 的 URL 呼叫 display_user(id) 這樣的函式,這兒引數 id 是正則表示式中匹配的 id。這種方法,任何 /users/<some_number>/ 這樣的 URL 都會對映到 display_user 函式。這些正則表示式可以非常複雜,包含關鍵字和引數。

Flask 中的路由

Flask 採取了一點不同的方法。將一個函式和請求的 URL 關聯起來的標準方法是通過使用 route() 裝飾器。下面是 Flask 程式碼,在功能上和上面正則表示式方法相同:

@app.route('/users/<id:int>/')
def display_user(id):
    # ...

就像你看到的這樣,裝飾器使用幾乎最簡單的正則表示式的形式來將 URL 對映到引數。通過傳遞給route() 的 URL 中包含的 <name:type> 指令,可以提取到引數。路由像 /info/about_us.html 這樣的靜態 URL,可以像你預想的這樣 @app.route('/info/about_us.html') 處理。

通過 Templates 產生 HTML

繼續上面的例子,一旦我們有合適的程式碼對映到正確的 URL,我們如何動態生成 HTML?對於 Django 和 Flask,答案都是通過 HTML Templating

HTML Templating 和使用 str.format() 類似:需要動態輸出值的地方使用佔位符填充,這些佔位符後來通過 str.format() 函式用引數替換掉。想象一下,整個 web 頁面就是一個字串,用括號標明動態資料的位置,最後再呼叫 str.format() 。Django 模板和 Flask 使用的模板引擎 Jinja2 都使用的是這種方法。

然而,不是所有的模板引擎都能相同的功能。Django 支援在模板裡基本的程式設計,而 Jinja2 只能讓你執行特定的程式碼(不是真正意義上的程式碼,但也差不多)。Jinja2 可以快取渲染之後的模板,讓接下來具有相同引數的請求可以直接從快取中返回結果,而不是用再次花大力氣渲染。

資料庫互動

Django 有著“功能齊全”的設計哲學,其中包含了一個 ORM(Object Realational Mapper,物件關係對映),ORM 的目的有兩方面:一是將 Python 的 class 與資料庫表建立對映,而是剝離出不同資料庫引擎直接的差異。沒人喜歡 ORM,因為在不同的域之間對映永遠不完美,然而這還在承受範圍之內。Django 是功能齊全的,而 Flask 是一個微框架,不包括 ORM,儘管它對 SQLAlchemy 相容性非常好,SQLAlchemy 是 Django ORM 的最大也是唯一的競爭對手。

內嵌 ORM 讓 Django 有能力建立一個功能豐富的 CRUD 應用,從伺服器端角度來看,CRUDCreateRead Update Delete)應用非常適合使用 web 框架技術。Django 和 Flask-SQLchemy 可以直接對每個 model 進行不同的 CRUD 操作。

再談 web 框架

到現在為止,web 框架的目的應該非常清晰了:向程式設計師隱藏了處理 HTTP 請求和響應相關的基礎程式碼。至於隱藏多少這取決於不同的框架,Django 和 Flask 走向了兩個極端:Django 包括了每種情形,幾乎成了它致命的一點;Flask 立足於“微框架”,僅僅實現 web 應用需要的最小功能,其它的不常用的 web 框架任務交由第三方庫來完成。

但是最後要記住的是,Python web 框架都以相同的方式工作的:它們接收 HTTP 請求,分派程式碼,產生 HTML,建立帶有內容的 HTTP 響應。事實上,所有主流的伺服器端框架都以這種方式工作的( JavaScript 框架除外)。但願瞭解了這些框架的目的,你能夠在不同的框架之間選擇適合你應用的框架進行開發。