引子
在前幾篇文章:SocketServer——網路通訊伺服器、BaseHTTPServer——實現Web伺服器、SimpleHTTPServer——一個簡單的HTTP伺服器,我們介紹了Python標準庫對於網路通訊的支援,並且還介紹了標準庫中的一些模組,例如TCPServer、UDPServer、BaseHTTPServer等。這些模組能夠實現基礎的網路通訊服務,例如TCP/UDP層的通訊、HTTP應用層的通訊。
上述模組對於網路通訊的實現,基本的流程是:
- 建立一個伺服器。例如TCP伺服器、UDP伺服器、HTTP伺服器。伺服器可以監聽和接收請求;
- 建立請求處理程式。請求處理程式可以解析到達的請求,併發回一個響應。
以上流程基本上反映了Python標準庫中關於網路通訊的基本過程。伺服器類和請求處理類的解耦,意味著很多應用可以使用某個現有的伺服器類,而不需要其他任何的修改,只需要提供一個可以處理這些應用的請求處理類即可。但隨著近些年網路程式設計越來越複雜,對於伺服器網路通訊提出了較大的挑戰。以Web程式設計為例,挑戰主要在於:
- 伺服器不再僅僅提供簡單的、靜態的HTML頁面,更多的是要與豐富的Web應用進行相互通訊。如果將請求處理程式的構建放在伺服器端實現,那對於每個Web應用構建一個請求處理程式顯然不現實;
- 如果將請求處理程式的構建放在開發Web應用的過程中,那無疑增加了Web應用程式開發的難度和複雜度,也是不太理想的。
為了解決這些問題,常用的做法是提供一箇中間層,通常稱為閘道器介面。閘道器介面在伺服器和應用中間承擔一個“翻譯官”的角色。只要應用程式符合閘道器介面的標準,那麼伺服器就只要做好伺服器的角色,應用程式只要做好應用程式的作用,伺服器和應用程式之間的通訊全靠閘道器介面來協調。常用的閘道器介面有CGI、WSGI,本文就以WSGI閘道器介面來對此進行說明。
WSGI閘道器介面
WSGI (Python Web Server Gateway Interface, Python Web伺服器閘道器介面)是一個Web伺服器和Web應用程式之間的標準化介面,用於增進應用程式在不同的Web伺服器和框架之間的可移植性。關於該標準的官方說明可以參考PEP333。
WSGI的主要作用是在Web伺服器和Web應用程式承擔“翻譯官”的角色。對於這一角色可以這樣理解:
- Web伺服器的責任在於監聽和接收請求。在處理請求的時候呼叫WSGI提供的標準化介面,將請求的資訊轉給WSGI;
- WSGI的責任在於“中轉”請求和響應資訊。WSGI接收到Web伺服器提供的請求資訊後可以做一些處理,之後通過標準化介面呼叫Web應用,並將請求資訊傳遞給Web應用。同時,WSGI還將會處理Web應用返回的響應資訊,並通過伺服器返回給客戶端;
- Web應用的責任在於接收請求資訊,並且生成響應。
根據以上分析,要實現符合WSGI標準的Web服務,伺服器和應用程式的設計就要符合WSGI規範。
WSGI規範
WSGI規範如下:
- 伺服器的請求處理程式中要呼叫符合WSGI規範的閘道器介面;
- 閘道器介面呼叫應用程式,並且要定義start_response(status, headers)函式,用於返回響應;
- 應用程式中實現一個函式或者一個可呼叫物件webapp(environ, start_response)。其中environ是環境設定的字典,由伺服器和WSGI閘道器介面設定,start_response是由閘道器介面定義的函式。
在Python標準庫中,wsgiref包就是符合WSGI標準的Web服務實現。後面簡單對wsgiref包進行介紹,以此來對符合WSGI標準的Web服務的實現過程進行梳理。
wsgiref包
wsgiref包為實現WSGI標準提供了一個參考,它可以作為獨立的伺服器測試和除錯應用程式。在實際的生產環境中儘量不要使用。wsgiref包含有以下模組:
- simple_server模組 ——simple_server模組實現了可以執行單個WSGI應用的簡單的HTTP伺服器。
- headers模組 ——管理響應首部的模組。
- handlers模組 ——符合WSGI標準的Web服務閘道器介面實現。該模組包含了一些處理程式物件,用來設定WSGI執行環境,以便應用程式能夠在其他的Web伺服器中執行。
- validate模組 ——“驗證包裝”模組,確保應用程式和伺服器都能夠按照WSGI標準進行操作。
- util模組 ——一些有用的工具集。
以上模組暫時不做詳細的介紹。本文剩餘內容將simple_server模組單獨拿出來,以其中的測試例子簡單說明符合WSGI標準的Web伺服器的實現過程。
simple_server——一個簡單的符合WSGI規範的伺服器
wsgiref包的simple_server模組實現了一個符合WSGI規範的伺服器。測試程式碼如下:
1 2 3 4 5 6 7 8 |
if __name__ == '__main__': httpd = make_server('', 8000, demo_app) sa = httpd.socket.getsockname() print "Serving HTTP on", sa[0], "port", sa[1], "..." import webbrowser webbrowser.open('http://localhost:8000/xyz?abc') httpd.handle_request() # serve one request, then exit httpd.server_close() |
1. 建立HTTP伺服器
上述測試程式碼中httpd = make_server(”, 8000, demo_app)建立了一個HTTP伺服器。
其中make_server函式用來建立伺服器:
1 2 3 4 5 6 7 |
def make_server( host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler ): """Create a new WSGI server listening on `host` and `port` for `app`""" server = server_class((host, port), handler_class) server.set_app(app) return server |
make_server函式使用WSGIServer類構建符合WSGI規範的HTTP伺服器,使用WSGIRequestHandler類作為處理請求的類,使用demo_app作為一個Web應用。該函式返回一個伺服器例項,並開始監聽請求。可以通過httpd.socket.getsockname()獲取伺服器地址和埠號。
2. 使用webbrowser模組建立請求
緊接著,測試例子匯入webbrowser模組,使用函式建立了一個請求。
1 |
webbrowser.open('http://localhost:8000/xyz?abc') |
3. 伺服器處理請求
伺服器通過handle_request()方法處理請求。關於處理請求的過程簡單介紹如下:
- handle_request()方法通過呼叫get_request、verify_request、process_request、finish_request等方法建立一個請求處理例項(該過程可以參考TCPServer、HTTPServer的實現過程);
- 請求處理例項呼叫handle()方法處理請求。handle()在WSGIRequestHandler類中進行了重寫。程式碼如下:
12345678910111213141516def handle(self):"""Handle a single HTTP request"""self.raw_requestline = self.rfile.readline(65537)if len(self.raw_requestline) > 65536:self.requestline = ''self.request_version = ''self.command = ''self.send_error(414)returnif not self.parse_request(): # An error code has been sent, just exitreturnhandler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())handler.request_handler = self # backpointer for logginghandler.run(self.server.get_app())
上面handle()函式先解析了請求,之後建立了一個WSGI閘道器類例項handler,這個例項可以作為伺服器和應用程式之間的介面存在。
4. WSGI閘道器的請求處理過程
WSGI閘道器的定義在handlers模組。上一步驟中通過呼叫WSGI閘道器類例項handler的run方法,WSGI閘道器開始處理請求。
run方法的程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def run(self, application): """Invoke the application""" # Note to self: don't move the close()! Asynchronous servers shouldn't # call close() from finish_response(), so if you close() anywhere but # the double-error branch here, you'll break asynchronous servers by # prematurely closing. Async servers must return from 'run()' without # closing if there might still be output to iterate over. try: self.setup_environ() self.result = application(self.environ, self.start_response) self.finish_response() except: try: self.handle_error() except: # If we get an error handling an error, just give up already! self.close() raise # ...and let the actual server figure it out. |
run方法的主要功能有:
- 通過setup_environ()方法建立WSGI相關的環境;
- 呼叫WSGI應用的函式或者WSGI應用的可呼叫物件。本測試例子中的WSGI應用是一個簡單的函式,其作用是將請求的environ資訊列印出來。
- 呼叫finish_response()方法將WSGI應用返回的資料作為響應發回。
5. 關閉伺服器
請求結束後,伺服器會呼叫一系列函式關閉請求連線。之後測試程式碼呼叫server_close()方法關閉伺服器。