Skip to content

Commit 623f3c8

Browse files
Optional HTTP/2 (#121)
* Lazy h2 imports * Use AsyncBaseHTTPConnection/SyncBaseHTTPConnection naming Co-authored-by: Florimond Manca <florimond.manca@gmail.com>
1 parent 23e3cbe commit 623f3c8

File tree

8 files changed

+128
-38
lines changed

8 files changed

+128
-38
lines changed

httpcore/_async/connection.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from ssl import SSLContext
2-
from typing import List, Optional, Tuple, Union
2+
from typing import List, Optional, Tuple
33

44
from .._backends.auto import AsyncLock, AsyncSocketStream, AutoBackend
55
from .._types import URL, Headers, Origin, TimeoutDict
@@ -10,8 +10,7 @@
1010
ConnectionState,
1111
NewConnectionRequired,
1212
)
13-
from .http2 import AsyncHTTP2Connection
14-
from .http11 import AsyncHTTP11Connection
13+
from .http import AsyncBaseHTTPConnection
1514

1615
logger = get_logger(__name__)
1716

@@ -32,7 +31,7 @@ def __init__(
3231
if self.http2:
3332
self.ssl_context.set_alpn_protocols(["http/1.1", "h2"])
3433

35-
self.connection: Union[None, AsyncHTTP11Connection, AsyncHTTP2Connection] = None
34+
self.connection: Optional[AsyncBaseHTTPConnection] = None
3635
self.is_http11 = False
3736
self.is_http2 = False
3837
self.connect_failed = False
@@ -110,11 +109,15 @@ def _create_connection(self, socket: AsyncSocketStream) -> None:
110109
"create_connection socket=%r http_version=%r", socket, http_version
111110
)
112111
if http_version == "HTTP/2":
112+
from .http2 import AsyncHTTP2Connection
113+
113114
self.is_http2 = True
114115
self.connection = AsyncHTTP2Connection(
115116
socket=socket, backend=self.backend, ssl_context=self.ssl_context
116117
)
117118
else:
119+
from .http11 import AsyncHTTP11Connection
120+
118121
self.is_http11 = True
119122
self.connection = AsyncHTTP11Connection(
120123
socket=socket, ssl_context=self.ssl_context
@@ -126,7 +129,7 @@ def state(self) -> ConnectionState:
126129
return ConnectionState.CLOSED
127130
elif self.connection is None:
128131
return ConnectionState.PENDING
129-
return self.connection.state
132+
return self.connection.get_state()
130133

131134
def is_connection_dropped(self) -> bool:
132135
return self.connection is not None and self.connection.is_connection_dropped()
@@ -138,9 +141,8 @@ def mark_as_ready(self) -> None:
138141
async def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None:
139142
if self.connection is not None:
140143
logger.trace("start_tls hostname=%r timeout=%r", hostname, timeout)
141-
await self.connection.start_tls(hostname, timeout)
144+
self.socket = await self.connection.start_tls(hostname, timeout)
142145
logger.trace("start_tls complete hostname=%r timeout=%r", hostname, timeout)
143-
self.socket = self.connection.socket
144146

145147
async def aclose(self) -> None:
146148
async with self.request_lock:

httpcore/_async/http.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from .._backends.auto import AsyncSocketStream
2+
from .._types import TimeoutDict
3+
from .base import AsyncHTTPTransport, ConnectionState
4+
5+
6+
class AsyncBaseHTTPConnection(AsyncHTTPTransport):
7+
def info(self) -> str:
8+
raise NotImplementedError() # pragma: nocover
9+
10+
def get_state(self) -> ConnectionState:
11+
"""
12+
Return the current state.
13+
"""
14+
raise NotImplementedError() # pragma: nocover
15+
16+
def mark_as_ready(self) -> None:
17+
"""
18+
The connection has been acquired from the pool, and the state
19+
should reflect that.
20+
"""
21+
raise NotImplementedError() # pragma: nocover
22+
23+
def is_connection_dropped(self) -> bool:
24+
"""
25+
Return 'True' if the connection has been dropped by the remote end.
26+
"""
27+
raise NotImplementedError() # pragma: nocover
28+
29+
async def start_tls(
30+
self, hostname: bytes, timeout: TimeoutDict = None
31+
) -> AsyncSocketStream:
32+
"""
33+
Upgrade the underlying socket to TLS.
34+
"""
35+
raise NotImplementedError() # pragma: nocover

httpcore/_async/http11.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from .._exceptions import ProtocolError, map_exceptions
88
from .._types import URL, Headers, TimeoutDict
99
from .._utils import get_logger
10-
from .base import AsyncByteStream, AsyncHTTPTransport, ConnectionState
10+
from .base import AsyncByteStream, ConnectionState
11+
from .http import AsyncBaseHTTPConnection
1112

1213
H11Event = Union[
1314
h11.Request,
@@ -21,7 +22,7 @@
2122
logger = get_logger(__name__)
2223

2324

24-
class AsyncHTTP11Connection(AsyncHTTPTransport):
25+
class AsyncHTTP11Connection(AsyncBaseHTTPConnection):
2526
READ_NUM_BYTES = 4096
2627

2728
def __init__(
@@ -40,6 +41,9 @@ def __repr__(self) -> str:
4041
def info(self) -> str:
4142
return f"HTTP/1.1, {self.state.name}"
4243

44+
def get_state(self) -> ConnectionState:
45+
return self.state
46+
4347
def mark_as_ready(self) -> None:
4448
if self.state == ConnectionState.IDLE:
4549
self.state = ConnectionState.READY
@@ -72,9 +76,12 @@ async def request(
7276
)
7377
return (http_version, status_code, reason_phrase, headers, stream)
7478

75-
async def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None:
79+
async def start_tls(
80+
self, hostname: bytes, timeout: TimeoutDict = None
81+
) -> AsyncSocketStream:
7682
timeout = {} if timeout is None else timeout
7783
self.socket = await self.socket.start_tls(hostname, self.ssl_context, timeout)
84+
return self.socket
7885

7986
async def _send_request(
8087
self, method: bytes, url: URL, headers: Headers, timeout: TimeoutDict,

httpcore/_async/http2.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@
1212
from .._exceptions import PoolTimeout, ProtocolError
1313
from .._types import URL, Headers, TimeoutDict
1414
from .._utils import get_logger
15-
from .base import (
16-
AsyncByteStream,
17-
AsyncHTTPTransport,
18-
ConnectionState,
19-
NewConnectionRequired,
20-
)
15+
from .base import AsyncByteStream, ConnectionState, NewConnectionRequired
16+
from .http import AsyncBaseHTTPConnection
2117

2218
logger = get_logger(__name__)
2319

@@ -29,7 +25,7 @@ def get_reason_phrase(status_code: int) -> bytes:
2925
return b""
3026

3127

32-
class AsyncHTTP2Connection(AsyncHTTPTransport):
28+
class AsyncHTTP2Connection(AsyncBaseHTTPConnection):
3329
READ_NUM_BYTES = 4096
3430
CONFIG = H2Configuration(validate_inbound_headers=False)
3531

@@ -84,8 +80,13 @@ def max_streams_semaphore(self) -> AsyncSemaphore:
8480
)
8581
return self._max_streams_semaphore
8682

87-
async def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None:
88-
pass
83+
async def start_tls(
84+
self, hostname: bytes, timeout: TimeoutDict = None
85+
) -> AsyncSocketStream:
86+
raise NotImplementedError("TLS upgrade not supported on HTTP/2 connections.")
87+
88+
def get_state(self) -> ConnectionState:
89+
return self.state
8990

9091
def mark_as_ready(self) -> None:
9192
if self.state == ConnectionState.IDLE:

httpcore/_sync/connection.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from ssl import SSLContext
2-
from typing import List, Optional, Tuple, Union
2+
from typing import List, Optional, Tuple
33

44
from .._backends.auto import SyncLock, SyncSocketStream, SyncBackend
55
from .._types import URL, Headers, Origin, TimeoutDict
@@ -10,8 +10,7 @@
1010
ConnectionState,
1111
NewConnectionRequired,
1212
)
13-
from .http2 import SyncHTTP2Connection
14-
from .http11 import SyncHTTP11Connection
13+
from .http import SyncBaseHTTPConnection
1514

1615
logger = get_logger(__name__)
1716

@@ -32,7 +31,7 @@ def __init__(
3231
if self.http2:
3332
self.ssl_context.set_alpn_protocols(["http/1.1", "h2"])
3433

35-
self.connection: Union[None, SyncHTTP11Connection, SyncHTTP2Connection] = None
34+
self.connection: Optional[SyncBaseHTTPConnection] = None
3635
self.is_http11 = False
3736
self.is_http2 = False
3837
self.connect_failed = False
@@ -110,11 +109,15 @@ def _create_connection(self, socket: SyncSocketStream) -> None:
110109
"create_connection socket=%r http_version=%r", socket, http_version
111110
)
112111
if http_version == "HTTP/2":
112+
from .http2 import SyncHTTP2Connection
113+
113114
self.is_http2 = True
114115
self.connection = SyncHTTP2Connection(
115116
socket=socket, backend=self.backend, ssl_context=self.ssl_context
116117
)
117118
else:
119+
from .http11 import SyncHTTP11Connection
120+
118121
self.is_http11 = True
119122
self.connection = SyncHTTP11Connection(
120123
socket=socket, ssl_context=self.ssl_context
@@ -126,7 +129,7 @@ def state(self) -> ConnectionState:
126129
return ConnectionState.CLOSED
127130
elif self.connection is None:
128131
return ConnectionState.PENDING
129-
return self.connection.state
132+
return self.connection.get_state()
130133

131134
def is_connection_dropped(self) -> bool:
132135
return self.connection is not None and self.connection.is_connection_dropped()
@@ -138,9 +141,8 @@ def mark_as_ready(self) -> None:
138141
def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None:
139142
if self.connection is not None:
140143
logger.trace("start_tls hostname=%r timeout=%r", hostname, timeout)
141-
self.connection.start_tls(hostname, timeout)
144+
self.socket = self.connection.start_tls(hostname, timeout)
142145
logger.trace("start_tls complete hostname=%r timeout=%r", hostname, timeout)
143-
self.socket = self.connection.socket
144146

145147
def close(self) -> None:
146148
with self.request_lock:

httpcore/_sync/http.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from .._backends.auto import SyncSocketStream
2+
from .._types import TimeoutDict
3+
from .base import SyncHTTPTransport, ConnectionState
4+
5+
6+
class SyncBaseHTTPConnection(SyncHTTPTransport):
7+
def info(self) -> str:
8+
raise NotImplementedError() # pragma: nocover
9+
10+
def get_state(self) -> ConnectionState:
11+
"""
12+
Return the current state.
13+
"""
14+
raise NotImplementedError() # pragma: nocover
15+
16+
def mark_as_ready(self) -> None:
17+
"""
18+
The connection has been acquired from the pool, and the state
19+
should reflect that.
20+
"""
21+
raise NotImplementedError() # pragma: nocover
22+
23+
def is_connection_dropped(self) -> bool:
24+
"""
25+
Return 'True' if the connection has been dropped by the remote end.
26+
"""
27+
raise NotImplementedError() # pragma: nocover
28+
29+
def start_tls(
30+
self, hostname: bytes, timeout: TimeoutDict = None
31+
) -> SyncSocketStream:
32+
"""
33+
Upgrade the underlying socket to TLS.
34+
"""
35+
raise NotImplementedError() # pragma: nocover

httpcore/_sync/http11.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from .._exceptions import ProtocolError, map_exceptions
88
from .._types import URL, Headers, TimeoutDict
99
from .._utils import get_logger
10-
from .base import SyncByteStream, SyncHTTPTransport, ConnectionState
10+
from .base import SyncByteStream, ConnectionState
11+
from .http import SyncBaseHTTPConnection
1112

1213
H11Event = Union[
1314
h11.Request,
@@ -21,7 +22,7 @@
2122
logger = get_logger(__name__)
2223

2324

24-
class SyncHTTP11Connection(SyncHTTPTransport):
25+
class SyncHTTP11Connection(SyncBaseHTTPConnection):
2526
READ_NUM_BYTES = 4096
2627

2728
def __init__(
@@ -40,6 +41,9 @@ def __repr__(self) -> str:
4041
def info(self) -> str:
4142
return f"HTTP/1.1, {self.state.name}"
4243

44+
def get_state(self) -> ConnectionState:
45+
return self.state
46+
4347
def mark_as_ready(self) -> None:
4448
if self.state == ConnectionState.IDLE:
4549
self.state = ConnectionState.READY
@@ -72,9 +76,12 @@ def request(
7276
)
7377
return (http_version, status_code, reason_phrase, headers, stream)
7478

75-
def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None:
79+
def start_tls(
80+
self, hostname: bytes, timeout: TimeoutDict = None
81+
) -> SyncSocketStream:
7682
timeout = {} if timeout is None else timeout
7783
self.socket = self.socket.start_tls(hostname, self.ssl_context, timeout)
84+
return self.socket
7885

7986
def _send_request(
8087
self, method: bytes, url: URL, headers: Headers, timeout: TimeoutDict,

httpcore/_sync/http2.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@
1212
from .._exceptions import PoolTimeout, ProtocolError
1313
from .._types import URL, Headers, TimeoutDict
1414
from .._utils import get_logger
15-
from .base import (
16-
SyncByteStream,
17-
SyncHTTPTransport,
18-
ConnectionState,
19-
NewConnectionRequired,
20-
)
15+
from .base import SyncByteStream, ConnectionState, NewConnectionRequired
16+
from .http import SyncBaseHTTPConnection
2117

2218
logger = get_logger(__name__)
2319

@@ -29,7 +25,7 @@ def get_reason_phrase(status_code: int) -> bytes:
2925
return b""
3026

3127

32-
class SyncHTTP2Connection(SyncHTTPTransport):
28+
class SyncHTTP2Connection(SyncBaseHTTPConnection):
3329
READ_NUM_BYTES = 4096
3430
CONFIG = H2Configuration(validate_inbound_headers=False)
3531

@@ -84,8 +80,13 @@ def max_streams_semaphore(self) -> SyncSemaphore:
8480
)
8581
return self._max_streams_semaphore
8682

87-
def start_tls(self, hostname: bytes, timeout: TimeoutDict = None) -> None:
88-
pass
83+
def start_tls(
84+
self, hostname: bytes, timeout: TimeoutDict = None
85+
) -> SyncSocketStream:
86+
raise NotImplementedError("TLS upgrade not supported on HTTP/2 connections.")
87+
88+
def get_state(self) -> ConnectionState:
89+
return self.state
8990

9091
def mark_as_ready(self) -> None:
9192
if self.state == ConnectionState.IDLE:

0 commit comments

Comments
 (0)