之前用 Django 做過一個小的網站,感覺Django太過笨重,於是就準備換一個比較輕量級的 Web 架構來玩玩。Web.py 作者已經掛掉,項目好久沒有更新,所以不準備用它。而 Flask 也是一個成熟的輕量級 Web 架構,在 github 上有眾多的 Star 和 Fork,文檔和擴充也很豐富,值得學習。
學習一個架構最好的方式就是用架構做一個項目,在實戰中理解掌握架構。這裡我用 Flask 架構,使用 Mysql 資料庫做了一個 論壇系統 。麻雀雖小,五髒俱全,論壇效果圖如下:
論壇系統截圖
下面是論壇的準系統:
完整的使用者模組(註冊、登入,更改、找回密碼、資訊修改、站內訊息通知); 豐富的論壇模組(建立、回複話題,站內搜尋,markdown支援,@user 提醒); 強大的後台管理,支援屏蔽使用者、話題、評論,支援各種條件搜尋話題、評論;
本部落格將會用一系列文章,記錄論壇系統搭建的過程,希望對剛入門Web開發的同學有所協助。
我們經常聽說 Django, Flask 這些 python 語言的Web 架構,那麼架構到底是什麼,Web架構和Web伺服器(Nginx, Apache等)有什麼區別?離開架構還能用 Python 搭建Web網站嗎?要解決這些疑問,我們有必要來理解下 Web 服務器的工作原理,以及 Web 架構的本質。
Web 服務器
當我們在瀏覽器輸入URL後,瀏覽器會先請求 DNS伺服器,獲得請求網站的 IP 位址 。然後發送一個HTTP Request(請求)給擁有該 IP 的主機,接著就會接收到伺服器給我們的 HTTP Response(響應),瀏覽器經過渲染後,以一種較好的效果呈現給我們。這個過程中,正是Web伺服器在幕後默默做貢獻。
簡單來說,Web伺服器是在運行在物理伺服器上的一個程式,它永久地等待用戶端(主要是瀏覽器,比如Chrome,Firefox等)發送請求。當收到請求之後,它會產生相應的響應並將其返回至用戶端。Web伺服器通過HTTP協議與用戶端通訊,因此也被稱為HTTP伺服器。
Web 服務器
Web伺服器的工作原理並不複雜,一般可分成如下4個步驟:建立串連、請求過程、應答過程以及關閉串連。
建立串連:客戶機通過TCP/IP協議建立到伺服器的TCP串連。 請求過程:用戶端向伺服器發送HTTP協議請求包,請求伺服器裡的資來源文件。 應答過程:伺服器向客戶機發送HTTP協議應答包,如果請求的資源套件含有動態語言的內容,那麼伺服器會調用動態語言的解釋引擎負責處理“動態內容”,並將處理得到的資料返回給用戶端。由用戶端解釋HTML文檔,在用戶端螢幕上渲染圖形結果。 關閉串連:客戶機與伺服器斷開。
下面我們實現一個簡單的 Web 服務器。運行樣本程式後,會監聽本地連接埠 8000,在瀏覽器訪問 http://localhost:8000 就能看到響應內容。而我們的程式也能夠列印出用戶端發來的請求內容,如下圖:
簡單Web伺服器
這裡Request 和 Response 都需要遵守 HTTP 協議,關於 HTTP 協議的詳細內容,可以讀讀《HTTP 權威指南》,或者看我整理的HTTP 部分內容。
雖然說web伺服器的主要工作是根據request返回response,但是實際中的 Web 服務器遠遠比上面樣本的複雜的多,因為要考慮的因素實在是太多了,比如:
緩衝機制:講一些經常被訪問的頁面緩衝起來,提高響應速度; 安全:防止駭客的各種攻擊,比如 SYN Flood 攻擊; 並發處理:如何響應不同用戶端同時發起的請求; 日誌:記錄訪問日至,方便做一些分析。
目前在UNIX和LINUX平台下使用最廣泛的免費 Web 服務器有Apache和 Nginx 。
Web 應用程式
Web 服務器接受 Http Request,返回 Response,很多時候 Response 並不是靜態檔案,因此需要有一個應用程式根據 Request 產生相應的 Response。這裡的應用程式主要用來處理相關商務邏輯,讀取或者更新資料庫,根據不同 Request 返回相應的 Response。注意這裡並不是 Web 服務器本身來做這件事,它只負責 Http 協議層面和一些諸如並發處理,安全,日誌等相關的事情。
應用程式可以用各種語言編寫(Java, PHP, Python, Ruby等),這個應用程式會從Web伺服器接收用戶端的請求,處理完成後,再返迴響應給Web伺服器,最後由Web伺服器返回給用戶端。整個架構如下:
Web應用程式
以 Python 為例,使用Python開發Web,最原始和直接的辦法是使用CGI標準,在1998年這種方式很流行。首先確保 Web 服務器支援CGI及已經配置了CGI的處理常式,然後設定好CGI目錄,在目錄裡面添加相應的 python 檔案,每一個 python 檔案處理相應輸入,產生一個 html 檔案即可,如下例:
# !/usr/bin/python # -*- coding: UTF-8 -*- print "Content-type:text/html" print # 空行,告訴伺服器結束頭部 print '<html>' print '<head>' print '<meta charset="utf-8">' print '</head>' print '<body>' print '<h2>Hello Word! 我是一個CGI程式</h2>' print '</body>' print '</html>'
這樣在瀏覽器訪問該檔案就可以得到一個簡單的 Hello World 網頁內容。直接通過 CGI 寫 Web 應用程式看起來很簡單,每一個檔案處理輸入,產生html。但是實際開發中,可能會遇到許多不方便的地方。比如:
每個獨立的CGI指令碼可能會重複寫資料庫連接,關閉的代碼; 後端開發人員會看到一堆 Content-Type 等和自己無關的 html 頁面元素; Web 架構
早期開發網站確做了許多重複性勞動,後來為了減少重複,避免寫出龐雜,混亂的代碼,人們將 Web 開發的關鍵性過程提取出來,開發出了各種 Web 架構。有了架構,就可以專註於編寫清晰、易維護的代碼,無需關心資料庫連接之類的重複性工作。
其中一種比較經典的Web架構採用了 MVC 架構,如下圖所示:
MVC 架構
使用者輸入 URL,用戶端發送請求,控制器(Controller)首先會拿到請求,然後用模型(Models)從資料庫取出所有需要的資料,進行必要的處理,將處理後的結果發送給 視圖(View),視圖利用擷取到的資料,進行渲染產生 Html Response返回給用戶端。
以 python web 架構 flask 為例,架構本身並不限定我們用哪種架構來組織我們的應用,不過 flask 可以很好地支援以 MVC 方式組織應用。
控制器:flask 可以用裝飾器來添加路由項,如下:
@app.route('/') def main_page(): pass
模型:主要用來取出需要的資料,如下面函數中操作:
@app.route('/') def main_page(): """Searches the database for entries, then displays them.""" db = get_db() cur = db.execute('select * from entries order by id desc') entries = cur.fetchall() return render_template('index.html', entries=entries)
視圖:flask 利用 jinja2 來渲染頁面,下面的模版檔案指定了頁面的樣式:
{% for entry in entries %} <li> <h2>{{ entry.title }}</h2> <div>{{ entry.text|safe }}</div> </li> {% else %} <li><em>No entries yet. Add some!</em></li> {% endfor %}
我們知道Python有著許多的 Web 架構,而同時又有著許多的 Web 服務器(Apache, Nginx, Gunicorn等),架構和Web伺服器之間需要進行通訊,如果在設計時它們之間不可以相互匹配的,那麼選擇了一個架構就會限制對 Web 服務器的選擇,這顯然是不合理的。
那麼,怎樣確保可以在不修改Web伺服器代碼或網路架構代碼的前提下,使用自己選擇的伺服器,並且匹配多個不同的網路架構呢?答案是介面,設計一套雙方都遵守的介面就可以了。對python來說,就是WSGI(Web Server Gateway Interface,Web伺服器網關介面)。其他程式設計語言也擁有類似的介面:例如Java的Servlet API和Ruby的Rack。
Python WSGI的出現,讓開發人員可以將 Web 架構與 Web 服務器的選擇分隔開來,不再相互限制。現在,你可以真正地將不同的 Web 服務器與Web架構進行混合搭配,選擇滿足自己需求的組合。例如,可以使用 Gunicorn 或Nginx/uWSGI來運行Django、Flask或web.py應用。
WSGI 適配
下一篇我們將會仔細分析 WSGI 介面標準,然後一起來寫一個簡單的 WSGI Web 服務器。