A built-in parser
REST framework includes several built Parser class allows you to accept the request of various media types. Also supports defining your own custom parser parser for parsing the data submitted by the client.
- Use the built-in parser
1, global settings
It can be used DEFAULT_PARSER_CLASSES
to set default global parser. For example, the following is provided only with the JSON
requested content, rather than the default JSON or form data.
REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', ) }
So that each view of the access request, the data are required to be JSON.
2, partial set
Individually configured in one view:
from rest_framework.parsers Import JSONParser class BookView (GenericViewSet): "" " POST request to view the data only accepts JSON " "" parser_classes = [JSONParser,] DEF Create (Self, Request): Pass
- Built-in parser API species
1, JSONParser
Parse JSON request content.
.media_type: application/json
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): """ 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' % six.text_type(exc))
2、FormParser
Parse HTML form content. request.data
Is a QueryDict
dictionary containing all form parameters, usually need to use FormParser
and MultiPartParser
to fully support HTML form data.
.media_type: application/x-www-form-urlencoded
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) data = QueryDict(stream.read(), encoding=encoding) return data
3、MultiPartParser
Parsing the multipart HTML file upload form content. request.data
It is a QueryDict
(which contains the parameters and form), often need to use FormParser
and MultiPartParser
, to fully support HTML form data.
.media_type: application/form-data
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): """ Parses the incoming bytestream as a multipart encoded form, and returns a DataAndFiles object. `.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' % six.text_type(exc))
4、FileUploadParser
Parses the file uploads. request.data
Is a QueryDict
(file contains only there a 'file'
key).
If the FileUploadParser
view is used in conjunction with a filename
URL keyword parameters of the call, then the argument will be used as the file name.
If you do not filename
call the next URL keyword parameters, the client must Content-Disposition
set the file name in the HTTP header. For example Content-Disposition: attachment; filename=upload.jpg
.
.media_type: */*
note:
FileUploadParser
A local client, as the original data file can be uploaded request. For Web-based upload, or for a local client segments with upload support, you should use theMultiPartParser
parser.- Since this parser
media_type
with any content type match, soFileUploadParser
usually it should be the only parser set on the API view. FileUploadParser
Django follows the standardFILE_UPLOAD_HANDLERS
settings andrequest.upload_handlers
properties.
Basic usage examples:
# views.py class FileUploadView(views.APIView): parser_classes = (FileUploadParser,) def put(self, request, filename, format=None): file_obj = request.data['file'] # ... # do some stuff with uploaded file # ... return Response(status=204) # urls.py urlpatterns = [ # ... url(r'^upload/(?P<filename>[^/]+)$', FileUploadView.as_view()) ]
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): """ 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']) # Note that this code is extracted from Django's handling of # file uploads in MultiPartParser. 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 # See if the handler will want to take care of the parsing. 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]}) # This is the standard case. 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 = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) filename_parm = disposition[1] if 'filename*' in filename_parm: return self.get_encoded_filename(filename_parm) return force_text(filename_parm['filename']) except (AttributeError, KeyError, ValueError): pass def get_encoded_filename(self, filename_parm): """ Handle encoded filenames per RFC6266. See also: https://tools.ietf.org/html/rfc2231#section-4 """ encoded_filename = force_text(filename_parm['filename*']) try: charset, lang, filename = encoded_filename.split('\'', 2) filename = urlparse.unquote(filename) except (ValueError, LookupError): filename = force_text(filename_parm['filename']) return filename
Second, the source
First, the request came or will come to dispatch method APIView, which prior to the certification , permission has been described in detail:
1、dispatch
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs #rest-framework重构request对象 request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method #这里和CBV一样进行方法的分发 if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
2、initialize_request
In the dispatch method of the request has been restructured in initialize_request method:
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, #传入原request parsers=self.get_parsers(),#解析器 authenticators=self.get_authenticators(), #认证 negotiator=self.get_content_negotiator(), parser_context=parser_context )
Obviously in the request object encapsulates parsers reconstructed, and it is a list obtained by the method of one get_parsers parser object:
def get_parsers(self): """ Instantiates and returns the list of parsers that this view can use. """ return [parser() for parser in self.parser_classes]
It can be seen, request the object has been packaged in the parser object list provided in the view.
3、request.data
Client over the data sent from the receiving request.data using internal parse view rest framework request object (located rest_framework.request.Request) of data:
class Request(object): ... @property def data(self): if not _hasattr(self, '_full_data'): self._load_data_and_files() return self._full_data ...
4、_load_data_and_files
class Request(object): ... def _load_data_and_files(self): """ Parses the request content into `self.data`. """ if not _hasattr(self, '_data'): #解析request.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 a form media type, copy data & files refs to the underlying # http request so that closable objects are handled appropriately. if is_form_media_type(self.content_type): self._request._post = self.POST self._request._files = self.FILES ...
5、_parse
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 request.POST has been accessed in middleware, and a method='POST' # request was made with 'multipart/form-data', then the request stream # will already have been exhausted. 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: # If we get an exception during parsing, fill in empty data and # re-raise. Ensures we don't simply repeat the error when # attempting to render the browsable renderer response, or when # logging the request or similar. self._data = QueryDict('', encoding=self._request._encoding) self._files = MultiValueDict() self._full_data = self._data raise # Parser classes may return the raw data, or a # DataAndFiles object. Unpack the result as required. try: return (parsed.data, parsed.files) except AttributeError: empty_files = MultiValueDict() return (parsed, empty_files)
Get media_type configuration parser, the parser then compared by selecting the appropriate request header content_type
parser = self.negotiator.select_parser(self, self.parsers)
6、select_parser
class DefaultContentNegotiation(BaseContentNegotiation): settings = api_settings def select_parser(self, request, parsers): """ Given a list of parsers and a media type, return the appropriate parser to handle the incoming request. """ for parser in parsers: #通过比较请求头中的content_type,匹配合适的解析器 if media_type_matches(parser.media_type, request.content_type): return parser return None
7, for example JSONParser
Suppose the selected configuration is a view JSONParser resolver, then the method invokes the parser parser parses, parserd parameters received and returns parserd.data, finally self._data = parserd.data
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): """ 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' % six.text_type(exc))
to sum up:
- The dispatch APIView parser object view of the package into the configuration request object
- request.data trigger parsing
- Acquisition request content_type, and matched with the parser media_type, select the appropriate resolver
- The method of using the parser parser parses
- The analytical results are assigned to request._data
Reference documents: https://q1mi.github.io/Django-REST-framework-documentation/api-guide/parsers_zh/#_1