def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): # This is all returned in a kind-of funky way # We tried to make this as fast as possible in pure python timeout_header = b'' if keep_alive and keep_alive_timeout is not None: timeout_header = b'Keep-Alive: %d\r\n' % keep_alive_timeout body = b'' if has_message_body(self.status): body = self.body self.headers['Content-Length'] = self.headers.get( 'Content-Length', len(self.body)) self.headers['Content-Type'] = self.headers.get( 'Content-Type', self.content_type) if self.status in (304, 412): self.headers = remove_entity_headers(self.headers) headers = self._parse_headers() if self.status is 200: status = b'OK' else: status = STATUS_CODES.get(self.status, b'UNKNOWN RESPONSE') return (b'HTTP/%b %d %b\r\n' b'Connection: %b\r\n' b'%b' b'%b\r\n' b'%b') % (version.encode(), self.status, status, b'keep-alive' if keep_alive else b'close', timeout_header, headers, body)
def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): # This is all returned in a kind-of funky way # We tried to make this as fast as possible in pure python timeout_header = b"" if keep_alive and keep_alive_timeout is not None: timeout_header = b"Keep-Alive: %d\r\n" % keep_alive_timeout body = b"" if has_message_body(self.status): body = self.body self.headers["Content-Length"] = self.headers.get( "Content-Length", len(self.body)) # self.headers get priority over content_type if self.content_type and "Content-Type" not in self.headers: self.headers["Content-Type"] = self.content_type if self.status in (304, 412): self.headers = remove_entity_headers(self.headers) headers = self._parse_headers() status = STATUS_CODES.get(self.status, b"UNKNOWN RESPONSE") return (b"HTTP/%b %d %b\r\n" b"Connection: %b\r\n" b"%b" b"%b\r\n" b"%b") % ( version.encode(), self.status, status, b"keep-alive" if keep_alive else b"close", timeout_header, headers, body, )
def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): body = b"" if has_message_body(self.status): body = self.body self.headers["Content-Length"] = self.headers.get( "Content-Length", len(self.body)) return self.get_headers(version, keep_alive, keep_alive_timeout, body)
def test_has_message_body(): tests = ( (100, False), (102, False), (204, False), (200, True), (304, False), (400, True), ) for status_code, expected in tests: assert helpers.has_message_body(status_code) is expected
def processed_headers(self): """Obtain a list of header tuples encoded in bytes for sending. Add and remove headers based on status and content_type. """ # TODO: Make a blacklist set of header names and then filter with that if self.status in (304, 412): # Not Modified, Precondition Failed self.headers = remove_entity_headers(self.headers) if has_message_body(self.status): self.headers.setdefault("content-type", self.content_type) # Encode headers into bytes return ((name.encode("ascii"), f"{value}".encode(errors="surrogateescape")) for name, value in self.headers.items())
async def http1_response_header(self, data: bytes, end_stream: bool) -> None: res = self.response # Compatibility with simple response body if not data and getattr(res, "body", None): data, end_stream = res.body, True # type: ignore size = len(data) headers = res.headers status = res.status if not isinstance(status, int) or status < 200: raise RuntimeError(f"Invalid response status {status!r}") if not has_message_body(status): # Header-only response status self.response_func = None if (data or not end_stream or "content-length" in headers or "transfer-encoding" in headers): data, size, end_stream = b"", 0, True headers.pop("content-length", None) headers.pop("transfer-encoding", None) logger.warning( f"Message body set in response on {self.request.path}. " f"A {status} response may only have headers, no body.") elif self.head_only and "content-length" in headers: self.response_func = None elif end_stream: # Non-streaming response (all in one block) headers["content-length"] = size self.response_func = None elif "content-length" in headers: # Streaming response with size known in advance self.response_bytes_left = int(headers["content-length"]) - size self.response_func = self.http1_response_normal else: # Length not known, use chunked encoding headers["transfer-encoding"] = "chunked" data = b"%x\r\n%b\r\n" % (size, data) if size else b"" self.response_func = self.http1_response_chunked if self.head_only: # Head request: don't send body data = b"" self.response_func = self.head_response_ignored headers["connection"] = "keep-alive" if self.keep_alive else "close" ret = format_http1_response(status, res.processed_headers) if data: ret += data # Send a 100-continue if expected and not Expectation Failed if self.expecting_continue: self.expecting_continue = False if status != 417: ret = HTTP_CONTINUE + ret # Send response if self.protocol.access_log: self.log_response() await self._send(ret) self.stage = Stage.IDLE if end_stream else Stage.RESPONSE