Comments (11)
Honestly, the best practice there would be to not have django serve static files.
A full-featured HTTP server like nginx can do it much better, or even a specialized static hosting with separate domains or sub-domains for app and backend.
from whitenoise.
Another option is to expose a serve
function to be used in views that is suitable for production, unlike django.contrib.staticfiles.views.serve
. Such as django.contrib.whitenoise.views.serve
. Then users can indeed have a catchall route and serve from their views.
My impression, though, is that you wanted Whitenoise to be used as middleware only and not appear anywhere in an app except for settings.py
.
from whitenoise.
@merwok thank you for the response! Did I misinterpret the Whitenoise documentation? It seemed to me to imply "sometimes Whitenoise is actually better than setting up Nginx yourself, unless you are an expert". Based on the SO posts I linked to, there are many people who
- are not experts (i.e. they don't already have this set up in Nginx)
- probably would prefer an easier-to-maintain stack (Nginx layer is more than they care to set up)
- want to use Whitenoise in a plug-and-play manner
- want to enable frontend routing
(Note that all of those describe me.)
from whitenoise.
Can this be achieved by directly using Whitenoise with a Django wsgi application instead of as middleware, as in this SO answer?
from my_django_project import MyWSGIApp
application = MyWSGIApp()
application = WhiteNoise(application, root='/path/to/static/files')
application.add_files('/path/to/more/static/files', prefix='more-files/',root='something')
The WhiteNoise
class has both a prefix
and a root
parameter that might enable this already. Either way, I think it should be possible to do a PR that exclusively edits that class to enable frontend routing via the non-middleware interface in this comment.
from whitenoise.
Did I misinterpret the Whitenoise documentation? It seemed to me to imply "sometimes Whitenoise is actually better than setting up Nginx yourself, unless you are an expert".
That is what the doc says! And I (just a user of whitenoise note, not a maintainer) do agree with it for django static files.
But I wouldn’t try to serve a single-page app with a catch-all route using whitenoise.
from whitenoise.
I think I came up with a decent solution, leveraging the way WhiteNoise middleware manages its own configuration.
views.py:
from django.conf import settings
from whitenoise.middleware import WhiteNoiseMiddleware
wn = WhiteNoiseMiddleware()
def frontend(request, route):
output = wn.process_request(request)
if output is not None:
return output
if wn.autorefresh:
static_file = wn.find_file(settings.STATIC_URL)
else:
static_file = wn.files.get(settings.STATIC_URL)
return wn.serve(static_file, request)
urls.py:
from . import views
from django.conf import settings
urlpatterns = [
...,
path(f"{settings.STATIC_URL_BASE}<path:route>", views.frontend, name="frontend")
]
- Note that this route captures a "path" keyword parameter called
route
that thefrontend
function ignores because theWhiteNoiseMiddleware
object is already getting it fromrequest.path_info
in itsWhiteNoiseMiddleware.process_request
.
settings.py
WHITENOISE_INDEX_FILE = True
- The last part is necessary just so that when you visit
settings.STATIC_URL
, theindex.html
file is automatically served if there is one. That's already part of Whitenoise.
Please comment on whether this is reasonable for production! Thank you.
from whitenoise.
When I say "reasonable in production", please know that I understand you ideally don't want a Python if
statement in between users and their static content, but since Whitenoise already does this, I am really just asking if this introduces additional issues beyond what is unavoidable with Whitenoise. As I mentioned above, "just use Nginx instead of Whitenoise" ignores the versioning that Whitenoise automatically manages. I basically want Whitenoise to be exactly what it already is but to allow frontend routing in SPAs. I think this achieves that.
Really hoping a Whitenoise developer can comment. Thank you.
from whitenoise.
If anyone comes across this, you can create a custom middleware that directs to your SPA's routes in your app that extends WhiteNoise as so:
yourapp/middleware/WhiteNoiseSPAMiddleware
from whitenoise.middleware import WhiteNoiseMiddleware
from django.http import HttpResponseNotFound
from django.conf import settings
class WhiteNoiseSPAMiddleware(WhiteNoiseMiddleware):
def __call__(self, request):
response = self.process_request(request)
if response is None:
response = self.get_response(request)
if settings.WHITENOISE_CUSTOM_FRONTEND_ROUTING and (
isinstance(response, HttpResponseNotFound) or response.status_code == 404
) and request.path_info.startswith(settings.STATIC_URL):
# serve index, route from frontend
if self.autorefresh:
static_file = self.find_file(settings.STATIC_URL)
else:
static_file = self.files.get(settings.STATIC_URL)
return self.serve(static_file, request)
return response
Then, in your settings.py
file, replace the Whitenoise middleware with this, and add the WHITENOISE_CUSTOM_FRONTEND_ROUTING
setting I created. This could easily be configured differently, but this configuration makes sense for the routes in my SPA.
MIDDLEWARE = [
...,
# "whitenoise.middleware.WhiteNoiseMiddleware", # remove - replace with extension
'yourapp.middleware.WhiteNoiseSPAMiddleware',
...,
]
...
WHITENOISE_CUSTOM_FRONTEND_ROUTING = True
With this middleware, you don't have to add ANYTHING to your urls.py
to make sure frontend routing is handled by your frontend. This basically does the following:
- Checks if the route is handled by Django. If so, return the response.
- Checks if the route corresponds to a static file (using exactly the method WhiteNoise uses). If so, return the static file.
- If neither is true, but the route starts with your "frontend" prefix (like
/app
or/static
), then just serve the frontend (e.g./static/index.html
), which presumably handles the routing.
The obvious drawback to this: if your frontend refers to a ./something.js
file, it will receive your index.html
file, which will definitely not work. This is bad, but it's kind of bad anyway if your frontend requests a file that doesn't exist.
I suppose you could prevent Django from serving your index.html
file if the requested route ends in something like /*.html
or '/*.js'. That would probably make sense. Maybe don't allow this to happen if the last sub-route (*/.../sub_route
) contains a period at all, because that would probably indicate it is requesting a file. Seems to be getting jankier the more I futz with it but it does the job for me for now.
from whitenoise.
If you want to use the idea in my last comment, use this:
from whitenoise.middleware import WhiteNoiseMiddleware
from django.http import HttpResponseNotFound
from django.conf import settings
class WhiteNoiseSPAMiddleware(WhiteNoiseMiddleware):
def __call__(self, request):
response = self.process_request(request)
if response is None:
response = self.get_response(request)
if settings.WHITENOISE_CUSTOM_FRONTEND_ROUTING and (
isinstance(response, HttpResponseNotFound) or response.status_code == 404
) and request.path_info.startswith(
settings.STATIC_URL
) and ('.' not in request.path_info.rsplit("/", 1)[-1]):
# serve index, route from frontend
if self.autorefresh:
static_file = self.find_file(settings.STATIC_URL)
else:
static_file = self.files.get(settings.STATIC_URL)
return self.serve(static_file, request)
return response
This excludes routes like .../static/something.js
so that if your frontend requests a dependency that does not exist (like <script src="./something.js"/>
, with a "."
in the filename), instead of accidentally serving index.html
, it just does whatever a normal Django+Whitenoise app does (serve 404 and let the Javascript app respond appropriately)
from whitenoise.
Honestly I think this functionality makes a great case for Whitenoise as a project. I would much rather implement this in Python than figure out the Nginx config mini-language. I know Nginx is used all over the world and it's probably very easy to figure this out from documentation, but I don't see why we should have to.
from whitenoise.
If you want to serve the same HTML file at multiple URL's, or even a catch-all URL, use plain Django to do that. I think it's out of scope for Whitenoise, whose focus is on serving static files which have single URL's.
For example, you can write catch-all-paths routing like so:
from django.urls import path
from example.core.views import frontend_index
urlpatterns = [
path("", frontend_index),
path("<path:path>", frontend_index),
]
And then write a view that serves a pre-built index.html
like so:
from django.conf import settings
from django.http import FileResponse
from django.views.decorators.http import require_GET
@require_GET
def frontend_index(request, path=""):
index_file = (settings.BASE_DIR / "frontend" / "index.html").open("rb")
return FileResponse(index_file)
There's no need to invoke Whitenoise to serve a file within Django, nor for whitenoise to provide a "serve" function. FileResponse
is sufficient, although do consider caching headers.
It's probably better to use a Django template for index.html
though, as within a template you can use {% static %}
to refer to hashed, cacheable static files from Whitenoise. But if that doesn't suit, for whatever reason, you can serve a folder of arbitrary files (built JS etc.) with the WHITENOISE_ROOT
setting - set an appropriate value for WHITENOISE_MAX_AGE
.
For more examples on serving a few arbitrary files with vanilla Django see my favicons post.
from whitenoise.
Related Issues (20)
- ModuleNotFoundError: No module named 'whitenoise' HOT 4
- Restrict *.map files HOT 1
- SSL handshake and open() errors after enabling whitenoise HOT 1
- Python 3.11 support? HOT 2
- Should not handle POST, PUT or DELETE HTTP methods? HOT 2
- Can some files be excluded? HOT 1
- Static files not found / bad args passed to finders.find (dev, Windows) HOT 2
- Azure app service web application is throwing 500 internal server error when using whitenoise - CompressedManifestStaticFilesStorage and DEBUG = False HOT 5
- Clarify interaction between WHITENOISE_KEEP_ONLY_HASHED_FILES and WHITENOISE_ROOT HOT 2
- Clarification for using whitenoise with CDN on digital ocean HOT 2
- do not raise exception if sourceMappingURL rereferenced map is missing HOT 1
- Documentation pointer for nginx users HOT 3
- whitenoise.storage.MissingFileError HOT 1
- Upgrade to Django 4.2 raises new warning HOT 9
- `render` and `redirect` not working with Django, Gunicorn, and Whitenoise HOT 2
- Merge ASGIWhiteNoise HOT 3
- Django app on Azure: STORAGES (previously STATICFILES_STORAGE) WhiteNoise variable breaks application HOT 1
- Random PostgreSQL connection errors HOT 12
- whitenoise's scantree slows down test suite performance HOT 9
- STORAGES results in Server Error 500 HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from whitenoise.