簡單來說,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 server 20 # from wsgiref.handlers import SimpleHandler 21 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 server 41 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] = function 27 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訪問肯定不止這些函數模塊的調用, 這就是主要的調用而已, 而且這已經很好的解釋了我之前的問題了, 好了, 根據圖自己去理解吧
