diff --git a/setup.cfg b/setup.cfg index 33c3e95a3..6a22be6b1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,7 +82,7 @@ plugins = [coverage:report] precision = 2 -fail_under = 97.69 +fail_under = 97.63 show_missing = true skip_covered = true exclude_lines = diff --git a/uvicorn/protocols/http/h11_impl.py b/uvicorn/protocols/http/h11_impl.py index 5fff70bb7..821416f4b 100644 --- a/uvicorn/protocols/http/h11_impl.py +++ b/uvicorn/protocols/http/h11_impl.py @@ -204,12 +204,18 @@ def handle_events(self) -> None: "headers": self.headers, } + is_upgrade, is_http2 = False, False for name, value in self.headers: if name == b"connection": tokens = [token.lower().strip() for token in value.split(b",")] if b"upgrade" in tokens: - self.handle_upgrade(event) - return + is_upgrade = True + elif name == b"upgrade" and value == b"h2c": + is_http2 = True + + if is_upgrade and not is_http2: + self.handle_upgrade(event) + return # Handle 503 responses when 'limit_concurrency' is exceeded. if self.limit_concurrency is not None and ( diff --git a/uvicorn/protocols/http/httptools_impl.py b/uvicorn/protocols/http/httptools_impl.py index f018c59af..82ee00924 100644 --- a/uvicorn/protocols/http/httptools_impl.py +++ b/uvicorn/protocols/http/httptools_impl.py @@ -104,6 +104,7 @@ def __init__( self.scope: HTTPScope = None # type: ignore[assignment] self.headers: List[Tuple[bytes, bytes]] = None # type: ignore[assignment] self.expect_100_continue = False + self.is_http2 = False self.cycle: RequestResponseCycle = None # type: ignore[assignment] # Protocol interface @@ -168,7 +169,9 @@ def handle_upgrade(self) -> None: if name == b"upgrade": upgrade_value = value.lower() - if upgrade_value != b"websocket" or self.ws_protocol_class is None: + if upgrade_value == b"h2c": + return + elif upgrade_value != b"websocket" or self.ws_protocol_class is None: msg = "Unsupported upgrade request." self.logger.warning(msg) from uvicorn.protocols.websockets.auto import AutoWebSocketsProtocol @@ -236,6 +239,8 @@ def on_header(self, name: bytes, value: bytes) -> None: name = name.lower() if name == b"expect" and value.lower() == b"100-continue": self.expect_100_continue = True + elif name == b"upgrade" and value == b"h2c": + self.is_http2 = True self.headers.append((name, value)) def on_headers_complete(self) -> None: @@ -244,7 +249,7 @@ def on_headers_complete(self) -> None: self.scope["method"] = method.decode("ascii") if http_version != "1.1": self.scope["http_version"] = http_version - if self.parser.should_upgrade(): + if self.parser.should_upgrade() and not self.is_http2: return parsed_url = httptools.parse_url(self.url) raw_path = parsed_url.path @@ -291,7 +296,9 @@ def on_headers_complete(self) -> None: self.pipeline.appendleft((self.cycle, app)) def on_body(self, body: bytes) -> None: - if self.parser.should_upgrade() or self.cycle.response_complete: + if ( + self.parser.should_upgrade() and not self.is_http2 + ) or self.cycle.response_complete: return self.cycle.body += body if len(self.cycle.body) > HIGH_WATER_LIMIT: @@ -299,7 +306,9 @@ def on_body(self, body: bytes) -> None: self.cycle.message_event.set() def on_message_complete(self) -> None: - if self.parser.should_upgrade() or self.cycle.response_complete: + if ( + self.parser.should_upgrade() and not self.is_http2 + ) or self.cycle.response_complete: return self.cycle.more_body = False self.cycle.message_event.set()