Skip to content

Migrate from httpx to httpx2 #4427

Open

Description

@r4victor

httpx sees little maintenance, and the ecosystem is moving to httpx2, Pydantic's maintained fork of httpx 0.28.1. openai 3.x already requires httpx2, starlette's TestClient prefers it, and opentelemetry-instrumentation-httpx supports it. dstack already uses httpx2 for gateway auth, for its pool fixes (#4338), while the shared proxy code still uses httpx, so the gateway runs both libraries and their exception classes don't mix.

We currently use three HTTP clients:

  • requests (+ requests-unixsocket): APIClient (CLI and Python API), backend API clients, runner/shim client, docker registry, REST plugin.
  • httpx: server and gateway proxy, gateway client, probes, router worker sync, job replica client, fluentbit, vastai.
  • httpx2: gateway auth and gateway server client.

Proposal

  1. Replace httpx with httpx2 everywhere. This is mostly mechanical, since httpx2 keeps the httpx API. It includes switching the OTel instrumentor to HTTPX2ClientInstrumentor, which HTTPXClientInstrumentor doesn't cover:

SQLAlchemyInstrumentor().instrument(engine=engine.sync_engine)
_register_db_span_renaming(engine.sync_engine)
HTTPXClientInstrumentor().instrument()
# unixsocket calls to runners should not be instrumented: every runner
# tunnel has a unique socket path that becomes the net.peer.name metric
# attribute, so the SDK would accumulate state for an unbounded number of series.
RequestsInstrumentor().instrument(excluded_urls=r"http\+unix://")

  1. Optionally, move our own requests code to httpx2 too, so we have one client for sync and async code, one TLS and proxy model, one exception hierarchy, one OTel instrumentor and one mocking approach. It would also drop requests-unixsocket, since httpx2 supports unix sockets natively. requests itself stays installed through third-party dependencies (gpuhunt, docker, kubernetes, google/azure SDKs, etc.).

TLS: certifi vs the system trust store

httpx2 no longer uses certifi. For verify=True (2.15.1+):

  • SSL_CERT_FILE / SSL_CERT_DIR replace the default store on all OSes.
  • macOS and Windows use the OS verifier via truststore (Keychain, Windows store).
  • Linux loads the system CA files (OpenSSL's compiled-in paths, then the common distro bundle locations) into a stdlib SSLContext once.
  • With no system CA bundle, all HTTPS fails with CERTIFICATE_VERIFY_FAILED. There is no certifi fallback.

User impact:

Options:

Python 3.10

httpx2 2.15+ requires Python 3.11, and on 3.10 the resolver stays on 2.14.0, which gets no further fixes. The migration should come with dropping Python 3.10, which depends on #4423.

Behaviour changes when moving off requests (optional part)

  • Timeouts: httpx2 defaults to a 5s timeout, while requests has none. APIClient sets no timeout today, so long calls would start failing unless we set one explicitly.

for _ in range(_MAX_RETRIES):
try:
# TODO: set adequate timeout here or everywhere the method is used
resp = self._s.request(method, f"{self._base_url}/{path}", **kwargs)
break
except requests.exceptions.ConnectionError as e:
self._logger.debug("Could not connect to server: %s", e)
time.sleep(_RETRY_INTERVAL)
else:
raise ClientError(f"Failed to connect to dstack server {self._base_url}")

  • Redirects: httpx2 doesn't follow them by default. APIClient treats any status below 400 as success, so a 3xx needs explicit handling.
  • Exceptions: requests.ConnectionError becomes httpx2.ConnectError / TransportError, and raise_for_status() also raises on 3xx. Python API users catching requests exceptions around dstack calls would break.
  • TLS errors and retries: httpcore2 doesn't map every TLS error, so ssl.SSLError can escape the retry loop. See ssl.SSLError during a request is no longer retried since 3.14.1 openai/openai-python#3969.
  • API differences: data=<str> becomes content=, .ok becomes .is_success, iter_content becomes iter_bytes.
  • Unix sockets: http+unix:// URLs become HTTPTransport(uds=...) with a regular base URL.
  • Tests: requests_mock (300+ usages) moves to httpx2.MockTransport or an httpx2 mocking plugin.

Plan

  • Drop Python 3.10 ([Feature]: Support Python version upgrade for gateways and migrate from Python 3.10 #4423)
  • Decide on TLS policy (A or B)
  • httpx → httpx2 in server, proxy, gateway, fluentbit, vastai; switch OTel instrumentor
  • (optional) APIClient and CLI → httpx2, with explicit timeouts, redirect handling, and release notes on REQUESTS_CA_BUNDLE → SSL_CERT_FILE
  • (optional) Runner/shim client and backend API clients → httpx2
  • Test the CLI on macOS with a private-CA server, on uv-managed Python, and in a minimal container
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions