Hi all,

Currently, middleware is initialized lazily on serving the first request, 
rather than on application start. There may well have been good reasons for 
this historically, but I don't think they apply any longer. Lazy 
initialization is unhelpful if a middleware class throws an error (e.g to 
report a misconfiguration) because the application will appear to start 
successfully and only later report the error when a request is made.

I'd like to propose initializing middleware when `get_wsgi_application` is 
called. This solves the problem described above and, as far as I can tell, 
raises no backwards compatibility issues.

More details on all this below.


### 1. Specific example of the problem

I recently wrote an adapter for the WhiteNoise static file server so it 
could function as Django middleware as well as WSGI middleware 
(https://github.com/evansd/whitenoise). WhiteNoise may be unusual in doing 
a non-trivial amount of work on initialization, but it doesn't seem 
unreasonable. When used as WSGI middleware any errors are triggered 
immediately on start up, but not so when used as Django middleware. This 
makes for a worse developer experience and an increased chance of 
deployment errors.


### 2. Reasons previously given for lazy initialization

There was some brief discussion in this ticket 4 years ago:
https://code.djangoproject.com/ticket/18577

The reason given there is that "resolving on first request makes most 
sense, especially for the case where you might not be serving requests at 
all". Presumably this refers to running non-http-related management 
commands. But in those cases we never instantiate a WSGI application anyway 
(wsgi.py is just never imported) so this is no reason not to initialize 
eagerly when constructing the WSGI application. (Of course, things may have 
been different 4 years ago.)

Another reason is given in the comments in django.core.handles.wsgi:
https://github.com/django/django/blob/3c1b572f1815c295878795b183b1957d0df2ca39/django/core/handlers/wsgi.py#L154

This says "Set up middleware if needed. We couldn't do this earlier, 
because settings weren't available". However `get_wsgi_application` (the 
only public WSGI API) now calls `django.setup()` before constructing the 
handler so settings are in fact available.


### 3. Proposed solution

My proposal is simply to amend `get_wsgi_application` as follows:

    def get_wsgi_application():
        django.setup(set_prefix=False)
        handler = WSGIHandler()
        handler.load_middleware()
        return handler

It's possible that this logic could be moved into the handler's __init__ 
method. This caused no problems with existing application when I tried it, 
however it did cause problems with the test suite which seems to rely on 
the old behaviour in places. The above proposal passes all existing tests 
as is.


### 4. Backwards compatibility issues

Middleware constructors have no means of accessing the request object or 
anything that depends on it. They are called right at the start of the 
handler's `__call__` method before the `request_started` signal is sent and 
before the `script_prefix` thread-local is set. Therefore it cannot matter, 
from the middleware class's perspective, whether it is instantiated before 
or after the first request comes in.


I'm aware this issue probably isn't high on anyone else's priority list, 
but I think it would count as a genuine -- if small -- improvement to 
Django.

Thanks,

Dave

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/a8b2fc46-5bc3-4797-9e94-052eb3e35e8a%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to