深入淺出web伺服器與python應用程式之間的聯絡,深入淺出python
簡單來說,Web伺服器是在運行在物理伺服器上的一個程式,它永久地等待用戶端(主要是瀏覽器,比如Chrome,Firefox等)發送請求。Web 服務器接受 Http Request,返回 Response,很多時候 Response 並不是靜態檔案,因此需要有一個應用程式根據 Request 產生相應的 Response。這裡的應用程式主要用來處理相關商務邏輯,讀取或者更新資料庫,根據不同 Request 返回相應的 Response。兩者之間的橋樑就是WSGI。
一直喜歡研究比較底層的技術, 之前就對python web架構web.py的運行機制比較迷惑, 大概學習了下之後發現flask架構以及Django架構都是基於python WSGI協議, python提供了一個簡易的wsgi伺服器實現--wsgiref, 在網站上找了兩個例子運行了一下, 講真, 第一次運行起來就比較懵逼, 儘管知道底層是依賴於socket, 但是深入一點就沒有再研究了, 也看不懂。於是花了幾天, 踏踏實實的看了源碼, 一邊百度一邊理解, 終於學到了很多。有時候覺得自己讓asp.net"慣壞"了, 因為微軟閉源的關係, 自己掌握的基礎知識並不全, 在很多的架構使用上, 僅僅會, 原理說個三三四四的, 還是差了很多, 果然開源就是好, 一言不合攻源碼, 的確是學到了很多, python也是個很強大的語言, 這是我閱讀其源碼最大的感受。
對於本篇文章需要有的基礎知識, 你可以從如下的連結中找到:
從零開始搭建論壇(1):Web伺服器與Web架構
從零開始搭建論壇(2):Web伺服器網關介面
講真, 在沒有讀這兩篇文章之前, 儘管我對web伺服器, web架構有瞭解, 但還是比較模糊, 這兩篇文章寫的很好。伯樂線上也是個不錯的技術網站!
閱讀完這兩篇文章後, 那就有一定的基礎了。先上代碼:
# main.py
1 # coding: utf-8 2 import time 3 from resty import PathDispatcher 4 from wsgiref.simple_server import make_server 5 6 7 _hello_resp = '''\ 8 <html> 9 <head>10 <title>Hello {name}</title>11 </head>12 <body>13 <h1>Hello {name}!</h1>14 </body>15 </html>'''16 17 18 def hello_world(environ, start_response):19 # 將響應狀態和回應標頭交給WSGI server20 # from wsgiref.handlers import SimpleHandler21 start_response('200 OK', [('Content-type', 'text/html')])22 params = environ['params']23 resp = _hello_resp.format(name=params.get('name'))24 yield resp.encode('utf-8')25 26 27 _localtime_resp = '''\28 <?xml version="1.0"?>29 <time>30 <year>{t.tm_year}</year>31 <month>{t.tm_mon}</month>32 <day>{t.tm_mday}</day>33 <hour>{t.tm_hour}</hour>34 <minute>{t.tm_min}</minute>35 <second>{t.tm_sec}</second>36 </time>'''37 38 39 def localtime(environ, start_response):40 # 將響應狀態和回應標頭交給WSGI server41 start_response('200 OK', [('Content-type', 'application/xml')])42 resp = _localtime_resp.format(t=time.localtime)43 yield resp.encode('utf-8')44 45 46 # 類比用戶端47 if __name__ == '__main__':48 dispatcher = PathDispatcher()49 dispatcher.register('GET', '/hello', hello_world)50 dispatcher.register('GET', '/localtime', localtime)51 52 # 啟動一個簡易的伺服器53 httpd = make_server('', 8080, dispatcher)54 print "Serving on port 8080..."55 httpd.serve_forever() # 開啟迴圈機制
# resty.py
1 # coding: utf-8 2 import cgi 3 4 5 def notfound_404(environ, start_response): 6 start_response('404 Not Found', [('Content-type', 'text/plain')]) 7 return ['Not Found'] 8 9 10 # 使用了中介軟體11 class PathDispatcher:12 def __init__(self):13 self.pathmap = {}14 15 # 使此類的對象具有函數的能力, 對象能夠接受傳參, 就像函數調用一樣, 此函數在handlers.py 中調用16 def __call__(self, environ, start_response):17 path = environ['PATH_INFO']18 params = cgi.FieldStorage(environ['wsgi.input'], environ=environ)19 20 method = environ['REQUEST_METHOD'].lower()21 environ['params'] = {key: params.getvalue(key) for key in params} # 字典推導式22 handler = self.pathmap.get((method, path), notfound_404)23 return handler(environ, start_response)24 25 def register(self, method, path, function):26 self.pathmap[method.lower(), path] = function27 return function
然後我分析下python內建的wsgi伺服器主要檔案的作用:
simple_server.py類比了一個簡單的web伺服器, handlers.py是wsgi協議對http協議的封裝處理函數。看吧:
如上所示, 我大概歸納了一下不同py檔案的作用。我之前對WSGI的作用比較模糊, 儘管知道WSGI就是串連web伺服器與web應用程式之間的橋樑, 但是講真!在用戶端瀏覽器敲入換行後, python應用程式的具體執行了哪些重要的函數, 其調用順序又是怎麼來的。而且看著上面的代碼,我問你一個問題:
__call__函數是啥時候調用的?在程式裡面你看見了__call__的調用嗎?儘管__call__函數是一個內建函數(我對其做了注釋), 已經有了定義, 現在又有了實現代碼, 那麼調用程式呢? 原諒作為一個工科生的牛角尖, 看到程式裡面有不明不白的調用實在憋屈-.-
看完下面的, 你應該就懂了
好好把<<從零開始搭建論壇(2):Web伺服器網關介面>>看完, 同時要弄明白__call
__函數的作用。不過, 我先來解釋下吧, 全程debug給你看看:
/////////////編程環境: win7 ////////// IDE: VS code ///////python version: 2.7.13
我分別在main.py, handlers, resty.py下了斷點, 開始debug把....
當偵錯主控台出現 "Serving on port 8080..." 表示此時等待用戶端瀏覽器的訪問了, 下面, 我們在瀏覽器中寫入 http://127.0.0.1:8080/hello?name=Ryan
圖15注意下envron變數的值, 這就是一個dict類型的變數, 可以看到, 我們在瀏覽器中的 "?"後面的key-value都給儲存進來了。傳給了python應用程式。
當然, 要完成一個完整的url訪問肯定不止這些函數模組的調用, 這就是主要的調用而已, 而且這已經很好的解釋了我之前的問題了, 好了, 根據圖自己去理解吧