zoukankan      html  css  js  c++  java
  • Django 源码小剖: 响应数据 response 的返回

    Django 源码小剖: 响应数据 response 的返回

    响应数据的返回

    在 WSGIHandler.__call__(self, environ, start_response) 方法调用了 WSGIHandler.get_response() 方法, 由此得到响应数据对象 response. 如今所要做的, 便是将其返回给客户端. 在 Django 源码小剖: 初探 WSGI 中, 简要的概括了请求到来时 django 自带服务器的执行关系, 摘抄如下:

    • 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 产生响应数据对象后, 会直接返回给客户端.

    代码剖析

    从「调用 ServerHandler.run()-->run() 调用 application() 这才是真正的逻辑.-->run() 中在调用 ServerHandler.finish_response() 返回数据」开始说起, 下面是主要的代码解说:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    # 下面的函数都在 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 实现)是否有新的请求, 不展开了.

    阶段性的总结

    请求到来至数据相应的流程已经走了一遍, 包括 django 内部服务器是如何运作的, 请求到来是如何工作的, 响应数据对象是如何产生的, url 是如何调度的, views.py 中定义的方法是何时调用的, 响应数据是如何返回的...另外还提出了一个更好的 url 调度策略, 如果你有更好的方法, 不忘与大家分享.

    我已经在 github 备份了 Django 源码的注释: Decode-Django, 有兴趣的童鞋 fork 吧.

    捣乱 2013-9-23

    http://daoluan.net

     
     
     
    标签: 源码剖析Django
  • 相关阅读:
    CCF2014123集合竞价(C语言版)
    CCF2016092火车购票
    CCF2013123最大的矩形(C语言版)
    CCF2015122消除类游戏(C语言版)
    CCF2014032窗口(C语言)
    CCF2016093炉石传说(C语言版)
    go module 获取码云私有仓库代码
    centos7 编译安装 redis-6.0.5
    goland2019.2破解方法
    mac下protobuf配置记录
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3336364.html
Copyright © 2011-2022 走看看