This commit is contained in:
Patrick J. McNerthney 2026-02-25 00:00:23 +09:00 committed by GitHub
commit 2f1968ee2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 129 additions and 0 deletions

View File

@ -35,6 +35,19 @@ connecting via a Unix Domain Socket that is only available via this low-level AP
{"ID": "...", "Containers": 4, "Images": 74, ...}
```
Another advanced configuration is supplying a custom httpcore [Network Backend](https://www.encode.io/httpcore/network-backends/).
```pycon
>>> import httpcore
>>> import httpx
>>> backend = httpcore.MockBackend([b"HTTP/1.1 200 OK\r\n\r\nHello, World!"])
>>> transport = httpx.HTTPTransport(network_backend=backend)
>>> client = httpx.Client(transport=transport)
>>> response = client.get("http://network-backend")
>>> reposne.text
'Hello, World!'
```
## WSGI Transport
You can configure an `httpx` client to call directly into a Python web application using the WSGI protocol.

View File

@ -6,6 +6,7 @@ The following additional keyword arguments are currently supported by httpcore..
* uds: str
* local_address: str
* retries: int
* network_backend: httpcore.NetworkBackend
Example usages...
@ -22,6 +23,14 @@ client = httpx.Client(transport=transport)
# Using advanced httpcore configuration, with unix domain sockets.
transport = httpx.HTTPTransport(uds="socket.uds")
client = httpx.Client(transport=transport)
# Using advanced httpcore configuration, with custom network backend.
import httpcore
backend = backend = httpcore.MockBackend([b"HTTP/1.1 200 OK\r\n\r\nHello, World!"])
transport = httpx.HTTPTransport(network_backend=backend)
client = httpx.Client(transport=transport)
response = client.get("http://network-backend")
content = response.text
"""
from __future__ import annotations
@ -33,6 +42,8 @@ from types import TracebackType
if typing.TYPE_CHECKING:
import ssl # pragma: no cover
import httpcore # pragma: no cover
import httpx # pragma: no cover
from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context
@ -146,6 +157,7 @@ class HTTPTransport(BaseTransport):
local_address: str | None = None,
retries: int = 0,
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
network_backend: httpcore.NetworkBackend | None = None,
) -> None:
import httpcore
@ -164,6 +176,7 @@ class HTTPTransport(BaseTransport):
local_address=local_address,
retries=retries,
socket_options=socket_options,
network_backend=network_backend,
)
elif proxy.url.scheme in ("http", "https"):
self._pool = httpcore.HTTPProxy(
@ -183,6 +196,7 @@ class HTTPTransport(BaseTransport):
http1=http1,
http2=http2,
socket_options=socket_options,
network_backend=network_backend,
)
elif proxy.url.scheme in ("socks5", "socks5h"):
try:
@ -207,6 +221,7 @@ class HTTPTransport(BaseTransport):
keepalive_expiry=limits.keepalive_expiry,
http1=http1,
http2=http2,
network_backend=network_backend,
)
else: # pragma: no cover
raise ValueError(
@ -290,6 +305,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
local_address: str | None = None,
retries: int = 0,
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
network_backend: httpcore.AsyncNetworkBackend | None = None,
) -> None:
import httpcore
@ -308,6 +324,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
local_address=local_address,
retries=retries,
socket_options=socket_options,
network_backend=network_backend,
)
elif proxy.url.scheme in ("http", "https"):
self._pool = httpcore.AsyncHTTPProxy(
@ -327,6 +344,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
http1=http1,
http2=http2,
socket_options=socket_options,
network_backend=network_backend,
)
elif proxy.url.scheme in ("socks5", "socks5h"):
try:
@ -351,6 +369,7 @@ class AsyncHTTPTransport(AsyncBaseTransport):
keepalive_expiry=limits.keepalive_expiry,
http1=http1,
http2=http2,
network_backend=network_backend,
)
else: # pragma: no cover
raise ValueError(

View File

@ -0,0 +1,97 @@
import typing
import httpcore
import pytest
import httpx
def test_network_backend():
class Backend(httpcore.NetworkBackend):
def connect_tcp(
self,
host: str,
port: int,
timeout: typing.Optional[float] = None,
local_address: typing.Optional[str] = None,
socket_options: typing.Optional[
typing.Iterable[httpcore.SOCKET_OPTION]
] = None,
) -> httpcore.NetworkStream:
return Stream()
class Stream(httpcore.NetworkStream):
body = b"\r\n".join(
[
b"HTTP/1.1 200 OK",
b"",
b"From Backend!",
]
)
def read(self, max_bytes: int, timeout: typing.Optional[float] = None) -> bytes:
body = self.body
if body:
self.body = b""
return body
def write(self, buffer: bytes, timeout: typing.Optional[float] = None) -> None:
pass
def close(self) -> None:
pass
backend = Backend()
transport = httpx.HTTPTransport(network_backend=backend)
with httpx.Client(transport=transport) as client:
response = client.get("http://www.example.org")
assert response.status_code == 200
assert response.text == "From Backend!"
@pytest.mark.anyio
async def test_async_network_backend():
class AsyncBackend(httpcore.AsyncNetworkBackend):
async def connect_tcp(
self,
host: str,
port: int,
timeout: typing.Optional[float] = None,
local_address: typing.Optional[str] = None,
socket_options: typing.Optional[
typing.Iterable[httpcore.SOCKET_OPTION]
] = None,
) -> httpcore.AsyncNetworkStream:
return AsyncStream()
class AsyncStream(httpcore.AsyncNetworkStream):
body = b"\r\n".join(
[
b"HTTP/1.1 200 OK",
b"",
b"From Async Backend!",
]
)
async def read(
self, max_bytes: int, timeout: typing.Optional[float] = None
) -> bytes:
body = self.body
if body:
self.body = b""
return body
async def write(
self, buffer: bytes, timeout: typing.Optional[float] = None
) -> None:
pass
async def aclose(self) -> None:
pass
backend = AsyncBackend()
transport = httpx.AsyncHTTPTransport(network_backend=backend)
async with httpx.AsyncClient(transport=transport) as client:
response = await client.get("http://www.example.org")
assert response.status_code == 200
assert response.text == "From Async Backend!"