question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Client middleware API

See original GitHub issue

There’s been quite a few PRs trying to take a stab at making the Client/AsyncClient extensible, mostly with a middleware approach whose idea seems to have originated in https://github.com/encode/httpx/pull/136#issuecomment-516085808 to help solve Digest Auth.

As mentioned in https://github.com/encode/httpx/pull/268#issuecomment-525347835 it might be useful to define and discuss in which direction we’d like to go.

Problem statement

A few requirements we may want to satisfy, gathered from looking at the various discussions:

  1. Support the simplest case: modify the request (before getting a response from the underlying middleware).
  2. Support middleware that may return an early response (e.g. a cache middleware), i.e. allow to skip calling the underlying middleware.
  3. Support middleware that need to make subsequent requests after getting the initial response (e.g. Redirect, Digest Auth).
  4. Allow users to write their own middleware, e.g. for particular authentication schemes.
  5. Allow users to assemble a middleware pipeline (with what level of control?).

None of the approaches attempt to solve 4) nor 5) — and that can definitely be dealt with in its own issue once this one is resolved. As a result, the scope of this issue is designing an internal API to solve 1), 2) and 3).

Existing implementation

A reminder of the features that HTTPX currently supports that may benefit from being refactored as as middleware:

  • Basic authentication: add an Authorization header based on a username and password.
  • “Custom authentication”: process the request via a req -> req callable. (I personally think this might be too lose — we could just require the callable to return an Authorization header — but let’s tighten the scope of this issue and keep things as they are.)
  • Redirect: automatically handle redirect responses by making request to the specified location.

These are currently implemented as part of the BaseClient itself. Them cluttering the BaseClient is, as far as I understand, the main reason for refactoring them into loosely-coupled middleware.

Proposed solutions

Here’s a summary of the various APIs that were proposed up to now and their respective “no-op middleware” for the sake of example:

#176 : process_request / process_response

class Middleware:
    async def process_request(self, request: AsyncRequest) -> AsyncRequest:
        return request

    async def process_response(
        self, request: AsyncRequest, response: AsyncResponse
    ) -> AsyncResponse:
        return response

#267 : building on top of the dispatcher API

from httpx.base import AsyncDispatcher

class Middleware(AsyncDispatcher):
    def __init__(self, next_dispatcher, **kwargs):
        self.next_dispatcher = next_dispatcher

    async def send(
        self,
        request: AsyncRequest,
        verify: VerifyTypes = None,
        cert: CertTypes = None,
        timeout: TimeoutTypes = None,
    ) -> AsyncResponse:
        return await self.next_dispatcher.send(request, verify=verify, cert=cert, timeout=timeout)

#268: callable middleware

class Middleware:
    async def __call__(self, request: AsyncRequest, get_response: typing.Callable) -> AsyncResponse:
        return await get_response(request)

I think the various PRs proved that all APIs tick 1), 2), and 3) as defined above, with various degrees of simplicity and boilerplate.

The callable middleware API seems to be the simplest one while offering enough flexibility. Making extra requests based on the initial response can be achieved by recursively calling the middleware, as in the RedirectMiddleware. As another PoC, a rough Digest-Auth implementation based on this snippet:

class DigestAuthMiddleware:
    async def __call__(self, request, get_response):
        response = await get_response(request)

        # Did the server respond with 401 + WWW-Authenticate?
        if self.requires_authentication(response):
            authorization = self.build_authorization_header(request, response)
            request.headers["Authorization"] = authorization
            return await self(request, get_response)

        return response

cc @tomchristie @yeraydiazdiaz @sethmlarson

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:2
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
yeraydiazdiazcommented, Sep 2, 2019

Great job on this @florimondmanca!

I think #268 is a great first implementation of this, chances are we’ll start noticing pain points as we start implementing more middlewares but at this point in the project it’s something we need to go through.

I’ll repurpose the Digest auth and look forward to more features using middlewares 🎉

0reactions
florimondmancacommented, Sep 2, 2019

Super @yeraydiazdiaz, I’m excited about Digest Auth!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Client middleware | Advanced Topics | Hilla Docs
The middleware in Hilla is a special TypeScript async callback function that's executed by the frontend client during calls to the backend.
Read more >
The Role of API Middleware - Adaptigent
In essence, the API Middleware layer plays a similar role as middleware plays in other IT solutions. It sits between the client level...
Read more >
What is HTTP middleware? Best practices for building ... - Moesif
Best practices and considerations for building, designing and using HTTP middleware.
Read more >
Build A Better API Client with Guzzle Middleware - Medium
Guzzle's middleware works much the same, allowing us to modify the request or response as needed. A common pattern I use to initialize...
Read more >
Intercepting RESTful Responses with Middleware
An article discussing the application of HTTP middleware to control server responses that would otherwise be influenced by third-party APIs.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found