async def read_body(self, receive): """Reads a HTTP body from an ASGI connection.""" # Use the tempfile that auto rolls-over to a disk file as it fills up. body_file = tempfile.SpooledTemporaryFile( max_size=settings.FILE_UPLOAD_MAX_MEMORY_SIZE, mode="w+b") while True: message = await receive() if message["type"] == "http.disconnect": # Early client disconnect. raise RequestAborted() # Add a body chunk from the message, if provided. if "body" in message: body_file.write(message["body"]) # Quit out if that's the end. if not message.get("more_body", False): break body_file.seek(0) return body_file
def __init__(self, message): self.message = message self.reply_channel = self.message.reply_channel self._content_length = 0 self._post_parse_error = False self.resolver_match = None # Path info self.path = self.message['path'] self.script_name = self.message.get('root_path', '') if self.script_name: # TODO: Better is-prefix checking, slash handling? self.path_info = self.path[len(self.script_name):] else: self.path_info = self.path # HTTP basics self.method = self.message['method'].upper() self.META = { "REQUEST_METHOD": self.method, "QUERY_STRING": self.message.get('query_string', ''), "SCRIPT_NAME": self.script_name, # Old code will need these for a while "wsgi.multithread": True, "wsgi.multiprocess": True, } if self.message.get('client', None): self.META['REMOTE_ADDR'] = self.message['client'][0] self.META['REMOTE_HOST'] = self.META['REMOTE_ADDR'] self.META['REMOTE_PORT'] = self.message['client'][1] if self.message.get('server', None): self.META['SERVER_NAME'] = self.message['server'][0] self.META['SERVER_PORT'] = self.message['server'][1] # Handle old style-headers for a transition period if "headers" in self.message and isinstance(self.message['headers'], dict): self.message['headers'] = [ (x.encode("latin1"), y) for x, y in self.message['headers'].items() ] # Headers go into META for name, value in self.message.get('headers', []): name = name.decode("latin1") if name == "content-length": corrected_name = "CONTENT_LENGTH" elif name == "content-type": corrected_name = "CONTENT_TYPE" else: corrected_name = 'HTTP_%s' % name.upper().replace("-", "_") # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in case value = value.decode("latin1") if corrected_name in self.META: value = self.META[corrected_name] + "," + value.decode( "latin1") self.META[corrected_name] = value # Pull out request encoding if we find it if "CONTENT_TYPE" in self.META: self.content_type, self.content_params = cgi.parse_header( self.META["CONTENT_TYPE"]) if 'charset' in self.content_params: try: codecs.lookup(self.content_params['charset']) except LookupError: pass else: self.encoding = self.content_params['charset'] else: self.content_type, self.content_params = "", {} # Pull out content length info if self.META.get('CONTENT_LENGTH', None): try: self._content_length = int(self.META['CONTENT_LENGTH']) except (ValueError, TypeError): pass # Body handling self._body = message.get("body", b"") if message.get("body_channel", None): body_handle_start = time.time() while True: # Get the next chunk from the request body channel chunk = None while chunk is None: # If they take too long, raise request timeout and the handler # will turn it into a response if time.time( ) - body_handle_start > self.body_receive_timeout: raise RequestTimeout() _, chunk = message.channel_layer.receive_many( [message['body_channel']], block=True, ) # If chunk contains close, abort. if chunk.get("closed", False): raise RequestAborted() # Add content to body self._body += chunk.get("content", "") # Exit loop if this was the last if not chunk.get("more_content", False): break assert isinstance(self._body, six.binary_type), "Body is not bytes" # Add a stream-a-like for the body self._stream = BytesIO(self._body) # Other bits self.resolver_match = None