DRF之Request源码分析
【一】路由入口
| from django.contrib import admin |
| from django.urls import path |
| from book import views |
| |
| urlpatterns = [ |
| path('admin/', admin.site.urls), |
| path('test/', views.TestView.as_view()), |
| path('test_http/', views.TestHttpResponse.as_view()), |
| ] |
【二】视图分析
【1】原生的request对象
| from django.shortcuts import render, HttpResponse |
| from django.views import View |
| class TestHttpResponse(View): |
| def get(self, request, *args, **kwargs): |
| print(request) |
| print(type(request)) |
| print(dir(request)) |
| |
| return HttpResponse('ok') |
| <WSGIRequest: GET '/test_http/'> |
| <class 'django.core.handlers.wsgi.WSGIRequest'> |
- 原生的request对象属于 WSGIRequest 的对象
| ['COOKIES', |
| 'FILES', |
| 'GET', |
| 'META', |
| 'POST', |
| '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_current_scheme_host', '_encoding', '_get_full_path', '_get_post', '_get_raw_host', '_get_scheme', '_initialize_handlers', '_load_post_and_files', '_mark_post_parse_error', '_messages', '_read_started', '_set_content_type_params', '_set_post', '_stream', |
| '_upload_handlers', |
| 'accepted_types', |
| 'accepts', |
| 'body', |
| 'build_absolute_uri', |
| 'close', |
| 'content_params', |
| 'content_type', |
| 'csrf_processing_done', |
| 'encoding', |
| 'environ', |
| 'get_full_path', |
| 'get_full_path_info', |
| 'get_host', |
| 'get_port', |
| 'get_raw_uri', |
| 'get_signed_cookie', |
| 'headers', |
| 'is_ajax', |
| 'is_secure', |
| 'method', |
| 'parse_file_upload', |
| 'path', |
| 'path_info', |
| 'read', |
| 'readline', |
| 'readlines', |
| 'resolver_match', |
| 'scheme', |
| 'session', |
| 'upload_handlers', |
| 'user'] |
- 原生的request对象中有上述类属性
- 我们筛选,找重要的筛一下
- COOKIES
- FILES
- GET
- META
- POST
【2】DRF的request对象
| from rest_framework.request import Request |
| from rest_framework.response import Response |
| from rest_framework.views import APIView |
| class TestView(APIView): |
| def get(self, request, *args, **kwargs): |
| print(request) |
| print(type(request)) |
| print(dir(request)) |
| |
| return Response('ok') |
| <rest_framework.request.Request: GET '/test/'> |
- 从上面的 request 对象中我们可以看到这是属于 rest_framework 中的 Request 的对象
| <class 'rest_framework.request.Request'> |
- 它的类型也验证了上述他就是一个 rest_framework 的新的 Request 对象
| ['DATA', |
| 'FILES', |
| 'POST', |
| 'QUERY_PARAMS', |
| '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_auth', '_authenticate', '_authenticator', '_content_type', '_data', '_default_negotiator', '_files', '_full_data', '_load_data_and_files', '_load_stream', '_not_authenticated', '_parse', '_request', '_stream', '_supports_form_parsing', '_user', |
| 'accepted_media_type', |
| 'accepted_renderer', |
| 'auth', |
| 'authenticators', |
| 'content_type', |
| 'data', |
| 'force_plaintext_errors', |
| 'negotiator', |
| 'parser_context', |
| 'parsers', |
| 'query_params', |
| 'stream', |
| 'successful_authenticator', |
| 'user', |
| 'version', |
| 'versioning_scheme'] |
- 我们通过查看他的类属性,可以发现和我们原生的 request 有很多不一样的方法
- 同样的我们筛一下
- DATA
- FILES
- POST
- QUERY_PARAMS
【3】小结
- 通过上述两个视图函数中的request对象的属性比较,我们可以清楚地看到,新的request对象多了一些不一样的方法
- 比如原生的request中的GET方法不见了,新的request方法多了一个QUERY_PARAMS方法
- 那接下来我们就来分析一下他们之间到底有哪些联系
【三】新的 Request 源码
【1】入口
- 既然是 rest_framework 中新封装的方法,那么在他的源码中就一定有这个方法,我们通过下述入口进入到 Request 类中
| from rest_framework.request import Request |
【2】源码
| class Request: |
| """ |
| Wrapper allowing to enhance a standard `HttpRequest` instance. |
| |
| Kwargs: |
| - request(HttpRequest). The original request instance. |
| - parsers(list/tuple). The parsers to use for parsing the |
| request content. |
| - authenticators(list/tuple). The authenticators used to try |
| authenticating the request's user. |
| """ |
| |
| def __init__(self, request, parsers=None, authenticators=None, |
| negotiator=None, parser_context=None): |
| assert isinstance(request, HttpRequest), ( |
| 'The `request` argument must be an instance of ' |
| '`django.http.HttpRequest`, not `{}.{}`.' |
| .format(request.__class__.__module__, request.__class__.__name__) |
| ) |
| |
| self._request = request |
| self.parsers = parsers or () |
| self.authenticators = authenticators or () |
| self.negotiator = negotiator or self._default_negotiator() |
| self.parser_context = parser_context |
| self._data = Empty |
| self._files = Empty |
| self._full_data = Empty |
| self._content_type = Empty |
| self._stream = Empty |
| |
| if self.parser_context is None: |
| self.parser_context = {} |
| self.parser_context['request'] = self |
| self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET |
| |
| force_user = getattr(request, '_force_auth_user', None) |
| force_token = getattr(request, '_force_auth_token', None) |
| if force_user is not None or force_token is not None: |
| forced_auth = ForcedAuthentication(force_user, force_token) |
| self.authenticators = (forced_auth,) |
| |
| def __repr__(self): |
| return '<%s.%s: %s %r>' % ( |
| self.__class__.__module__, |
| self.__class__.__name__, |
| self.method, |
| self.get_full_path()) |
| |
| def _default_negotiator(self): |
| return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() |
| |
| @property |
| def content_type(self): |
| meta = self._request.META |
| return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', '')) |
| |
| @property |
| def stream(self): |
| """ |
| Returns an object that may be used to stream the request content. |
| """ |
| if not _hasattr(self, '_stream'): |
| self._load_stream() |
| return self._stream |
| |
| @property |
| def query_params(self): |
| """ |
| More semantically correct name for request.GET. |
| """ |
| return self._request.GET |
| |
| @property |
| def data(self): |
| if not _hasattr(self, '_full_data'): |
| self._load_data_and_files() |
| return self._full_data |
| |
| @property |
| def user(self): |
| """ |
| Returns the user associated with the current request, as authenticated |
| by the authentication classes provided to the request. |
| """ |
| if not hasattr(self, '_user'): |
| with wrap_attributeerrors(): |
| self._authenticate() |
| return self._user |
| |
| @user.setter |
| def user(self, value): |
| """ |
| Sets the user on the current request. This is necessary to maintain |
| compatibility with django.contrib.auth where the user property is |
| set in the login and logout functions. |
| |
| Note that we also set the user on Django's underlying `HttpRequest` |
| instance, ensuring that it is available to any middleware in the stack. |
| """ |
| self._user = value |
| self._request.user = value |
| |
| @property |
| def auth(self): |
| """ |
| Returns any non-user authentication information associated with the |
| request, such as an authentication token. |
| """ |
| if not hasattr(self, '_auth'): |
| with wrap_attributeerrors(): |
| self._authenticate() |
| return self._auth |
| |
| @auth.setter |
| def auth(self, value): |
| """ |
| Sets any non-user authentication information associated with the |
| request, such as an authentication token. |
| """ |
| self._auth = value |
| self._request.auth = value |
| |
| @property |
| def successful_authenticator(self): |
| """ |
| Return the instance of the authentication instance class that was used |
| to authenticate the request, or `None`. |
| """ |
| if not hasattr(self, '_authenticator'): |
| with wrap_attributeerrors(): |
| self._authenticate() |
| return self._authenticator |
| |
| def _load_data_and_files(self): |
| """ |
| Parses the request content into `self.data`. |
| """ |
| if not _hasattr(self, '_data'): |
| self._data, self._files = self._parse() |
| if self._files: |
| self._full_data = self._data.copy() |
| self._full_data.update(self._files) |
| else: |
| self._full_data = self._data |
| |
| |
| |
| if is_form_media_type(self.content_type): |
| self._request._post = self.POST |
| self._request._files = self.FILES |
| |
| def _load_stream(self): |
| """ |
| Return the content body of the request, as a stream. |
| """ |
| meta = self._request.META |
| try: |
| content_length = int( |
| meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0)) |
| ) |
| except (ValueError, TypeError): |
| content_length = 0 |
| |
| if content_length == 0: |
| self._stream = None |
| elif not self._request._read_started: |
| self._stream = self._request |
| else: |
| self._stream = io.BytesIO(self.body) |
| |
| def _supports_form_parsing(self): |
| """ |
| Return True if this requests supports parsing form data. |
| """ |
| form_media = ( |
| 'application/x-www-form-urlencoded', |
| 'multipart/form-data' |
| ) |
| return any(parser.media_type in form_media for parser in self.parsers) |
| |
| def _parse(self): |
| """ |
| Parse the request content, returning a two-tuple of (data, files) |
| |
| May raise an `UnsupportedMediaType`, or `ParseError` exception. |
| """ |
| media_type = self.content_type |
| try: |
| stream = self.stream |
| except RawPostDataException: |
| if not hasattr(self._request, '_post'): |
| raise |
| |
| |
| |
| if self._supports_form_parsing(): |
| return (self._request.POST, self._request.FILES) |
| stream = None |
| |
| if stream is None or media_type is None: |
| if media_type and is_form_media_type(media_type): |
| empty_data = QueryDict('', encoding=self._request._encoding) |
| else: |
| empty_data = {} |
| empty_files = MultiValueDict() |
| return (empty_data, empty_files) |
| |
| parser = self.negotiator.select_parser(self, self.parsers) |
| |
| if not parser: |
| raise exceptions.UnsupportedMediaType(media_type) |
| |
| try: |
| parsed = parser.parse(stream, media_type, self.parser_context) |
| except Exception: |
| |
| |
| |
| |
| self._data = QueryDict('', encoding=self._request._encoding) |
| self._files = MultiValueDict() |
| self._full_data = self._data |
| raise |
| |
| |
| |
| try: |
| return (parsed.data, parsed.files) |
| except AttributeError: |
| empty_files = MultiValueDict() |
| return (parsed, empty_files) |
| |
| def _authenticate(self): |
| """ |
| Attempt to authenticate the request using each authentication instance |
| in turn. |
| """ |
| for authenticator in self.authenticators: |
| try: |
| user_auth_tuple = authenticator.authenticate(self) |
| except exceptions.APIException: |
| self._not_authenticated() |
| raise |
| |
| if user_auth_tuple is not None: |
| self._authenticator = authenticator |
| self.user, self.auth = user_auth_tuple |
| return |
| |
| self._not_authenticated() |
| |
| def _not_authenticated(self): |
| """ |
| Set authenticator, user & authtoken representing an unauthenticated request. |
| |
| Defaults are None, AnonymousUser & None. |
| """ |
| self._authenticator = None |
| |
| if api_settings.UNAUTHENTICATED_USER: |
| self.user = api_settings.UNAUTHENTICATED_USER() |
| else: |
| self.user = None |
| |
| if api_settings.UNAUTHENTICATED_TOKEN: |
| self.auth = api_settings.UNAUTHENTICATED_TOKEN() |
| else: |
| self.auth = None |
| |
| def __getattr__(self, attr): |
| """ |
| If an attribute does not exist on this instance, then we also attempt |
| to proxy it to the underlying HttpRequest object. |
| """ |
| try: |
| return getattr(self._request, attr) |
| except AttributeError: |
| return self.__getattribute__(attr) |
| |
| @property |
| def DATA(self): |
| raise NotImplementedError( |
| '`request.DATA` has been deprecated in favor of `request.data` ' |
| 'since version 3.0, and has been fully removed as of version 3.2.' |
| ) |
| |
| @property |
| def POST(self): |
| |
| if not _hasattr(self, '_data'): |
| self._load_data_and_files() |
| if is_form_media_type(self.content_type): |
| return self._data |
| return QueryDict('', encoding=self._request._encoding) |
| |
| @property |
| def FILES(self): |
| |
| |
| |
| if not _hasattr(self, '_files'): |
| self._load_data_and_files() |
| return self._files |
| |
| @property |
| def QUERY_PARAMS(self): |
| raise NotImplementedError( |
| '`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` ' |
| 'since version 3.0, and has been fully removed as of version 3.2.' |
| ) |
| |
| def force_plaintext_errors(self, value): |
| |
| |
| self._request.is_ajax = lambda: value |
【3】逐段源码分析
(1)init方法
| |
| class Request: |
| """ |
| # 允许对标准的'HttpRequest'实例进行增强处理,意思就是可以调用原来的类的方法 |
| Wrapper allowing to enhance a standard `HttpRequest` instance. |
| |
| Kwargs: |
| # 请求(HttpRequest)原始的请求实例 |
| 该行给出了第一个参数的说明,即原始的请求实例(HttpRequest) |
| - request(HttpRequest). The original request instance. |
| |
| # 解析器(列表/元组)。用于解析请求内容的解析器。 |
| 该行给出了第二个参数的说明,即用于解析请求内容的解析器。 |
| - parsers(list/tuple). The parsers to use for parsing the |
| request content. |
| |
| # 用于验证请求用户身份的认证器 |
| - authenticators(list/tuple). The authenticators used to try |
| authenticating the request's user. |
| """ |
| |
| |
| |
| |
| |
| def __init__(self, request, parsers=None, authenticators=None, |
| negotiator=None, parser_context=None): |
| |
| assert isinstance(request, HttpRequest), ( |
| |
| |
| 'The `request` argument must be an instance of ' |
| '`django.http.HttpRequest`, not `{}.{}`.' |
| .format(request.__class__.__module__, request.__class__.__name__) |
| ) |
| |
| |
| self._request = request |
| |
| |
| self.parsers = parsers or () |
| |
| |
| self.authenticators = authenticators or () |
| |
| |
| self.negotiator = negotiator or self._default_negotiator() |
| |
| self.parser_context = parser_context |
| |
| |
| self._data = Empty |
| |
| |
| self._files = Empty |
| |
| |
| self._full_data = Empty |
| |
| |
| self._content_type = Empty |
| |
| |
| self._stream = Empty |
| |
| |
| if self.parser_context is None: |
| self.parser_context = {} |
| |
| |
| self.parser_context['request'] = self |
| |
| |
| self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET |
| |
| |
| force_user = getattr(request, '_force_auth_user', None) |
| force_token = getattr(request, '_force_auth_token', None) |
| |
| |
| |
| if force_user is not None or force_token is not None: |
| forced_auth = ForcedAuthentication(force_user, force_token) |
| self.authenticators = (forced_auth,) |
(2)repr 和 _default_negotiator
| |
| |
| |
| def __repr__(self): |
| return '<%s.%s: %s %r>' % ( |
| self.__class__.__module__, |
| self.__class__.__name__, |
| self.method, |
| self.get_full_path()) |
| |
| |
| |
| |
| def _default_negotiator(self): |
| return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() |
(3)content_type
| |
| @property |
| def content_type(self): |
| |
| meta = self._request.META |
| |
| |
| |
| return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', '')) |
(4)stream
| |
| @property |
| def stream(self): |
| """ |
| # 返回一个对象,可用于流式传输请求内容。 |
| Returns an object that may be used to stream the request content. |
| """ |
| |
| |
| |
| |
| if not _hasattr(self, '_stream'): |
| self._load_stream() |
| return self._stream |
| def _load_stream(self): |
| """ |
| # 返回请求体的内容作为流式数据 |
| Return the content body of the request, as a stream. |
| """ |
| |
| |
| meta = self._request.META |
| |
| |
| |
| try: |
| |
| |
| content_length = int( |
| meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0)) |
| ) |
| |
| except (ValueError, TypeError): |
| content_length = 0 |
| |
| |
| if content_length == 0: |
| self._stream = None |
| |
| |
| |
| elif not self._request._read_started: |
| self._stream = self._request |
| |
| |
| else: |
| self._stream = io.BytesIO(self.body) |
| def read(self, *args, **kwargs): |
| |
| self._read_started = True |
| try: |
| |
| return self._stream.read(*args, **kwargs) |
| except OSError as e: |
| |
| raise UnreadablePostError(*e.args) from e |
(5)query_params
| |
| @property |
| def query_params(self): |
| """ |
| More semantically correct name for request.GET. |
| """ |
| |
| return self._request.GET |
(6)data
| |
| @property |
| def data(self): |
| |
| if not _hasattr(self, '_full_data'): |
| |
| self._load_data_and_files() |
| |
| return self._full_data |
| def _load_data_and_files(self): |
| """ |
| # 从 self.data 中解析 request 携带的内容 |
| Parses the request content into `self.data`. |
| """ |
| |
| |
| if not _hasattr(self, '_data'): |
| |
| |
| |
| self._data, self._files = self._parse() |
| |
| |
| if self._files: |
| |
| |
| self._full_data = self._data.copy() |
| self._full_data.update(self._files) |
| |
| |
| else: |
| self._full_data = self._data |
| |
| |
| |
| |
| if is_form_media_type(self.content_type): |
| |
| |
| self._request._post = self.POST |
| |
| |
| self._request._files = self.FILES |
(7)user(@property)
| |
| @property |
| def user(self): |
| """ |
| # 返回一个用户对象是被认证过的 |
| Returns the user associated with the current request, as authenticated |
| by the authentication classes provided to the request. |
| """ |
| |
| |
| if not hasattr(self, '_user'): |
| |
| |
| with wrap_attributeerrors(): |
| self._authenticate() |
| return self._user |
| |
| @contextmanager |
| def wrap_attributeerrors(): |
| """ |
| # 用于捕获并重新引发在身份验证期间遇到的 AttributeError 异常 |
| Used to re-raise AttributeErrors caught during authentication, preventing |
| these errors from otherwise being handled by the attribute access protocol. |
| """ |
| |
| |
| try: |
| |
| |
| yield |
| |
| |
| except AttributeError: |
| |
| |
| info = sys.exc_info() |
| |
| exc = WrappedAttributeError(str(info[1])) |
| |
| |
| raise exc.with_traceback(info[2]) |
| def _authenticate(self): |
| """ |
| # 尝试使用每个身份验证实例依次对请求进行身份验证。 |
| Attempt to authenticate the request using each authentication instance |
| in turn. |
| """ |
| |
| |
| for authenticator in self.authenticators: |
| try: |
| |
| |
| user_auth_tuple = authenticator.authenticate(self) |
| |
| |
| except exceptions.APIException: |
| |
| |
| self._not_authenticated() |
| raise |
| |
| |
| if user_auth_tuple is not None: |
| |
| self._authenticator = authenticator |
| |
| self.user, self.auth = user_auth_tuple |
| return |
| |
| |
| self._not_authenticated() |
authenticator.authenticate
| |
| class ForcedAuthentication: |
| """ |
| This authentication class is used if the test client or request factory |
| forcibly authenticated the request. |
| """ |
| |
| def __init__(self, force_user, force_token): |
| self.force_user = force_user |
| self.force_token = force_token |
| |
| def authenticate(self, request): |
| |
| |
| |
| return (self.force_user, self.force_token) |
(8)_parse
| def _parse(self): |
| """ |
| # 解析请求内容,并返回一个包含数据和文件的二元组 (data, files) |
| Parse the request content, returning a two-tuple of (data, files) |
| |
| May raise an `UnsupportedMediaType`, or `ParseError` exception. |
| """ |
| |
| |
| media_type = self.content_type |
| try: |
| |
| stream = self.stream |
| except RawPostDataException: |
| |
| |
| if not hasattr(self._request, '_post'): |
| raise |
| |
| |
| |
| |
| |
| if self._supports_form_parsing(): |
| return (self._request.POST, self._request.FILES) |
| stream = None |
| |
| |
| if stream is None or media_type is None: |
| |
| |
| if media_type and is_form_media_type(media_type): |
| empty_data = QueryDict('', encoding=self._request._encoding) |
| else: |
| empty_data = {} |
| empty_files = MultiValueDict() |
| return (empty_data, empty_files) |
| |
| |
| parser = self.negotiator.select_parser(self, self.parsers) |
| |
| |
| if not parser: |
| raise exceptions.UnsupportedMediaType(media_type) |
| |
| try: |
| |
| |
| |
| parsed = parser.parse(stream, media_type, self.parser_context) |
| except Exception: |
| |
| |
| |
| |
| self._data = QueryDict('', encoding=self._request._encoding) |
| self._files = MultiValueDict() |
| self._full_data = self._data |
| raise |
| |
| |
| |
| try: |
| |
| |
| return (parsed.data, parsed.files) |
| except AttributeError: |
| |
| empty_files = MultiValueDict() |
| return (parsed, empty_files) |
【四】总结
| class TestView(APIView): |
| def post(self, request, *args, **kwargs): |
| ''' |
| |
| :param request: 新的request,不是原来的那个 |
| :return: |
| ''' |
| |
| print(type(request)) |
| |
| |
| |
| print(request.data) |
| |
| |
| print(request.POST) |
| |
| |
| print(request.FILES) |
| |
| |
| print(request.method) |
| |
| print(request.body) |
| |
| print(request.path) |
| |
| |
| |
| return Response("ok") |
【补充】限制请求的数据类型
【1】前端传入的数据支持的格式
- DRF支持以下三种请求编码格式:
- urlencoded
- form-data
- json
【2】限制只能接受某种或某几种编码格式
- 有两种方式可以限制请求只能接受某种或某几种编码格式。
方式一:在视图类上设置parser_classes
属性
| from rest_framework.parsers import JSONParser, FormParser, MultiPartParser |
| |
| class BookView(APIView): |
| parser_classes = [JSONParser, FormParser] |
方式二:在DRF配置文件中设置DEFAULT_PARSER_CLASSES
- 这种方式是全局有效,在项目的DRF配置文件(一般是
settings.py
文件)中添加如下配置:
| REST_FRAMEWORK = { |
| 'DEFAULT_PARSER_CLASSES': [ |
| 'rest_framework.parsers.JSONParser', |
| |
| |
| ], |
| } |
【3】全局配置了只支持JSON,局部想支持所有三种编码格式
- 如果全局配置了只支持JSON编码格式
- 但您的局部视图类想支持所有三种编码格式,只需要在该视图类中指定所有三个编码器。
| from rest_framework.parsers import JSONParser, FormParser, MultiPartParser |
| |
| class BookView(APIView): |
| parser_classes = [JSONParser, FormParser, MultiPartParser] |
【4】总结
- DRF处理请求的编码格式遵循以下顺序:
- 首先从视图类中查找
parser_classes
属性,如果设置了特定的编码器,将使用该编码器。
- 如果视图类没有设置特定的编码器,则会到项目的DRF配置文件中查找
DEFAULT_PARSER_CLASSES
,如果配置了编码器列表,将使用该列表中的编码器。
- 最后,如果既没有在视图类中设置编码器,也没有在项目的DRF配置文件中配置编码器列表,将使用DRF默认的配置。
【5】源码分析
(1)位置

(2)源码分析
| """ |
| Parsers are used to parse the content of incoming HTTP requests. |
| |
| They give us a generic way of being able to handle various media types |
| on the request, such as form content or json encoded data. |
| """ |
| import codecs |
| |
| from django.conf import settings |
| from django.core.files.uploadhandler import StopFutureHandlers |
| from django.http import QueryDict |
| from django.http.multipartparser import ChunkIter |
| from django.http.multipartparser import \ |
| MultiPartParser as DjangoMultiPartParser |
| from django.http.multipartparser import MultiPartParserError |
| |
| from rest_framework import renderers |
| from rest_framework.compat import parse_header_parameters |
| from rest_framework.exceptions import ParseError |
| from rest_framework.settings import api_settings |
| from rest_framework.utils import json |
| |
| |
| |
| class DataAndFiles: |
| def __init__(self, data, files): |
| self.data = data |
| self.files = files |
| |
| |
| class BaseParser: |
| """ |
| # 所有解析器都应该扩展' BaseParser ',指定' media_type '属性,并重写' .parse() '方法。 |
| All parsers should extend `BaseParser`, specifying a `media_type` |
| attribute, and overriding the `.parse()` method. |
| """ |
| |
| media_type = None |
| |
| |
| def parse(self, stream, media_type=None, parser_context=None): |
| """ |
| # 给定要读取的流,返回解析后的表示形式。应该返回解析过的数据,还是一个由解析的数据和文件。 |
| Given a stream to read from, return the parsed representation. |
| Should return parsed data, or a `DataAndFiles` object consisting of the |
| parsed data and files. |
| """ |
| raise NotImplementedError(".parse() must be overridden.") |
| |
| |
| class JSONParser(BaseParser): |
| """ |
| Parses JSON-serialized data. |
| """ |
| |
| media_type = 'application/json' |
| |
| renderer_class = renderers.JSONRenderer |
| |
| strict = api_settings.STRICT_JSON |
| |
| |
| |
| def parse(self, stream, media_type=None, parser_context=None): |
| """ |
| # 将传入字节流解析为JSON并返回结果数据。 |
| Parses the incoming bytestream as JSON and returns the resulting data. |
| """ |
| |
| parser_context = parser_context or {} |
| |
| |
| encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) |
| |
| try: |
| |
| decoded_stream = codecs.getreader(encoding)(stream) |
| |
| |
| parse_constant = json.strict_constant if self.strict else None |
| |
| |
| return json.load(decoded_stream, parse_constant=parse_constant) |
| except ValueError as exc: |
| |
| |
| raise ParseError('JSON parse error - %s' % str(exc)) |
| |
| |
| class FormParser(BaseParser): |
| """ |
| Parser for form data. |
| """ |
| |
| |
| media_type = 'application/x-www-form-urlencoded' |
| |
| |
| |
| def parse(self, stream, media_type=None, parser_context=None): |
| """ |
| Parses the incoming bytestream as a URL encoded form, |
| and returns the resulting QueryDict. |
| """ |
| |
| parser_context = parser_context or {} |
| |
| |
| encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) |
| |
| |
| |
| |
| return QueryDict(stream.read(), encoding=encoding) |
| |
| |
| class MultiPartParser(BaseParser): |
| """ |
| # 多部分表单数据的解析器,其中可能包括文件数据。 |
| Parser for multipart form data, which may include file data. |
| """ |
| |
| |
| media_type = 'multipart/form-data' |
| |
| |
| |
| def parse(self, stream, media_type=None, parser_context=None): |
| """ |
| 将传入字节流解析为多部分编码形式,并返回一个DataAndFiles对象。 |
| Parses the incoming bytestream as a multipart encoded form, |
| and returns a DataAndFiles object. |
| |
| 文本数据将是一个包含所有表单参数的' QueryDict '。 |
| 文件类型将是一个包含所有表单文件的“QueryDict” |
| `.data` will be a `QueryDict` containing all the form parameters. |
| `.files` will be a `QueryDict` containing all the form files. |
| """ |
| |
| |
| parser_context = parser_context or {} |
| |
| |
| |
| request = parser_context['request'] |
| |
| |
| encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) |
| |
| |
| |
| meta = request.META.copy() |
| meta['CONTENT_TYPE'] = media_type |
| |
| |
| upload_handlers = request.upload_handlers |
| |
| try: |
| |
| parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding) |
| |
| |
| |
| data, files = parser.parse() |
| |
| |
| return DataAndFiles(data, files) |
| except MultiPartParserError as exc: |
| |
| |
| raise ParseError('Multipart form parse error - %s' % str(exc)) |
| |
| |
| class FileUploadParser(BaseParser): |
| """ |
| # 解析文件类型的数据 |
| Parser for file upload data. |
| """ |
| |
| media_type = '*/*' |
| errors = { |
| 'unhandled': 'FileUpload parse error - none of upload handlers can handle the stream', |
| 'no_filename': 'Missing filename. Request should include a Content-Disposition header with a filename parameter.', |
| } |
| |
| |
| def parse(self, stream, media_type=None, parser_context=None): |
| """ |
| # 将传入的字节流视为原始文件上传并返回' DataAndFiles '对象。 |
| Treats the incoming bytestream as a raw file upload and returns |
| a `DataAndFiles` object. |
| |
| `.data` will be None (we expect request body to be a file content). |
| `.files` will be a `QueryDict` containing one 'file' element. |
| """ |
| |
| |
| parser_context = parser_context or {} |
| |
| |
| |
| request = parser_context['request'] |
| |
| |
| encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) |
| |
| |
| meta = request.META |
| |
| |
| upload_handlers = request.upload_handlers |
| |
| |
| filename = self.get_filename(stream, media_type, parser_context) |
| |
| |
| if not filename: |
| raise ParseError(self.errors['no_filename']) |
| |
| |
| |
| |
| |
| |
| content_type = meta.get('HTTP_CONTENT_TYPE', |
| meta.get('CONTENT_TYPE', '')) |
| try: |
| |
| |
| content_length = int(meta.get('HTTP_CONTENT_LENGTH', |
| meta.get('CONTENT_LENGTH', 0))) |
| except (ValueError, TypeError): |
| |
| content_length = None |
| |
| |
| |
| |
| |
| for handler in upload_handlers: |
| result = handler.handle_raw_input(stream, |
| meta, |
| content_length, |
| None, |
| encoding) |
| |
| |
| if result is not None: |
| |
| return DataAndFiles({}, {'file': result[1]}) |
| |
| |
| |
| |
| |
| |
| possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] |
| |
| |
| |
| |
| chunk_size = min([2 ** 31 - 4] + possible_sizes) |
| |
| |
| |
| chunks = ChunkIter(stream, chunk_size) |
| |
| |
| counters = [0] * len(upload_handlers) |
| |
| |
| for index, handler in enumerate(upload_handlers): |
| try: |
| |
| handler.new_file(None, filename, content_type, |
| content_length, encoding) |
| except StopFutureHandlers: |
| |
| |
| upload_handlers = upload_handlers[:index + 1] |
| break |
| |
| |
| for chunk in chunks: |
| |
| |
| for index, handler in enumerate(upload_handlers): |
| chunk_length = len(chunk) |
| chunk = handler.receive_data_chunk(chunk, counters[index]) |
| |
| counters[index] += chunk_length |
| |
| |
| if chunk is None: |
| break |
| |
| |
| |
| for index, handler in enumerate(upload_handlers): |
| |
| |
| file_obj = handler.file_complete(counters[index]) |
| |
| |
| if file_obj is not None: |
| return DataAndFiles({}, {'file': file_obj}) |
| |
| |
| |
| raise ParseError(self.errors['unhandled']) |
| |
| def get_filename(self, stream, media_type, parser_context): |
| """ |
| Detects the uploaded file name. First searches a 'filename' url kwarg. |
| Then tries to parse Content-Disposition header. |
| """ |
| try: |
| |
| |
| return parser_context['kwargs']['filename'] |
| except KeyError: |
| pass |
| |
| try: |
| meta = parser_context['request'].META |
| |
| |
| disposition, params = parse_header_parameters(meta['HTTP_CONTENT_DISPOSITION']) |
| |
| |
| if 'filename*' in params: |
| return params['filename*'] |
| |
| |
| else: |
| return params['filename'] |
| except (AttributeError, KeyError, ValueError): |
| pass |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通