-
-
Notifications
You must be signed in to change notification settings - Fork 869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extensions docs #3080
Merged
Merged
Extensions docs #3080
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
80fbfce
Deprecate app=... in favour of explicit WSGITransport/ASGITransport
tomchristie 63143fb
Linting
tomchristie 647b250
Linting
tomchristie 7e14912
Merge branch 'master' into deprecate-app
tomchristie 9f786f2
Update WSGITransport and ASGITransport docs
tomchristie 041470b
Merge branch 'master' into deprecate-app
tomchristie 943ab73
Merge master
tomchristie aab0b00
Merge branch 'master' into deprecate-app
tomchristie fb2e30e
Deprecate app
tomchristie 34ff320
Drop deprecation tests
tomchristie 7e34a37
Add CHANGELOG
tomchristie 21b36a5
Merge branch 'master' into deprecate-app
tomchristie 4ab79fb
Deprecate 'app=...' shortcut, rather than removing it.
tomchristie 468bdfc
Update CHANGELOG
tomchristie abf9dee
Fix test_asgi.test_deprecated_shortcut
tomchristie 6742a94
Extensions docs
tomchristie d255720
Merge branch 'master' into extensions-docs
tomchristie 4b5133d
Merge branch 'master' into extensions-docs
tomchristie 97d8f58
Include 'extensions' in docs index
tomchristie 4f34b3b
Update docs/advanced/extensions.md
tomchristie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
# Extensions | ||
|
||
Request and response extensions provide a untyped space where additional information may be added. | ||
|
||
Extensions should be used for features that may not be available on all transports, and that do not fit neatly into [the simplified request/response model](https://www.encode.io/httpcore/extensions/) that the underlying `httpcore` pacakge uses as it's API. | ||
|
||
Several extensions are supported on the request: | ||
|
||
```python | ||
# Request timeouts actually implemented as an extension on | ||
# the request, ensuring that they are passed throughout the | ||
# entire call stack. | ||
client = httpx.Client() | ||
response = client.get( | ||
"https://www.example.com", | ||
extensions={"timeout": {"connect": 5.0}} | ||
) | ||
response.request.extensions["timeout"] | ||
{"connect": 5.0} | ||
``` | ||
|
||
And on the response: | ||
|
||
```python | ||
client = httpx.Client() | ||
response = client.get("https://www.example.com") | ||
print(response.extensions["http_version"]) # b"HTTP/1.1" | ||
# Other server responses could have been | ||
# b"HTTP/0.9", b"HTTP/1.0", or b"HTTP/1.1" | ||
``` | ||
|
||
## Request Extensions | ||
|
||
### `"trace"` | ||
|
||
The trace extension allows a callback handler to be installed to monitor the internal | ||
flow of events within the underlying `httpcore` transport. | ||
|
||
The simplest way to explain this is with an example: | ||
|
||
```python | ||
import httpx | ||
|
||
def log(event_name, info): | ||
print(event_name, info) | ||
|
||
client = httpx.Client() | ||
response = client.get("https://www.example.com/", extensions={"trace": log}) | ||
# connection.connect_tcp.started {'host': 'www.example.com', 'port': 443, 'local_address': None, 'timeout': None} | ||
# connection.connect_tcp.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f94d0>} | ||
# connection.start_tls.started {'ssl_context': <ssl.SSLContext object at 0x1093ee750>, 'server_hostname': b'www.example.com', 'timeout': None} | ||
# connection.start_tls.complete {'return_value': <httpcore.backends.sync.SyncStream object at 0x1093f9450>} | ||
# http11.send_request_headers.started {'request': <Request [b'GET']>} | ||
# http11.send_request_headers.complete {'return_value': None} | ||
# http11.send_request_body.started {'request': <Request [b'GET']>} | ||
# http11.send_request_body.complete {'return_value': None} | ||
# http11.receive_response_headers.started {'request': <Request [b'GET']>} | ||
# http11.receive_response_headers.complete {'return_value': (b'HTTP/1.1', 200, b'OK', [(b'Age', b'553715'), (b'Cache-Control', b'max-age=604800'), (b'Content-Type', b'text/html; charset=UTF-8'), (b'Date', b'Thu, 21 Oct 2021 17:08:42 GMT'), (b'Etag', b'"3147526947+ident"'), (b'Expires', b'Thu, 28 Oct 2021 17:08:42 GMT'), (b'Last-Modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'Server', b'ECS (nyb/1DCD)'), (b'Vary', b'Accept-Encoding'), (b'X-Cache', b'HIT'), (b'Content-Length', b'1256')])} | ||
# http11.receive_response_body.started {'request': <Request [b'GET']>} | ||
# http11.receive_response_body.complete {'return_value': None} | ||
# http11.response_closed.started {} | ||
# http11.response_closed.complete {'return_value': None} | ||
``` | ||
|
||
The `event_name` and `info` arguments here will be one of the following: | ||
|
||
* `{event_type}.{event_name}.started`, `<dictionary of keyword arguments>` | ||
* `{event_type}.{event_name}.complete`, `{"return_value": <...>}` | ||
* `{event_type}.{event_name}.failed`, `{"exception": <...>}` | ||
|
||
Note that when using async code the handler function passed to `"trace"` must be an `async def ...` function. | ||
|
||
The following event types are currently exposed... | ||
|
||
**Establishing the connection** | ||
|
||
* `"connection.connect_tcp"` | ||
* `"connection.connect_unix_socket"` | ||
* `"connection.start_tls"` | ||
|
||
**HTTP/1.1 events** | ||
|
||
* `"http11.send_request_headers"` | ||
* `"http11.send_request_body"` | ||
* `"http11.receive_response"` | ||
* `"http11.receive_response_body"` | ||
* `"http11.response_closed"` | ||
|
||
**HTTP/2 events** | ||
|
||
* `"http2.send_connection_init"` | ||
* `"http2.send_request_headers"` | ||
* `"http2.send_request_body"` | ||
* `"http2.receive_response_headers"` | ||
* `"http2.receive_response_body"` | ||
* `"http2.response_closed"` | ||
|
||
The exact set of trace events may be subject to change across different versions of `httpcore`. If you need to rely on a particular set of events it is recommended that you pin installation of the package to a fixed version. | ||
|
||
### `"sni_hostname"` | ||
|
||
The server's hostname, which is used to confirm the hostname supplied by the SSL certificate. | ||
|
||
If you want to connect to an explicit IP address rather than using the standard DNS hostname lookup, then you'll need to use this request extension. | ||
|
||
For example: | ||
|
||
``` python | ||
# Connect to '185.199.108.153' but use 'www.encode.io' in the Host header, | ||
# and use 'www.encode.io' when SSL verifying the server hostname. | ||
client = httpx.Client() | ||
headers = {"Host": "www.encode.io"} | ||
extensions = {"sni_hostname": "www.encode.io"} | ||
response = client.get( | ||
"https://185.199.108.153/path", | ||
headers=headers, | ||
extensions=extensions | ||
) | ||
``` | ||
|
||
### `"timeout"` | ||
|
||
A dictionary of `str: Optional[float]` timeout values. | ||
|
||
May include values for `'connect'`, `'read'`, `'write'`, or `'pool'`. | ||
|
||
For example: | ||
|
||
```python | ||
# Timeout if a connection takes more than 5 seconds to established, or if | ||
# we are blocked waiting on the connection pool for more than 10 seconds. | ||
client = httpx.Client() | ||
response = client.get( | ||
"https://www.example.com", | ||
extensions={"timeout": {"connect": 5.0, "pool": 10.0}} | ||
) | ||
``` | ||
|
||
This extension is how the `httpx` timeouts are implemented, ensuring that the timeout values are associated with the request instance and passed throughout the stack. You shouldn't typically be working with this extension directly, but use the higher level `timeout` API instead. | ||
|
||
## Response Extensions | ||
|
||
### `"http_version"` | ||
|
||
The HTTP version, as bytes. Eg. `b"HTTP/1.1"`. | ||
|
||
When using HTTP/1.1 the response line includes an explicit version, and the value of this key could feasibly be one of `b"HTTP/0.9"`, `b"HTTP/1.0"`, or `b"HTTP/1.1"`. | ||
|
||
When using HTTP/2 there is no further response versioning included in the protocol, and the value of this key will always be `b"HTTP/2"`. | ||
|
||
### `"reason_phrase"` | ||
|
||
The reason-phrase of the HTTP response, as bytes. For example `b"OK"`. Some servers may include a custom reason phrase, although this is not recommended. | ||
|
||
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. | ||
|
||
### `"stream_id"` | ||
|
||
When HTTP/2 is being used the `"stream_id"` response extension can be accessed to determine the ID of the data stream that the response was sent on. | ||
|
||
### `"network_stream"` | ||
|
||
The `"network_stream"` extension allows developers to handle HTTP `CONNECT` and `Upgrade` requests, by providing an API that steps outside the standard request/response model, and can directly read or write to the network. | ||
|
||
The interface provided by the network stream: | ||
|
||
* `read(max_bytes, timeout = None) -> bytes` | ||
* `write(buffer, timeout = None)` | ||
* `close()` | ||
* `start_tls(ssl_context, server_hostname = None, timeout = None) -> NetworkStream` | ||
* `get_extra_info(info) -> Any` | ||
|
||
This API can be used as the foundation for working with HTTP proxies, WebSocket upgrades, and other advanced use-cases. | ||
|
||
See the [network backends documentation](https://www.encode.io/httpcore/network-backends/) for more information on working directly with network streams. | ||
|
||
**Extra network information** | ||
|
||
The network stream abstraction also allows access to various low-level information that may be exposed by the underlying socket: | ||
|
||
```python | ||
response = httpx.get("https://www.example.com") | ||
network_stream = response.extensions["network_stream"] | ||
|
||
client_addr = network_stream.get_extra_info("client_addr") | ||
server_addr = network_stream.get_extra_info("server_addr") | ||
print("Client address", client_addr) | ||
print("Server address", server_addr) | ||
``` | ||
|
||
The socket SSL information is also available through this interface, although you need to ensure that the underlying connection is still open, in order to access it... | ||
|
||
```python | ||
with httpx.stream("GET", "https://www.example.com") as response: | ||
network_stream = response.extensions["network_stream"] | ||
|
||
ssl_object = network_stream.get_extra_info("ssl_object") | ||
print("TLS version", ssl_object.version()) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we shouldn't list all of the supported events here. We should remember to update this list every time we introduce a new event to httpcore. We could simply mention them in httpcore and provide a link here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(an automatic workflow here would also help - mkdocs plugins are easy to implement)