Skip to content

Commit 80d21ee

Browse files
karpetrosyanTom Christielovelydinosaur
authored
Add target request extension (#888)
* Add target request extension * Add changelog * Implement target in the models.py, add test * Update docs/extensions.md * Update extensions.md --------- Co-authored-by: Tom Christie <[email protected]> Co-authored-by: Tom Christie <[email protected]>
1 parent accae7b commit 80d21ee

File tree

4 files changed

+49
-4
lines changed

4 files changed

+49
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## Unreleased
88

9-
- Fix support for connection Upgrade and CONNECT when some data in the stream has been read. (#882)
9+
- Add `target` request extension. (#888)
10+
- Fix support for connection `Upgrade` and `CONNECT` when some data in the stream has been read. (#882)
1011

1112
## 1.0.3 (February 13th, 2024)
1213

docs/extensions.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,28 @@ response = httpcore.request(
166166
)
167167
```
168168

169+
### `"target"`
170+
171+
The target that is used as [the HTTP target instead of the URL path](https://datatracker.ietf.org/doc/html/rfc2616#section-5.1.2).
172+
173+
This enables support constructing requests that would otherwise be unsupported. In particular...
174+
175+
* Forward proxy requests using an absolute URI.
176+
* Tunneling proxy requests using `CONNECT` with hostname as the target.
177+
* Server-wide `OPTIONS *` requests.
178+
179+
For example:
180+
181+
```python
182+
extensions = {"target": b"www.encode.io:443"}
183+
response = httpcore.request(
184+
"CONNECT",
185+
"http://your-tunnel-proxy.com",
186+
headers=headers,
187+
extensions=extensions
188+
)
189+
```
190+
169191
## Response Extensions
170192

171193
### `"http_version"`
@@ -214,9 +236,9 @@ A proxy CONNECT request using the network stream:
214236
# This will establish a connection to 127.0.0.1:8080, and then send the following...
215237
#
216238
# CONNECT http://www.example.com HTTP/1.1
217-
# Host: 127.0.0.1:8080
218-
url = httpcore.URL(b"http", b"127.0.0.1", 8080, b"http://www.example.com")
219-
with httpcore.stream("CONNECT", url) as response:
239+
url = "http://127.0.0.1:8080"
240+
extensions = {"target: "http://www.example.com"}
241+
with httpcore.stream("CONNECT", url, extensions=extensions) as response:
220242
network_stream = response.extensions["network_stream"]
221243

222244
# Upgrade to an SSL stream...

httpcore/_models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,14 @@ def __init__(
353353
)
354354
self.extensions = {} if extensions is None else extensions
355355

356+
if "target" in self.extensions:
357+
self.url = URL(
358+
scheme=self.url.scheme,
359+
host=self.url.host,
360+
port=self.url.port,
361+
target=self.extensions["target"],
362+
)
363+
356364
def __repr__(self) -> str:
357365
return f"<{self.__class__.__name__} [{self.method!r}]>"
358366

tests/test_models.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ def test_request():
5858
assert repr(request.stream) == "<ByteStream [0 bytes]>"
5959

6060

61+
def test_request_with_target_extension():
62+
extensions = {"target": b"/another_path"}
63+
request = httpcore.Request(
64+
"GET", "https://www.example.com/path", extensions=extensions
65+
)
66+
assert request.url.target == b"/another_path"
67+
68+
extensions = {"target": b"/unescaped|path"}
69+
request = httpcore.Request(
70+
"GET", "https://www.example.com/path", extensions=extensions
71+
)
72+
assert request.url.target == b"/unescaped|path"
73+
74+
6175
def test_request_with_invalid_method():
6276
with pytest.raises(TypeError) as exc_info:
6377
httpcore.Request(123, "https://www.example.com/") # type: ignore

0 commit comments

Comments
 (0)