Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 23 additions & 29 deletions src/hip/_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"Connection",
"ResponseStream",
"Transport",
"open_connection",
]


Expand Down Expand Up @@ -80,11 +79,30 @@ def stream(


class Connection(Transport):
def __init__(self, stream: Stream, origin: URL | str):
self._stream = stream
def __init__(self, origin: URL | str, stream: Stream | None = None):
self._stream = Stream() if (stream is None) else stream
self._origin = URL(origin) if not isinstance(origin, URL) else origin
self._request_lock = Lock()
self._parser = HTTPParser(stream, mode="CLIENT")
self._parser = HTTPParser(self._stream, mode="CLIENT")

# Ensure valid URL
if self._origin.scheme not in ("http", "https"):
raise ValueError(
f"Invalid URL. Expected http:// or https://, got {self._origin.scheme}://"
)

def open(self, backend: NetworkBackend | None = None) -> None:
backend = NetworkBackend() if (backend is None) else backend

scheme = self._origin.scheme
host = self._origin.host
port = self._origin.port or {"http": 80, "https": 443}[scheme]

if scheme == "https":
self._stream = backend.connect_tls(host, port)
else:
self._stream = backend.connect(host, port)
self._parser = HTTPParser(self._stream, mode="CLIENT")

# API for connection pool management...
def origin(self) -> URL:
Expand Down Expand Up @@ -180,6 +198,7 @@ def __repr__(self) -> str:
return f"<Connection [{self._origin} {self.description()}]>"

def __enter__(self) -> "Self":
self.open()
return self

def __exit__(
Expand All @@ -189,28 +208,3 @@ def __exit__(
traceback: types.TracebackType | None = None,
):
self.close()


def open_connection(
url: URL | str,
hostname: str = "",
backend: NetworkBackend | None = None,
) -> Connection:

if isinstance(url, str):
url = URL(url)

if url.scheme not in ("http", "https"):
raise ValueError("URL scheme must be 'http://' or 'https://'.")
if backend is None:
backend = NetworkBackend()

host = url.host
port = url.port or {"http": 80, "https": 443}[url.scheme]

if url.scheme == "https":
stream = backend.connect_tls(host, port, hostname)
else:
stream = backend.connect(host, port)

return Connection(stream, url)
7 changes: 3 additions & 4 deletions src/hip/_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Once we drop 3.10 support, we can use 'typing.Self'.
from typing_extensions import Self

from ._connection import Connection, ResponseStream, Transport, open_connection
from ._connection import Connection, ResponseStream, Transport
from ._request import Request
from ._urls import URL
from .backends.sync import NetworkBackend, Semaphore
Expand Down Expand Up @@ -53,9 +53,8 @@ def _get_connection(self, request: Request) -> "Connection":
return conn

# Or else create a new connection.
conn = open_connection(
origin, hostname=request.headers["Host"], backend=self._network_backend
)
conn = Connection(origin)
conn.open(backend=self._network_backend)
self._connections.append(conn)
return conn

Expand Down
3 changes: 1 addition & 2 deletions src/hip/transports.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from ._connection import Connection, ResponseStream, Transport, open_connection
from ._connection import Connection, ResponseStream, Transport
from ._pool import ConnectionPool

__all__ = [
"Connection",
"ConnectionPool",
"ResponseStream",
"Transport",
"open_connection",
]
24 changes: 11 additions & 13 deletions tests/test_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_connection_pool_should_have_managed_lifespan(server):


def test_connection_request(server):
with hip.transports.open_connection(server.url) as conn:
with hip.transports.Connection(server.url) as conn:
assert repr(conn) == f"<Connection [{server.url} idle]>"

r = conn.request("GET", "/")
Expand All @@ -90,7 +90,7 @@ def test_connection_request(server):


def test_connection_stream(server):
with hip.transports.open_connection(server.url) as conn:
with hip.transports.Connection(server.url) as conn:
assert repr(conn) == f"<Connection [{server.url} idle]>"
with conn.stream("GET", "/") as r:
assert r.status_code == 200
Expand All @@ -101,7 +101,7 @@ def test_connection_stream(server):

def test_connection_stream_read_in_chunks(server):
with (
hip.transports.open_connection(server.url) as conn,
hip.transports.Connection(server.url) as conn,
conn.stream("GET", "/") as response,
):
assert response.read(5) == b"Hello"
Expand All @@ -110,19 +110,17 @@ def test_connection_stream_read_in_chunks(server):


class TLSBackend:
def connect_tls(self, host, port, hostname):
self.connect_tls_args = (host, port, hostname)
def connect_tls(self, host, port):
self.connect_tls_args = (host, port)
return hip.Binary(b"").open()


def test_open_https_connection():
backend = TLSBackend()

with hip.transports.open_connection(
"https://example.com",
hostname="example.com",
backend=backend,
):
pass

assert backend.connect_tls_args == ("example.com", 443, "example.com")
conn = hip.transports.Connection("https://example.com")
conn.open(backend=backend)
try:
assert backend.connect_tls_args == ("example.com", 443)
finally:
conn.close()
Loading