We sometimes see rendering errors in the error page itself, which then cause another attempt at rendering the error page. I'm not sure _exactly_ how the loop is occurring, but it looks something like this: 1. An error is raised in a view or middleware and is not caught by application code 2. Django catches the error and calls the registered uncaught error handler 3. Our handler tries to render an error page 4. The rendering code raises an error 5. GOTO 2 (until some sort of server limit is reached) By catching all errors raised during error-page render and substituting in a hardcoded string, we can reduce server resources, avoid logging massive sequences of recursive stack traces, and still give the user *some* indication that yes, there was a problem. This should help address https://github.com/openedx/edx-platform/issues/35151 At least one of these rendering errors is known to be due to a translation error. There's a separate issue for restoring translation quality so that we avoid those issues in the future (https://github.com/openedx/openedx-translations/issues/549) but in general we should catch all rendering errors, including unknown ones. Testing: - In `lms/envs/devstack.py` change `DEBUG` to `False` to ensure that the usual error page is displayed (rather than the debug error page). - Add line `1/0` to the top of the `student_dashboard` function in `common/djangoapps/student/views/dashboard.py` to make that view error. - In `lms/templates/static_templates/server-error.html` replace `static.get_platform_name()` with `None * 7` to make the error template itself produce an error. - Visit <http://localhost:18000/dashboard>. Without the fix, the response takes 10 seconds and produces a 6 MB, 85k line set of stack traces and the page displays "A server error occurred. Please contact the administrator." With the fix, the response takes less than a second and produces three stack traces (one of which contains the error page's rendering error).
145 lines
5.3 KiB
Python
145 lines
5.3 KiB
Python
# pylint: disable=missing-module-docstring
|
|
|
|
# View for semi-static templatized content.
|
|
#
|
|
# List of valid templates is explicitly managed for (short-term)
|
|
# security reasons.
|
|
|
|
import logging
|
|
import mimetypes
|
|
|
|
from django.conf import settings
|
|
from django.http import Http404, HttpResponse, HttpResponseNotFound, HttpResponseServerError
|
|
from django.shortcuts import redirect
|
|
from django.template import TemplateDoesNotExist
|
|
from django.utils.safestring import mark_safe
|
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
|
from django.views.defaults import permission_denied
|
|
from django_ratelimit.exceptions import Ratelimited
|
|
from mako.exceptions import TopLevelLookupException
|
|
|
|
from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string
|
|
from common.djangoapps.util.cache import cache_if_anonymous
|
|
from common.djangoapps.util.views import fix_crum_request
|
|
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
valid_templates = []
|
|
|
|
if settings.STATIC_GRAB:
|
|
valid_templates = valid_templates + [
|
|
'server-down.html',
|
|
'server-error.html'
|
|
'server-overloaded.html',
|
|
]
|
|
|
|
|
|
def index(request, template):
|
|
if template in valid_templates:
|
|
return render_to_response('static_templates/' + template, {})
|
|
else:
|
|
return redirect('/')
|
|
|
|
|
|
@ensure_csrf_cookie
|
|
@cache_if_anonymous()
|
|
def render(request, template):
|
|
"""
|
|
This view function renders the template sent without checking that it
|
|
exists. Do not expose template as a regex part of the url. The user should
|
|
not be able to ender any arbitray template name. The correct usage would be:
|
|
|
|
url(r'^jobs$', 'static_template_view.views.render', {'template': 'jobs.html'}, name="jobs")
|
|
"""
|
|
|
|
# Guess content type from file extension
|
|
content_type, __ = mimetypes.guess_type(template)
|
|
|
|
try:
|
|
context = {}
|
|
# This is necessary for the dialog presented with the TOS in /register
|
|
if template == 'honor.html':
|
|
context['allow_iframing'] = True
|
|
# Format Examples: static_template_about_header
|
|
configuration_base = 'static_template_' + template.replace('.html', '').replace('-', '_')
|
|
page_header = configuration_helpers.get_value(configuration_base + '_header')
|
|
page_content = configuration_helpers.get_value(configuration_base + '_content')
|
|
if page_header:
|
|
context['page_header'] = mark_safe(page_header)
|
|
if page_content:
|
|
context['page_content'] = mark_safe(page_content)
|
|
result = render_to_response('static_templates/' + template, context, content_type=content_type)
|
|
return result
|
|
except TopLevelLookupException:
|
|
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
|
except TemplateDoesNotExist:
|
|
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
|
|
|
|
|
@ensure_csrf_cookie
|
|
@cache_if_anonymous()
|
|
def render_press_release(request, slug):
|
|
"""
|
|
Render a press release given a slug. Similar to the "render" function above,
|
|
but takes a slug and does a basic conversion to convert it to a template file.
|
|
a) all lower case,
|
|
b) convert dashes to underscores, and
|
|
c) appending ".html"
|
|
"""
|
|
template = slug.lower().replace('-', '_') + ".html"
|
|
try:
|
|
resp = render_to_response('static_templates/press_releases/' + template, {})
|
|
except TemplateDoesNotExist:
|
|
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
|
else:
|
|
return resp
|
|
|
|
|
|
@fix_crum_request
|
|
def render_403(request, exception=None):
|
|
"""
|
|
Render the permission_denied template unless it's a ratelimit exception in which case use the rate limit template.
|
|
"""
|
|
if isinstance(exception, Ratelimited):
|
|
return render_429(request, exception)
|
|
|
|
return permission_denied(request, exception)
|
|
|
|
|
|
@fix_crum_request
|
|
def render_404(request, exception=None): # lint-amnesty, pylint: disable=unused-argument
|
|
request.view_name = '404'
|
|
return HttpResponseNotFound(render_to_string('static_templates/404.html', {}, request=request))
|
|
|
|
|
|
@fix_crum_request
|
|
def render_429(request, exception=None): # lint-amnesty, pylint: disable=unused-argument
|
|
"""
|
|
Render the rate limit template as an HttpResponse.
|
|
"""
|
|
request.view_name = '429'
|
|
return HttpResponse(render_to_string('static_templates/429.html', {}, request=request), status=429)
|
|
|
|
|
|
@fix_crum_request
|
|
def render_500(request):
|
|
"""
|
|
Render the generic error page when we have an uncaught error.
|
|
"""
|
|
try:
|
|
return HttpResponseServerError(render_to_string('static_templates/server-error.html', {}, request=request))
|
|
except BaseException as e:
|
|
# If we can't render the error page, ensure we don't raise another
|
|
# exception -- because if we do, we'll probably just end up back
|
|
# at the same rendering error.
|
|
#
|
|
# This is an attempt at working around the recursive error handling issues
|
|
# observed in <https://github.com/openedx/edx-platform/issues/35151>, which
|
|
# were triggered by Mako and translation errors.
|
|
|
|
log.error("Encountered error while rendering error page.", exc_info=True)
|
|
# This message is intentionally hardcoded and does not involve
|
|
# any translation, templating, etc. Do not translate.
|
|
return HttpResponseServerError("Encountered error while rendering error page.")
|