Django 源碼小剖: 響應數據 response 的返回

響應數據的返回

在 WSGIHandler.__call__(self, environ, start_response) 方法調用了 WSGIHandler.get_response() 方法, 由此獲得響應數據對象 response. 現在所要作的, 即是將其返回給客戶端. 在 Django 源碼小剖: 初探 WSGI 中, 簡要的歸納了請求到來時 django 自帶服務器的執行關係, 摘抄以下:python

  • make_server() 中 WSGIServer 類已經做爲服務器類, 負責接收請求, 調用 application 的處理, 返回相應;
  • WSGIRequestHandler 做爲請求處理類, 並已經配置在 WSGIServer 中;
  • 接着還設置了 WSGIServer.application 屬性(set_app(app));
  • 返回 server 實例.
  • 接着打開瀏覽器, 即發起請求. 服務器實例 WSGIServer httpd 調用自身 handle_request() 函數處理請求. handle_request() 的工做流程以下:請求-->WSGIServer 收到-->調用 WSGIServer.handle_request()-->調用 _handle_request_noblock()-->調用 process_request()-->調用 finish_request()-->finish_request() 中實例化 WSGIRequestHandler-->實例化過程當中會調用 handle()-->handle() 中實例化 ServerHandler-->調用 ServerHandler.run()-->run() 調用 application() 這纔是真正的邏輯.-->run() 中在調用 ServerHandler.finish_response() 返回數據-->回到 process_request() 中調用 WSGIServer.shutdown_request() 關閉請求(其實什麼也沒作)

事實上, WSGIServer 並無負責將響應數據返回給客戶端, 它將客戶端的信息(如最重要的客戶端 socket 套接字)交接給了 WSGIRequestHandler, WSGIRequestHandler 又將客戶端的信息交接給了 ServerHandler, 因此 ServerHandler 產生響應數據對象後, 會直接返回給客戶端.git

代碼剖析

從「調用 ServerHandler.run()-->run() 調用 application() 這纔是真正的邏輯.-->run() 中在調用 ServerHandler.finish_response() 返回數據」開始提及, 下面是主要的代碼解說:github

# 下面的函數都在 ServerHandler 的繼承鏈上方法, 有些方法父類只定義了空方法, 具體邏輯交由子類實現. 有關繼承鏈請參看: http://daoluan.net/blog/decode-django-wsgi/
def run(self, application):
    """Invoke the application"""
    try:
        self.setup_environ()
        # application 在 django 中就是 WSGIHandler 類, 他實現了 __call__ 方法, 因此行爲和函數同樣.
        self.result = application(self.environ, self.start_response)
        self.finish_response()
    except:
        # handle error

def finish_response(self):
    try:
        if not self.result_is_file() or not self.sendfile():
            for data in self.result:
                # 向套接字寫數據, 將數據返回給客戶端
                self.write(data)
            self.finish_content()
    finally:
        self.close()

def write(self, data):
    """'write()' callable as specified by PEP 333"""

    # 必須是都是字符
    assert type(data) is StringType,"write() argument must be string"

    if not self.status:
        raise AssertionError("write() before start_response()")

    # 須要先發送 HTTP 頭
    elif not self.headers_sent:
        # Before the first output, send the stored headers
        self.bytes_sent = len(data)    # make sure we know content-length
        self.send_headers()
    # 再發送實體
    else:
        self.bytes_sent += len(data)

    # XXX check Content-Length and truncate if too many bytes written?
    self._write(data)
    self._flush()

def write(self, data):
    """'write()' callable as specified by PEP 3333"""

    assert isinstance(data, bytes), "write() argument must be bytestring"

    # 必須先調用 self.start_response() 設置狀態碼
    if not self.status:
        raise AssertionError("write() before start_response()")

    # 須要先發送 HTTP 頭
    elif not self.headers_sent:
        # Before the first output, send the stored headers
        self.bytes_sent = len(data)    # make sure we know content-length
        self.send_headers()
    # 再發送實體
    else:
        self.bytes_sent += len(data)

    # XXX check Content-Length and truncate if too many bytes written? 是否須要分段發送過大的數據?

    # If data is too large, socket will choke, 窒息死掉 so write chunks no larger
    # than 32MB at a time.

    # 分片發送
    length = len(data)
    if length > 33554432:
        offset = 0
        while offset < length:
            chunk_size = min(33554432, length)
            self._write(data[offset:offset+chunk_size])
            self._flush()
            offset += chunk_size
    else:
        self._write(data)
        self._flush()

def _write(self,data):
    # 若是是第一次調用, 則調用 stdout.write(), 理解爲一個套接字對象
    self.stdout.write(data)

    # 第二次調用就是直接調用 stdout.write() 了
    self._write = self.stdout.write

接下來的事情, 就是回到 WSGIServer 關閉套接字, 清理現場, web 應用程序由此結束; 但服務器依舊在監聽(WSGIServer 用 select 實現)是否有新的請求, 不展開了.web

階段性的總結

請求到來至數據相應的流程已經走了一遍, 包括 django 內部服務器是如何運做的, 請求到來是如何工做的, 響應數據對象是如何產生的, url 是如何調度的, views.py 中定義的方法是什麼時候調用的, 響應數據是如何返回的...另外還提出了一個更好的 url 調度策略, 若是你有更好的方法, 不忘與你們分享.django

我已經在 github 備份了 Django 源碼的註釋: Decode-Django, 有興趣的童鞋 fork 吧.瀏覽器

搗亂 2013-9-23服務器

http://daoluan.netapp

相關文章
相關標籤/搜索