Opened 2 years ago

Closed 2 years ago

Last modified 14 months ago

#29925 closed New feature (wontfix)

Redirect with HTTP/2 Server Push

Reported by: Jaap Roes Owned by: nobody
Component: HTTP handling Version: master
Severity: Normal Keywords: http2 server push redirects
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I was looking into upgrading nginx and reading it's changelog when I noticed HTTP/2 Server Push support was introduced semi-recently.

This reminded me of this Tweet by Simon Willison about accelerating redirects using a HTTP/2 Server Push header. I did some further research and stumbled upon this article about the same concept.

This got me thinking about using this technique in Django.

There are a few places in Django's code base that will perform redirects to "better" urls, e.g. when using CommonMiddleware and APPEND_SLASH is True and/or PREPEND_WWW is True or using LocaleMiddleware i.c.w. i18n_patterns. Wouldn't it be nice if these redirects also included a Link: <redirect-location>; rel=preload header?

Would adding something like this be acceptable to the Django codebase? I'm unsure if it's safe to include this header on non-http/2 connections/servers, but I'd assume it will be.

A step further would be to always do this for all HttpResponseRedirectBase classes, or alternatively, introduce a Http2ServerPushRedirectBase class.

Change History (6)

comment:1 Changed 2 years ago by Claude Paroz

Interesting. Did you experiment such a configuration in a real project successfully? In that case, I don't currently see what would prevent implementing this optimization in Django.

comment:2 Changed 2 years ago by Jaap Roes

No, I did not experiment with this at all and was just basing all of this on the Tweet and article I mentioned in the ticket. Sadly none of our production servers have a recent enough nginx version, so I cannot do any "real world" tests.

However, I did create a small docker-compose project that sets up nginx and a Django project with a custom redirect class and a patched CommonMiddleware. It seems to work pretty nice. You can check it out here: https://github.com/leukeleu/django-push-redirect

comment:3 Changed 2 years ago by Claude Paroz

Triage Stage: UnreviewedAccepted

Nice! I guess the next step is to prepare a patch for Django.

comment:4 Changed 2 years ago by Jaap Roes

After contemplating for a while on how to implement this nicely, I came to the conclusion that the easiest/neatest way to do this would be to just introduce a new middleware:

from django.utils.http import is_safe_url

class Http2ServerPushRedirectMiddleware:
    redirect_status_codes = {301, 302}

    def __init__(self, get_response):
        self.get_response = get_response

    def should_preload(self, request, response):
        return (
            request.is_secure
            and response.status_code in self.redirect_status_codes
            and hasattr(response, 'url')
            and not response.has_header('Link')
        )

    def __call__(self, request):
        response = self.get_response(request)
        if self.should_preload(request, response):
            url = response.url
            if is_safe_url(url, allowed_hosts={request.get_host()}, require_https=True):
                response['Link'] = f'<{url}>; rel=preload'
    return response

This works as long as this middleware is placed before CommonMiddleware.

I've updated the test project to use this approach as well.

Are you still interested in having a middleware like this in Django core? Releasing this as a 3rd party package could be a sufficient solution as well.

Last edited 2 years ago by Jaap Roes (previous) (diff)

comment:5 Changed 2 years ago by Claude Paroz

Resolution: wontfix
Status: newclosed

Yes, in that case I guess having a 3rd-party app might be the way to go.
A request to integrate it into Django may come later when the app is well-tested and HTTP/2 usage is a bit higher.

comment:6 Changed 14 months ago by Jaap Roes

I finally got around to packaging this up and releasing it to PyPI https://pypi.org/project/django-push-redirect/

Note: See TracTickets for help on using tickets.
Back to Top