一起寫一個 Web 伺服器(2)

高世界發表於2015-06-06

還記得嗎?在本系列第一部分我問過你:“怎樣在你的剛完成的WEB伺服器下執行 Django 應用、Flask 應用和 Pyramid 應用?在不單獨修改伺服器來適應這些不同的WEB框架的情況下。”往下看,來找出答案。

過去,你所選擇的一個Python Web框架會限制你選擇可用的Web伺服器,反之亦然。如果框架和伺服器設計的是可以一起工作的,那就很好:

但是,當你試著結合沒有設計成可以一起工作的伺服器和框架時,你可能要面對(可能你已經面對了)下面這種問題:

基本上,你只能用可以在一起工作的部分,而不是你想用的部分。

那麼,怎樣確保在不修改Web伺服器和Web框架下,用你的Web伺服器執行不同的Web框架?答案就是Python Web伺服器閘道器介面(或者縮寫為WSGI,讀作“wizgy”)。

WSGI允許開發者把框架的選擇和伺服器的選擇分開。現在你可以真正地混合、匹配Web伺服器和Web框架了。例如,你可以在Gunicorn或者Nginx/uWSGI或者Waitress上面執行Django,Flask,或Pyramid。真正的混合和匹配喲,感謝WSGI伺服器和框架兩者都支援:

就這樣,WSGI成了我在本系列第一部分和本文開頭重複問的問題的答案。你的Web伺服器必須實現WSGI介面的伺服器端,所有的現代Python Web框架已經實現 了WSGI介面的框架端了,這就讓你可以不用修改伺服器程式碼,適應某個框架。

現在你瞭解了Web伺服器和WEb框架支援的WSGI允許你選擇一對兒合適的(伺服器和框架),它對伺服器和框架的開發者也有益,因為他們可以專注於他們特定的領域,而不是越俎代庖。其他語言也有相似的介面:例如,Java有Servlet API,Ruby有Rack。

一切都還不錯,但我打賭你會說:“秀程式碼給我看!” 好吧,看看這個漂亮且簡約的WSGI伺服器實現:

它明顯比本系列第一部分中的伺服器程式碼大,但為了方便你理解,而不陷入具體細節,它也足夠小了(只有150行不到)。上面的伺服器還做了別的事 – 它可以執行你喜歡的Web框架寫的基本的Web應用,可以是Pyramid,Flask,Django,或者其他的Python WSGI框架。

不信?自己試試看。把上面的程式碼儲存成webserver2.py或者直接從Github上下載。如果你不帶引數地直接執行它,它就會報怨然後退出。

它真的想給Web框架提供服務,從這開始有趣起來。要執行伺服器你唯一需要做的是安裝Python。但是要執行使用Pyramid,Flask,和Django寫的應用,你得先安裝這些框架。一起安裝這三個吧。我比較喜歡使用virtualenv。跟著以下步驟來建立和啟用一個虛擬環境,然後安裝這三個Web框架。

此時你需要建立一個Web應用。我們先拿Pyramid開始吧。儲存以下程式碼到儲存webserver2.py時相同的目錄。命名為pyramidapp.py。或者直接從Github上下載:

現在你已經準備好用完全屬於自己的Web伺服器來執行Pyramid應用了:

剛才你告訴你的伺服器從python模組‘pyramidapp’中載入可呼叫的‘app’,現在你的伺服器準備好了接受請求然後轉發它們給你的Pyramid應用。目前應用只處理一個路由:/hello 路由。在瀏覽器裡輸入http://localhost:8888/hello地址,按Enter鍵,觀察結果:

你也可以在命令列下使用‘curl’工具來測試伺服器:

檢查伺服器和curl輸出了什麼到標準輸出。

現在弄Flask。按照相同的步驟。

儲存以上程式碼為flaskapp.py或者從Github上下載它。然後像這樣執行伺服器:

現在在瀏覽器裡輸入http://localhost:8888/hello然後按回車:

再一次,試試‘curl’,看看伺服器返回了一條Flask應用產生的訊息:

伺服器也能處理Django應用嗎?試試吧!儘管這有點複雜,但我還是推薦克隆整個倉庫,然後使用djangoapp.py,它是GitHub倉庫的一部分。以下的原始碼,簡單地把Django ‘helloworld’ 工程(使用Django的django-admin.py啟動專案預建立的)新增到當前Python路徑,然後匯入了工程的WSGI應用。

把以上程式碼儲存為djangoapp.py,然後用你的Web伺服器執行Django應用:

輸入下面的地址,然後按Enter鍵:

雖然你已經做過兩次啦,你還是可以再在命令列測試一下,確認一下,這次是Django應用處理了請求。

你試了吧?你確定伺服器可以和這三個框架一起工作吧?如果沒試,請試一下。閱讀挺重要,但這個系列是關於重建的,也就是說,你要自己動手。去動手試試吧。別擔心,我等你喲。你必須試下,最好呢,你親自輸入所有的東西,確保它工作起來像你期望的那樣。

很好,你已經體驗到了WSGI的強大:它可以讓你把Web伺服器和Web框架結合起來。WSGI提供了Python Web伺服器和Python Web框架之間的一個最小介面。它非常簡單,在伺服器和框架端都可以輕易實現。下面的程式碼片段展示了(WSGI)介面的伺服器和框架端:

以下是它如何工作的:

  • 1.框架提供一個可呼叫的’應用’(WSGI規格並沒有要求如何實現)
  • 2.伺服器每次接收到HTTP客戶端請求後,執行可呼叫的’應用’。伺服器把一個包含了WSGI/CGI變數的字典和一個可呼叫的’start_response’做為引數給可呼叫的’application’。
  • 3.框架/應用生成HTTP狀態和HTTP響應頭,然後把它們傳給可呼叫的’start_response’,讓伺服器儲存它們。框架/應用也返回一個響應體。
  • 4.伺服器把狀態,響應頭,響應體合併到HTTP響應裡,然後傳給(HTTP)客戶端(這步不是(WSGI)規格里的一部分,但它是後面流程中的一步,為了解釋清楚我加上了這步)

以下是介面的視覺描述: 

目前為止,你已經瞭解了Pyramid,Flask,和Django Web應用,你還了解了實現了WSGI規範伺服器端的伺服器程式碼。你甚至已經知道了不使用任何框架的基本的WSGI應用程式碼片段。

問題就在於,當你使用這些框架中的一個來寫Web應用時,你站在一個比較高的層次,並不直接和WSGI打交道,但我知道你對WSGI介面的框架端好奇,因為你在讀本文。所以,我們們一起寫個極簡的WSGI Web應用/Web框架吧,不用Pyramid,Flask,或者Django,然後用你的伺服器執行它:

再次,儲存以上程式碼到wsgiapp.py檔案,或者直接從GitHub上下載,然後像下面這樣使用你的Web伺服器執行應用:

輸入下面地址,敲回車。你應該就看到下面結果了:

在你學習怎樣寫一個Web伺服器時,你剛剛寫了一個你自己的極簡的WSGI Web框架!棒極啦。

現在,讓我們回頭看看伺服器傳輸了什麼給客戶端。以下就是使用HTTP客戶端呼叫Pyramid應用時生成的HTTP響應: 

這個響應跟你在本系列第一部分看到的有一些相近的部分,但也有一些新東西。例如,你以前沒見過的4個HTTP頭:Content-Type, Content-Length, Date, 和Servedr。這些頭是Web伺服器生成的響應應該有的。雖然他們並不是必須的。頭的目的傳輸HTTP請求/響應的額外資訊。

現在你對WSGI介面瞭解的更多啦,同樣,以下是帶有更多資訊的HTTP響應,這些資訊表示了哪些部件產生的它(響應): 

我還沒有介紹’environ’字典呢,但它基本上就是一個Python字典,必須包含WSGI規範規定的必要的WSGI和CGI變數。伺服器在解析請求後,從HTTP請求拿到了字典的值,字典的內容看起來像下面這樣: 

Web框架使用字典裡的資訊來決定使用哪個檢視,基於指定的路由,請求方法等,從哪裡讀請求體,錯誤寫到哪裡去,如果有的話。

現在你已經建立了你自己的WSGI Web伺服器,使用不同的Web框架寫Web應用。還有,你還順手寫了個簡單的Web應用/Web框架。真是段難忘的旅程。我們們簡要重述下WSGI Web伺服器必須做哪些工作才能處理髮給WSGI應用的請求吧:

  • 首先,伺服器啟動並載入一個由Web框架/應用提供的可呼叫的’application’
  • 然後,伺服器讀取請求
  • 然後,伺服器解析它
  • 然後,伺服器使用請求的資料建立了一個’environ’字典
  • 然後,伺服器使用’environ’字典和’start_response’做為引數呼叫’application’,並拿到返回的響應體。
  • 然後,伺服器使用呼叫’application’返回的資料,由’start_response’設定的狀態和響應頭,來構造HTTP響應。
  • 最終,伺服器把HTTP響應傳回給戶端。 

這就是全部啦。現在你有了一個可工作的WSGI伺服器,它可以處理使用像Django,Flask,Pyramid或者 你自己的WSGI框架這樣的相容WSGI的Web框架寫的基本的Web應用。最優秀的地方是,伺服器可以在不修改程式碼的情況下,使用不同的Web框架。

在你離開之前,還有個問題請你想一下,“該怎麼做才能讓伺服器同一時間處理多個請求呢?”

保持關注,我會在本系列第三部分秀給你看實現它的一種方式。歡呼!

順便說下,我在寫一本書《一起構建WEB伺服器:第一步》,它解釋了從零開始寫一個基本的WEB伺服器,還更詳細地講解了我上面提到的話題。訂閱郵件組來獲取關於書籍和釋出時間和最近更新。

相關文章