Connections

The mechanics of sending HTTP requests is dealt with by the ConnectionPool and Connection classes.

We can introspect a Client instance to get some visibility onto the state of the connection pool.

httpx
>>> with httpx.Client() as cli
>>>     urls = [
...         "https://www.wikipedia.org/",
...         "https://www.theguardian.com/",
...         "https://news.ycombinator.com/",
...     ]
...     for url in urls:
...         cli.get(url)
...      print(cli.transport)
...      # <ConnectionPool [3 idle]>
...      print(cli.transport.connections)
...      # [
...      #     <Connection "https://www.wikipedia.org/" IDLE>,
...      #     <Connection "https://www.theguardian.com/" IDLE>,
...      #     <Connection "https://news.ycombinator.com/" IDLE>,
...      # ]

Understanding the stack

The Client class is responsible for handling redirects and cookies.

It also ensures that outgoing requests include a default set of headers such as User-Agent and Accept-Encoding.

httpx
>>> with httpx.Client() as cli:
>>>     r = cli.request("GET", "https://www.example.com/")

The Client class sends requests using a ConnectionPool, which is responsible for managing a pool of HTTP connections. This ensures quicker and more efficient use of resources than opening and closing a TCP connection with each request. The connection pool also handles HTTP proxying if required.

A single connection pool is able to handle multiple concurrent requests, with locking in place to ensure that the pool does not become over-saturated.

httpx
>>> with httpx.ConnectionPool() as pool:
>>>     r = pool.request("GET", "https://www.example.com/")

Individual HTTP connections can be managed directly with the Connection class. A single connection can only handle requests sequentially. Locking is provided to ensure that requests are strictly queued sequentially.

httpx
>>> with httpx.open_connection("https://www.example.com/") as conn:
>>>     r = conn.request("GET", "/")

Protocol handling is dealt with using the h11 package, a rigorously designed HTTP/1.1 implementation which follows the Sans-IO design pattern.

The NetworkBackend is responsible for managing the TCP stream, providing a raw byte-wise interface onto the underlying socket.


ConnectionPool

httpx
>>> pool = httpx.ConnectionPool()
>>> pool
<ConnectionPool [0 active]>

.request(method, url, headers=None, content=None)

httpx
>>> with httpx.ConnectionPool() as pool:
>>>     res = pool.request("GET", "https://www.example.com")
>>>     res, pool
<Response [200 OK]>, <ConnectionPool [1 idle]>

.stream(method, url, headers=None, content=None)

httpx
>>> with httpx.ConnectionPool() as pool:
>>>     with pool.stream("GET", "https://www.example.com") as res:
>>>         res, pool
<Response [200 OK]>, <ConnectionPool [1 active]>

.send(request)

httpx
>>> with httpx.ConnectionPool() as pool:
>>>     req = httpx.Request("GET", "https://www.example.com")
>>>     with pool.send(req) as res:
>>>         res.read()
>>>     res, pool
<Response [200 OK]>, <ConnectionPool [1 idle]>

.close()

httpx
>>> with httpx.ConnectionPool() as pool:
>>>     pool.close()
<ConnectionPool [0 active]>

Connection

TODO


Protocol upgrades

httpx
with httpx.open_connection("https://www.example.com/") as conn:
    with conn.upgrade("GET", "/feed", {"Upgrade": "WebSocket"}) as stream:
        ...

<Connection “https://www.example.com/feed” WEBSOCKET>

Proxy CONNECT requests

httpx
with httpx.open_connection("http://127.0.0.1:8080") as conn:
    with conn.upgrade("CONNECT", "www.encode.io:443") as stream:
        stream.start_tls(ctx, hostname="www.encode.io")
        ...

<Connection "https://www.encode.io" VIA “http://127.0.0.1:8080” CONNECT>


Describe .send() rationale, and the Transport interface.


Content Types Low Level Networking