【譯】什麼是 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 框架除外)。但願瞭解了這些框架的目的,你能夠在不同的框架之間選擇適合你應用的框架進行開發。

 

原文地址:http://www.jeffknupp.com/blog/2014/03/03/what-is-a-web-framework/  ,翻譯有點粗糙,建議直接看原文!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章