Compare commits
10 Commits
master
...
network-op
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd83dac70 | ||
|
|
b9d9045a8b | ||
|
|
a35ffe6b54 | ||
|
|
489dda15ce | ||
|
|
6ac2101706 | ||
|
|
157f122742 | ||
|
|
febc827299 | ||
|
|
ee7eb664e1 | ||
|
|
913ea35324 | ||
|
|
83b5e4bf13 |
44
docs/advanced/network-options.md
Normal file
44
docs/advanced/network-options.md
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
There are several advanced network options that are made available through the `httpx.NetworkOptions` configuration class.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Configure an HTTPTransport with some specific network options.
|
||||||
|
network_options = httpx.NetworkOptions(
|
||||||
|
connection_retries=1,
|
||||||
|
local_address="0.0.0.0",
|
||||||
|
)
|
||||||
|
transport = httpx.HTTPTransport(network_options=network_options)
|
||||||
|
|
||||||
|
# Instantiate a client with the configured transport.
|
||||||
|
client = httpx.Client(transport=transport)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The options available on this class are...
|
||||||
|
|
||||||
|
### `connection_retries`
|
||||||
|
|
||||||
|
Configure a number of retries that may be attempted when initially establishing a TCP connection. Defaults to `0`.
|
||||||
|
|
||||||
|
### `local_address`
|
||||||
|
|
||||||
|
Configure the local address that the socket should be bound too. The most common usage is for enforcing binding to either IPv4 `local_address="0.0.0.0"` or IPv6 `local_address="::"`.
|
||||||
|
|
||||||
|
### `socket_options`
|
||||||
|
|
||||||
|
Configure the list of socket options to be applied to the underlying sockets used for network connections.
|
||||||
|
For example, you can use it to explicitly specify which network interface should be used for the connection in this manner:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
socket_options = [(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, b"ETH999")]
|
||||||
|
|
||||||
|
network_options = httpx.NetworkOptions(
|
||||||
|
socket_options=socket_options
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `uds`
|
||||||
|
|
||||||
|
Connect to a Unix Domain Socket, rather than over the network. Should be a string providing the path to the UDS.
|
||||||
@ -63,6 +63,7 @@ __all__ = [
|
|||||||
"MockTransport",
|
"MockTransport",
|
||||||
"NetRCAuth",
|
"NetRCAuth",
|
||||||
"NetworkError",
|
"NetworkError",
|
||||||
|
"NetworkOptions",
|
||||||
"options",
|
"options",
|
||||||
"patch",
|
"patch",
|
||||||
"PoolTimeout",
|
"PoolTimeout",
|
||||||
|
|||||||
@ -14,7 +14,13 @@ from ._types import CertTypes, HeaderTypes, TimeoutTypes, URLTypes, VerifyTypes
|
|||||||
from ._urls import URL
|
from ._urls import URL
|
||||||
from ._utils import get_ca_bundle_from_env
|
from ._utils import get_ca_bundle_from_env
|
||||||
|
|
||||||
__all__ = ["Limits", "Proxy", "Timeout", "create_ssl_context"]
|
__all__ = ["Limits", "Proxy", "Timeout", "NetworkOptions", "create_ssl_context"]
|
||||||
|
|
||||||
|
SOCKET_OPTION = typing.Union[
|
||||||
|
typing.Tuple[int, int, int],
|
||||||
|
typing.Tuple[int, int, typing.Union[bytes, bytearray]],
|
||||||
|
typing.Tuple[int, int, None, int],
|
||||||
|
]
|
||||||
|
|
||||||
DEFAULT_CIPHERS = ":".join(
|
DEFAULT_CIPHERS = ":".join(
|
||||||
[
|
[
|
||||||
@ -367,6 +373,37 @@ class Proxy:
|
|||||||
return f"Proxy({url_str}{auth_str}{headers_str})"
|
return f"Proxy({url_str}{auth_str}{headers_str})"
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkOptions:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
connection_retries: int = 0,
|
||||||
|
local_address: typing.Optional[str] = None,
|
||||||
|
socket_options: typing.Optional[typing.Iterable[SOCKET_OPTION]] = None,
|
||||||
|
uds: typing.Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
self.connection_retries = connection_retries
|
||||||
|
self.local_address = local_address
|
||||||
|
self.socket_options = socket_options
|
||||||
|
self.uds = uds
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
defaults = {
|
||||||
|
"connection_retries": 0,
|
||||||
|
"local_address": None,
|
||||||
|
"socket_options": None,
|
||||||
|
"uds": None,
|
||||||
|
}
|
||||||
|
params = ", ".join(
|
||||||
|
[
|
||||||
|
f"{attr}={getattr(self, attr)!r}"
|
||||||
|
for attr, default in defaults.items()
|
||||||
|
if getattr(self, attr) != default
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return f"NetworkOptions({params})"
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0)
|
DEFAULT_TIMEOUT_CONFIG = Timeout(timeout=5.0)
|
||||||
DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20)
|
DEFAULT_LIMITS = Limits(max_connections=100, max_keepalive_connections=20)
|
||||||
|
DEFAULT_NETWORK_OPTIONS = NetworkOptions(connection_retries=0)
|
||||||
DEFAULT_MAX_REDIRECTS = 20
|
DEFAULT_MAX_REDIRECTS = 20
|
||||||
|
|||||||
@ -32,7 +32,14 @@ from types import TracebackType
|
|||||||
|
|
||||||
import httpcore
|
import httpcore
|
||||||
|
|
||||||
from .._config import DEFAULT_LIMITS, Limits, Proxy, create_ssl_context
|
from .._config import (
|
||||||
|
DEFAULT_LIMITS,
|
||||||
|
DEFAULT_NETWORK_OPTIONS,
|
||||||
|
Limits,
|
||||||
|
NetworkOptions,
|
||||||
|
Proxy,
|
||||||
|
create_ssl_context,
|
||||||
|
)
|
||||||
from .._exceptions import (
|
from .._exceptions import (
|
||||||
ConnectError,
|
ConnectError,
|
||||||
ConnectTimeout,
|
ConnectTimeout,
|
||||||
@ -57,14 +64,16 @@ from .base import AsyncBaseTransport, BaseTransport
|
|||||||
T = typing.TypeVar("T", bound="HTTPTransport")
|
T = typing.TypeVar("T", bound="HTTPTransport")
|
||||||
A = typing.TypeVar("A", bound="AsyncHTTPTransport")
|
A = typing.TypeVar("A", bound="AsyncHTTPTransport")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["AsyncHTTPTransport", "HTTPTransport"]
|
||||||
|
|
||||||
|
|
||||||
SOCKET_OPTION = typing.Union[
|
SOCKET_OPTION = typing.Union[
|
||||||
typing.Tuple[int, int, int],
|
typing.Tuple[int, int, int],
|
||||||
typing.Tuple[int, int, typing.Union[bytes, bytearray]],
|
typing.Tuple[int, int, typing.Union[bytes, bytearray]],
|
||||||
typing.Tuple[int, int, None, int],
|
typing.Tuple[int, int, None, int],
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ["AsyncHTTPTransport", "HTTPTransport"]
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def map_httpcore_exceptions() -> typing.Iterator[None]:
|
def map_httpcore_exceptions() -> typing.Iterator[None]:
|
||||||
@ -130,11 +139,8 @@ class HTTPTransport(BaseTransport):
|
|||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: typing.Optional[ProxyTypes] = None,
|
||||||
uds: str | None = None,
|
network_options: NetworkOptions = DEFAULT_NETWORK_OPTIONS,
|
||||||
local_address: str | None = None,
|
|
||||||
retries: int = 0,
|
|
||||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
||||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||||
@ -147,10 +153,10 @@ class HTTPTransport(BaseTransport):
|
|||||||
keepalive_expiry=limits.keepalive_expiry,
|
keepalive_expiry=limits.keepalive_expiry,
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
uds=uds,
|
uds=network_options.uds,
|
||||||
local_address=local_address,
|
local_address=network_options.local_address,
|
||||||
retries=retries,
|
retries=network_options.connection_retries,
|
||||||
socket_options=socket_options,
|
socket_options=network_options.socket_options,
|
||||||
)
|
)
|
||||||
elif proxy.url.scheme in ("http", "https"):
|
elif proxy.url.scheme in ("http", "https"):
|
||||||
self._pool = httpcore.HTTPProxy(
|
self._pool = httpcore.HTTPProxy(
|
||||||
@ -169,7 +175,10 @@ class HTTPTransport(BaseTransport):
|
|||||||
keepalive_expiry=limits.keepalive_expiry,
|
keepalive_expiry=limits.keepalive_expiry,
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
socket_options=socket_options,
|
uds=network_options.uds,
|
||||||
|
local_address=network_options.local_address,
|
||||||
|
retries=network_options.connection_retries,
|
||||||
|
socket_options=network_options.socket_options,
|
||||||
)
|
)
|
||||||
elif proxy.url.scheme == "socks5":
|
elif proxy.url.scheme == "socks5":
|
||||||
try:
|
try:
|
||||||
@ -271,11 +280,8 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
|||||||
http2: bool = False,
|
http2: bool = False,
|
||||||
limits: Limits = DEFAULT_LIMITS,
|
limits: Limits = DEFAULT_LIMITS,
|
||||||
trust_env: bool = True,
|
trust_env: bool = True,
|
||||||
proxy: ProxyTypes | None = None,
|
proxy: typing.Optional[ProxyTypes] = None,
|
||||||
uds: str | None = None,
|
network_options: NetworkOptions = DEFAULT_NETWORK_OPTIONS,
|
||||||
local_address: str | None = None,
|
|
||||||
retries: int = 0,
|
|
||||||
socket_options: typing.Iterable[SOCKET_OPTION] | None = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
ssl_context = create_ssl_context(verify=verify, cert=cert, trust_env=trust_env)
|
||||||
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
proxy = Proxy(url=proxy) if isinstance(proxy, (str, URL)) else proxy
|
||||||
@ -288,10 +294,10 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
|||||||
keepalive_expiry=limits.keepalive_expiry,
|
keepalive_expiry=limits.keepalive_expiry,
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
uds=uds,
|
uds=network_options.uds,
|
||||||
local_address=local_address,
|
local_address=network_options.local_address,
|
||||||
retries=retries,
|
retries=network_options.connection_retries,
|
||||||
socket_options=socket_options,
|
socket_options=network_options.socket_options,
|
||||||
)
|
)
|
||||||
elif proxy.url.scheme in ("http", "https"):
|
elif proxy.url.scheme in ("http", "https"):
|
||||||
self._pool = httpcore.AsyncHTTPProxy(
|
self._pool = httpcore.AsyncHTTPProxy(
|
||||||
@ -310,7 +316,10 @@ class AsyncHTTPTransport(AsyncBaseTransport):
|
|||||||
keepalive_expiry=limits.keepalive_expiry,
|
keepalive_expiry=limits.keepalive_expiry,
|
||||||
http1=http1,
|
http1=http1,
|
||||||
http2=http2,
|
http2=http2,
|
||||||
socket_options=socket_options,
|
uds=network_options.uds,
|
||||||
|
local_address=network_options.local_address,
|
||||||
|
retries=network_options.connection_retries,
|
||||||
|
socket_options=network_options.socket_options,
|
||||||
)
|
)
|
||||||
elif proxy.url.scheme == "socks5":
|
elif proxy.url.scheme == "socks5":
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -221,3 +221,18 @@ def test_proxy_with_auth_from_url():
|
|||||||
def test_invalid_proxy_scheme():
|
def test_invalid_proxy_scheme():
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
httpx.Proxy("invalid://example.com")
|
httpx.Proxy("invalid://example.com")
|
||||||
|
|
||||||
|
|
||||||
|
def test_network_options():
|
||||||
|
network_options = httpx.NetworkOptions()
|
||||||
|
assert repr(network_options) == "NetworkOptions()"
|
||||||
|
|
||||||
|
network_options = httpx.NetworkOptions(connection_retries=1)
|
||||||
|
assert repr(network_options) == "NetworkOptions(connection_retries=1)"
|
||||||
|
|
||||||
|
network_options = httpx.NetworkOptions(
|
||||||
|
connection_retries=1, local_address="0.0.0.0"
|
||||||
|
)
|
||||||
|
assert repr(network_options) == (
|
||||||
|
"NetworkOptions(connection_retries=1, local_address='0.0.0.0')"
|
||||||
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user