django之请求处理过程分析

在开发过程中,可能会遇到一些框架的报错,比如http方法不允许、中间件检查不通过、路由匹配错误等,此类情况下因为报错是框架层面报的,这时候就有必要在框架的源代码层面打断点从而定位问题了。而源代码层面的断点打在哪,就需要了解清楚本文说的一些流程。

1、服务启动过程

我们最熟悉的服务启动方式就是这个runserver了:

python 复制代码
python3 manange.py runserver

先讲下runserver的代码内容。

在django的源码里,有这样一个目录django/core/managemtn/commands/,这个目录下有很多我们熟悉的文件,比如runserver.pymakemigrations.py、migrate.py等。

这些就是python3 manange.py --help显示的命令行对应的处理程序。

我们也可以在这里增加自定义的脚本,从而实现自定义命令。

我们看下runserver.py里的handle函数,这是每个命令的入口函数。

python 复制代码
# 启动过程中的函数调用
def handle()
  self.run()
    self.inner_run()
      handler = self.get_handler()
      run(handler)    # 这里的run是引自django.core.servers.basehttp  

# 先看下这个run()函数
# django/core/servers/basehttp.py
def run(addr,port,wsgi_handler):
  # 绑定IP、端口
  server_address = (addr, port)
  # 初始化http服务器
  httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
  # 设置请求处理的函数
  httpd.set_app(wsgi_handler)
  # 启动
  httpd.serve_forever()

# 其中这个请求处理的函数handler就是self.get_handler()获取的。

def get_handler()
  get_internal_wsgi_application()
    from django.conf import settings
    app_path = getattr(settings, "WSGI_APPLICATION")
    return import_string(app_path)
# 这个handler是在setting文件里通过WSGI_APPLICATION参数指定的。

# 比如我项目的setting文件里
WSGI_APPLICATION = "application.wsgi.application"
# 这是一个路径,可以通过import导入的,从项目根目录下顺着找,找到这样一个文件

# {项目根目录}/application/wsgi.py
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
application = get_wsgi_application()
# 在我的项目里没有做过多自定义,调用django提供的get_wsgi_application,
# 这个函数返回的内容就是handler了

# django/core/wsgi.py
def get_wsgi_application():
  return WSGIHandler() 

# 至此,我们找到了handler的真面目,就是这个WSGIHandler()

上面只讲了下django自带的runserver的启动过程,在生产环境下,我们会看到用比如uwsgi、gunicorn等命令来启动服务,其实原理都差不多,最主要的就是给这些http服务传递一个请求的入口函数。

再扩展一下,django其实是支持两类服务器,同步服务器(wsgi)和异步服务器(asgi),我们上面介绍的uwsgi这些都是同步服务器,异步服务器比如uvicorn,此处就不介绍了,感兴趣自行研究。

2、请求处理过程

浏览器发起一个http请求,最后如何转到我们写的一个view函数上呢,继续挖一挖。

首先请求的处理函数是这个WSGIHandler()。

python 复制代码
# django.core.handlers.wsgi.py
# WSGIHandler()这个类继承自BaseHandler
# 其自身的代码很少,主要的几个方法都在BaseHandler里

class WSGIHandler(base.BaseHandler):
  # 定义一个初始化HttpRequest的类,
  # 这个类的作用就是将http请求封装成django标准的HttpRequest,方便后续使用
  request_class = WSGIRequest

  # 初始化方法
  # 调用self.load_middleware()加载setting文件里定义的中间件
  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.load_middleware()

  # 请求到达时进入__call_函数处理
  # 先用request_class把http请求封装成HttpRequest
  # 具体的处理逻辑就在BaseHandler的get_response函数里了
  def __call__(self, environ, start_response):
    request = self.request_class(environ)
    response = self.get_response(request)
    return response

# django.core.handlers.base.py
# 下面就是BaseHandler的get_response函数
# 通过调用self._middleware_chain函数,拿到响应结果
def get_response(self, request):
  set_urlconf(settings.ROOT_URLCONF)
  response = self._middleware_chain(request)

# 这个self._middleware_chain其实就是_get_response
# 两者是在load_middleware函数里绑定的,主要还是区分同步和异步,我们本篇只说同步服务器的情况
# get_response = self._get_response_async if is_async else self._get_response

# 核心的处理函数_get_response
def _get_response(self, request):
   # resolve_request:解析路由,获得请求对应的处理函数
   callback, callback_args, callback_kwargs = self.resolve_request(request)
   # 先依次应用中间件里的处理
   for middleware_method in self._view_middleware:
       response = middleware_method(
           request, callback, callback_args, callback_kwargs
            )
   # 再执行callback处理函数,就是我们写的view视图函数
   wrapped_callback = self.make_view_atomic(callback)
   response = wrapped_callback(request, *callback_args, **callback_kwargs)
   # 返回处理结果
   return response

# 关于resolve_request如何通过路由表找到对应视图函数的解析,可以参考本系列的路由相关内容

至此,http请求的处理过程基本清晰了,核心逻辑在_get_response中,主要包含解析路由表--应用中间件--执行视图函数--返回结果这几个步骤。

所以后续再遇到比如路由不到自己预想的函数,就可以跟一下resolve_request等等。

3、django中的HttpRequest和HttpResponse

在django框架中,请求在内部处理的流程如下:

  1. 客户端发送请求 → Django 创建 HttpRequest 对象,封装所有请求数据。
  2. 视图函数处理 → 开发者通过 request 获取数据,处理后生成 HttpResponse
  3. 服务器返回响应 → Django 将 HttpResponse 转换为符合 HTTP 协议的响应,发送给客户端。

为什么会出现HttpRequestHttpResponse呢?

主要是为了避免开发者处理底层协议细节,简化数据访问。

如果没有它们,开发者需要手动解析原始 HTTP 数据,代码会变得复杂且难以维护。

学习这两个类的目的是什么呢?

对于HttpRequest,主要是为了取出请求里的数据,比如header信息、表单信息、参数信息等,因此要搞清楚这些信息存在HttpRequest的哪些属性里。

对于HttpResponse,因为是开发者要封装的,要搞清楚如何将自己要返回的数据给到HttpResponse。

HttpRequest

httpReqeust的属性有:

属性 类型 说明
method str HTTP 请求方法(GET/POST/PUT/DELETE等)
GET QueryDict 包含所有GET参数的类字典对象
POST QueryDict 包含所有POST参数的类字典对象
FILES MultiValueDict 包含所有上传文件的类字典对象
COOKIES dict 包含所有cookies的字典
META dict 包含所有HTTP头信息的字典
path str 请求的完整路径(不包括域名和查询参数)
path_info str 类似于path,但在某些服务器配置下可能不同
body bytes 原始HTTP请求体(字节字符串)
headers HttpHeaders Django 3.2+引入,更友好的headers访问方式
session SessionStore 可读写的类字典对象,表示当前会话
user User 表示当前登录用户的User对象

属性说明:

1、取参数,get方法的参数和post方法的参数分别存在request.GET和request.POST中,后端在取参数的时候要对判断http方法。

python 复制代码
def example_view(request):
    if request.method == "POST":
        username = request.POST.get("username")  # 获取 POST 参数
        file = request.FILES.get("avatar")       # 获取文件
    elif request.method == "GET":
        username = request.GET.get("username")

2、特殊情况下,取参需要使用body中的内容,注意body属于原始字符串,后端使用前需要解码。

python 复制代码
import json
data = json.loads(request.body.decode('utf-8'))

3、META部分包含了所有HTTP头信息,请求中的任何 HTTP 头都会被转换为 META 键,方法是将所有字符转换为大写字母,用下划线代替任何连字符,并在名称前加上 HTTP_ 前缀,例如,一个名为 X-Name 的头将被映射到 META 键 HTTP_X_NAME。

python 复制代码
def example_view(request):
    name = request.META['HTTP_X_NAME']

httpReqeust还提供了一些获取常用属性的方法,比如客户端的主机名、端口等

方法 返回类型 说明
get_full_path() str 返回path + 查询字符串
is_secure() bool 如果请求是通过HTTPS发出的返回True
get_host() str 获取请求的主机名
get_port() str 获取请求的端口号
build_absolute_uri(location) str 返回location的绝对URI
get_signed_cookie(key) str 获取签名cookie的值
read(size=None) bytes 从请求体中读取数据

httpResponse

学习httpResponse就是搞清楚如何构造一个django支持的响应。先看这个类的构造函数。

python 复制代码
# HttpResponse是在view处理阶段要自行构造的,因此需要搞清楚要传的参数
class HttpResponse(HttpResponseBase):
   def __init__(self,
                content=b'',
                content_type=None,
                status=None,
                reason=None,
                charset=None):

可以看出我们要返回的内容主要是放在content属性里,以字节的格式返回。

python 复制代码
def my_view(request):
    return HttpResponse('ok')

# 最常见的比如就是把'ok'赋值给content返回。

除了上面说的简单的字符串,HttpResponse还支持以下属性设置。

属性 类型 说明
content bytes 响应内容(字节形式)
charset str 响应编码(如 'utf-8')
status_code int HTTP 状态码(如 200, 404)
reason_phrase str 状态码描述(如 "OK", "Not Found")
headers HttpHeaders 响应头(类字典对象)
cookies SimpleCookie 用于设置 Cookie 的对象

还可以通过内置的函数,进行属性设置和数据写入。

方法 说明
set_cookie() 设置 Cookie
delete_cookie() 删除 Cookie
write(content) 写入响应内容(用于流式响应)
setdefault(key, value) 设置默认响应头

HttpResponse常用的子类如下,主要是对特定场景做了定制化,减少开发工作量。

子类 说明
JsonResponse 返回 JSON 响应(自动设置 Content-Type)
FileResponse 返回文件下载响应
StreamingHttpResponse 流式响应(大文件处理)
HttpResponseRedirect 302 重定向响应
HttpResponsePermanentRedirect 301 永久重定向
HttpResponseNotFound 404 响应
HttpResponseForbidden 403 响应
HttpResponseBadRequest 400 响应

4、DRF中的Request和Response

Request

在DRF中,基于httpReqeust做了扩展,封装成了Requests类。

从DRF的Request获取数据和django的HttpRequest获取数据有哪些区别:

1、data属性,支持所有的文件输入和非文件输入,支持除POST之外的HTTP方法的内容,这意味着你可以访问PUT和PATCH请求的内容。(在djanog中读取文件需要读取request.FILES,非文件需要读取request.GET或request.POST或request.body)。

2、query_params属性,就是django中的request.GET。

3、增加了认证支持(此处不展开)。

DRF是在请求的哪个步骤引入了Request呢?

python 复制代码
# 首先我们知道请求经过self.resolve_request(request)解析出了处理函数。
# 对于drf中基于类的视图中,通常会调用类的as_view()函数
# 而as_view函数返回的就是该类的dispatch()函数。
# 也就是resolve_request之后的函数处理入口就是这个类的dispatch函数。


# rest_framework/views.py
class APIView(View):
  def dispatch(self, request, *args, **kwargs):
    request = self.initialize_request(request, *args, **kwargs)


   def initialize_request(self, request, *args, **kwargs):
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
# 从上面代码中可以看出,Request是在dispatch函数中引入的。
# 而DRF中的viewset、modelViewSet都是继承自APIView,都是复用的这部分代码。
# 所以在DRF中只要你的视图是基于APIView、viewset、modelViewSet,
# 那么你用的request大概率就是Request实例,否则就还是django的httpRequest.

Response

对于DRF的响应类Response,如何构造返回实例。

python 复制代码
class Response(SimpleTemplateResponse)

  def __init__(self, data=None, status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):

可以看出数据主要赋值给data进行返回。data要求是序列化后的数据,不能是类实例。

相关推荐
恸流失2 小时前
DJango项目
后端·python·django
潘yi.3 小时前
NoSQL之Redis配置与优化
数据库·redis·nosql
zdkdchao3 小时前
hbase资源和数据权限控制
大数据·数据库·hbase
伤不起bb3 小时前
NoSQL 之 Redis 配置与优化
linux·运维·数据库·redis·nosql
leo__5203 小时前
PostgreSQL配置文件修改及启用方法
数据库·postgresql
南風_入弦5 小时前
优化09-表连接
数据库·oracle
Snk0xHeart6 小时前
极客大挑战 2019 EasySQL 1(万能账号密码,SQL注入,HackBar)
数据库·sql·网络安全
····懂···6 小时前
数据库OCP专业认证培训
数据库·oracle·ocp
学习中的码虫7 小时前
数据库-MySQL
数据库
Karry的巡洋舰7 小时前
【数据库】安全性
数据库·oracle