diff --git a/zerver/middleware.py b/zerver/middleware.py index 5df075dcaa..201c8b2911 100644 --- a/zerver/middleware.py +++ b/zerver/middleware.py @@ -6,6 +6,7 @@ from typing import Any, AnyStr, Callable, Dict, Iterable, List, MutableMapping, from django.conf import settings from django.core.exceptions import DisallowedHost from django.utils.translation import ugettext as _ +from django.utils.deprecation import MiddlewareMixin from zerver.lib.response import json_error from zerver.lib.request import JsonableError @@ -228,7 +229,7 @@ def write_log_line(log_data, path, method, remote_ip, email, client_name, error_data = u"[content more than 100 characters]" logger.info('status=%3d, data=%s, uid=%s' % (status_code, error_data, email)) -class LogRequests(object): +class LogRequests(MiddlewareMixin): # We primarily are doing logging using the process_view hook, but # for some views, process_view isn't run, so we call the start # method here too @@ -281,7 +282,7 @@ class LogRequests(object): error_content=content, error_content_iter=content_iter) return response -class JsonErrorHandler(object): +class JsonErrorHandler(MiddlewareMixin): def process_exception(self, request, exception): # type: (HttpRequest, Any) -> Optional[HttpResponse] if hasattr(exception, 'to_json_error_msg') and callable(exception.to_json_error_msg): @@ -296,7 +297,7 @@ class JsonErrorHandler(object): return json_error(_("Internal server error"), status=500) return None -class TagRequests(object): +class TagRequests(MiddlewareMixin): def process_view(self, request, view_func, args, kwargs): # type: (HttpRequest, Callable[..., HttpResponse], List[str], Dict[str, Any]) -> None self.process_request(request) @@ -315,7 +316,7 @@ def csrf_failure(request, reason=""): else: return html_csrf_failure(request, reason) -class RateLimitMiddleware(object): +class RateLimitMiddleware(MiddlewareMixin): def process_response(self, request, response): # type: (HttpRequest, HttpResponse) -> HttpResponse if not settings.RATE_LIMITING: @@ -342,7 +343,7 @@ class RateLimitMiddleware(object): resp['Retry-After'] = request._ratelimit_secs_to_freedom return resp -class FlushDisplayRecipientCache(object): +class FlushDisplayRecipientCache(MiddlewareMixin): def process_response(self, request, response): # type: (HttpRequest, HttpResponse) -> HttpResponse # We flush the per-request caches after every request, so they @@ -416,7 +417,7 @@ class SessionHostDomainMiddleware(SessionMiddleware): httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response -class SetRemoteAddrFromForwardedFor(object): +class SetRemoteAddrFromForwardedFor(MiddlewareMixin): """ Middleware that sets REMOTE_ADDR based on the HTTP_X_FORWARDED_FOR. diff --git a/zerver/tornado/handlers.py b/zerver/tornado/handlers.py index 3fa3437430..ec17c47249 100644 --- a/zerver/tornado/handlers.py +++ b/zerver/tornado/handlers.py @@ -4,14 +4,18 @@ from __future__ import print_function import sys import tornado.web import logging +import six from django import http from django.conf import settings from django.core.handlers.wsgi import WSGIRequest, get_script_name from django.core.handlers import base +from django.core.handlers.exception import convert_exception_to_response from django.core.urlresolvers import set_script_prefix from django.core import signals from django.core import exceptions, urlresolvers +from django.core.exceptions import MiddlewareNotUsed from django.http import HttpRequest, HttpResponse +from django.utils.module_loading import import_string from threading import Lock from tornado.wsgi import WSGIContainer from six.moves import urllib @@ -21,7 +25,7 @@ from zerver.lib.response import json_response from zerver.middleware import async_request_stop, async_request_restart from zerver.tornado.descriptors import get_descriptor_by_handler_id -from typing import Any, Callable, Dict, List +from typing import Any, Callable, Dict, List, Optional current_handler_id = 0 handlers = {} # type: Dict[int, AsyncDjangoHandler] @@ -85,7 +89,7 @@ class AsyncDjangoHandler(tornado.web.RequestHandler, base.BaseHandler): # Set up middleware if needed. We couldn't do this earlier, because # settings weren't available. - self._request_middleware = None # type: ignore # See self._request_middleware block below + self._request_middleware = None # type: Optional[List[Callable]] self.initLock.acquire() # Check that middleware is still uninitialised. if self._request_middleware is None: @@ -101,6 +105,53 @@ class AsyncDjangoHandler(tornado.web.RequestHandler, base.BaseHandler): descriptor = get_descriptor_by_handler_id(self.handler_id) return "AsyncDjangoHandler<%s, %s>" % (self.handler_id, descriptor) + def load_middleware(self): + # type: () -> None + """ + Populate middleware lists from settings.MIDDLEWARE. This is copied + from Django. This uses settings.MIDDLEWARE setting with the old + business logic. The middleware architecture is not compatible + with our asynchronous handlers. The problem occurs when we return + None from our handler. The Django middlewares throw exception + because they can't handler None, so we can either upgrade the Django + middlewares or just override this method to use the new setting with + the old logic. The added advantage is that due to this our event + system code doesn't change. + """ + self._request_middleware = [] # type: Optional[List[Callable]] + self._view_middleware = [] # type: List[Callable] + self._template_response_middleware = [] # type: List[Callable] + self._response_middleware = [] # type: List[Callable] + self._exception_middleware = [] # type: List[Callable] + + handler = convert_exception_to_response(self._legacy_get_response) + for middleware_path in settings.MIDDLEWARE: + mw_class = import_string(middleware_path) + try: + mw_instance = mw_class() + except MiddlewareNotUsed as exc: + if settings.DEBUG: + if six.text_type(exc): + base.logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc) + else: + base.logger.debug('MiddlewareNotUsed: %r', middleware_path) + continue + + if hasattr(mw_instance, 'process_request'): + self._request_middleware.append(mw_instance.process_request) + if hasattr(mw_instance, 'process_view'): + self._view_middleware.append(mw_instance.process_view) + if hasattr(mw_instance, 'process_template_response'): + self._template_response_middleware.insert(0, mw_instance.process_template_response) + if hasattr(mw_instance, 'process_response'): + self._response_middleware.insert(0, mw_instance.process_response) + if hasattr(mw_instance, 'process_exception'): + self._exception_middleware.insert(0, mw_instance.process_exception) + + # We only assign to this when initialization is complete as it is used + # as a flag for initialization being complete. + self._middleware_chain = handler + def get(self, *args, **kwargs): # type: (*Any, **Any) -> None environ = WSGIContainer.environ(self.request) diff --git a/zproject/settings.py b/zproject/settings.py index 88a6051c8a..efde09e670 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -344,7 +344,7 @@ TEMPLATES = [ non_html_template_engine_settings, ] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( # With the exception of it's dependencies, # our logging middleware should be the top middleware item. 'zerver.middleware.TagRequests',