Django 响应数据response的返回源码详解


Posted in Python onAugust 06, 2019

响应数据的返回

在 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() 返回数据」开始说起, 下面是主要的代码解说:

# 下面的函数都在 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 吧.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python脚本实现网卡流量监控
Feb 14 Python
Python下的常用下载安装工具pip的安装方法
Nov 13 Python
python+django+sql学生信息管理后台开发
Jan 11 Python
python链接oracle数据库以及数据库的增删改查实例
Jan 30 Python
对numpy和pandas中数组的合并和拆分详解
Apr 11 Python
Python爬虫之pandas基本安装与使用方法示例
Aug 08 Python
Python 获取中文字拼音首个字母的方法
Nov 28 Python
Django错误:TypeError at / 'bool' object is not callable解决
Aug 16 Python
python爬取Ajax动态加载网页过程解析
Sep 05 Python
Django多个app urls配置代码实例
Nov 26 Python
python+selenium自动化实战携带cookies模拟登陆微博
Jan 19 Python
python区块链持久化和命令行接口实现简版
May 25 Python
详解Python Matplotlib解决绘图X轴值不按数组排序问题
Aug 05 #Python
Django中提供的6种缓存方式详解
Aug 05 #Python
python修改字典键(key)的方法
Aug 05 #Python
python中使用while循环的实例
Aug 05 #Python
Python3 列表,数组,矩阵的相互转换的方法示例
Aug 05 #Python
Python中print函数简单使用总结
Aug 05 #Python
Numpy数组array和矩阵matrix转换方法
Aug 05 #Python
You might like
无数据库的详细域名查询程序PHP版(3)
2006/10/09 PHP
php 获取当前访问的url文件名的方法小结
2010/02/08 PHP
php过滤所有恶意字符(批量过滤post,get敏感数据)
2014/03/18 PHP
php发送与接收流文件的方法
2015/02/11 PHP
php连接mysql数据库
2017/03/21 PHP
PHP使用Nginx实现反向代理
2017/09/20 PHP
ExtJs使用IFrame的实现代码
2010/03/24 Javascript
基于jquery的高性能td和input切换并可修改内容实现代码
2011/01/09 Javascript
apycom出品的jQuery精美菜单破解方法
2011/02/18 Javascript
在jQuery中 关于json空对象筛选替换
2013/04/15 Javascript
HTML5之lang属性与dir属性的详解
2013/06/19 Javascript
Javascript表格翻页效果的具体实现
2013/10/05 Javascript
在JavaScript中使用NaN值的方法
2015/06/05 Javascript
基于JavaScript实现树形下拉框
2016/08/10 Javascript
js改变html的原有内容实现方法
2016/10/05 Javascript
vue.js 实现图片本地预览 裁剪 压缩 上传功能
2018/03/01 Javascript
vue如何截取字符串
2019/05/06 Javascript
js键盘事件实现人物的行走
2020/01/17 Javascript
解决vue prop传值default属性如何使用,为何不生效的问题
2020/09/21 Javascript
在Python的web框架中编写创建日志的程序的教程
2015/04/30 Python
Python+Opencv识别两张相似图片
2020/03/23 Python
Python编程中NotImplementedError的使用方法
2018/04/21 Python
python RabbitMQ 使用详细介绍(小结)
2018/11/08 Python
详解Python 爬取13个旅游城市,告诉你五一大家最爱去哪玩?
2019/05/07 Python
Python数据结构与算法(几种排序)小结
2019/06/22 Python
Python爬虫爬取电影票房数据及图表展示操作示例
2020/03/27 Python
Html5 localStorage入门教程
2018/04/26 HTML / CSS
EJB的基本架构
2016/09/22 面试题
行政前台岗位职责
2013/12/04 职场文书
小学生成长感言
2014/01/30 职场文书
法制宣传日活动总结
2014/04/29 职场文书
研究生论文答辩开场白
2015/05/27 职场文书
《夸父追日》教学反思
2016/02/20 职场文书
党组织关系的介绍信模板
2019/06/21 职场文书
MySQL批量更新不同表中的数据
2022/05/11 MySQL
MySQL 语句执行顺序举例解析
2022/06/05 MySQL