(message)
| 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 |
| 988 | |
| 989 | # No-body responses (HEAD/1xx/204/304) must not carry a body. |
| 990 | # Always drop Transfer-Encoding (no chunked terminator without |
| 991 | # a body); Content-Length is dropped only for statuses that |
| 992 | # forbid it per RFC 9110 §6.4.2 (1xx, 204). HEAD and 304 keep |
| 993 | # an app-supplied Content-Length. |
| 994 | omits_body = self._response_omits_body(request.method, response_status) |
| 995 | if omits_body and (has_content_length or has_transfer_encoding): |
| 996 | response_headers = self._strip_body_framing_headers( |
| 997 | response_headers, response_status |
| 998 | ) |
| 999 | if self._response_forbids_content_length(response_status): |
| 1000 | has_content_length = False |
| 1001 | has_transfer_encoding = False |
| 1002 | use_chunked = False |
| 1003 | |
| 1004 | # Use chunked encoding for HTTP/1.1 streaming responses without Content-Length. |
| 1005 | # Skip when the response cannot carry a body or when Transfer-Encoding was |
| 1006 | # already set by the framework. |
| 1007 | needs_chunked = ( |
| 1008 | not has_content_length |
no test coverage detected