web 框架詳解(python)

Web 應用框架,簡稱爲 web 框架,是編寫 web 應用程序的基石。不管簡單的博客系統,還是 Ajax 爲主的應用,網絡上所有的頁面都是代碼構成的。進來我發現,很多想學習諸如 Flask 或者 Django 等 web 框架的開發者,並不很瞭解 web 框架是什麼,它們的作用和工作原理。這篇文章,我將會講一下這個通常會被忽略的話題。希望讀完這篇文章,你能比較深刻地理解 web 框架到底是什麼,還有爲什麼會有 web 框架。這些知識將有利於你學習新的 web 框架,而且在選擇 web 框架的時候有法可依。

WEB 是什麼工作的?

在討論框架之前,我們要先了解一下網頁是怎麼工作。我們就從你在瀏覽器輸入一個網址,摁下 enter 鍵說起。打開你的瀏覽器,輸入 http://jeffknupp.com(譯者注:原作者的個人網站首頁),我們來看看你的瀏覽器做了那些事情(DNS 查詢的 buffer 就略過),才能顯示你看到的網頁。

web servers,和 web … servers …

瀏覽器接接收到的網頁都是 HTML 文件,HTML 是一種描述網頁內容和結構的語言。負責給瀏覽器發送 HTML 的程序稱爲 web server,容易混淆的是,這個應用程序所在的機器通常也被稱爲 web server。

最重要的一點是,所有的 web 應用做的事情就是把 HTML 內容發送給瀏覽器。不論這個 web 應用有多麼複雜,最終的任務都是把 HTML(我故意忽略掉其他格式的內容,比如 JSON,CSS 文件,因爲原理都是一樣的)發送給瀏覽器。

問題來了:web 應用如何知道要發送什麼內容給瀏覽器呢?答案:它會發送瀏覽器請求的內容。

HTTP

瀏覽器從 web server 下載內容所用的是 HTTP 協議(協議在計算機科學中,指的是雙方通信所共同遵循的數據格式和通信步驟)。HTTP 協議的基礎是 請求-應答(request-response) 模型。客戶端(你的瀏覽器)請求 某臺物理機上 web 應用的數據,web server 則負責 應答請求的數據。

有個重要的事情是:所有的通信都是客戶端(你的瀏覽器)發起的。服務端(web server)是不可能主動連接你,發送沒有請求的數據的。如果你收到了數據,只是因爲你的瀏覽器主動請求了這些數據。

HTTP 方法

HTTP 協議的每條消息都有對應的方法(method),不同的方法對應了客戶端能發起的不同請求,也對應了客戶端不同的意圖。比如,請求 網頁的 HTML 和提交一個表格在邏輯上是不同的,所以這兩種方法需要兩種不同的方法。

HTTP GET

顧名思義,GET 方法就是從 web server 獲取(get)數據,GET 請求也是目前最常用的 HTTP 請求。 處理 GET 請求的過程中,web 應用只需要返回請求的數據,無需其他操作。尤其是,不應該修改應用的狀態(比如, GET 請求不應該導致一個新用戶被創建)。因爲這個原因,GET 請求通常被看做是 安全 的。

HTTP POST

和網站的交互,明顯不只是查看網頁的。我們還會通過表格等形式發送數據給 web 應用,這些操作需要用到另外一種請求:POSTPOST 請求通常會傳遞用戶創建的信息,導致 web 應用執行某些動作。輸入自己的信息,來註冊某個網站就會用到 POST 請求,請求中會包含你輸入的數據。

和 GET 請求不同的是, POST 請求通常會導致 web 應用狀態的改變。上面提及的例子中,表單被提交後,一個新的用戶會被創建。還有一點不同,POST 請求的結果可能不會返回 HTML 數據給客戶端,客戶端需要通過 response code 來判斷操作是否成功。

HTTP response code

正常情況下,web server 會返回 200 的 response code,意思是:我已經完成了你要我做的事情,並且一切都沒有問題。response code 是三位的數字,每次應答都要包含一個 response code,來標識請求的結果。200 表示 OK,是 GET 方法常見的返回值。POST請求經常會返還 204(No contnet),表示:一切正常,但是我沒有數據可以展示給你。

還需要注意的是:POST 請求發送給的 url,可能和數據發送出去的 url 不同。繼續以我們的註冊頁面爲例,註冊表可能位於 http://foo.com/signup,點擊 submit 之後,包含着註冊數據的 POST 請求可能被髮送到 http://foo.com/process_signupPOST 請求要發送到的地址,一般在註冊表格的 HTML 源碼裏指定。

Web 應用

掌握 GET 和 POST 方法就能做很多事情,因爲它們是 web 上最常用的兩個方法。總結一下,web 應用就是接收 HTTP 請求,然後返回 HTTP 應答,一般是包含請求數據的 HTMLPOST 方法會導致 web 應用執行某些動作,例如在數據庫添加一條記錄。當然還有其他的 HTTP 方法,但目前我們只需要關心 GET 和 POST 就足夠啦。

最簡單的 web 應用長什麼樣呢?我們就來寫一個監聽在 80 端口的 web 應用,一旦和客戶端建立連接,就等待客戶端發起請求,並返回非常簡單的 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("""HTTP/1.1 200 OK
Content-type: text/html


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

(如果上面的程序報端口錯誤,可以把 PORT 的值修改成其他值,比如 8080。)

上面的代碼只會接收一個連接和一個請求,不管請求的 URL 是什麼,都會返回同樣的 HTTP 內容,response code 是 200。(很明顯,這不算真正的 web server)。在這個例子,我們告訴客戶端,返回的數據格式爲 HTML,而不是其他的格式,比如 JSON

request 請求解析

如果看一下上面例子中發送的 HTTP 請求(譯者注:可以使用 chrome 的 inspect elements -> Network,或者抓包工具 tcpdump 等工具查看發送的 HTTP 請求),就會發現它和應答很相似。請求的第一行是:

<HTTP Method> <URL> <HTTP version>

在這個例子裏就是 GET / HTTP/1.1。第一行後面跟着的是請求的頭部(headers),比如 Accept: */*(表示可以接收任何格式的內容作爲應答)。基本上就這麼多。

我們發送的應答也是類似的格式,第一行是:

<HTTP version> <HTTP Status-Code> <Status-Code Reason-Phrase>

這個例子中就是 HTTP/1.1 200 OK。然後是頭部,和請求的頭部一樣。最後是應答的實際內容。注意:應答也可能是字符串或者二進制對象,頭部的 Content-typ 就是來標識應答內容,讓客戶端來解析的。

web server 更多瑣碎的細節

要在上面的例子基礎上繼續擴展的話,還有很多需要我們來解決的問題:

  1. 怎麼查看請求的 URL,然後返回不同的頁面?
  2. 除了 GET 請求,怎麼處理 POST 請求?
  3. 怎麼處理很複雜的概念,比如 sessions 和 cookies?
  4. 如何擴展這個應用,讓它可以同時處理數千條連接?

可以想象,沒人會願意每次編寫 web 應用的時候都要自己處理這些問題。爲了解決這個難題,就會存在很多的軟件包幫你處理這些煩人的細節,讓開發者可以把心思放到業務邏輯上。記住,不管 web 框架多麼負責,其最核心的功能和我們上面的例子是一樣的:監聽客戶端請求,然後返回 HTML 給客戶端。

NOTE:客戶端的框架和上面的內容迥然不同。

解決兩個難題:路由(routing)和模板(templates)

在構建 web 應用的所有的問題中,有兩個比較突出:

  1. 怎麼把請求 URL 和處理它的那部分代碼對應起來?
  2. 怎麼動態地生產請求內容?包括所有要計算的值,和從數據庫獲取的信息?

每個 web 框架解決這兩個問題的方法都不太相同,我們就舉 Flask 和 Django 的例子來說明這個問題。首先,我們還要來說一下 MVC 模式。

Django 中的 MVC

Django 採用 MVC 模式, 所以要求使用這個框架的代碼都遵循這個模式。MVC,是 Model-View-Controller 的縮寫,用來分離應用的不同責任。數據庫表所代表的資源用 models 來表示,controllers 負責應用的業務邏輯和操作 models。Views 則負責動態生成代表頁面的 HTML

不過容易讓人混淆的是,django 中 controllers 被稱作 views,而 views 被稱爲 templates。除了命名外,django 算是比較直接的 MVC 架構。

Django 的路由(routing)機制

這裏說的路由(routing)就是把請求的 URL 對應到處理生成相關 HTML 的代碼。最簡單的例子,所有的請求都是相同的代碼處理(就是我們之前編寫的代碼)。複雜一點呢,每個的 URL 都對應一個不同的 view function。比如,有個地方的邏輯是接收到 www.foo.com/bar 請求,就把它交給 handle_bar() 函數處理。我們可以這樣依次編寫出所有 url 對應的處理函數。

不過,這個方法有個致命傷:沒有辦法處理帶有動態數據的 URL,比如說資源的 ID(例如 http://www.foo.com/users/3)。我們怎麼把這個 URL 映射到函數,同時能傳過去用戶 ID 信息呢?

Django 採用的方法是利用正則表達式:用正則表達式匹配 URL,然後把匹配的數據作爲參數傳遞給處理函數。比如,我可以說匹配 ^/users/(?P<id>\d+)/$ 的 URL 會調用 display_user(id) 函數,其中 id 就是正則表達式括號裏匹配的內容。利用這種方式,任何 /users/<some number> 類型的 URL 都能對應到 display_user 函數,並且正則表達式可以無限複雜,包含任意的關鍵字和未知參數。

Flask 的 路由機制

Flask 採用的是另外一種方法。把 url 對應到函數參照的是 route() 裝飾器。下面的 Flask 代碼和上面提到的正則表達式代碼功能相同:

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

如你所見,裝飾器使用的是簡化版的正則表達式來傳遞參數,參數被 route 參數中 <name:type> 的指令捕獲。要路由 /info/about.html 這樣的頁面,就需要 @app.route('/info/about_us.html')

根據模板生成 HTML

繼續上面的例子,一旦我們知道怎麼把 URL 對應到邏輯代碼,那麼要怎麼動態地生成 HTML,並且方便開發者手動編輯呢?Django 和 Flask 兩者這次方法一樣,那就是 —— HTML 模板。

HTML 模板 有點像 string.format():預期的輸出首先要用站位標識,然後再填入動態的數據。可以把這個網頁想象成一個字符串,裏面用括號標識動態的數據,最後調用 str.format() 生成最終的結果。Django 的 模板引擎和 Flask 採用的 jinja2 都是這個原理。

不過,並不是所有的模板引擎地位都一樣。Django 的模板只支持簡單的變成,而 Jinja2 卻能讓你執行任意的代碼(當然併發完全可以,不過已經很近似)。Jinja2 很會 cache 渲染的結果,下次有同樣的參數傳過來的時候,就會直接從 cache 獲取結果,而不需要重新渲染。

數據庫集成

Django,宣稱“自帶電池”(batteries included),然後也會包含 ORM(Object Relational Mapper)。ORM 的目的有兩個:把 python 的類映射到數據的表結構,和通過封裝隱藏不同數據庫之間的差異(第一點是它更主要的功能)。沒有人喜歡 ORM(因爲不同域之間的 mapping 從不完美),不過這些缺點都是可以接受的。 Django 功能比較全面,Flask 作爲一個微框架,並不自帶 ORM(不過它很好兼容 SQLAlchemy,Django ORM 最大的競爭者)。

因爲包含 ORM,Django 能夠創建功能齊全的 CRUD 應用。 CRUD(Create Read Update Delete)是 web 框架(服務器端)最美好的地方,Django 和 Flask-SQLAlchemy 使得 CRUD 操作很直接。

Web 框架總結

寫到這,web 框架出現的目的也比較明確了:隱藏基礎而又煩人的處理 HTTP 請求和應答的代碼。至於要隱藏多少內容,就要看框架啦。Django 和 Flask 代表了兩個極端。Django 每種情況都有涉及,而 Flask 標榜自己是“微框架“,只處理 web 程序最核心的功能,依賴其他三方插件來完成其他不常用的工作。

寫了這麼多,記住,所有的 python web 框架功能方式都一樣:它們接收 HTTP 請求,然後分發任務,並生成 HTML,然後返回包含 HTML 的 HTTP 應答。事實上,所有的 server 端框架(除了 Javascript 框架)都是這麼工作的。希望,看完這篇文章,你已經知道 web 框架的目的,也知道怎麼去選擇 web 框架啦。

翻譯:

https://jeffknupp.com/blog/2014/03/03/what-is-a-web-framework/

參考:

http://cizixs.com/2015/09/21/what-is-a-web-framework

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