Files
edx-platform/openedx/core/lib/request_utils.py
Régis Behmo 307457a255 Simplify hack to obtain waffle module names
Instead of going up the stacktrace to find the module names of waffle
flags and switches, we manually pass the module __name__ whenever the
flag is created. This is similar to `logging.getLogger(__name__)`
standard behaviour.

As the waffle classes are used outside of edx-platform, we make the new
module_name argument an optional keyword argument. This will change once
we pull waffle_utils outside of edx-platform.

Note that the module name is normally only required to view the list of
existing waffle flags and switches. The module name should not be
necessary to verify if a flag is enabled. Thus, maybe it would make
sense to create a `add` class methor similar to:

    class WaffleFlag:
        @classmethod
        def add(cls, namespace, flag, module):
            instance = cls(namespace, flag)
            cls._class_instances.add((instance, module))
2020-09-14 09:30:24 +02:00

128 lines
4.0 KiB
Python

""" Utility functions related to HTTP requests """
import logging
import re
import crum
from django.conf import settings
from django.test.client import RequestFactory
from django.utils.deprecation import MiddlewareMixin
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from six.moves.urllib.parse import urlparse
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace
try:
import newrelic.agent
except ImportError:
newrelic = None # pylint: disable=invalid-name
# accommodates course api urls, excluding any course api routes that do not fall under v*/courses, such as v1/blocks.
COURSE_REGEX = re.compile(r'^(.*?/courses/)(?!v[0-9]+/[^/]+){}'.format(settings.COURSE_ID_PATTERN))
WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='request_utils')
CAPTURE_COOKIE_SIZES = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'capture_cookie_sizes', __name__)
log = logging.getLogger(__name__)
def get_request_or_stub():
"""
Return the current request or a stub request.
If called outside the context of a request, construct a fake
request that can be used to build an absolute URI.
This is useful in cases where we need to pass in a request object
but don't have an active request (for example, in tests, celery tasks, and XBlocks).
"""
request = crum.get_current_request()
if request is None:
# The settings SITE_NAME may contain a port number, so we need to
# parse the full URL.
full_url = "http://{site_name}".format(site_name=settings.SITE_NAME)
parsed_url = urlparse(full_url)
# Construct the fake request. This can be used to construct absolute
# URIs to other paths.
return RequestFactory(
SERVER_NAME=parsed_url.hostname,
SERVER_PORT=parsed_url.port or 80,
).get("/")
else:
return request
def safe_get_host(request):
"""
Get the host name for this request, as safely as possible.
If ALLOWED_HOSTS is properly set, this calls request.get_host;
otherwise, this returns whatever settings.SITE_NAME is set to.
This ensures we will never accept an untrusted value of get_host()
"""
if isinstance(settings.ALLOWED_HOSTS, (list, tuple)) and '*' not in settings.ALLOWED_HOSTS:
return request.get_host()
else:
return configuration_helpers.get_value('site_domain', settings.SITE_NAME)
def course_id_from_url(url):
"""
Extracts the course_id from the given `url`.
"""
if not url:
return None
match = COURSE_REGEX.match(url)
if match is None:
return None
course_id = match.group('course_id')
if course_id is None:
return None
try:
return CourseKey.from_string(course_id)
except InvalidKeyError:
return None
class CookieMetricsMiddleware(MiddlewareMixin):
"""
Middleware for monitoring the size and growth of all our cookies, to see if
we're running into browser limits.
"""
def process_request(self, request):
"""
Emit custom metrics for cookie size values for every cookie we have.
Don't log contents of cookies because that might cause a security issue.
We just want to see if any cookies are growing out of control.
"""
if not newrelic:
return
if not CAPTURE_COOKIE_SIZES.is_enabled():
return
cookie_names_to_size = {
name: len(value)
for name, value in request.COOKIES.items()
}
for name, size in cookie_names_to_size.items():
metric_name = 'cookies.{}.size'.format(name)
newrelic.agent.add_custom_parameter(metric_name, size)
log.debug(u'%s = %d', metric_name, size)
total_cookie_size = sum(cookie_names_to_size.values())
newrelic.agent.add_custom_parameter('cookies_total_size', total_cookie_size)
log.debug(u'cookies_total_size = %d', total_cookie_size)