什麼是WSGI?什麼是wsgiref?
WSGI(Web Server Common Interface)是專門爲Python語言制定的web服務器與應用程序之間的網關接口規範,通俗的來說,只要一個服務器擁有一個實現了WSGI標準規範的模塊(例如apache的mod_wsgi模塊),那麼任意的實現了WSGI規範的應用程序都能與它進行交互。因此,WSGI也主要分爲兩個程序部分:服務器部分和應用程序部分。
wsgiref則是官方給出的一個實現了WSGI標準用於演示用的簡單Python內置庫,它實現了一個簡單的WSGI Server和WSGI Application(在simple_server模塊中),主要分爲五個模塊:simple_server, util, headers, handlers, validate。
wsgiref源碼地址:https://pypi.python.org/pypi/wsgiref
我們先不急着說明WSGI的詳細標準部分,我們先看一下如何用wsgiref實現一個WSGI Server與WSGI Application交互的例子。
wsgiref簡單實現WSGI Server與WSGI Application
由於simple_server中已經實現了一個簡單的WSGI Server和WSGI Application,我們只要直接調用API就可以了。
from wsgiref.simple_server import make_server, demo_app
application = demo_app
server = make_server("127.0.0.1", 8000, application)
#參數分別爲服務器的IP地址和端口,以及應用程序。
server.handle_request()
#處理一個request之後立即退出程序
默認的demo_app將會返回Hello World以及環境變量。
接下來我們將定義自己的應用程序。
WSGI對於應用程序有以下標準規定:
1. 應用程序必須是一個可調用的對象,因此,應用程序可以是一個函數,一個類,或者一個重載了__call__的類的實例。
2. 應用程序必須接受兩個參數並且要按照位置順序,分別是environ(環境變量),以及start_response函數(負責將響應的status code,headers寫進緩衝區但不返回給客戶端)。
3. 應用程序返回的結果必須是一個可迭代的對象。
遵照以上標準,實現的應用程序代碼爲:
def function_app(environ, start_response):
status = "200 OK"
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Function : My Own Hello World!']
class class_app:
def __init__(self, environ, start_response):
self.env = environ
self.start = start_response
def __iter__(self):
status = "200 OK"
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Class : My Own Hello World!"
#使用yield使應用程序返回一個可迭代對象
class instance_app:
"""
當使用類的實例作爲應用程序吧,application = instance_app(), not instance_app
"""
def __call__(self, environ, start_response):
status = "200 OK"
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ["Instantiate : My Own Hello World!"]
wsgiref.simple_server 中make_server函數剖析
想必各位對僅僅止步於一個簡單的make_server函數的簡單調用並不能滿足,這個WSGI Server是如何實現的呢?所以接下來我們將會對wsgiref中的make_server函數的實現進行源碼分析。
#wsgiref中的make_server實現
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函數默認使用的服務器類爲WSGI Server,調用了構造函數(但是它的構造函數到底藏在哪一層服務器上呢?),相對應的使用WSGIRequestHandler 類作爲請求的處理類(這兩個類都定義在wsgiref.simple_server模塊中),在實例化一個WSGI Server後設置它的application後返回該實例。
WSGI Server類的初始化與繼承關係
WSGI Server作爲一個服務器,自然免不了要調用socket來建立TCP連接,因此這裏的WSGI Server是基於Python的內置網絡庫BaseHTTPServer.py以及SocketServer.py實現的。
給出繼承關係圖。
在上述的圖中我只是把一個WSGI Server的實例化(即調用它的__init__ 函數)才涉及的屬性與方法畫出來了,我們可以看到,WSGI Server的初始化是在TCPServer中初始化的,並且進行了TCP連接,HTTPServer以及WSGI Server都是進行了一些簡單的封裝,還有一些未提及的方法和屬性我將會在對handle_request函數的源碼分析中提及。
wsgiref.simple_server中handle_request函數剖析
從上面對make_server函數的分析,我們現在已經有一個處於監聽狀態的服務器實例了,接着我們要讓服務器具有處理接受請求的能力,這就是handle_request函數的作用了。
從上一張圖中,我們可以知道,handle_request是在BaseServer中實現的。
def handle_request(self):
"""Handle one request, possibly blocking.
Respects self.timeout.
"""
# Support people who used socket.settimeout() to escape
# handle_request before self.timeout was available.
timeout = self.socket.gettimeout()
if timeout is None:
timeout = self.timeout
#self.timeout是BaseServer中的一個類屬性,默認爲None
elif self.timeout is not None:
timeout = min(timeout, self.timeout)
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
#因爲self提供了fileno的接口,所以可以直接作爲參數傳進去,而不必將套接字傳進去,fileno的實現在TCPServer中
if not fd_sets[0]:
self.handle_timeout()
return
self._handle_request_noblock()
handle_request函數主要用於確認客戶端與服務器的socket連接已經建立了起來,並且服務器套接字處於接收狀態,確保調用accept函數能夠接收到客戶端的套接字地址而不是處於阻塞狀態。在剛纔我們建立的WSGI服務器例子中,默認的socket連接都是阻塞的,因此通過gettimeout()得到的timeout值爲None。
接下來我們就將處理EINTR錯誤,這裏用到了IO多路複用技術(select.select)來處理。
def _eintr_retry(func, *args):
"""restart a system call interrupted by EINTR"""
while True:
try:
return func(*args)
except (OSError, select.error) as e:
if e.args[0] != errno.EINTR:
raise
因爲我們把timeout設置爲None,導致select.select永遠不會超時,因此如果一直沒有客戶端連接服務器,服務器就會阻塞在select函數。當一個EINTR錯誤提出時,select可以重複調用。
關於使用select解決EINTR錯誤請參考這裏:PEP 475 – Retry system calls failing with EINTR
通過select函數當我們確認已經收到了來自客戶端的請求連接,此時調用accept函數不會阻塞時,於是調用handle_request_noblock函數,在函數中再依次調用了verify_request, process_request, finish_request。
def _handle_request_noblock(self):
"""Handle one request, without blocking.
I assume that select.select has returned that the socket is
readable before this function was called, so there should be
no risk of blocking in get_request().
"""
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.shutdown_request(request)
get_request其實就是socket.accept(),不過定義在TCPServer中。
def verify_request(self, request, client_address):
"""Verify the request. May be overridden.
Return True if we should proceed with this request.
"""
return True
def process_request(self, request, client_address):
"""Call finish_request.
Overridden by ForkingMixIn and ThreadingMixIn.
"""
self.finish_request(request, client_address)
self.shutdown_request(request)
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)
可以看見,整個handle_request最終以調用finish_request,實例化了RequestHandlerClass作爲結束,給出調用handle_request的流程圖。
RequestHandlerClass的初始化和繼承關係
和WSGI Server的分析一樣,將給出繼承關係的圖。
RequestHandlerClass主要用於處理請求,生成一些必要的環境參數之後才傳給負責發送響應請求的ServerHandler。
ServerHandlerClass的初始化和繼承關係
ServerHandler函數主要功能集中在run函數上,同時start_response函數也定義在同一文件中,start_response函數(在application中調用)也必須要按照PEP-333標準定義。
run函數源碼
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.
最終所有的數據都在finish_response()中寫回給客戶端。finish_response函數調用了write函數,write函數每次調用時都會檢查headers是否已發送,否則先發送headers在發送data。
start_response函數源碼
def start_response(self, status, headers,exc_info=None):
"""'start_response()' callable as specified by PEP 333"""
if exc_info:
try:
if self.headers_sent:
# Re-raise original exception if headers sent
raise exc_info[0], exc_info[1], exc_info[2]
finally:
exc_info = None # avoid dangling circular ref
elif self.headers is not None:
raise AssertionError("Headers already set!")
assert type(status) is StringType,"Status must be a string"
assert len(status)>=4,"Status must be at least 4 characters"
assert int(status[:3]),"Status message must begin w/3-digit code"
assert status[3]==" ", "Status message must have a space after code"
if __debug__:
for name,val in headers:
assert type(name) is StringType,"Header names must be strings"
assert type(val) is StringType,"Header values must be strings"
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
self.status = status
self.headers = self.headers_class(headers)
return self.write
start_response函數主要用於檢測headers是不是已經發送了,如果發送了必須提出異常,同時檢測headers是否有不規範的地方,最後返回一個write函數(用於向套接字相關文件寫入數據,PEP要求)。
至此,整個wsgiref的簡單調用分析完畢。