# The Trouble With Middleware

**URL:** https://forum.djangoproject.com/t/the-trouble-with-middleware/19
**Category:** Async
**Created:** [September 9, 2019, 1:35am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19 "2019-09-09T01:35:49Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)
#### Post date: [September 9, 2019, 1:35am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/1 "2019-09-09T01:35:49Z")

</div>

So, the current sticking point I have with the async work is middleware - specifically, synchronous middleware.

The design of Django’s “new style” middleware - a callable that calls another callable - means that the context of the middleware has to stay open while the view runs. I have synchronous middleware adapting around asynchronous views just fine, but this does mean we waste a whole synchronous thread per async view call, which defeats the point of having async in the first place, really.

I can’t think of an easy way out of this; so far, the only options I can consider (neither of which are good) are:

- Rewrite all the basic Django middleware to be async and tell people not to use non-async middleware if they want massive parallelism (throwing anyone with non-standard middleware under the bus)
- Somehow pause the sync middleware and suspend execution in a way where we can come back to it (not even sure this is possible)

Alternative suggestions of how to approach this would be most welcome…

---

<div class="post-metadata">

### Author: ![manfre](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/manfre/32/33_2.png) [@manfre](https://forum.djangoproject.com/u/manfre)
#### Post date: [September 10, 2019, 2:06pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/2 "2019-09-10T14:06:51Z")

</div>

> [@andrewgodwin](#):
>
> Rewrite all the basic Django middleware to be async and tell people not to use non-async middleware if they want massive parallelism (throwing anyone with non-standard middleware under the bus)

Rewriting all the Django provided middleware to support async doesn’t really throw anyone under the bus. It provides a few examples to reference when updating their own and until that happens, they can keep running their current sync infrastructure until that happens. Those who need the extra parallelism will put forth the effort to take advantage of it. Extra documentation on some common patterns for moving sync to async could also help.

> [@andrewgodwin](#):
>
> Somehow pause the sync middleware and suspend execution in a way where we can come back to it (not even sure this is possible)

This sounds like it the best case scenario it would add a lot of complexity or more moving parts.

---

<div class="post-metadata">

### Author: ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)
#### Post date: [September 10, 2019, 4:27pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/3 "2019-09-10T16:27:09Z")

</div>

Yeah, I’m not averse to rewriting the Django middleware, it just makes the whole thing a much bigger effort until it’s properly useable. I’m going to probably sit down and play with the suspension idea at DjangoCon US in a couple of weeks, when there’s some more talented minds I can steal ideas from!

---

<div class="post-metadata">

### Author: ![patrys](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/patrys/32/119_2.png) [@patrys](https://forum.djangoproject.com/u/patrys)
#### Post date: [September 11, 2019, 6:43am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/4 "2019-09-11T06:43:58Z")

</div>

I think rewriting everything is what we actually want in the long run. Existing projects with custom middleware won’t get any faster but also won’t get any slower.

---

<div class="post-metadata">

### Author: ![nicolaslara](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/nicolaslara/32/154_2.png) [@nicolaslara](https://forum.djangoproject.com/u/nicolaslara)
#### Post date: [September 11, 2019, 11:17am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/5 "2019-09-11T11:17:02Z")

</div>

> [@patrys](#):
>
> I think rewriting everything is what we actually want in the long run

I generally agree with this. Particularly since, while having the view run within an open thread doesn’t improve concurrency across views, it does allow users to take advantage of async within the view (cache, ORM, templates, etc)

When adapting third-party sync middlewares, we could also raise a warning and point to the documentation on how to port them to incentivize users to rewrite them.

I would suggest adding configuration to specify the behaviour of the middleware processing. An example:

- `MIDDLWARE_BEHAVIOUR='async'` (default). All middlwares must be async, raise an error if they aren’t
- `MIDDLWARE_BEHAVIOUR='adapt'`. Automatically adapt the middlewares, raise a warning as described above and explain the consequences for concurrency
- `MIDDLWARE_BEHAVIOUR='suspend_sync'`. Whatever the sync suspension magic does.

I’m not sure if there are other behaviours that would make sense here.

* * *

As for pausing the sync middleware, this can probably be achieved via AST manipulations, but if ` __init_subclass__ ` was considered [ugly monkeypatching](https://forum.djangoproject.com/t/supporting-class-based-views-in-async-django/27/2), then this is way off the mark.

---

<div class="post-metadata">

### Author: ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)
#### Post date: [September 11, 2019, 4:11pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/6 "2019-09-11T16:11:57Z")

</div>

> [@nicolaslara](#):
>
> As for pausing the sync middleware, this can probably be achieved via AST manipulations, but if ` __init_subclass__ ` was considered [ugly monkeypatching](https://forum.djangoproject.com/t/supporting-class-based-views-in-async-django/27/2), then this is way off the mark.

Yeah, I am not expecting this to be pretty which is why I’m not assuming we’ll use it (even if we can pull it off).

Your proposed idea of how to adapt middleware is nice - I like the “explicit failure if there’s non-async middleware” mode. That could make rewriting them more palatable. And, as you mention, merely having an async context is worth something, even if it does consume a thread.

---

<div class="post-metadata">

### Author: ![davidfstr](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/davidfstr/32/269_2.png) [@davidfstr](https://forum.djangoproject.com/u/davidfstr)
#### Post date: [September 26, 2019, 11:08pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/7 "2019-09-26T23:08:51Z")

</div>

I have done research on all the default middlewares that come with a new Django project. Most of them look like they can be entirely be rewritten as natively async, with a few exceptions. Details below.

√ = can be rewritten as fully async  
⚠= has parts that need to be sync under certain conditions

- SecurityMiddleware √
- **SessionMiddleware** ⚠
  - session get - may database query
  - session save - may database query

- CommonMiddleware √
- **CsrfViewMiddleware** ⚠
  - render template (only if request rejected) - template render is sync
  - session set - ok for built-in but not for 3rd-party

- AuthenticationMiddleware √
  - HttpRequest.user = SimpleLazyObject(hits database)

- **MessageMiddleware** ⚠
  - message storage load - may session lookup - ok for built-in but not for 3rd-party
  - message storage update - may session set - ok for built-in but not for 3rd-party

- XFrameOptionsMiddleware √

Based on this research, I’ll move to rewrite all the middlewares with √ as async, altering MiddlewareMixin (which is use by all the above middlewares) to support old-style middlewares that are async.

---

<div class="post-metadata">

### Author: ![davidfstr](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/davidfstr/32/269_2.png) [@davidfstr](https://forum.djangoproject.com/u/davidfstr)
#### Post date: [September 27, 2019, 8:34pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/8 "2019-09-27T20:34:33Z")

</div>

I now have a branch [async\_middleware](https://github.com/davidfstr/django/tree/async_middleware), based off the tip of Andrew’s `async_views`, that:

1. alters MiddlewareMixin to export an async interface and support wrapping old-style middleware classes (which can be now async in addition to sync),
2. changes all of the built-in middlewares mentioned in the previous post to be async.

More work is still needed:

[.] **Test suite fails**

- [x] generic\_views.test\_dates.ArchiveIndexViewTests.test\_archive\_view\_invalid - Fix make\_middleware\_decorator() to support async old-style middleware classes.
- [.] flatpages\_tests.test\_csrf.FlatpageCSRFTests.test\_fallback\_flatpage - Fix “django.db.utils.OperationalError: database table is locked: django\_site”. **Anybody know what this error means?**
- (… probably more …)

[] Add more **tests** to do things like running the standard middleware stack with 3rd party session backends, message storage backends, etc that are `@async_unsafe`. Fix any issues identified.

[] **Documentation** for MiddlewareMixin should be extended to show how it now supports mixing in to async middleware classes. Also show caveats in upgrading older users of MiddlewareMixin, who must now call super(). **init** (…) properly.

---

<div class="post-metadata">

### Author: ![griff\_rees](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/griff_rees/32/310_2.png) [@griff\_rees](https://forum.djangoproject.com/u/griff_rees)
#### Post date: [September 27, 2019, 9:35pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/9 "2019-09-27T21:35:16Z")

</div>

Hi. Mostly a note to myself to remember what I’m working on but: _perhaps_ my work on adding async methods to the `Client` class may be helpful…

---

<div class="post-metadata">

### Author: ![andyide](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andyide/32/5218_2.png) [@andyide](https://forum.djangoproject.com/u/andyide)
#### Post date: [September 29, 2019, 10:55pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/10 "2019-09-29T22:55:32Z")

</div>

I just want to again thank all the folks working on the async functionality.

It is appreciated.

---

<div class="post-metadata">

### Author: ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)
#### Post date: [January 28, 2020, 8:32pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/11 "2020-01-28T20:32:17Z")

</div>

Just thought I’d come back here and cap this off with the news that a redesign of how middleware operates lets a piece of middleware be both async- and sync-capable simultaneously, making this problem very tractable! We can likely port all of Django’s shipped middleware to this model without too much effort.

---

<div class="post-metadata">

### Author: ![andyide](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andyide/32/5218_2.png) [@andyide](https://forum.djangoproject.com/u/andyide)
#### Post date: [January 28, 2020, 9:49pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/12 "2020-01-28T21:49:59Z")

</div>

Very good news indeed! Another monkey off your back!

Damn monkeys!

---

<div class="post-metadata">

### Author: ![JonasKs](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/jonasks/32/557_2.png) [@JonasKs](https://forum.djangoproject.com/u/JonasKs)
#### Post date: [January 29, 2020, 7:52am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/13 "2020-01-29T07:52:06Z")

</div>

Interesting! Please post when it’s ready to be viewed by the public. I need to rewrite mine to be async too.

---

<div class="post-metadata">

### Author: ![allen-munsch](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/allen-munsch/32/1627_2.png) [@allen-munsch](https://forum.djangoproject.com/u/allen-munsch)
#### Post date: [July 10, 2020, 7:14pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/14 "2020-07-10T19:14:36Z")

</div>

For anyone else who finds their way to this thread. Saw some notes related to how the sync/async issue with middleware might be handled in 3.1 here: [https://docs.djangoproject.com/en/3.1/topics/http/middleware/#async-middleware](https://docs.djangoproject.com/en/3.1/topics/http/middleware/#async-middleware)

---

<div class="post-metadata">

### Author: ![JonasKs](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/jonasks/32/557_2.png) [@JonasKs](https://forum.djangoproject.com/u/JonasKs)
#### Post date: [July 13, 2020, 6:36am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/15 "2020-07-13T06:36:39Z")

</div>

Nice! I’m a maintaner of a middleware and need to look more into this soon. Has some of the Django shipped middlewares been rewritten for async?

Last time I tested there were also only one request per thread, has this been changed to multiple requests per thread now?

---

<div class="post-metadata">

### Author: ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)
#### Post date: [July 13, 2020, 11:07pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/16 "2020-07-13T23:07:52Z")

</div>

The Django middlewares have been made async-compatible so they only use a thread on the way in and out, rather than keeping it open the entire request, but they’re not fully async.

Full-async mode allows as many requests per thread as your CPU can handle, but it’s still the case that if you bring a sync middleware in that’s totally incompatible that it takes one request per thread  
(Python forces that on us). With the async-aware middleware that’s still running things in threads for handling requests/responses, though, I think it should be able to fit quite a few requests per thread, but I’d need to go check.

---

<div class="post-metadata">

### Author: ![JonasKs](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/jonasks/32/557_2.png) [@JonasKs](https://forum.djangoproject.com/u/JonasKs)
#### Post date: [August 4, 2020, 10:05am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/17 "2020-08-04T10:05:04Z")

</div>

> The Django middlewares have been made async-compatible so they only use a thread on the way in and out, rather than keeping it open the entire request, but they’re not fully async.

I’m not sure if I fully understand this. What do you mean it only uses a thread on the way in and out? Is the request object passed over to another thread for the views? Or is the entire request just handled on a thread, but has the async context set up for you? (This is already a huge thing obviously, just want to ensure I understand correctly)

> Full-async mode allows as many requests per thread as your CPU can handle, but it’s still the case that if you bring a sync middleware in that’s totally incompatible that it takes one request per thread  
> (Python forces that on us).

In order to make the Django middlewares fully async, the ORM needs to be async first - right? So at the moment no middlewares are 100% async?

In addition to this, I’ve also been a bit confused about why Django keep using the deprecated `MiddlewareMixin`? My understanding is that we shouldn’t use this at all, yet I see it’s been [updated for async support](https://github.com/django/django/blob/6c1923029748de4a0f443260751a93c1e0ea10fa/django/utils/deprecation.py#L90).

I’m happy to take these questions elsewhere if you feel that it would do better in another forum section or over the maillist. 🙂

---

<div class="post-metadata">

### Author: ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)
#### Post date: [August 4, 2020, 10:32pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/18 "2020-08-04T22:32:56Z")

</div>

So, what it means is that a synchronous thread is used for the short calls of handling the request and response, but unlike the naive solution, is not held open while the main view runs. This means a single thread can service many middlewares on many concurrent requests.

MiddlewareMixin is still around in Django because all the built in middleware uses it - I don’t claim to have all the answers why, but it was a very convenient single point to upgrade and fix every single middleware rather than patching each middleware individually.

---

<div class="post-metadata">

### Author: ![andrewgodwin](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/andrewgodwin/32/2_2.png) [@andrewgodwin](https://forum.djangoproject.com/u/andrewgodwin)
#### Post date: [August 4, 2020, 10:35pm UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/19 "2020-08-04T22:35:59Z")

</div>

Oh, and with regards to the ORM - yes, the middlewares are not fully async, they are merely async compatible. Some middlewares do no DB access, though, and those could be made fully async right now if we wanted (but they all just use a sync thread for the moment because I didn’t want to poke too many wasps’ nests at once.

---

<div class="post-metadata">

### Author: ![hendrikfrentrup](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/hendrikfrentrup/32/2189_2.png) [@hendrikfrentrup](https://forum.djangoproject.com/u/hendrikfrentrup)
#### Post date: [October 7, 2020, 6:34am UTC](https://forum.djangoproject.com/t/the-trouble-with-middleware/19/20 "2020-10-07T06:34:45Z")

</div>

I hope this fits into the discussion about “trouble with middleware”, it’s about the HTTP view decorators (I think it’s actually closer to the Django core than middleware, but lines are blurry sometimes, no?) The `@require_http_methods` decorator doesn’t work with async views and I think it certainly should since it doesn’t touch the ORM (unlike the `@login_required`)

I don’t want to just mention the [bug I filed about this](https://code.djangoproject.com/ticket/32077#ticket), but rather point to a bit of experimenting I did to try to fix it, which you can see [here](https://github.com/django/django/compare/master...hendrikfrentrup:master). It seems to solve the problem by awaiting the coroutine for an async view and return without awaiting for sync views.

I have not contributed to the Django project before and I’d be keen to contribute this as a patch. So, I am planning to write a test for the async decorators (sync tests are passing). Anything else to consider? All feedback appreciated.

[Next page](https://forum.djangoproject.com/t/the-trouble-with-middleware/19.md?page=2)
