Skip to content

Commit

Permalink
Add target request extension (#888)
Browse files Browse the repository at this point in the history
* 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 <tom.christie@krakentechnologies.ltd>
Co-authored-by: Tom Christie <tom@tomchristie.com>
  • Loading branch information
3 people committed Feb 21, 2024
1 parent accae7b commit 80d21ee
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

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

## 1.0.3 (February 13th, 2024)

Expand Down
28 changes: 25 additions & 3 deletions docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,28 @@ response = httpcore.request(
)
```

### `"target"`

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).

This enables support constructing requests that would otherwise be unsupported. In particular...

* Forward proxy requests using an absolute URI.
* Tunneling proxy requests using `CONNECT` with hostname as the target.
* Server-wide `OPTIONS *` requests.

For example:

```python
extensions = {"target": b"www.encode.io:443"}
response = httpcore.request(
"CONNECT",
"http://your-tunnel-proxy.com",
headers=headers,
extensions=extensions
)
```

## Response Extensions

### `"http_version"`
Expand Down Expand Up @@ -214,9 +236,9 @@ A proxy CONNECT request using the network stream:
# This will establish a connection to 127.0.0.1:8080, and then send the following...
#
# CONNECT http://www.example.com HTTP/1.1
# Host: 127.0.0.1:8080
url = httpcore.URL(b"http", b"127.0.0.1", 8080, b"http://www.example.com")
with httpcore.stream("CONNECT", url) as response:
url = "http://127.0.0.1:8080"
extensions = {"target: "http://www.example.com"}
with httpcore.stream("CONNECT", url, extensions=extensions) as response:
network_stream = response.extensions["network_stream"]

# Upgrade to an SSL stream...
Expand Down
8 changes: 8 additions & 0 deletions httpcore/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,14 @@ def __init__(
)
self.extensions = {} if extensions is None else extensions

if "target" in self.extensions:
self.url = URL(
scheme=self.url.scheme,
host=self.url.host,
port=self.url.port,
target=self.extensions["target"],
)

def __repr__(self) -> str:
return f"<{self.__class__.__name__} [{self.method!r}]>"

Expand Down
14 changes: 14 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ def test_request():
assert repr(request.stream) == "<ByteStream [0 bytes]>"


def test_request_with_target_extension():
extensions = {"target": b"/another_path"}
request = httpcore.Request(
"GET", "https://www.example.com/path", extensions=extensions
)
assert request.url.target == b"/another_path"

extensions = {"target": b"/unescaped|path"}
request = httpcore.Request(
"GET", "https://www.example.com/path", extensions=extensions
)
assert request.url.target == b"/unescaped|path"


def test_request_with_invalid_method():
with pytest.raises(TypeError) as exc_info:
httpcore.Request(123, "https://www.example.com/") # type: ignore
Expand Down

0 comments on commit 80d21ee

Please sign in to comment.