无论是asgi还是wsgi协议,均已application为调用入口点
1. asgi入口点
def get_asgi_application():
"""
The public interface to Django's ASGI support. Return an ASGI 3 callable.
Avoids making django.core.handlers.ASGIHandler a public API, in case the
internal implementation changes or moves in the future.
"""
django.setup(set_prefix=False)
return ASGIHandler()
2. ASGIHandler实现
请求到达django的过程大致是下面一段代码
class ASGIHandler(base.BaseHandler):
"""Handler for ASGI requests."""
request_class = ASGIRequest
# 切割响应体.
chunk_size = 2 ** 16
def __init__(self):
super().__init__()
# 加载中间件并传入异步参数
self.load_middleware(is_async=True)
async def __call__(self, scope, receive, send):
""" 每次请求到达时将从这里开始被处理
Async 入口点 - 解析请求,然后传递给get_response
"""
# django 目前3.1版本均只处理http协议的请求
# 如果处理websocket等请求可以用channel项目 11月发布版本已支持asgi3协议
# FIXME: 允许覆盖.
if scope['type'] != 'http':
raise ValueError(
'Django can only handle ASGI/HTTP connections, not %s.'
% scope['type']
)
# 以流对象的形式接收HTTP请求体
try:
body_file = await self.read_body(receive)
except RequestAborted:*
* return
# 请求接收完成,可以被处理,设置线程头.
set_script_prefix(self.get_script_prefix(scope))
# 触发信号
# django 目前直到3.1版本信号机制均是同步,所以用sync_to_async包装
await sync_to_async(signals.request_started.send, thread_sensitive=True)(sender=self.__class__, scope=scope)
# 获取请求并检查基本问题.
request, error_response = self.create_request(scope, body_file) # 调用ASGIrequest实例化
if request is None:
await self.send_response(error_response, send)
return
# 获取响应,使用BaseHandler的async模式.
response = await self.get_response_async(request)
response._handler_class = self.__class__
# 增加文件响应的分块大小(ASGI服务器处理低级分块)。.
if isinstance(response, FileResponse):
response.block_size = self.chunk_size
# 发送响应.
await self.send_response(response, send)
......
3. DB连接处理
处理DB的方式在哪呢
这里与WSGIHandler一致,在call中,有对request_started信号的异步触发
await sync_to_async(
signals.request_started.send, thread_sensitive=True
)(sender=self.__class__, scope=scope)
进入request_started信号,我们可以看到
from django.dispatch import Signal
request_started = Signal()
request_finished = Signal()
got_request_exception = Signal()
setting_changed = Signal()
emmmmmmmmm… 原来这四个信号都是信号基类Signal 的实例化,sync_to_async包装中传递的thread_sensitive参数为了线程安全,将该信号与该request请求处理逻辑运行在统一线程
那么在请求结束时,在django.http.response中,我们一定能找到可以找到
class HttpResponseBase:
......
def close(self):
......
# 结束处理发送结束信号
signals.request_finished.send(sender=self._handler_class)
这里的request_started和request_finished信号仅仅实例化,一定有具体实现注册信号的地方,查询搜索Django源码,找到在
我这里算是比较笨的办法了,如果有好的办法请告诉我一下,我如果发现了更好的办法会更新
django/db/init.py注册了两个request_started信号
# 当Django请求启动时,注册一个事件来重置保存的查询.
def reset_queries(**kwargs):
for conn in connections.all():
conn.queries_log.clear()
signals.request_started.connect(reset_queries)
# 注册一个事件来重置事务状态,并关闭超过其生命周期的连接。
def close_old_connections(**kwargs):
for conn in connections.all():
conn.close_if_unusable_or_obsolete()
signals.request_started.connect(close_old_connections)
signals.request_finished.connect(close_old_connections)
django/core/cache/init.py和django/db/init.py注册了request_finished信号
我们先看request_started 信号,从注释可以看出
接下来上Debug大法
我们可以使用uvicorn启动asgi服务器
3.1 调试
从异步信号触发开始

下一步,从send开始

由receiver触发信号
在django/db/__init__.py中,找到信号实现,链接建立初始化

当Django请求启动时,注册一个事件来重置保存的查询,在这里connections.all() 获取所有数据库链接,即在settings中配置的那些,在调试模式下或显示启用时查询日志,并调用clear方法
注意这里的信号,是第一次建立链接时触发,在同一条链接多次复用时,触发下面一个信号

注册一个事件来重置事务状态,并关闭超过其生命周期的连接(不可用或超时的链接)
可以看到这里request_started触发调用了close_old_connections,而close_old_connections里调用了connections(即ConnetionHandler实例)的all方法,其实它返回的是一个列表,里面的元素为DatabaseWrapper类,其实就是循环setting配置的DATABASES,实例化各ENGINE指定的db后端。
3.2 分析
connections 对应的类是ConnectionHandler() ,其主要实现如下:
# django/db/utils.py
class ConnectionHandler:
def __init__(self, databases=None):
"""
databases 是一个可选的数据库定义字典(结构类似 settings.DATABASES)
"""
self._databases = databases
# Connections仍然需要是一个实际的本地线程,因为它是真正的关键型线程。
# 数据库后端应该使用@async_unsafe来保护他们的代码不受异步上下文的影响,
# 但这将给这些上下文提供单独的连接,以防万一也需要。
# 不过在异步上下文之后没有清理,所以如果可以的话,我们不允许这样做。
self._connections = Local(thread_critical=True)
@cached_property
def databases(self): # 2.2
if self._databases is None:
self._databases = settings.DATABASES # 2.2.1 获取一个数据库配置
if self._databases == {}:
self._databases = {
DEFAULT_DB_ALIAS: {
'ENGINE': 'django.db.backends.dummy',
},
}
if DEFAULT_DB_ALIAS not in self._databases: # 判断是否配置了数据库
raise ImproperlyConfigured("You must define a '%s' database." % DEFAULT_DB_ALIAS)
if self._databases[DEFAULT_DB_ALIAS] == {}:
self._databases[DEFAULT_DB_ALIAS]['ENGINE'] = 'django.db.backends.dummy'
return self._databases
def __getitem__(self, alias):
# 2.1 关键点,如果local内有的话直接返回,调用方式 connections['db_name']
if hasattr(self._connections, alias):
return getattr(self._connections, alias)
self.ensure_defaults(alias)
self.prepare_test_settings(alias)
db = self.databases[alias] # 2.2 获取配置文件中db信息
backend = load_backend(db['ENGINE']) # 2.3 加载对应数据库后端
# 2.5 上面load的base.py文件里都有DatabaseWrapper类,这里实例化这个类。
# 它主要负责对应db后端的连接和关闭
conn = backend.DatabaseWrapper(db, alias)
setattr(self._connections, alias, conn) # 2.6 连接放到local里,以接下来的复用
return conn
def __iter__(self): # 3. self.databases即为setting配置文件里的DATABASES
return iter(self.databases)
def all(self): # 1. connections.all()调用这里
# 2. self可迭代,alias即为self.databases。而self[alias]即调用的__getitem__的实现
# self[alias]就是各db实现的后端
return [self[alias] for alias in self]
其中backend = load_backend(db['ENGINE']) 对应方法实现为
def load_backend(backend_name):
"""
返回一个数据库后端的 "基础 "模块,
给定一个完全限定的数据库后端名称,如果它不存在,则引发一个错误.
"""
# This backend was renamed in Django 1.9.
if backend_name == 'django.db.backends.postgresql_psycopg2':
backend_name = 'django.db.backends.postgresql'
try:
# 2.4 加载对应的数据库处理类,实际就是django.db.backends.mysql.base类
return import_module('%s.base' % backend_name)
except ImportError as e_user:
# 找不到数据库后台。
# 显示一个有用的错误信息,列出所有内置的数据库后端。
backend_dir = str(Path(__file__).parent / 'backends')
builtin_backends = [
name for _, name, ispkg in pkgutil.iter_modules([backend_dir])
if ispkg and name not in {'base', 'dummy', 'postgresql_psycopg2'}
]
if backend_name not in ['django.db.backends.%s' % b for b in builtin_backends]:
backend_reprs = map(repr, sorted(builtin_backends))
raise ImproperlyConfigured(
"%r isn't an available database backend.\n"
"Try using 'django.db.backends.XXX', where XXX is one of:\n"
" %s" % (backend_name, ", ".join(backend_reprs))
) from e_user
else:
# 如果有其他错误,这一定是Django的错误。
raise
上面可以看到connections.all()里就是DatabaseWrapper类,每个都继承于BaseDatabaseWrapper,提供了基本的connect函数用于数据库连接,且赋值于属性self.connection(self.connection = self.get_new_connection(conn_params))
再看看DatabaseWrapper 的基类BaseDatabaseWrapper 主要实现,
# django/db/backends/base/base.py
class BaseDatabaseWrapper:
"""表示一个数据库链接."""
# 将Field对象映射到其列类型.
data_types = {}
# 将Field对象映射到其SQL后缀,例如AUTOINCREMENT.
data_types_suffix = {}
# 将字段对象映射到其SQL以进行CHECK约束.
data_type_check_constraints = {}
ops = None
vendor = 'unknown'
display_name = 'unknown'
SchemaEditorClass = None
# 在__init__()中实例化的类.
client_class = None
creation_class = None
features_class = None
introspection_class = None
ops_class = None
validation_class = BaseDatabaseValidation
queries_limit = 9000
......
def get_new_connection(self, conn_params):
"""打开与数据库的链接."""
raise NotImplementedError('subclasses of BaseDatabaseWrapper may require a get_new_connection() method')
......
@async_unsafe
def connect(self):
"""链接到数据库. 假定链接关闭."""
# 检查配置是否有效.
self.check_settings()
# 如果之前的链接是在事务中关闭的
self.in_atomic_block = False
self.savepoint_ids = []
self.needs_rollback = False
# 重置参数定义何时关闭链接
max_age = self.settings_dict['CONN_MAX_AGE']
self.close_at = None if max_age is None else time.monotonic() + max_age
self.closed_in_transaction = False
self.errors_occurred = False
# 建立链接
conn_params = self.get_connection_params()
self.connection = self.get_new_connection(conn_params)
self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
self.init_connection_state()
connection_created.send(sender=self.__class__, connection=self)
self.run_on_commit = []
......
@async_unsafe
def ensure_connection(self): # 主要调这里建立连接
"""确保建立与数据库的连接."""
if self.connection is None:
with self.wrap_database_errors:
self.connect()
......
def _close(self):
if self.connection is not None:
with self.wrap_database_errors:
return self.connection.close()
......
@async_unsafe
def close(self):
"""关闭数据库链接."""
self.validate_thread_sharing()
self.run_on_commit = []
# 不要调用validate_no_atomic_block(),以免在无效状态下难以摆脱连接。
# 反正下一个connect()会重置事务状态。
if self.closed_in_transaction or self.connection is None:
return
try:
self._close()
finally:
if self.in_atomic_block:
self.closed_in_transaction = True
self.needs_rollback = True
else:
self.connection = None
这里通过self.get_new_connection 与对应数据库后端建立连接,以mysql为例,对应的mysql后端中通过MySQLdb建立新的链接
# django/db/backends/mysql/base.py
try:
import MySQLdb as Database
except ImportError as err:
raise ImproperlyConfigured(
'Error loading MySQLdb module.\n'
'Did you install mysqlclient?'
) from err
......
class DatabaseWrapper(BaseDatabaseWrapper):
......
@async_unsafe
def get_new_connection(self, conn_params):
return Database.connect(**conn_params) # 通过MySQLdb新建db连接
顺便一提,django默认使用与mysql创建链接的包是mysqlclient,MySQLdb是mysqlclient的部分了
到此,一个request请求到来后,检查数据库链接配置并实例化DatabaseWrapper类并关闭超时长链接部分已经完成,执行ORM是另一部分,我们再看。
4. ORM链接 —— 执行DB持久化
在此,我们首先要了解的是,QuerySet是惰性的,只有在我们真正需要对ORM操作取出的数据进行操作时,django才会去执行数据库查询,调用数据的情况包括迭代、序列化、与if合用
例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集books
books = BookInfo.objects.all()
继续执行遍历迭代操作后,才真正的进行了数据库的查询
for book in books:
print(book.name)

本文详细解析了Django中ASGI入口点、ASGIHandler的实现,以及请求处理过程。着重分析了请求开始时如何通过request_started信号重置数据库查询和关闭旧连接。Django通过`django.db.utils.ConnectionHandler`管理数据库连接,使用`BaseDatabaseWrapper`类建立与数据库的链接。ORM操作时,QuerySet在实际使用时才会执行数据库查询。
264

被折叠的 条评论
为什么被折叠?



