From 741c8340be22d2e3109a43aaa4008c493731691e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 21 Jul 2020 15:09:38 +0100 Subject: [PATCH 1/2] Lazy h2 imports --- httpcore/_async/connection.py | 16 +++++++++------- httpcore/_async/http.py | 35 +++++++++++++++++++++++++++++++++++ httpcore/_async/http11.py | 13 ++++++++++--- httpcore/_async/http2.py | 19 ++++++++++--------- httpcore/_sync/connection.py | 16 +++++++++------- httpcore/_sync/http.py | 35 +++++++++++++++++++++++++++++++++++ httpcore/_sync/http11.py | 13 ++++++++++--- httpcore/_sync/http2.py | 19 ++++++++++--------- 8 files changed, 128 insertions(+), 38 deletions(-) create mode 100644 httpcore/_async/http.py create mode 100644 httpcore/_sync/http.py diff --git a/httpcore/_async/connection.py b/httpcore/_async/connection.py index daf1a0a7..24c53bf6 100644 --- a/httpcore/_async/connection.py +++ b/httpcore/_async/connection.py @@ -1,5 +1,5 @@ from ssl import SSLContext -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Tuple from .._backends.auto import AsyncLock, AsyncSocketStream, AutoBackend from .._types import URL, Headers, Origin, TimeoutDict @@ -10,8 +10,7 @@ ConnectionState, NewConnectionRequired, ) -from .http2 import AsyncHTTP2Connection -from .http11 import AsyncHTTP11Connection +from .http import BaseHTTPConnection logger = get_logger(__name__) @@ -32,7 +31,7 @@ def __init__( if self.http2: self.ssl_context.set_alpn_protocols(["http/1.1", "h2"]) - self.connection: Union[None, AsyncHTTP11Connection, AsyncHTTP2Connection] = None + self.connection: Optional[BaseHTTPConnection] = None self.is_http11 = False self.is_http2 = False self.connect_failed = False @@ -110,11 +109,15 @@ def _create_connection(self, socket: AsyncSocketStream) -> None: "create_connection socket=%r http_version=%r", socket, http_version ) if http_version == "HTTP/2": + from .http2 import AsyncHTTP2Connection + self.is_http2 = True self.connection = AsyncHTTP2Connection( socket=socket, backend=self.backend, ssl_context=self.ssl_context ) else: + from .http11 import AsyncHTTP11Connection + self.is_http11 = True self.connection = AsyncHTTP11Connection( socket=socket, ssl_context=self.ssl_context @@ -126,7 +129,7 @@ def state(self) -> ConnectionState: return ConnectionState.CLOSED elif self.connection is None: return ConnectionState.PENDING - return self.connection.state + return self.connection.get_state() def is_connection_dropped(self) -> bool: return self.connection is not None and self.connection.is_connection_dropped() @@ -138,9 +141,8 @@ def mark_as_ready(self) -> None: async def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None: if self.connection is not None: logger.trace("start_tls hostname=%r timeout=%r", hostname, timeout) - await self.connection.start_tls(hostname, timeout) + self.socket = await self.connection.start_tls(hostname, timeout) logger.trace("start_tls complete hostname=%r timeout=%r", hostname, timeout) - self.socket = self.connection.socket async def aclose(self) -> None: async with self.request_lock: diff --git a/httpcore/_async/http.py b/httpcore/_async/http.py new file mode 100644 index 00000000..a207588a --- /dev/null +++ b/httpcore/_async/http.py @@ -0,0 +1,35 @@ +from .._backends.auto import AsyncSocketStream +from .._types import TimeoutDict +from .base import AsyncHTTPTransport, ConnectionState + + +class BaseHTTPConnection(AsyncHTTPTransport): + def info(self) -> str: + raise NotImplementedError() # pragma: nocover + + def get_state(self) -> ConnectionState: + """ + Return the current state. + """ + raise NotImplementedError() # pragma: nocover + + def mark_as_ready(self) -> None: + """ + The connection has been acquired from the pool, and the state + should reflect that. + """ + raise NotImplementedError() # pragma: nocover + + def is_connection_dropped(self) -> bool: + """ + Return 'True' if the connection has been dropped by the remote end. + """ + raise NotImplementedError() # pragma: nocover + + async def start_tls( + self, hostname: bytes, timeout: TimeoutDict = None + ) -> AsyncSocketStream: + """ + Upgrade the underlying socket to TLS. + """ + raise NotImplementedError() # pragma: nocover diff --git a/httpcore/_async/http11.py b/httpcore/_async/http11.py index 0ea49993..72c0aca8 100644 --- a/httpcore/_async/http11.py +++ b/httpcore/_async/http11.py @@ -7,7 +7,8 @@ from .._exceptions import ProtocolError, map_exceptions from .._types import URL, Headers, TimeoutDict from .._utils import get_logger -from .base import AsyncByteStream, AsyncHTTPTransport, ConnectionState +from .base import AsyncByteStream, ConnectionState +from .http import BaseHTTPConnection H11Event = Union[ h11.Request, @@ -21,7 +22,7 @@ logger = get_logger(__name__) -class AsyncHTTP11Connection(AsyncHTTPTransport): +class AsyncHTTP11Connection(BaseHTTPConnection): READ_NUM_BYTES = 4096 def __init__( @@ -40,6 +41,9 @@ def __repr__(self) -> str: def info(self) -> str: return f"HTTP/1.1, {self.state.name}" + def get_state(self) -> ConnectionState: + return self.state + def mark_as_ready(self) -> None: if self.state == ConnectionState.IDLE: self.state = ConnectionState.READY @@ -72,9 +76,12 @@ async def request( ) return (http_version, status_code, reason_phrase, headers, stream) - async def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None: + async def start_tls( + self, hostname: bytes, timeout: TimeoutDict = None + ) -> AsyncSocketStream: timeout = {} if timeout is None else timeout self.socket = await self.socket.start_tls(hostname, self.ssl_context, timeout) + return self.socket async def _send_request( self, method: bytes, url: URL, headers: Headers, timeout: TimeoutDict, diff --git a/httpcore/_async/http2.py b/httpcore/_async/http2.py index deb919de..d8b86bf8 100644 --- a/httpcore/_async/http2.py +++ b/httpcore/_async/http2.py @@ -12,12 +12,8 @@ from .._exceptions import PoolTimeout, ProtocolError from .._types import URL, Headers, TimeoutDict from .._utils import get_logger -from .base import ( - AsyncByteStream, - AsyncHTTPTransport, - ConnectionState, - NewConnectionRequired, -) +from .base import AsyncByteStream, ConnectionState, NewConnectionRequired +from .http import BaseHTTPConnection logger = get_logger(__name__) @@ -29,7 +25,7 @@ def get_reason_phrase(status_code: int) -> bytes: return b"" -class AsyncHTTP2Connection(AsyncHTTPTransport): +class AsyncHTTP2Connection(BaseHTTPConnection): READ_NUM_BYTES = 4096 CONFIG = H2Configuration(validate_inbound_headers=False) @@ -84,8 +80,13 @@ def max_streams_semaphore(self) -> AsyncSemaphore: ) return self._max_streams_semaphore - async def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None: - pass + async def start_tls( + self, hostname: bytes, timeout: TimeoutDict = None + ) -> AsyncSocketStream: + raise NotImplementedError("TLS upgrade not supported on HTTP/2 connections.") + + def get_state(self) -> ConnectionState: + return self.state def mark_as_ready(self) -> None: if self.state == ConnectionState.IDLE: diff --git a/httpcore/_sync/connection.py b/httpcore/_sync/connection.py index a3b45094..b81a35c9 100644 --- a/httpcore/_sync/connection.py +++ b/httpcore/_sync/connection.py @@ -1,5 +1,5 @@ from ssl import SSLContext -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Tuple from .._backends.auto import SyncLock, SyncSocketStream, SyncBackend from .._types import URL, Headers, Origin, TimeoutDict @@ -10,8 +10,7 @@ ConnectionState, NewConnectionRequired, ) -from .http2 import SyncHTTP2Connection -from .http11 import SyncHTTP11Connection +from .http import BaseHTTPConnection logger = get_logger(__name__) @@ -32,7 +31,7 @@ def __init__( if self.http2: self.ssl_context.set_alpn_protocols(["http/1.1", "h2"]) - self.connection: Union[None, SyncHTTP11Connection, SyncHTTP2Connection] = None + self.connection: Optional[BaseHTTPConnection] = None self.is_http11 = False self.is_http2 = False self.connect_failed = False @@ -110,11 +109,15 @@ def _create_connection(self, socket: SyncSocketStream) -> None: "create_connection socket=%r http_version=%r", socket, http_version ) if http_version == "HTTP/2": + from .http2 import SyncHTTP2Connection + self.is_http2 = True self.connection = SyncHTTP2Connection( socket=socket, backend=self.backend, ssl_context=self.ssl_context ) else: + from .http11 import SyncHTTP11Connection + self.is_http11 = True self.connection = SyncHTTP11Connection( socket=socket, ssl_context=self.ssl_context @@ -126,7 +129,7 @@ def state(self) -> ConnectionState: return ConnectionState.CLOSED elif self.connection is None: return ConnectionState.PENDING - return self.connection.state + return self.connection.get_state() def is_connection_dropped(self) -> bool: return self.connection is not None and self.connection.is_connection_dropped() @@ -138,9 +141,8 @@ def mark_as_ready(self) -> None: def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None: if self.connection is not None: logger.trace("start_tls hostname=%r timeout=%r", hostname, timeout) - self.connection.start_tls(hostname, timeout) + self.socket = self.connection.start_tls(hostname, timeout) logger.trace("start_tls complete hostname=%r timeout=%r", hostname, timeout) - self.socket = self.connection.socket def close(self) -> None: with self.request_lock: diff --git a/httpcore/_sync/http.py b/httpcore/_sync/http.py new file mode 100644 index 00000000..1fb81073 --- /dev/null +++ b/httpcore/_sync/http.py @@ -0,0 +1,35 @@ +from .._backends.auto import SyncSocketStream +from .._types import TimeoutDict +from .base import SyncHTTPTransport, ConnectionState + + +class BaseHTTPConnection(SyncHTTPTransport): + def info(self) -> str: + raise NotImplementedError() # pragma: nocover + + def get_state(self) -> ConnectionState: + """ + Return the current state. + """ + raise NotImplementedError() # pragma: nocover + + def mark_as_ready(self) -> None: + """ + The connection has been acquired from the pool, and the state + should reflect that. + """ + raise NotImplementedError() # pragma: nocover + + def is_connection_dropped(self) -> bool: + """ + Return 'True' if the connection has been dropped by the remote end. + """ + raise NotImplementedError() # pragma: nocover + + def start_tls( + self, hostname: bytes, timeout: TimeoutDict = None + ) -> SyncSocketStream: + """ + Upgrade the underlying socket to TLS. + """ + raise NotImplementedError() # pragma: nocover diff --git a/httpcore/_sync/http11.py b/httpcore/_sync/http11.py index 22bc504e..0d175e52 100644 --- a/httpcore/_sync/http11.py +++ b/httpcore/_sync/http11.py @@ -7,7 +7,8 @@ from .._exceptions import ProtocolError, map_exceptions from .._types import URL, Headers, TimeoutDict from .._utils import get_logger -from .base import SyncByteStream, SyncHTTPTransport, ConnectionState +from .base import SyncByteStream, ConnectionState +from .http import BaseHTTPConnection H11Event = Union[ h11.Request, @@ -21,7 +22,7 @@ logger = get_logger(__name__) -class SyncHTTP11Connection(SyncHTTPTransport): +class SyncHTTP11Connection(BaseHTTPConnection): READ_NUM_BYTES = 4096 def __init__( @@ -40,6 +41,9 @@ def __repr__(self) -> str: def info(self) -> str: return f"HTTP/1.1, {self.state.name}" + def get_state(self) -> ConnectionState: + return self.state + def mark_as_ready(self) -> None: if self.state == ConnectionState.IDLE: self.state = ConnectionState.READY @@ -72,9 +76,12 @@ def request( ) return (http_version, status_code, reason_phrase, headers, stream) - def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None: + def start_tls( + self, hostname: bytes, timeout: TimeoutDict = None + ) -> SyncSocketStream: timeout = {} if timeout is None else timeout self.socket = self.socket.start_tls(hostname, self.ssl_context, timeout) + return self.socket def _send_request( self, method: bytes, url: URL, headers: Headers, timeout: TimeoutDict, diff --git a/httpcore/_sync/http2.py b/httpcore/_sync/http2.py index 2220fa87..c0e3dca9 100644 --- a/httpcore/_sync/http2.py +++ b/httpcore/_sync/http2.py @@ -12,12 +12,8 @@ from .._exceptions import PoolTimeout, ProtocolError from .._types import URL, Headers, TimeoutDict from .._utils import get_logger -from .base import ( - SyncByteStream, - SyncHTTPTransport, - ConnectionState, - NewConnectionRequired, -) +from .base import SyncByteStream, ConnectionState, NewConnectionRequired +from .http import BaseHTTPConnection logger = get_logger(__name__) @@ -29,7 +25,7 @@ def get_reason_phrase(status_code: int) -> bytes: return b"" -class SyncHTTP2Connection(SyncHTTPTransport): +class SyncHTTP2Connection(BaseHTTPConnection): READ_NUM_BYTES = 4096 CONFIG = H2Configuration(validate_inbound_headers=False) @@ -84,8 +80,13 @@ def max_streams_semaphore(self) -> SyncSemaphore: ) return self._max_streams_semaphore - def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None: - pass + def start_tls( + self, hostname: bytes, timeout: TimeoutDict = None + ) -> SyncSocketStream: + raise NotImplementedError("TLS upgrade not supported on HTTP/2 connections.") + + def get_state(self) -> ConnectionState: + return self.state def mark_as_ready(self) -> None: if self.state == ConnectionState.IDLE: From f739ffaf41a80b492b353612702daad95cb0c67c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 2 Aug 2020 11:03:00 +0100 Subject: [PATCH 2/2] Use AsyncBaseHTTPConnection/SyncBaseHTTPConnection naming --- httpcore/_async/connection.py | 4 ++-- httpcore/_async/http.py | 2 +- httpcore/_async/http11.py | 4 ++-- httpcore/_async/http2.py | 4 ++-- httpcore/_sync/connection.py | 4 ++-- httpcore/_sync/http.py | 2 +- httpcore/_sync/http11.py | 4 ++-- httpcore/_sync/http2.py | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/httpcore/_async/connection.py b/httpcore/_async/connection.py index 24c53bf6..9679d6eb 100644 --- a/httpcore/_async/connection.py +++ b/httpcore/_async/connection.py @@ -10,7 +10,7 @@ ConnectionState, NewConnectionRequired, ) -from .http import BaseHTTPConnection +from .http import AsyncBaseHTTPConnection logger = get_logger(__name__) @@ -31,7 +31,7 @@ def __init__( if self.http2: self.ssl_context.set_alpn_protocols(["http/1.1", "h2"]) - self.connection: Optional[BaseHTTPConnection] = None + self.connection: Optional[AsyncBaseHTTPConnection] = None self.is_http11 = False self.is_http2 = False self.connect_failed = False diff --git a/httpcore/_async/http.py b/httpcore/_async/http.py index a207588a..aa66ec4a 100644 --- a/httpcore/_async/http.py +++ b/httpcore/_async/http.py @@ -3,7 +3,7 @@ from .base import AsyncHTTPTransport, ConnectionState -class BaseHTTPConnection(AsyncHTTPTransport): +class AsyncBaseHTTPConnection(AsyncHTTPTransport): def info(self) -> str: raise NotImplementedError() # pragma: nocover diff --git a/httpcore/_async/http11.py b/httpcore/_async/http11.py index 72c0aca8..80204075 100644 --- a/httpcore/_async/http11.py +++ b/httpcore/_async/http11.py @@ -8,7 +8,7 @@ from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import AsyncByteStream, ConnectionState -from .http import BaseHTTPConnection +from .http import AsyncBaseHTTPConnection H11Event = Union[ h11.Request, @@ -22,7 +22,7 @@ logger = get_logger(__name__) -class AsyncHTTP11Connection(BaseHTTPConnection): +class AsyncHTTP11Connection(AsyncBaseHTTPConnection): READ_NUM_BYTES = 4096 def __init__( diff --git a/httpcore/_async/http2.py b/httpcore/_async/http2.py index d8b86bf8..e13d9f38 100644 --- a/httpcore/_async/http2.py +++ b/httpcore/_async/http2.py @@ -13,7 +13,7 @@ from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import AsyncByteStream, ConnectionState, NewConnectionRequired -from .http import BaseHTTPConnection +from .http import AsyncBaseHTTPConnection logger = get_logger(__name__) @@ -25,7 +25,7 @@ def get_reason_phrase(status_code: int) -> bytes: return b"" -class AsyncHTTP2Connection(BaseHTTPConnection): +class AsyncHTTP2Connection(AsyncBaseHTTPConnection): READ_NUM_BYTES = 4096 CONFIG = H2Configuration(validate_inbound_headers=False) diff --git a/httpcore/_sync/connection.py b/httpcore/_sync/connection.py index b81a35c9..24fc3982 100644 --- a/httpcore/_sync/connection.py +++ b/httpcore/_sync/connection.py @@ -10,7 +10,7 @@ ConnectionState, NewConnectionRequired, ) -from .http import BaseHTTPConnection +from .http import SyncBaseHTTPConnection logger = get_logger(__name__) @@ -31,7 +31,7 @@ def __init__( if self.http2: self.ssl_context.set_alpn_protocols(["http/1.1", "h2"]) - self.connection: Optional[BaseHTTPConnection] = None + self.connection: Optional[SyncBaseHTTPConnection] = None self.is_http11 = False self.is_http2 = False self.connect_failed = False diff --git a/httpcore/_sync/http.py b/httpcore/_sync/http.py index 1fb81073..0befd6a3 100644 --- a/httpcore/_sync/http.py +++ b/httpcore/_sync/http.py @@ -3,7 +3,7 @@ from .base import SyncHTTPTransport, ConnectionState -class BaseHTTPConnection(SyncHTTPTransport): +class SyncBaseHTTPConnection(SyncHTTPTransport): def info(self) -> str: raise NotImplementedError() # pragma: nocover diff --git a/httpcore/_sync/http11.py b/httpcore/_sync/http11.py index 0d175e52..537b55ab 100644 --- a/httpcore/_sync/http11.py +++ b/httpcore/_sync/http11.py @@ -8,7 +8,7 @@ from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import SyncByteStream, ConnectionState -from .http import BaseHTTPConnection +from .http import SyncBaseHTTPConnection H11Event = Union[ h11.Request, @@ -22,7 +22,7 @@ logger = get_logger(__name__) -class SyncHTTP11Connection(BaseHTTPConnection): +class SyncHTTP11Connection(SyncBaseHTTPConnection): READ_NUM_BYTES = 4096 def __init__( diff --git a/httpcore/_sync/http2.py b/httpcore/_sync/http2.py index c0e3dca9..2c420527 100644 --- a/httpcore/_sync/http2.py +++ b/httpcore/_sync/http2.py @@ -13,7 +13,7 @@ from .._types import URL, Headers, TimeoutDict from .._utils import get_logger from .base import SyncByteStream, ConnectionState, NewConnectionRequired -from .http import BaseHTTPConnection +from .http import SyncBaseHTTPConnection logger = get_logger(__name__) @@ -25,7 +25,7 @@ def get_reason_phrase(status_code: int) -> bytes: return b"" -class SyncHTTP2Connection(BaseHTTPConnection): +class SyncHTTP2Connection(SyncBaseHTTPConnection): READ_NUM_BYTES = 4096 CONFIG = H2Configuration(validate_inbound_headers=False)