+# -*- coding: utf-8 -*-
+# This file is part of django-ssify, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See README.md for more information.
+#
+"""
+Middleware classes provide by django-ssify.
+
+The main middleware you should use is SsiMiddleware. It's responsible
+for providing the SSI variables needed for the SSI part of rendering.
+
+If you're using django's UpdateCacheMiddleware, add
+PrepareForCacheMiddleware *after it* also. It will add all the data
+needed by SsiMiddleware to the response.
+
+If you're using SessionMiddleware with LocaleMiddleware and your
+USE_I18N or USE_L10N is True, you should also use the provided
+LocaleMiddleware instead of the stock one.
+
+And, last but not least, if using CsrfViewMiddleware, move it to the
+top of MIDDLEWARE_CLASSES, even before SsiMiddleware, and use
+`csrf_token` from `ssify` tags library in your templates, this way
+your CSRF tokens will be set correctly.
+
+So, you should end up with something like this:
+
+ MIDDLEWARE_CLASSES = [
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'ssify.middleware.SsiMiddleware',
+ 'django.middleware.cache.UpdateCacheMiddleware',
+ 'ssify.middleware.PrepareForCacheMiddleware',
+ ...
+ 'ssify.middleware.LocaleMiddleware',
+ ...
+ ]
+
+
+"""
+from __future__ import unicode_literals
from django.conf import settings
-from django.utils.cache import patch_vary_headers
from django.middleware import locale
+from django.utils.cache import patch_vary_headers
+from .conf import conf
from .serializers import json_decode, json_encode
+from .utils import ssi_vary_on_cookie
from .variables import SsiVariable, provide_vars
-from . import DEBUG
+
+
+CACHE_HEADERS = ('Pragma', 'Cache-Control', 'Vary')
class PrepareForCacheMiddleware(object):
+ """
+ Patches the response object with all the data SsiMiddleware needs.
+
+ This should go after UpdateCacheMiddleware in MIDDLEWARE_CLASSES.
+ """
@staticmethod
def process_response(request, response):
- if getattr(request, 'ssi_vars_needed', None):
- vars_needed = {k: v.definition
- for (k, v) in request.ssi_vars_needed.items()}
+ """Adds a 'X-Ssi-Vars-Needed' header to the response."""
+ if ('X-Ssi-Vars-Needed' not in response and
+ getattr(request, 'ssi_vars_needed', None)):
+ vars_needed = {}
+ for (k, v) in request.ssi_vars_needed.items():
+ vars_needed[k] = v.definition
response['X-Ssi-Vars-Needed'] = json_encode(
vars_needed, sort_keys=True)
+
+ if ('X-ssi-restore' not in response and
+ getattr(request, 'ssi_patch_response', None)):
+ # We have some response modifiers set by ssi_includes and
+ # ssi_variables. Those are used, because unrendered SSI
+ # templates Django cache receives should have different
+ # caching headers, than pages rendered with request-specific
+ # information.
+ # What we do here is apply the modifiers, but restore
+ # previous values of any cache-relevant headers and set
+ # a custom header with modified values to set them
+ # after-cache.
+ original_fields = {}
+ for field in CACHE_HEADERS:
+ original_fields[field] = response.get(field, None)
+ for modifier in request.ssi_patch_response:
+ modifier(response)
+ restore_fields = {}
+ for field in CACHE_HEADERS:
+ new_value = response.get(field, None)
+ if new_value != original_fields[field]:
+ restore_fields[field] = new_value
+ if original_fields[field] is None:
+ del response[field]
+ else:
+ response[field] = original_fields[field]
+ response['X-ssi-restore'] = json_encode(restore_fields)
+
return response
class SsiMiddleware(object):
+ """
+ The main django-ssify middleware.
+
+ It prepends the response content with SSI set statements,
+ providing values for any SSI variables used in the templates.
+
+ It also patches the Vary header with the values given by
+ the SSI variables.
+
+ If SSIFY_RENDER is set, it also passes the response through
+ SsiRenderMiddleware, which interprets and renders the SSI
+ statements, so you can see the output without an actual
+ SSI-enabled webserver.
+
+ """
def process_request(self, request):
- request.ssi_vary = set()
- #request.ssi_cache_control_after = set()
+ request.ssi_patch_response = []
def process_view(self, request, view_func, view_args, view_kwargs):
request.ssi_vars_needed = {}
vars_needed = request.ssi_vars_needed
else:
vars_needed = json_decode(response.get('X-Ssi-Vars-Needed', '{}'))
- vars_needed = {k: SsiVariable(*v)
- for (k, v) in vars_needed.items()}
+ for k, v in vars_needed.items():
+ vars_needed[k] = SsiVariable(*v)
if vars_needed:
response.content = provide_vars(request, vars_needed) + \
response.content
- # Add the Vary headers declared by all the SSI vars.
- patch_vary_headers(response, sorted(request.ssi_vary))
- # TODO: cache control?
-
- # With a cached response, CsrfViewMiddleware.process_response
- # was never called, so if we used the csrf token, we must do
- # its job of setting the csrf token cookie on our own.
- if (not getattr(request, 'csrf_processing_done', False)
- and request.META.get("CSRF_COOKIE_USED", False)):
- response.set_cookie(settings.CSRF_COOKIE_NAME,
- request.META["CSRF_COOKIE"],
- max_age=getattr(settings, 'CSRF_COOKIE_AGE',
- 60 * 60 * 24 * 7 * 52),
- domain=settings.CSRF_COOKIE_DOMAIN,
- path=settings.CSRF_COOKIE_PATH,
- secure=settings.CSRF_COOKIE_SECURE,
- httponly=settings.CSRF_COOKIE_HTTPONLY
- )
- request.csrf_processing_done = True
+ if 'X-ssi-restore' in response:
+ # The modifiers have already been applied to the response
+ # by the PrepareForCacheMiddleware.
+ # All we need to do is restore cache-relevant headers.
+ for header, content in json_decode(response['X-ssi-restore']).items():
+ if content is None:
+ del response[header]
+ else:
+ response[header] = content
+ else:
+ for response_modifier in getattr(request, 'ssi_patch_response', []):
+ response_modifier(response)
def process_response(self, request, response):
if hasattr(response, 'render') and callable(response.render):
else:
self._process_rendered_response(request, response)
- if DEBUG:
- from .middleware_debug import DebugUnSsiMiddleware
- response = DebugUnSsiMiddleware().process_response(
+ if conf.RENDER:
+ from .middleware_debug import SsiRenderMiddleware
+ response = SsiRenderMiddleware().process_response(
request, response)
return response
if (request.session.accessed and
(settings.USE_I18N or settings.USE_L10N)):
request.session.accessed = False
- request.ssi_vary.add('Cookie')
+ if not hasattr(request, 'ssi_patch_response'):
+ request.ssi_patch_response = []
+ request.ssi_patch_response.append(ssi_vary_on_cookie)