-
-
Notifications
You must be signed in to change notification settings - Fork 857
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
Middleware API #1134
Middleware API #1134
Conversation
Another use case for middleware is tracing. We’re using Datadog APM tracing in our services and in order to get trace spans for all HTTP requests I’m currently monkeypatching |
@johnanthonyowens It might be worth opening a separate issue to discuss that in more detail. Things that'd be useful reference points here would be...
|
Is there anything I can use right now to implement a response cache? |
@ionelmc Most likely a |
@ionelmc A sensible first thing to do with any question like that is to start with "how would I do this with The main override points for
|
Just described a lighter form of this "middleware API" idea in the form of "interceptors", here… #790 (comment) It's basically the same than the API proposed in this draft PR, except it's callable-based (sync functions for |
Okay, going to close this off again. For "middleware" that just wraps a request between the client and the final transport, it's already perfectly doable using the transport API, even though the instantiation pattern is a bit quirky for now (though not terrible). import hstspreload
import httpcore
import httpx
class HSTSTransport(httpcore.SyncHTTPTransport, httpcore.AsyncHTTPTransport):
"""
A transport wrapper that enforces HTTPS on websites that are on the
Chromium HSTS Preload list, mimicking the behavior of web browsers.
"""
def __init__(self, transport: Union[httpcore.SyncHTTPTransport, httpcore.AsyncHTTPTransport]) -> None:
self._transport = transport
def _maybe_https_url(self, url: tuple) -> tuple:
scheme, host, port, path = url
if (
scheme == b"http"
and hstspreload.in_hsts_preload(host.decode())
and len(host.decode().split(".")) > 1
):
port = None if port == 80 else port
return b"https", host, port, path
return url
def request(self, method, url, headers, stream, ext):
url = self._maybe_https_url(url)
return self._transport.request(method, url, headers, stream, ext)
async def arequest(self, method, url, headers, stream, ext):
url = self._maybe_https_url(url)
return await self._transport.arequest(method, url, headers, stream, ext)
transport = httpx.HTTPTransport() # Soon
transport = HSTSTransport(transport)
with httpx.Client(transport=transport):
... |
@ionelmc also, regarding response caching.. this should be usable https://github.com/johtso/httpx-caching |
And here I am pitching the idea of a middleware API again… :-)
Definitely not for 1.0, and not pressing at all, but I wanted to give this a new shot to see what this looks like now that we're closer to 1.0 (compared to eg #345).
As I mentioned in #984 (comment), and more recently in #1110 (comment), there is a variety of use cases that don't fall in the "make a custom transport" domain, the "subclass the client without using private API" domain. In general this is transport-agnostic feature that intercepts the request/response cycle, for example...
After playing with ideas, this PR is all we would need to support a middleware API that would support the above use cases and, I believe, many more.
Core ideas are…
BaseHTTPMiddleware
: a base class with "dispatch hooks" that are given theRequest
and acall_next
function for calling into the next middleware in the stack.For example, a
ThrottleMiddleware
could be implemented as follows:Likewise, a caching middleware could look like this:
Lastly, here's
hstspreload
back in the game:Things I'm not sure about:
middleware=[SomeMiddleware(arg=1, ...)]
. I think this is okay. (We don't need an equivalent of Starlette'sMiddleware
wrapping helper, since HTTPX middleware aren't given any "parent app" on init.).send()
/.asend()
.for m in middleware
orfor m in reverse(middleware)
.call_next
function on each request, which could be a performance burden. OTOH Starlette is able to define.call_next()
statically as a method ofBaseHTTPMiddleware
, and the middleware stack is built once and for all on app init. But HTTPX gets parameters that may differ on each request (auth
,allow_redirects
) so it seems hard to do differently. But not impossible I guess - needs some more thinking.TODO:
RetryMiddleware
.