WSGI初探及wsgiref簡單實現

什麼是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

在上述的圖中我只是把一個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的流程圖。

Created with Raphaël 2.1.0handle_requesthandle_request_noblockverify_request process_requestfinish_requestRequestHandlerClassyes

RequestHandlerClass的初始化和繼承關係

和WSGI Server的分析一樣,將給出繼承關係的圖。
RequestHandlerClass
RequestHandlerClass主要用於處理請求,生成一些必要的環境參數之後才傳給負責發送響應請求的ServerHandler。

ServerHandlerClass的初始化和繼承關係

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的簡單調用分析完畢。

發佈了51 篇原創文章 · 獲贊 22 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章