django源码分析

本文环境python3.5.2,django1.10.x系列1.在上一篇文章中已经分析过handler的处理过程,其中load_middleware就是将配置的中间件进行初始化,然后调用相应的设置方法。
django框架提供的认证,回话保持,静态文件调试处理等都是通过以中间件的形式来处理。
2.本节就分析一下django框架提供的staticfiles中间件,该中间件分别实现了三个框架的命令,分别为collectstatic,findstatic,runserver。
其中,runserver方法是使用框架的开发者在本地调试使用的方法,使用该方式替换django.core中的runserver是为了使开发时,Django框架能够在本地调试时处理静态文件,这样更有利于提升本地开发的效率。
3.下面就一起分析一下该runserver的执行过程。

分析

1.该代码位于django/contrib/staticfiles/目录下,首先来看management/commands/runserver.py
class Command(RunserverCommand):                                                                # 继承自核心包的runserverhelp = "Starts a lightweight Web server for development and also serves static files."def add_arguments(self, parser):super(Command, self).add_arguments(parser)parser.add_argument('--nostatic', action="store_false", dest='use_static_handler', default=True,help='Tells Django to NOT automatically serve static files at STATIC_URL.',)                                                                                       # 新增是否使用默认静态文件处理handlerparser.add_argument('--insecure', action="store_true", dest='insecure_serving', default=False,help='Allows serving static files even if DEBUG is False.',)                                                                                       # 是否使用server处理静态文件,这样调试的环境可以访问静态文件def get_handler(self, *args, **options):"""Returns the static files serving handler wrapping the default handler,if static files should be served. Otherwise just returns the defaulthandler."""handler = super(Command, self).get_handler(*args, **options)                            # 获取core中的runserver处理对象use_static_handler = options['use_static_handler']                                      # 是否使用静态handler处理,默认使用insecure_serving = options['insecure_serving']                                          # 是否使用静态handler处理静态文件,默认不使用if use_static_handler and (settings.DEBUG or insecure_serving):                         # 如果使用静态handler,并且在调试或者设置使用静态handler处理则使用静态handlerreturn StaticFilesHandler(handler)return handler

首先该runserver是为了实现在处理接口的同时,处理静态文件,所以Command继承了core核心中的RunserverCommand类,这样只需要在已有的基础上,改写是该类处理静态文件即可。
该类又增加了两个参数,nostatic表示不自动处理静态文件,insecure表示就算不是调试模式Django框架也要处理静态文件。
当调用get_handler的时候,先判断是否配置了自动处理静态文件,或者是否开启了insecure模式,如果自动处理静态文件,并且调试为true或者开启了自动处理静态文件,就StaticFilesHandler(handler)处理返回。
我们分析一下StaticFilesHandler(handler)
该类位于staticfiles/handlers.py中

from django.conf import settings
from django.contrib.staticfiles import utils
from django.contrib.staticfiles.views import serve
from django.core.handlers.wsgi import WSGIHandler, get_path_info
from django.utils.six.moves.urllib.parse import urlparse
from django.utils.six.moves.urllib.request import url2pathnameclass StaticFilesHandler(WSGIHandler):                                          # 继承自wsgi"""WSGI middleware that intercepts calls to the static files directory, asdefined by the STATIC_URL setting, and serves those files."""# May be used to differentiate between handler types (e.g. in a# request_finished signal)handles_files = Truedef __init__(self, application):self.application = application                                          # 传入处理handlerself.base_url = urlparse(self.get_base_url())                           # 解析配置的静态文件路径super(StaticFilesHandler, self).__init__()def get_base_url(self):utils.check_settings()                                                  # 检查静态文件相关配置是否正确return settings.STATIC_URL                                              # 返回配置中的静态文件def _should_handle(self, path):"""Checks if the path should be handled. Ignores the path if:* the host is provided as part of the base_url* the request's path isn't under the media path (or equal)"""return path.startswith(self.base_url[2]) and not self.base_url[1]       # 路径是否以静态路径开头,并且配置文件没有给出静态文件的Hostdef file_path(self, url):"""Returns the relative path to the media file on disk for the given URL."""relative_url = url[len(self.base_url[2]):]                              # 获取文件的相对路径return url2pathname(relative_url)def serve(self, request):"""Actually serves the request path."""return serve(request, self.file_path(request.path), insecure=True)      # 启动server处理静态文件def get_response(self, request):from django.http import Http404if self._should_handle(request.path):                                       # 如果是静态文件路径则使用server处理try:return self.serve(request)except Http404 as e:if settings.DEBUG:from django.views import debugreturn debug.technical_404_response(request, e)return super(StaticFilesHandler, self).get_response(request)def __call__(self, environ, start_response):if not self._should_handle(get_path_info(environ)):                         # 先判断请求url是否是静态文件路径return self.application(environ, start_response)                        # 如果不是静态文件路径,则正常处理return super(StaticFilesHandler, self).__call__(environ, start_response)    # 如果是静态文件路径则调用父方法处理
该类继承自WSGIHandler,此时当调用该handler的call方法时,会调用该类的__call__,会先获取environ中的请求路径,判断该url是否是配置文件中静态文件路径开头。
如果是静态文件路径开头则使用传入的handler直接处理不执行一下步骤,如果是静态文件路径则调用该了的父类的处理方法。
只不过处理过程调用该类的get_response方法,该方法的主要作用是:
    def get_response(self, request):from django.http import Http404if self._should_handle(request.path):                                       # 如果是静态文件路径则使用server处理try:return self.serve(request)except Http404 as e:if settings.DEBUG:from django.views import debugreturn debug.technical_404_response(request, e)return super(StaticFilesHandler, self).get_response(request)

如果给url是静态文件路径则调用self.server方法处理,否则调用父类正常的get_response方法。
当调用self.server方法时,就使用了导入的django.contrib.staticfiles.views中的server方法处理。
分析该server的内容如下:

def serve(request, path, insecure=False, **kwargs):"""Serve static files below a given point in the directory structure orfrom locations inferred from the staticfiles finders.To use, put a URL pattern such as::from django.contrib.staticfiles import viewsurl(r'^(?P<path>.*)$', views.serve)in your URLconf.It uses the django.views.static.serve() view to serve the found files."""if not settings.DEBUG and not insecure:                                     # 再次检查配置是否为调试模式,是否设置框架处理静态文件raise Http404normalized_path = posixpath.normpath(unquote(path)).lstrip('/')             # 解析url并分解出路径,并去除最左边的/absolute_path = finders.find(normalized_path)                               # 查找静态文件,如果查找到文件就返回文件的绝对地址if not absolute_path:if path.endswith('/') or path == '':raise Http404("Directory indexes are not allowed here.")raise Http404("'%s' could not be found" % path)document_root, path = os.path.split(absolute_path)                          # 返回匹配上的文件夹,与文件    return static.serve(request, path, document_root=document_root, **kwargs)   # 处理该静态文件的response
再次检查是否是处理静态文件,如果不是则直接报错404,否则调用finders去查找该静态文件,我们继续查看finders.find方法。
当找到该静态文件时候,调用static.server处理该静态文件。
def find(path, all=False):"""Find a static file with the given path using all enabled finders.If ``all`` is ``False`` (default), return the first matchingabsolute path (or ``None`` if no match). Otherwise return a list."""searched_locations[:] = []                                          matches = []for finder in get_finders():                                        # 获取配置的finder类result = finder.find(path, all=all)                             # 通过finder来查找静态文件if not all and result:                                          # 如果不是全部查找,找到对应文件就返回数据return resultif not isinstance(result, (list, tuple)):                       # 如果是全部查找,而result不是列表或元组,则手动转换result = [result]matches.extend(result)                                          # 将查找到的结果,加入到列表中if matches:                                                         # 如果有结果就返回return matches# No match.return [] if all else None                                          # 如果全局查找就返回空列表,否则返回None
get_finders()该函数返回finder对象,我们查看该方法
def get_finders():for finder_path in settings.STATICFILES_FINDERS:                    # 获取配置文件中的查找文件对象,默认配置在conf/global_settings.py中 yield get_finder(finder_path)                                   # 获取finder对象   django.contrib.staticfiles.finders.FileSystemFinder,django.contrib.staticfiles.finders.AppDirectoriesFinder@lru_cache.lru_cache(maxsize=None)
def get_finder(import_path):"""Imports the staticfiles finder class described by import_path, whereimport_path is the full Python path to the class."""Finder = import_string(import_path)                                         # 通过配置的路径,导入finderif not issubclass(Finder, BaseFinder):                                      # 检查导入Finder是否是BaseFinder子类raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %(Finder, BaseFinder))return Finder()                                                             # 返回finder实例
如果配置文件中没有配置,则使用conf/global_settings.py中的配置文件,配置的两个类就位于该FileSystemFinder和AppDirectoriesFinder两个类。
FileSystemFinder主要是查找文件系统的静态文件,AppDirectoriesFinder主要是查找位于app应用中的静态文件。
其中FileSystemFinder分析如下:
class FileSystemFinder(BaseFinder):"""A static files finder that uses the ``STATICFILES_DIRS`` settingto locate files."""def __init__(self, app_names=None, *args, **kwargs):# List of locations with static filesself.locations = []# Maps dir paths to an appropriate storage instanceself.storages = OrderedDict()if not isinstance(settings.STATICFILES_DIRS, (list, tuple)):            # 检查配置文件中的静态文件处理是否是列表或者元组raise ImproperlyConfigured("Your STATICFILES_DIRS setting is not a tuple or list; ""perhaps you forgot a trailing comma?")for root in settings.STATICFILES_DIRS:                                  # 获取配置的文件路径if isinstance(root, (list, tuple)):                                 # 如果配置的静态路径是列表或者元组prefix, root = root                                             # 获取前缀与路径else: prefix = ''                                                     # 如果不是列表或元组则为''if settings.STATIC_ROOT and os.path.abspath(settings.STATIC_ROOT) == os.path.abspath(root):   # 判断静态文件static是否与media重合raise ImproperlyConfigured("The STATICFILES_DIRS setting should ""not contain the STATIC_ROOT setting")if (prefix, root) not in self.locations:                            # 如果解析出来的前缀与路径不再loactions中则添加进去self.locations.append((prefix, root))for prefix, root in self.locations:                                     # 遍历locationsfilesystem_storage = FileSystemStorage(location=root)               # 给每个值生成一个FileSystemStorage实例filesystem_storage.prefix = prefixself.storages[root] = filesystem_storage                            # 将生成实例保存进字典中super(FileSystemFinder, self).__init__(*args, **kwargs)def find(self, path, all=False):"""Looks for files in the extra locationsas defined in ``STATICFILES_DIRS``."""matches = []for prefix, root in self.locations:                                     # 根据locations的值匹配if root not in searched_locations:                                  # 如果root不再全局搜索路径中,则添加到搜索路径中searched_locations.append(root)matched_path = self.find_location(root, path, prefix)               # 查找文件if matched_path:                                                    # 如果找到if not all:                                                     # 如果不是查找全部则找到第一个就返回return matched_pathmatches.append(matched_path)                                    # 如果查找全部则添加到返回数组中return matchesdef find_location(self, root, path, prefix=None):"""Finds a requested static file in a location, returning the foundabsolute path (or ``None`` if no match)."""if prefix:                                                              # 是否有前缀prefix = '%s%s' % (prefix, os.sep)                                  # 添加前缀加系统的分隔符, '/'if not path.startswith(prefix):                                     # 如果路径不是前缀开头则直接返回return None     path = path[len(prefix):]                                           # 获取除去前缀的路径path = safe_join(root, path)                                            # 获取最终的文件路径if os.path.exists(path):return pathdef list(self, ignore_patterns):"""List all files in all locations."""for prefix, root in self.locations:                                     # 获取所有文件的设置的静态文件处理storage = self.storages[root]for path in utils.get_files(storage, ignore_patterns):yield path, storage
主要是查找配置的静态文件查找目录,匹配当前是否找到文件。
AppDirectoriesFinder主要是查找配置在app中的静态文件。
class AppDirectoriesFinder(BaseFinder):"""A static files finder that looks in the directory of each app asspecified in the source_dir attribute."""storage_class = FileSystemStoragesource_dir = 'static'                                                       # 源文件夹def __init__(self, app_names=None, *args, **kwargs):# The list of apps that are handledself.apps = []                                                          # 需要查找文件的应用# Mapping of app names to storage instancesself.storages = OrderedDict()                                           # 存储需要查找的应用app_configs = apps.get_app_configs()                                    # 获取所有app的配置if app_names:                                                           # 如果有传入值,app_names = set(app_names)app_configs = [ac for ac in app_configs if ac.name in app_names]    # 将app_configs设置为在默认配置中的项目for app_config in app_configs:                                          # 遍历筛选出来的应用配置app_storage = self.storage_class(os.path.join(app_config.path, self.source_dir))                 # 将应用下面的static目录初始化一个app_storage对象if os.path.isdir(app_storage.location):                             # 检查生成的静态文件夹是否存在self.storages[app_config.name] = app_storage                    # 根据配置应用的名称对应,app_storage对象if app_config.name not in self.apps:                            # 如果app没在app列表中,则将该应用的名称添加到列表self.apps.append(app_config.name)super(AppDirectoriesFinder, self).__init__(*args, **kwargs)def list(self, ignore_patterns):"""List all files in all app storages."""for storage in six.itervalues(self.storages):                           # 迭代列表中的应用下的app_storage实例if storage.exists(''):  # check if storage location exists          # 检查app_storage实例是否存在当前目录for path in utils.get_files(storage, ignore_patterns):          # 获取返回的路径yield path, storage                                         # 返回当前路径,与app_storage实例def find(self, path, all=False):"""Looks for files in the app directories."""matches = []for app in self.apps:                                                   # 查找app中的文件app_location = self.storages[app].location                          # 获取app的绝对路径if app_location not in searched_locations:                          # 如果当前路径不在搜索路径中则添加到全局搜索列表中searched_locations.append(app_location)match = self.find_in_app(app, path)                                 # 在app中的路径中查找if match:                                                           # 如果匹配if not all:                                                     # 如果不是全局搜索,则立马返回第一个匹配的return matchmatches.append(match)                                           # 如果是全局搜索则添加到返回列表中return matches                                                          # 返回所有匹配的数据def find_in_app(self, app, path):"""Find a requested static file in an app's static locations."""storage = self.storages.get(app)                                        # 获取app_storage实例if storage: # only try to find a file if the source dir actually existsif storage.exists(path):                                            # 检查当前文件是否存在matched_path = storage.path(path)                               # 返回匹配后的文件路径if matched_path:return matched_path
当调用find时会调用find_in_app方法,该方法中的每个实例都是FileSystemStorage,storage.path(path)调用该方法
    def path(self, name):return safe_join(self.location, name)
查找当前文件夹路径,当找到时就返回。
当通过这两种方式找到文件时,返回文件时,
    document_root, path = os.path.split(absolute_path)                          # 返回匹配上的文件夹,与文件    return static.serve(request, path, document_root=document_root, **kwargs)   # 处理该静态文件的response
执行到该方法,django.views.static.server的代码为
def serve(request, path, document_root=None, show_indexes=False):"""Serve static files below a given point in the directory structure.To use, put a URL pattern such as::from django.views.static import serveurl(r'^(?P<path>.*)$', serve, {'document_root': '/path/to/my/files/'})in your URLconf. You must provide the ``document_root`` param. You mayalso set ``show_indexes`` to ``True`` if you'd like to serve a basic indexof the directory.  This index view will use the template hardcoded below,but if you'd like to override it, you can create a template called``static/directory_index.html``."""path = posixpath.normpath(unquote(path)).lstrip('/')                                fullpath = safe_join(document_root, path)                                           # 获取文件的全路径if os.path.isdir(fullpath):                                                         # 判断是否是文件夹if show_indexes:                                                                # 如果显示文件的树结构则显示return directory_index(path, fullpath)                              raise Http404(_("Directory indexes are not allowed here."))if not os.path.exists(fullpath):                                                    # 如果不存在则报错raise Http404(_('"%(path)s" does not exist') % {'path': fullpath})# Respect the If-Modified-Since header.statobj = os.stat(fullpath)                                                         # 获取文件的状态if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),               # 判断该文件是否已经客户端缓存过期,如果还在缓存期就直接返回statobj.st_mtime, statobj.st_size):return HttpResponseNotModified()content_type, encoding = mimetypes.guess_type(fullpath)                             # 获取文件的文件类型,获取文件的编码格式content_type = content_type or 'application/octet-stream'                           # 设置返回文件的文件类型response = FileResponse(open(fullpath, 'rb'), content_type=content_type)            # 将文件读入到缓存流中,并返回responseresponse["Last-Modified"] = http_date(statobj.st_mtime)                             # 添加最后的文件modified的时间if stat.S_ISREG(statobj.st_mode):                                                   # 是否是一般文件response["Content-Length"] = statobj.st_size                                    # 设置返回文件的长度if encoding: response["Content-Encoding"] = encoding                                         # 如果返回有文件的编码格式就设置文件的编码格式return response  
当找到文件后,获取到文件路径后,FileResponse来进行文件返回
FileResponse的代码为
class FileResponse(StreamingHttpResponse):"""A streaming HTTP response class optimized for files."""block_size = 4096def _set_streaming_content(self, value):                                        # 重写父类的设置stream方法if hasattr(value, 'read'):                                                  # 如果有read方法self.file_to_stream = value                                             # 将file_to_stream设值filelike = value                                                        # if hasattr(filelike, 'close'):                                          # 如果有close方法,添加到完成时关闭self._closable_objects.append(filelike)value = iter(lambda: filelike.read(self.block_size), b'')               # 迭代读文件的block_size大小的文件,直到读为空为止else:self.file_to_stream = Nonesuper(FileResponse, self)._set_streaming_content(value)                     # 调用父类方法处理value
 我们查看StreamingHttpResponse
class StreamingHttpResponse(HttpResponseBase):"""A streaming HTTP response class with an iterator as content.This should only be iterated once, when the response is streamed to theclient. However, it can be appended to or replaced with a new iteratorthat wraps the original content (or yields entirely new content)."""streaming = Truedef __init__(self, streaming_content=(), *args, **kwargs):super(StreamingHttpResponse, self).__init__(*args, **kwargs)# `streaming_content` should be an iterable of bytestrings.# See the `streaming_content` property methods.self.streaming_content = streaming_content                                  # 设置stream调用streaming_content.setter方法@propertydef content(self):raise AttributeError("This %s instance has no `content` attribute. Use ""`streaming_content` instead." % self.__class__.__name__)@propertydef streaming_content(self):return map(self.make_bytes, self._iterator)@streaming_content.setterdef streaming_content(self, value):self._set_streaming_content(value)                                          # 调用_set_streaming_contentdef _set_streaming_content(self, value):# Ensure we can never iterate on "value" more than once.self._iterator = iter(value)                                                # 设置可迭代对象if hasattr(value, 'close'):                                                 # 如果对象有close方法则在迭代结束后关闭self._closable_objects.append(value)def __iter__(self):return self.streaming_content                                               # 迭代streaming_contentdef getvalue(self):return b''.join(self.streaming_content)
通过将response生成一个可迭代对象,将返回的数据进行分块发送,文件块大小为4096,此时就将文件内容分块发送出去,此时一个静态文件的响应过程完成。

Django源码分析4:staticfiles静态文件处理中间件分析相关推荐

  1. django源码阅读 manage.py文件

    Django源码阅读之manager.py文件阅读 我们知道,我们运行一个django项目的时候,需要进入项目的根目录,然后输入命令,python manage.py runserver,这样,我们就 ...

  2. Django源码分析5:session会话中间件分析

    django源码分析 本文环境python3.5.2,django1.10.x系列 1.这次分析django框架中的会话中间件. 2.会话保持是目前框架都支持的一个功能,因为http是无状态协议,无法 ...

  3. Django源码分析10:makemigrations命令概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-makemigrations命令概述 Django项目中的数据库管理命令就是通过makemig ...

  4. Django源码分析9:model.py表结构的初始化概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-model概述 Django项目中提供了内置的orm框架,只需要在models.py文件中添加 ...

  5. Django源码分析8:单元测试test命令浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-test命令分析 Django项目中提供了,test命令行命令来执行django的单元测试,该 ...

  6. Django源码分析7:migrate命令的浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-migrate命令分析 Django项目中提供了,通过migrations操作数据库的结构的命 ...

  7. Django源码分析3:处理请求wsgi分析与视图View

    django源码分析 本文环境python3.5.2,django1.10.x系列 根据前上一篇runserver的博文,已经分析了本地调试服务器的大致流程,现在我们来分析一下当runserver运行 ...

  8. Django源码分析2:本地运行runserver分析

    django源码分析 本文环境python3.5.2,django1.10.x系列1.根据上一篇文章分析了,django-admin startproject与startapp的分析流程后,根据dja ...

  9. Django源码分析6:auth认证及登陆保持

    django源码分析 本文环境python3.5.2,django1.10.x系列 1.这次分析django框架中登陆认证与接口权限检查. 2.在后端开发中,难免会对接口进行权限验证,其中对于接口是否 ...

最新文章

  1. 修改ceph-disk源码,增加指定ceph.conf部署osd的功能
  2. django项目mysql中文编码问题
  3. 谈一谈flex布局使用中碰到的一些问题
  4. 13 代码分割之import静动态导入
  5. 输入姓名打印年龄练习
  6. 钉钉人脸识别,戴个太阳帽就找不到人脸
  7. 王佩丰VBA学习笔记
  8. 我的大学之路---《大学之路》读后感
  9. 模型预测控制matlab工具箱,MATLAB模型预测控制工具箱函数..
  10. 黑马Java学科资料
  11. SketchUp:SketchUp草图大师经典案例之利用跟随路径工具工具设计椭球图文教程
  12. java 微服务 dubbo_Dubbo Spring Cloud 重塑微服务治理
  13. 蚂蚁金融NLP竞赛——文本语义相似度赛题总结
  14. 十一大排序算法的实现
  15. 【历史上的今天】9 月 29 日:“美国支付宝” Stripe 正式上线;HotJava 面世;VR/AR 领域先驱诞生
  16. scratch编程打字机
  17. 信道编码算法的发展和应用
  18. 电磁兼容(EMC)基础(二)
  19. 弱校胡策 大逃亡(BFS灌水+二分答案)
  20. Nodejs 正则表达式

热门文章

  1. 普通大学生和大厂的距离有多长?
  2. 一学就会的 Python 时间转化总结(超全)
  3. GPT-3 的到来,程序员会被 AI 取代吗?
  4. 中国开源大爆发进行时,你没掉队吧?
  5. Google提出移动端新SOTA模型MixNets:用混合深度卷积核提升精度
  6. 18段代码带你玩转18个机器学习必备交互工具
  7. 如何用Python和BERT做中文文本二元分类?| 程序员硬核评测
  8. 盛会再临,2018中国大数据技术大会(BDTC)首曝日程及议题
  9. 重磅!阿里开源AI核心技术,95%算法工程师受用
  10. 详细介绍 IOC 和 DI