HEAD being handled as GET by default is not cheap...

Hi. I was wondering what was behind the design decision for django.views.generic.base.View.dispatch:

By default, a HEAD request will be delegated to get(). If you need to handle HEAD requests in a different way than GET, you can override the head() method. See Supporting other HTTP methods for an example.

I knew about this for a long time but i guess i never really realized the performance penalty for this as many monitoring services (correctly) use HEAD to “be polite” when checking an application is up and basically dont care about the body as per the RFC they can’t even get it:

The HTTP HEAD method requests the headers that would be returned if the HEAD request’s URL was instead requested with the HTTP GET method.

Warning: A response to a HEAD method should not have a body. If it has one anyway, that body must be ignored: any representation headers that might describe the erroneous body are instead assumed to describe the response which a similar GET request would have received.

Having nginx in front of django+uwsgi looks like this:

nginx:
aa.bb.cc.dd - - [13/Jul/2023:12:07:10 +0000] "HEAD / HTTP/1.1" 200 0 "https://example.com" "Mozilla/5.0+(compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)" 0.131 TLSv1.2

django:
2023-07-13 12:07:10 aa.bb.cc.dd "HEAD / HTTP/1.1" 200 164833 "Mozilla/5.0+(compatible; UptimeRobot/2.0; http://www.uptimerobot.com/)" 0.131188
1 Like

i think i also see a possibly easier avenue for DDoS using HEAD requests, as the likelyhood of being rate limited is lower than GET/POST requests…

The response to a HEAD request should include all the headers that would have been included in response to a GET. Since the function handling a GET request can add arbitrary headers (e.g. response.headers["Age"] = 120), Django has no way of knowing what headers to return for a HEAD request without generating the full GET response.

If, in your particular application, you have a way to respond to HEAD more efficiently then you can implement that (the documentation gives an example), but Django is doing the only thing it can do at the framework level.

1 Like

would it perhaps be an option to skip the template rendering part for HEAD requests and return an empty body?

In your own views certainly. Framework-wide probably not possible. In the worst case rendering a template could set stuff on the request object which would then get picked up by a middleware etc to set further response headers. So the only safe generic way is to throw the rendered body away.