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要求是序列化后的数据,不能是类实例。

相关推荐
.Eyes1 小时前
OceanBase 分区裁剪(Partition Pruning)原理解读
数据库·oceanbase
MrZhangBaby2 小时前
SQL-leetcode— 2356. 每位教师所教授的科目种类的数量
数据库
一水鉴天2 小时前
整体设计 之定稿 “凝聚式中心点”原型 --整除:智能合约和DBMS的在表层挂接 能/所 依据的深层套接 之2
数据库·人工智能·智能合约
翔云1234563 小时前
Python 中 SQLAlchemy 和 MySQLdb 的关系
数据库·python·mysql
孙霸天3 小时前
Ubuntu20系统上离线安装MongoDB
数据库·mongodb·ubuntu·备份还原
Java 码农3 小时前
nodejs mongodb基础
数据库·mongodb·node.js
TDengine (老段)3 小时前
TDengine IDMP 运维指南(4. 使用 Docker 部署)
运维·数据库·物联网·docker·时序数据库·tdengine·涛思数据
TDengine (老段)3 小时前
TDengine IDMP 最佳实践
大数据·数据库·物联网·ai·时序数据库·tdengine·涛思数据
彬彬醤4 小时前
Mac怎么连接VPS?可以参考这几种方法
大数据·运维·服务器·数据库·线性代数·macos·矩阵
废喵喵呜4 小时前
达梦数据库-实时主备集群部署详解(附图文)手工搭建一主一备数据守护集群DW
网络·数据库·tcp/ip