Handle a single HTTP request.
(self, request, sockname, peername)
| 928 | await ws_protocol.run() |
| 929 | |
| 930 | async def _handle_http_request(self, request, sockname, peername): |
| 931 | """Handle a single HTTP request.""" |
| 932 | scope = self._build_http_scope(request, sockname, peername) |
| 933 | response_started = False |
| 934 | response_complete = False |
| 935 | exc_to_raise = None |
| 936 | use_chunked = False |
| 937 | omits_body = False |
| 938 | omits_body_warned = False |
| 939 | |
| 940 | # Reset response buffer for write batching |
| 941 | self._response_buffer = None |
| 942 | |
| 943 | # Response tracking for access logging |
| 944 | response_status = 500 |
| 945 | response_headers = [] |
| 946 | response_sent = 0 |
| 947 | |
| 948 | # Use body receiver created in _on_headers_complete (receives data via callbacks) |
| 949 | body_receiver = self._body_receiver |
| 950 | |
| 951 | async def send(message): |
| 952 | nonlocal response_started, response_complete, exc_to_raise |
| 953 | nonlocal response_status, response_headers, response_sent, use_chunked, omits_body |
| 954 | nonlocal omits_body_warned |
| 955 | |
| 956 | # If client disconnected, silently ignore send attempts |
| 957 | # This allows apps to finish cleanup without errors |
| 958 | if self._closed: |
| 959 | return |
| 960 | |
| 961 | msg_type = message["type"] |
| 962 | |
| 963 | if msg_type == "http.response.informational": |
| 964 | # Handle informational responses (1xx) like 103 Early Hints |
| 965 | info_status = message.get("status") |
| 966 | info_headers = message.get("headers", []) |
| 967 | self._send_informational(info_status, info_headers, request) |
| 968 | return |
| 969 | |
| 970 | if msg_type == "http.response.start": |
| 971 | if response_started: |
| 972 | exc_to_raise = RuntimeError("Response already started") |
| 973 | return |
| 974 | response_started = True |
| 975 | response_status = message["status"] |
| 976 | response_headers = message.get("headers", []) |
| 977 | |
| 978 | # Check if Content-Length or Transfer-Encoding is present |
| 979 | has_content_length = False |
| 980 | has_transfer_encoding = False |
| 981 | for name, _ in response_headers: |
| 982 | name_lower = name.lower() if isinstance(name, str) else name.lower() |
| 983 | if name_lower in (b"content-length", "content-length"): |
| 984 | has_content_length = True |
| 985 | elif name_lower in (b"transfer-encoding", "transfer-encoding"): |
| 986 | has_transfer_encoding = True |
| 987 | use_chunked = True # Framework already set chunked encoding |