Skip to content

Commit

Permalink
Merge pull request #1180 from jefer94/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
jefer94 authored Nov 21, 2023
2 parents 2004817 + 9c399d5 commit f8c2196
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 8 deletions.
9 changes: 5 additions & 4 deletions breathecode/commons/tests/signals/tests_update_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def test_set_cache(cache_cls: Cache, value, params, key):
def test_set_cache_compressed(monkeypatch, cache_cls: Cache, value, params, key):
monkeypatch.setattr('sys.getsizeof', lambda _: (random.randint(10, 1000) * 1024) + 1)

res = cache_cls.set(value, params=params)
res = cache_cls.set(value, params=params, encoding='br')

serialized = brotli.compress(json.dumps(value).encode('utf-8'))
assert res == {
Expand Down Expand Up @@ -319,11 +319,12 @@ def test_set_cache_compressed(monkeypatch, cache_cls: Cache, value, params, key)
'x=1&y=2',
),
])
def test_set_cache_compressed__gzip(monkeypatch, cache_cls: Cache, value, params, key):
@pytest.mark.parametrize('use_gzip,encoding', [(True, 'br'), (False, 'gzip'), (True, 'gzip')])
def test_set_cache_compressed__gzip(monkeypatch, cache_cls: Cache, value, params, key, use_gzip, encoding):
monkeypatch.setattr('sys.getsizeof', lambda _: (random.randint(10, 1000) * 1024) + 1)
monkeypatch.setattr('breathecode.utils.cache.use_gzip', lambda: True)
monkeypatch.setattr('breathecode.utils.cache.use_gzip', lambda: use_gzip)

res = cache_cls.set(value, params=params)
res = cache_cls.set(value, params=params, encoding=encoding)

serialized = gzip.compress(json.dumps(value).encode('utf-8'))
assert res == {
Expand Down
51 changes: 51 additions & 0 deletions breathecode/middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import functools
import gzip
from io import BytesIO
import os
import sys
from django.utils.deprecation import MiddlewareMixin

IS_TEST = os.getenv('ENV', '') not in ['production', 'staging', 'development']
ENABLE_LIST_OPTIONS = ['true', '1', 'yes', 'y']


@functools.lru_cache(maxsize=1)
def is_compression_enabled():
return os.getenv('COMPRESSION', '1').lower() in ENABLE_LIST_OPTIONS


@functools.lru_cache(maxsize=1)
def min_compression_size():
return int(os.getenv('MIN_COMPRESSION_SIZE', '10'))


def must_compress(data):
size = min_compression_size()
if size == 0:
return True

return sys.getsizeof(data) / 1024 > size


class CompressResponseMiddleware(MiddlewareMixin):

def process_response(self, request, response):
# If the response is already compressed, do nothing
if 'Content-Encoding' in response.headers or is_compression_enabled() is False or must_compress(
response.content) is False or IS_TEST:
return response

# Compress the response if it's large enough
if response.content:
accept_encoding = request.META.get('HTTP_ACCEPT_ENCODING', '')
if 'gzip' in accept_encoding:
buffer = BytesIO()
with gzip.GzipFile(fileobj=buffer, mode='wb') as f:
f.write(response.content)
compressed_content = buffer.getvalue()

response.content = compressed_content
response['Content-Encoding'] = 'gzip'
response['Content-Length'] = str(len(compressed_content))

return response
1 change: 1 addition & 0 deletions breathecode/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
#'breathecode.utils.admin_timezone.TimezoneMiddleware',
'breathecode.middlewares.CompressResponseMiddleware',
'django.middleware.http.ConditionalGetMiddleware',
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ class CacheExtension(ExtensionBase):
_cache: Cache
_cache_per_user: bool
_cache_prefix: str
_encoding: Optional[str]

def __init__(self, cache: Cache, **kwargs) -> None:
self._cache = cache()
self._encoding = None

def _optional_dependencies(self, cache_per_user: bool = False, cache_prefix: str = '', **kwargs):
self._cache_per_user = cache_per_user
Expand All @@ -52,6 +54,16 @@ def _get_params(self):
if lang := self._request.META.get('HTTP_ACCEPT_LANGUAGE'):
extends['request.headers.accept-language'] = lang

# including the encoding in the params allow to support compression encoding
encoding = self._request.META.get('HTTP_ACCEPT_ENCODING')
if 'br' in encoding:
extends['request.headers.accept-encoding'] = 'br'
self._encoding = 'br'

elif 'gzip' in encoding:
extends['request.headers.accept-encoding'] = 'gzip'
self._encoding = 'gzip'

if accept := self._request.META.get('HTTP_ACCEPT'):
extends['request.headers.accept'] = accept

Expand Down Expand Up @@ -110,7 +122,11 @@ def _apply_response_mutation(self,
timeout = user_timeout()

try:
res = self._cache.set(data, format=format, params=params, timeout=timeout)
res = self._cache.set(data,
format=format,
params=params,
timeout=timeout,
encoding=self._encoding)
data = res['data']
headers = {
**headers,
Expand Down
28 changes: 25 additions & 3 deletions breathecode/utils/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def set(cls,
data: str | dict | list[dict],
format: str = 'application/json',
timeout: int = -1,
encoding: Optional[str] = None,
params: Optional[dict] = None) -> str:
"""Set a key value pair on the cache in bytes, it reminds the format and compress the data if needed."""

Expand All @@ -261,13 +262,20 @@ def set(cls,

data = b'application/json:gzip ' + data

elif compress:
elif compress and encoding == 'br':
data = brotli.compress(data)
res['data'] = data
res['headers']['Content-Encoding'] = 'br'

data = b'application/json:br ' + data

elif compress and encoding == 'gzip':
data = gzip.compress(data)
res['data'] = data
res['headers']['Content-Encoding'] = 'gzip'

data = b'application/json:gzip ' + data

else:
res['data'] = data

Expand All @@ -284,13 +292,20 @@ def set(cls,

data = b'text/html:gzip ' + data

if compress:
if compress and encoding == 'br':
data = brotli.compress(data)
res['data'] = data
res['headers']['Content-Encoding'] = 'br'

data = b'text/html:br ' + data

if compress and encoding == 'gzip':
data = gzip.compress(data)
res['data'] = data
res['headers']['Content-Encoding'] = 'gzip'

data = b'text/html:gzip ' + data

else:
res['data'] = data

Expand All @@ -307,13 +322,20 @@ def set(cls,

data = b'text/plain:gzip ' + data

if compress:
if compress and encoding == 'br':
data = brotli.compress(data)
res['data'] = data
res['headers']['Content-Encoding'] = 'br'

data = b'text/plain:br ' + data

if compress and encoding == 'gzip':
data = gzip.compress(data)
res['data'] = data
res['headers']['Content-Encoding'] = 'gzip'

data = b'text/plain:gzip ' + data

else:
res['data'] = data

Expand Down

0 comments on commit f8c2196

Please sign in to comment.