作者:俞坤
原文:https://www.yukunweb.com/2018/2/understand-python-web/
因為 python
程式碼的優雅美觀且易於維護這一特點,越來越多的人選擇使用 Python
做Web開發。而 Python
的 Web
框架百花齊放,目前比較流行的框架有大包大攬的 Django
,小巧靈活的 Flask
、 Bottle
,還有效能高效的非同步框架 Tornado
、 sanic
。這麼多框架只要選擇一個,閱讀他的檔案,就可以很輕鬆的搭建一個 web app
,完全不需要去管他實現的原理。
本篇文章意在對一個web開發做一個梳理。
前端網頁三劍客
我們開啟瀏覽器輸入一個網址 yukunweb.com
,然後就看到了瀏覽器給我們顯示的頁面,這個時候開啟瀏覽器開發者工具,點選 Network
,掃清頁面,會看到下方的請求的 url
,點選 Response
,就可以看到伺服器傳回給瀏覽器的 html
檔案資訊了。如果複製 Response
響應的內容,儲存為 index.html
並且在瀏覽器開啟,依然可以看到首頁的內容,但是似乎缺少了一些頁面的樣式和功能。
這是因為當瀏覽器接收到首頁的 HTML
原始碼後,它會根據 HTML
的規則去顯示頁面,然後再根據 HTML
裡的連結,自動傳送HTTP請求給伺服器,拿到相應的圖片,和 JavaScript
、 CSS
等資源,最終顯示出一個完整的頁面。所以我們會在 Network
下麵能看到很多額外的以 .js
, .css
等字尾的請求了。
其實我們看到的頁面就是瀏覽器按照 HTML
的規則,展示給我們的。 HTML
告訴瀏覽器那裡是導航,那裡是主欄,那裡是側欄。而這些資訊如何顯示,或者是顯示的樣式,就是 CSS
檔案的功勞。至於比如導航的下拉隱藏上拉顯示就是 JavaScript
的作用。
如果想要做Web開發,就一定得熟悉 HTML
、 CSS
、 JavaScript
三劍客的知識,這裡推薦W3school的前端教程,也是我學習前端的地方:W3school
客戶端和伺服器通訊
理解了前段三劍客,就知道如何去寫一個網頁。那麼從我們在瀏覽器的位址列輸入 URL
,到 Web
頁面呈現出來到底經歷了什麼。
如圖,一般這種透過傳送請求獲取伺服器資源的Web瀏覽器,都可以稱為客戶端(client)。首先傳送一個請求(request)給伺服器,大多是以GET請求方式訪問,伺服器接收到你的請求,然後取到請求的資源,傳回給客戶端。
伺服器和客戶端之間交流是怎麼進行的呢,伺服器是怎麼理解客戶端的請求的呢。這裡就需要一種協議規範,就是HTTP(HyperText Transfer Protocol,超文字傳輸協議)。可以說, Web
是建立在 HTTP
協議上通訊的。
如圖,仍然是之前的例子,開啟瀏覽器訪問 yukunweb.com
,開啟瀏覽器開發者工具,點選圖中標記的選項卡(記得點view parsed),可以看到客戶端發給伺服器的請求頭前兩行。
GET / HTTP/1.1
Host: www.yukunweb.com
第一行開頭的GET表示請求訪問伺服器的型別,稱為方法(method)。隨後的字元 /
指明瞭請求訪問的資源物件,即請求URI。最後的 HTTP/1.1
,即HTTP的版本號,用來提示客戶端使用的 HTTP
協議功能。
綜上所述,第一行請求內容的意思是:請求訪問某臺 HTTP
伺服器上的 /
(首頁)頁面資源。所以第二行的 Host
表示請求的域名也就是伺服器所在地址。
如圖,如果是 POST
請求的話,不僅會有請求頭部資訊,還有一個 Form Data
的請求物體內容。
接收到請求的伺服器呢,他會將請求內容的處理結果以響應的形式傳回,看圖中的第一行:
開頭的部分仍然是伺服器對應的 HTTP
版本,緊接著的 200 OK
表示請求的處理結果的狀態碼 (status code) 和原因短語。 200
狀態碼就表示響應成功,常見的 404
表示訪問錯誤, 500
表示伺服器響應錯誤。這裡的 OK
是沒有固定的規則的,你也可以讓他傳回 GOOD
啥的。
下一行是伺服器資訊,本站用的是 Nginx
伺服器,在下一行顯示了建立響應的日期時間。在下一行的 Content-Type
表示內容的型別,客戶端會依賴他判斷響應的內容是網頁還是音訊,圖片等型別。
這裡只是簡單的介紹了 HTTP
協議,即是客戶端與伺服器之間的通訊協議。如果想要深入瞭解推薦閱讀《HTTP權威指南》。
WSGI
如果你瀏覽一個地址 http://www.yukunweb.com/search-result/?keywords=音樂
,你會訪問到本站的音樂關鍵詞的搜尋結果。我們知道客戶端傳送請求給伺服器,那麼伺服器是怎麼拿到資源的呢。其實這是交給後端執行的應用傳回的,好比你抓取一個頁面到獲取到資訊,這些邏輯的處理肯定是我們的程式再跑。
但是,接收並且解析客戶端的 HTTP
請求在傳送 HTTP
響應這些底層操作,後端的程式肯定是不會去處理的。所以,要想只專註於Web業務邏輯,還需要一個伺服器和 web
應用之間的嫁接層————WSGI。
什麼是WSGI(Web Server Gateway Interface)?
WSGI
翻譯過來就是Web伺服器閘道器介面。他只是一個規範,定義了 Web
伺服器如何與 Python
應用程式進行互動,使得使用 Python
寫的 Web
應用程式可以和Web伺服器(nginx/apache)對接起來。
該規範的地址:PEP 333
WSGI
是 Python
的Web開發的基石,有了它你就有了一切,它存在的目的有兩個:
-
描述 Web 伺服器如何與 Web 應用程式互動(將客戶端請求傳給應用程式),
-
描述 Web 應用程式如何處理請求和如何傳回資料給伺服器。
由於 Python
內建的標準庫裡有一個 WSGI
庫 wsgiref
,我們基於他來寫一個體現 WSGI
目的的例子:
from wsgiref.simple_server import make_server
def application(environ, start_response):
status = '200 OK'
response_essay-headers = [('Content-type', 'text/html')]
start_response(status, response_essay-headers)
body = 'Hello, {name} !!!'.format(name=environ['PATH_INFO'][1:] or 'WSGI')
return [body.encode('utf-8')]
app = make_server('', 8000, application)
app.serve_forever()
執行程式,如果沒有報錯,此時開啟瀏覽器輸入地址 127.0.0.1:8000
和 127.0.0.1:8000/GuTianle
,就可以看到程式傳回的頁面了。如圖:
我們可以看到一個請求,他的入口只需要一個 WSGI
的處理函式。因為所有的請求資訊都包含在 environ
中,這樣我們就可以根據這些資訊去傳回不同的資料。
引數:
-
environ:字典型別,存放了所有和客戶端相關的資訊。如果想知道他裡面有哪些引數,可以更改上面的程式碼在 return 行上面加一個
for k, v in environ.items()
的迴圈,打印出字典裡的所有引數。 -
startresponse:一個可呼叫物件,接收兩個必選引數和一個可選引數:
-
status: 一個字串,表示 HTTP 響應狀態字串,如 200,404
-
responseessay-headers: 一個串列,包含有如下形式的元組:(essay-headername, essay-headervalue),用來表示 HTTP 響應的 essay-headers ,如('Content-type', 'text/html')
-
exc_info(可選): 用於出錯時,伺服器需要傳回給瀏覽器的資訊
傳回:一個可迭代物件, 伺服器透過遍歷這個可迭代物件可以獲得body的全部內容,內容可以是 html
也可以是 json
。
這裡簡單的介紹了 WSGI
是什麼,乾什麼。如果理解了 WSGI
,那麼寫一個 Python
的Web框架就很簡單了。這也是為什麼 Python
有成百上千web框架的原因。
實現基於WSGI的框架
上面我們理解了 WSGI
是乾什麼的,那麼我們基於它實現一個簡單的 web
框架可以說輕而易舉了。
from wsgiref.simple_server import make_server
class Application(object):
def __init__(self, environ, start_response):
self.start_response = start_response
self.path = environ['PATH_INFO']
def __iter__(self):
if self.path == '/':
status = '200 OK'
response_essay-headers = [('Content-type', 'text/html')]
self.start_response(status, esponse_essay-headers)
yield 'Hello,World!'.encode('utf-8')
elif self.path == '/wsgi':
status = '200 OK'
response_essay-headers = [('Content-type', 'text/html')]
self.start_response(status, response_essay-headers)
yield 'Hello,WSGI!'.encode('utf-8')
else:
status = '404 NOT FOUND'
response_essay-headers = [('Content-type', 'text/html')]
self.start_response(status, response_essay-headers)
yield '404 NOT FOUND'.encode('utf-8')
if __name__ == "__main__":
app = make_server('127.0.0.1', 8000, Application)
print('Serving HTTP on port 8000...')
app.serve_forever()
這個 Application
類只不過是對 WSGI
又做了一層簡單的封裝而已,由於上面說過 WSGI
函式傳回的是一個可以迭代物件,所以需要實現一個iter方法,裡面控制了客戶端的請求路由並且傳回不同的輸出。
當然如果你想擴充套件成一個像樣的框架還需要考慮很多,比如像 flask
那樣方便的路由系統,還有對於使用者請求方式的處理等等。總之是個很需要折騰的過程,好比 flask的0.1
版本去掉註釋也就 200 多行,而如今最新版本。。。
●編號428,輸入編號直達本文
●輸入m獲取文章目錄
Web開發
更多推薦《18個技術類微信公眾號》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。