過去的這個月,接觸的最多的就是Python的WSGI了,WSGI不是框架不是模塊,僅僅是一個規范協議,定義了一些接口,卻影響着Python網絡開發的方方面面。對於WSGI有這么一段定義:WSGI is the Web Server Gateway Interface. It is a specification for web servers and application servers to communicate with web applications (though it can also be used for more than that).我想我這篇文章不是詳細介紹WSGI內容的,只是想扯扯我對WSGI相關的學習。
誠如那個WSGI的定義所說的,協議定義了一套接口來實現服務器端與應用端通信的規范化(或者說是統一化)。這是怎樣的一套接口呢?很簡單,尤其是對於應用端。
應用端只需要實現一個接受兩個參數的,含有__call__方法的,返回一個可遍歷的含有零個或多個string結果的Python對象(我強調說Python對象,只是想和Java的對象區別開,在Python里一個方法、一個類型……都是對象,Python是真“一切皆對象”,詳見《Python源碼分析》)即可。碼農都知道,傳入參數的名字可以任意取,這里也不例外,但習慣把第一個參數命名為“environ”,第二個為“start_response”。至於這個對象的內容怎樣,應用自由發揮去吧……
服務器端要做的也不復雜,就是對於每一個來訪的請求,調用一次應用端“注冊”的那個協議規定應用端必須要實現的對象,然后返回相應的響應消息。這樣一次服務器端與應用端的通信也就完成了,一次對用戶請求的處理也隨之完成了!當然了,既然協議規定了服務器端在調用的時候要傳遞兩個參數,自然也規定了這兩個參數的一些細節。比如第一個參數其實就是一個字典對象,里面是所有從用戶請求和服務器環境變量中獲取的信息內容,協議當然會定義一些必須有的值,及這些值對應的變量名;第二個參數其實就是一個回調函數,它向應用端傳遞一個用來生成響應內容體的write對象,這個對象也是有__call__方法的。
協議也提到了,還可以設計中間件來連接服務器端與應用端,來實現一些通用的功能,比如session、routing等。
具體怎么應用這個協議呢?Python自帶的wsgiref模塊有個簡單的例子:
- from wsgiref.simple_server import make_server
- def hello_world_app(environ, start_response):
- status = '200 OK' # HTTP Status
- headers = [('Content-type', 'text/plain')] # HTTP Headers
- start_response(status, headers)
- # The returned object is going to be printed
- return ["Hello World"]
- httpd = make_server('', 8000, hello_world_app)
- print "Serving on port 8000..."
- # Serve until process is killed
- httpd.serve_forever()
這個例子更多體現的是應用端的開發方法,很簡單的按照協議實現一個了滿足規范的方法,這樣當瀏覽器向本機8000端口發起一個請求時,就會得到一個“Hello World”的字符串文本響應。這個例子雖然簡單,但非常清楚的說明了應用端與服務器端的接口應用方式。
你可能會想到:現在對該端口的不同地址的請求都是由這個“hello_world_app”函數處理的,你可以實現一個功能,解析一下請求的PATH信息,針對不同的地址轉發給不同的函數或是類來處理;你可能會覺得使用environ和start_response這兩個參數不直觀,你可以像Java的servlet那樣自己封裝成兩個request和response對象來用;你覺得有些常用功能可以提取出來,在具體應用邏輯之外來做……哈哈,那你就已經在思考怎么做中間件或是Web框架了!其實這些也都有人做過了,比如Routes、WebOb、Beaker……當然你大可以自己造自己獨有的輪子,有時候自己做過一遍了才會對現有的成熟的東西有更好的理解,最重要的是在Python的世界里這些都不難做到!
不知你是不是和我一樣,在寫應用的時候或多或少的會想一下服務器端是怎么運作的呢?可能最模糊的流程大家都能想得到:服務器開一個socket等待客戶端連接;請求來了,服務器會讀出傳來的數據,然后根據HTTP協議做一些初步的封裝,接着就可以調用事先注冊的應用程序了,並將請求的數據塞進去;等響應處理完畢了再把數據通過socket發出去,over。好在Python的代碼簡潔,而自帶的wsgiref中的simple server也很簡單,就讓我們探究一下更具體的實現吧!
首先看一下類的繼承關系,這個simple server真正的類是WSGIServer,繼承自HTTPServer,HTTPServer類又繼承自TCPServer,TCPServer又繼承自BaseServer;與server類直接打交道的還有RequestHandler類,從最上層的WSGIRequestHandler —> BaseHTTPRequestHandler —> StreamRequestHandler —> BaseRequestHandler。相對Java而言不是很復雜吧,它們是怎么工作的呢?容我稍微解釋一下。
讓我們從Server的最基類BaseServer看起。它有一段注釋非常清楚的介紹了它定義的方法的用處:
- Methods for the caller:
- - __init__(server_address, RequestHandlerClass)
- - serve_forever()
- - handle_request() # if you do not use serve_forever()
- - fileno() -> int # for select()
- Methods that may be overridden:
- - server_bind()
- - server_activate()
- - get_request() -> request, client_address
- - verify_request(request, client_address)
- - server_close()
- - process_request(request, client_address)
- - close_request(request)
- - handle_error()
- Methods for derived classes:
- - finish_request(request, client_address)
可見,一個server類其實就這么幾個方法。
在可以被外部調用的四個方法中,構造方法顯然就是用來創建實例的;第四個可能是和構建異步服務器有關的,這里就略過了;從具體的代碼可以看到,剩下兩個方法的用處是相同的,就是處理收到的請求,只是serve_forever()方法會在server進程存在期間循環處理,而handle_request()處理一次就退出了(其實server_forever()就是循環調用了handle_request())。在handle_request()中說明了具體的從接受到返回一個請求的全部流程,代碼也很簡單:
- def handle_request(self):
- """Handle one request, possibly blocking."""
- try:
- request, client_address = self.get_request()
- except socket.error:
- return
- if self.verify_request(request, client_address):
- try:
- self.process_request(request, client_address)
- except:
- self.handle_error(request, client_address)
- self.close_request(request)
BaseServer雖然定義了這些內部調用的方法,但內容基本都是空的,留給了具體的Server類去實現。從BaseServer的代碼中就可以看到RequestHandler類的用處了,它是具體的解析了request的內容,它由finish_request()調用,而這個finsh_request()方法顯然應該是在process_request()方法中被調用的。
TCPServer繼承BaseServer類,它真正具體化了我們猜測的socket連接的初始化過程。
在與上面兩個類相同的源文件中,還有兩個主要的類:ThreadingMixIn和ForkingMixIn,這兩個類分別重載了process_request()方法,並且相應使用了新建一個線程或是進程的方式來調用finish_request()方法。這也從應用的角度解釋了為什么要在finish_request()外套一層process_request(),而不是直接在handle_request()的第二個try塊中調用。
HTTPServer其實做的工作很簡單,就是記錄了socket server的名字。
接下來就該看看WSGIServer了。它做了兩件新的工作:設置了一些基本的環境變量值,並且接受應用程序的注冊。從這個Server的代碼可以看出,應用端實現的那個接口就是從這里注冊到服務器端的,而且只能注冊一個哦!所以要有多個應用只能通過routing的方式來轉發調用了。而且這個WSGIServer不是多線程或是多進程的~
至於具體封裝請求內容的RequestHandler類就不打算分析了,感興趣的話,看官們自個看一下源碼吧,也很簡單哦!下一篇博客打算分享一下我對pylons框架的運行過程的學習。