Build request from callback parser state. Args: parser: H1CProtocol or PythonProtocol instance is_ssl: Whether connection is SSL/TLS Returns: CallbackRequest instance
(cls, parser, is_ssl=False)
| 861 | |
| 862 | @classmethod |
| 863 | def from_parser(cls, parser, is_ssl=False): |
| 864 | """Build request from callback parser state. |
| 865 | |
| 866 | Args: |
| 867 | parser: H1CProtocol or PythonProtocol instance |
| 868 | is_ssl: Whether connection is SSL/TLS |
| 869 | |
| 870 | Returns: |
| 871 | CallbackRequest instance |
| 872 | """ |
| 873 | from urllib.parse import unquote_to_bytes |
| 874 | |
| 875 | req = cls() |
| 876 | req.method = parser.method.decode('ascii') |
| 877 | |
| 878 | # Parse path and query from URL |
| 879 | # Per ASGI spec: |
| 880 | # - path: percent-decoded UTF-8 string |
| 881 | # - raw_path: original bytes as received |
| 882 | raw_url = parser.path |
| 883 | if b'?' in raw_url: |
| 884 | path_part, query_part = raw_url.split(b'?', 1) |
| 885 | req.raw_path = path_part # Store original bytes |
| 886 | req.path = unquote_to_bytes(path_part).decode('utf-8', errors='replace') |
| 887 | req.query = query_part.decode('latin-1') |
| 888 | else: |
| 889 | req.raw_path = raw_url # Store original bytes |
| 890 | req.path = unquote_to_bytes(raw_url).decode('utf-8', errors='replace') |
| 891 | req.query = '' |
| 892 | |
| 893 | req.uri = raw_url.decode('latin-1') |
| 894 | req.fragment = '' |
| 895 | req.version = parser.http_version |
| 896 | |
| 897 | # Headers - store both bytes (for ASGI scope) and strings (for compatibility) |
| 898 | # Use asgi_headers (lowercase names) if available (fast parser >= 0.6.2), |
| 899 | # otherwise fall back to headers (Python parser already uses lowercase) |
| 900 | req.headers_bytes = list(getattr(parser, 'asgi_headers', None) or parser.headers) |
| 901 | req.headers = [ |
| 902 | (n.decode('latin-1').upper(), v.decode('latin-1')) |
| 903 | for n, v in parser.headers |
| 904 | ] |
| 905 | |
| 906 | req.scheme = 'https' if is_ssl else 'http' |
| 907 | req.content_length = parser.content_length or 0 |
| 908 | req.chunked = parser.is_chunked |
| 909 | req.must_close = not parser.should_keep_alive |
| 910 | |
| 911 | # Check for Expect: 100-continue |
| 912 | for name, value in parser.headers: |
| 913 | if name == b'expect' and value.lower() == b'100-continue': |
| 914 | req._expect_100_continue = True |
| 915 | break |
| 916 | |
| 917 | return req |
| 918 | |
| 919 | def should_close(self): |
| 920 | """Check if connection should be closed after this request.""" |