The Fix
Upgrade to version 0.17.0 or later.
Based on closed encode/httpx issue #1468 · PR/commit linked
Production note: Most teams hit this during upgrades or environment changes. Roll out with a canary and smoke critical endpoints (health, OpenAPI/docs) before 100%.
@@ -74,6 +74,9 @@ def request(
scheme, host, port, full_path = url
path, _, query = full_path.partition(b"?")
+ if port is None:
+ port = {b"http": 80, b"https": 443}[scheme]
+
=================================== FAILURES ===================================
____________ test_convert_asgi_to_wsgi[app1-a2wsgi-ASGIMiddleware] _____________
app = <a2wsgi.asgi.ASGIMiddleware object at 0x7fced147eb80>
name = 'a2wsgi-ASGIMiddleware'
@pytest.mark.parametrize(
"app, name",
[(wsgi_echo, "pure-WSGI"), (ASGIMiddleware(asgi_echo), "a2wsgi-ASGIMiddleware")],
)
def test_convert_asgi_to_wsgi(app, name):
with httpx.Client(app=app, base_url="http://testserver") as client:
start_time = time.time_ns()
for _ in range(100):
client.post("/", data=b"hello world")
a2wsgi/benchmark.py:99:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.8/site-packages/httpx/_client.py:992: in post
return self.request(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:733: in request
return self.send(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:767: in send
response = self._send_handling_auth(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:805: in _send_handling_auth
response = self._send_handling_redirects(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:837: in _send_handling_redirects
response = self._send_single_request(request, timeout)
/usr/local/lib/python3.8/site-packages/httpx/_client.py:861: in _send_single_request
(status_code, headers, stream, ext) = transport.request(
/usr/local/lib/python3.8/site-packages/httpx/_transports/wsgi.py:113: in request
result = _skip_leading_empty_chunks(result)
/usr/local/lib/python3.8/site-packages/httpx/_transports/wsgi.py:10: in _skip_leading_empty_chunks
for chunk in body:
a2wsgi/a2wsgi/asgi.py:160: in __call__
self.app(build_scope(environ), self.asgi_receive, self.asgi_send)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
environ = {'CONTENT_LENGTH': '11', 'HTTP_ACCEPT': '*/*', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', 'HTTP_CONNECTION': 'keep-alive', ...}
def build_scope(environ: Environ) -> Scope:
headers = [
(
each[5:].lower().replace("_", "-").encode("latin1"),
environ[each].encode("latin1"),
)
for each in environ.keys()
if each.startswith("HTTP_")
]
if environ.get("CONTENT_TYPE"):
headers.append((b"content-type", environ["CONTENT_TYPE"].encode("latin1")))
if environ.get("CONTENT_LENGTH"):
headers.append((b"content-length", environ["CONTENT_LENGTH"].encode("latin1")))
if environ.get("REMOTE_ADDR") and environ.get("REMOTE_PORT"):
client = (environ.get("REMOTE_ADDR"), int(environ.get("REMOTE_PORT")))
else:
client = None
return {
"type": "http",
"asgi": {"version": "3.0", "spec_version": "3.0"},
"http_version": environ.get("SERVER_PROTOCOL", "http/1.0").split("/")[1],
"method": environ["REQUEST_METHOD"],
"scheme": environ.get("wsgi.url_scheme", "http"),
"path": environ["PATH_INFO"].encode("latin1").decode("utf8"),
"query_string": environ["QUERY_STRING"].encode("ascii"),
"root_path": environ.get("SCRIPT_NAME", "").encode("latin1").decode("utf8"),
"client": client,
"server": (environ["SERVER_NAME"], int(environ["SERVER_PORT"])),
"headers": headers,
}
E ValueError: invalid literal for int() with base 10: 'None'
a2wsgi/a2wsgi/asgi.py:94: ValueErro
... (truncated) ...
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Upgrade to fixed release\nUpgrade to version 0.17.0 or later.\nWhen NOT to use: This fix is not applicable if the application does not use WSGITransport.\n\n
Why This Fix Works in Production
- Trigger: environ["SERVER_PORT"] can't be "None"
- Mechanism: The WSGITransport did not handle cases where SERVER_PORT is None
- Why the fix works: Handle default ports in WSGITransport to ensure SERVER_PORT is correctly populated. (first fixed release: 0.17.0).
- If left unfixed, the same config can fail only in production (env differences), causing startup failures or partial feature outages.
Why This Breaks in Prod
- Shows up under Python 3.8 in real deployments (not just unit tests).
- The WSGITransport did not handle cases where SERVER_PORT is None
- Production symptom (often without a traceback): environ["SERVER_PORT"] can't be "None"
Proof / Evidence
- GitHub issue: #1468
- Fix PR: https://github.com/encode/httpx/pull/1469
- First fixed release: 0.17.0
- Reproduced locally: No (not executed)
- Last verified: 2026-02-08
- Confidence: 0.85
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.41
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“@abersheeran Good catch! Indeed, in WSGITransport the url is unpacked as scheme, host, port, full_path, and port is an Optional[int], obtained from here: https://github.com/encode/httpx/blob/86964054d60da7796c65e501ba021440b14cc36c/httpx/_models.py#L208-L214 S”
Failure Signature (Search String)
- environ["SERVER_PORT"] can't be "None"
- =================================== FAILURES ===================================
Copy-friendly signature
Failure Signature
-----------------
environ["SERVER_PORT"] can't be "None"
=================================== FAILURES ===================================
Error Message
Signature-only (no traceback captured)
Error Message
-------------
environ["SERVER_PORT"] can't be "None"
=================================== FAILURES ===================================
Minimal Reproduction
=================================== FAILURES ===================================
____________ test_convert_asgi_to_wsgi[app1-a2wsgi-ASGIMiddleware] _____________
app = <a2wsgi.asgi.ASGIMiddleware object at 0x7fced147eb80>
name = 'a2wsgi-ASGIMiddleware'
@pytest.mark.parametrize(
"app, name",
[(wsgi_echo, "pure-WSGI"), (ASGIMiddleware(asgi_echo), "a2wsgi-ASGIMiddleware")],
)
def test_convert_asgi_to_wsgi(app, name):
with httpx.Client(app=app, base_url="http://testserver") as client:
start_time = time.time_ns()
for _ in range(100):
client.post("/", data=b"hello world")
a2wsgi/benchmark.py:99:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.8/site-packages/httpx/_client.py:992: in post
return self.request(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:733: in request
return self.send(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:767: in send
response = self._send_handling_auth(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:805: in _send_handling_auth
response = self._send_handling_redirects(
/usr/local/lib/python3.8/site-packages/httpx/_client.py:837: in _send_handling_redirects
response = self._send_single_request(request, timeout)
/usr/local/lib/python3.8/site-packages/httpx/_client.py:861: in _send_single_request
(status_code, headers, stream, ext) = transport.request(
/usr/local/lib/python3.8/site-packages/httpx/_transports/wsgi.py:113: in request
result = _skip_leading_empty_chunks(result)
/usr/local/lib/python3.8/site-packages/httpx/_transports/wsgi.py:10: in _skip_leading_empty_chunks
for chunk in body:
a2wsgi/a2wsgi/asgi.py:160: in __call__
self.app(build_scope(environ), self.asgi_receive, self.asgi_send)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
environ = {'CONTENT_LENGTH': '11', 'HTTP_ACCEPT': '*/*', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate', 'HTTP_CONNECTION': 'keep-alive', ...}
def build_scope(environ: Environ) -> Scope:
headers = [
(
each[5:].lower().replace("_", "-").encode("latin1"),
environ[each].encode("latin1"),
)
for each in environ.keys()
if each.startswith("HTTP_")
]
if environ.get("CONTENT_TYPE"):
headers.append((b"content-type", environ["CONTENT_TYPE"].encode("latin1")))
if environ.get("CONTENT_LENGTH"):
headers.append((b"content-length", environ["CONTENT_LENGTH"].encode("latin1")))
if environ.get("REMOTE_ADDR") and environ.get("REMOTE_PORT"):
client = (environ.get("REMOTE_ADDR"), int(environ.get("REMOTE_PORT")))
else:
client = None
return {
"type": "http",
"asgi": {"version": "3.0", "spec_version": "3.0"},
"http_version": environ.get("SERVER_PROTOCOL", "http/1.0").split("/")[1],
"method": environ["REQUEST_METHOD"],
"scheme": environ.get("wsgi.url_scheme", "http"),
"path": environ["PATH_INFO"].encode("latin1").decode("utf8"),
"query_string": environ["QUERY_STRING"].encode("ascii"),
"root_path": environ.get("SCRIPT_NAME", "").encode("latin1").decode("utf8"),
"client": client,
"server": (environ["SERVER_NAME"], int(environ["SERVER_PORT"])),
"headers": headers,
}
E ValueError: invalid literal for int() with base 10: 'None'
a2wsgi/a2wsgi/asgi.py:94: ValueErro
... (truncated) ...
Environment
- Python: 3.8
What Broke
Requests to the server failed due to missing SERVER_PORT, causing timeouts.
Why It Broke
The WSGITransport did not handle cases where SERVER_PORT is None
Fix Options (Details)
Option A — Upgrade to fixed release Safe default (recommended)
Upgrade to version 0.17.0 or later.
Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.
Fix reference: https://github.com/encode/httpx/pull/1469
First fixed release: 0.17.0
Last verified: 2026-02-08. Validate in your environment.
When NOT to Use This Fix
- This fix is not applicable if the application does not use WSGITransport.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Add a CI check that diffs key outputs after upgrades (OpenAPI schema snapshots, JSON payload shapes, CLI output).
- Upgrade behind a canary and run integration tests against the canary before 100% rollout.
- Make timeouts explicit and test them (unit + integration) to avoid silent behavior changes.
- Instrument retries (attempt count + reason) and alert on spikes to catch dependency slowdowns.
Version Compatibility Table
| Version | Status |
|---|---|
| 0.17.0 | Fixed |
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.