django之请求处理过程分析
在开发过程中,可能会遇到一些框架的报错,比如http方法不允许、中间件检查不通过、路由匹配错误等,此类情况下因为报错是框架层面报的,这时候就有必要在框架的源代码层面打断点从而定位问题了。而源代码层面的断点打在哪,就需要了解清楚本文说的一些流程。
1、服务启动过程
我们最熟悉的服务启动方式就是这个runserver了:
python3 manange.py runserver
先讲下runserver的代码内容。
在django的源码里,有这样一个目录django/core/managemtn/commands/,这个目录下有很多我们熟悉的文件,比如runserver.py、makemigrations.py、migrate.py等。
这些就是python3 manange.py --help显示的命令行对应的处理程序。
我们也可以在这里增加自定义的脚本,从而实现自定义命令。
我们看下runserver.py里的handle函数,这是每个命令的入口函数。
# 启动过程中的函数调用
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 settingsapp_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()。
# 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框架中,请求在内部处理的流程如下:
- 客户端发送请求 → Django 创建
HttpRequest
对象,封装所有请求数据。 - 视图函数处理 → 开发者通过
request
获取数据,处理后生成HttpResponse
。 - 服务器返回响应 → Django 将
HttpResponse
转换为符合 HTTP 协议的响应,发送给客户端。
为什么会出现HttpRequest
和 HttpResponse
呢?
主要是为了避免开发者处理底层协议细节,简化数据访问。
如果没有它们,开发者需要手动解析原始 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方法。
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属于原始字符串,后端使用前需要解码。
import json
data = json.loads(request.body.decode('utf-8'))
3、META部分包含了所有HTTP头信息,请求中的任何 HTTP 头都会被转换为 META 键,方法是将所有字符转换为大写字母,用下划线代替任何连字符,并在名称前加上 HTTP_ 前缀,例如,一个名为 X-Name 的头将被映射到 META 键 HTTP_X_NAME。
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支持的响应。先看这个类的构造函数。
# HttpResponse是在view处理阶段要自行构造的,因此需要搞清楚要传的参数
class HttpResponse(HttpResponseBase):def __init__(self,content=b'',content_type=None,status=None,reason=None,charset=None):
可以看出我们要返回的内容主要是放在content属性里,以字节的格式返回。
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呢?
# 首先我们知道请求经过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,如何构造返回实例。
class Response(SimpleTemplateResponse)def __init__(self, data=None, status=None,template_name=None, headers=None,exception=False, content_type=None):
可以看出数据主要赋值给data进行返回。data要求是序列化后的数据,不能是类实例。