Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions httpx/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,11 +985,17 @@ def request(self, value: Request) -> None:

@property
def http_version(self) -> str:
return self.extensions.get("http_version", "HTTP/1.1")
try:
return self.extensions["http_version"].decode("ascii", errors="ignore")
except KeyError:
return "HTTP/1.1"

@property
def reason_phrase(self) -> str:
return self.extensions.get("reason", codes.get_reason_phrase(self.status_code))
try:
return self.extensions["reason_phrase"].decode("ascii", errors="ignore")
except KeyError:
return codes.get_reason_phrase(self.status_code)

@property
def url(self) -> typing.Optional[URL]:
Expand Down
6 changes: 3 additions & 3 deletions httpx/_transports/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ def handle_request(
stream: The response body as a bytes iterator.
extensions: An open ended dictionary, including optional extensions to the
core request/response API. Keys are plain strings, and may include:
reason: The reason-phrase of the HTTP response, as bytes. Eg 'OK'.
reason_phrase: The reason-phrase of the HTTP response, as bytes. Eg b'OK'.
HTTP/2 onwards does not include a reason phrase on the wire.
When no key is included, a default based on the status code may
be used. An empty-string reason phrase should not be substituted
for a default, as it indicates the server left the portion blank
eg. the leading response bytes were b"HTTP/1.1 200 <CRLF>".
http_version: The HTTP version, as a string. Eg. "HTTP/1.1".
When no http_version key is included, "HTTP/1.1" may be assumed.
http_version: The HTTP version, as bytes. Eg. b"HTTP/1.1".
When no http_version key is included, HTTP/1.1 may be assumed.
close: A callback which should be invoked to release any network
resources.
aclose: An async callback which should be invoked to release any
Expand Down
15 changes: 15 additions & 0 deletions httpx/_transports/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ def map_httpcore_exceptions() -> typing.Iterator[None]:
raise mapped_exc(message) from exc


def ensure_http_version_reason_phrase_as_bytes(extensions: dict) -> None:
# From HTTPX 0.18 onwards we're treating the "reason_phrase" and "http_version"
# extensions as bytes, in order to be more precise. Also we're using the
# "reason_phrase" key in preference to "reason", in order to match properly
# with the HTTP spec naming.
# HTTPCore 0.12 does not yet use these same conventions for the extensions,
# so we bridge between the two styles for now.
if "reason" in extensions:
extensions["reason_phrase"] = extensions.pop("reason").encode("ascii")
if "http_version" in extensions:
extensions["http_version"] = extensions["http_version"].encode("ascii")


HTTPCORE_EXC_MAP = {
httpcore.TimeoutException: TimeoutException,
httpcore.ConnectTimeout: ConnectTimeout,
Expand Down Expand Up @@ -178,6 +191,7 @@ def close() -> None:
with map_httpcore_exceptions():
byte_stream.close()

ensure_http_version_reason_phrase_as_bytes(extensions)
extensions["close"] = close

return status_code, headers, response_stream(), extensions
Expand Down Expand Up @@ -267,6 +281,7 @@ async def aclose() -> None:
with map_httpcore_exceptions():
await byte_stream.aclose()

ensure_http_version_reason_phrase_as_bytes(extensions)
extensions["aclose"] = aclose

return status_code, headers, response_stream(), extensions
Expand Down