2
0
mirror of https://github.com/codl/forget synced 2024-12-12 00:19:52 +01:00
forget-cancellare-vecchi-toot/libforget/brotli.py

91 lines
3.4 KiB
Python
Raw Normal View History

2017-08-11 21:05:11 +02:00
import brotli as brotli_
from flask import request, make_response
2017-08-11 19:13:37 +02:00
from threading import Thread
from hashlib import sha256
2017-09-24 16:43:54 +02:00
import redis as libredis
import os.path
import mimetypes
2017-08-11 19:13:37 +02:00
2017-08-29 14:46:32 +02:00
2017-08-11 19:13:37 +02:00
class BrotliCache(object):
2017-09-23 19:35:59 +02:00
def __init__(self, redis_uri='redis://', timeout=0.100, expire=60*60*6):
2017-09-24 16:43:54 +02:00
self._redis = None
self._redis_uri = redis_uri
2017-09-08 23:19:59 +02:00
self.timeout = timeout
self.expire = expire
2017-08-11 19:13:37 +02:00
2017-09-24 16:43:54 +02:00
@property
def redis(self):
if not self._redis:
self._redis = libredis.StrictRedis.from_url(self._redis_uri)
self._redis.client_setname('brotlicache')
return self._redis
def compress_and_cache(self, cache_key, lock_key, body, mode=brotli_.MODE_GENERIC):
2017-08-11 21:05:11 +02:00
encbody = brotli_.compress(body, mode=mode)
2017-09-08 23:19:59 +02:00
self.redis.set(cache_key, encbody, px=int(self.expire*1000))
2017-08-11 19:13:37 +02:00
self.redis.delete(lock_key)
def wrap_response(self, response):
if 'br' not in request.accept_encodings or response.is_streamed:
return response
body = response.get_data()
digest = sha256(body).hexdigest()
cache_key = 'brotlicache:{}'.format(digest)
encbody = self.redis.get(cache_key)
2018-11-30 03:25:24 +01:00
response.headers.set('brotli-cache', 'HIT')
if not encbody:
2018-11-30 03:25:24 +01:00
response.headers.set('brotli-cache', 'MISS')
lock_key = 'brotlicache:lock:{}'.format(digest)
if self.redis.set(lock_key, 1, nx=True, ex=10):
2017-08-29 14:46:32 +02:00
mode = (
brotli_.MODE_TEXT
if response.content_type.startswith('text/')
else brotli_.MODE_GENERIC)
t = Thread(
target=self.compress_and_cache,
2017-08-29 14:46:32 +02:00
args=(cache_key, lock_key, body, mode))
t.start()
2017-09-08 23:19:59 +02:00
if self.timeout > 0:
t.join(self.timeout)
encbody = self.redis.get(cache_key)
if not encbody:
2018-11-30 03:25:24 +01:00
response.headers.set('brotli-cache', 'TIMEOUT')
2017-08-23 11:42:53 +02:00
else:
2018-11-30 03:25:24 +01:00
response.headers.set('brotli-cache', 'LOCKED')
2017-08-11 19:13:37 +02:00
if encbody:
response.headers.set('content-encoding', 'br')
response.headers.set('vary', 'accept-encoding')
2017-08-11 19:13:37 +02:00
response.set_data(encbody)
return response
2017-08-11 17:57:32 +02:00
return response
2017-08-29 14:46:32 +02:00
2017-09-08 23:19:59 +02:00
def brotli(app, static=True, dynamic=True, **kwargs):
original_static = app.view_functions['static']
2017-08-29 14:46:32 +02:00
def static_maybe_gzip_brotli(filename=None):
path = os.path.join(app.static_folder, filename)
for encoding, extension in (('br', '.br'), ('gzip', '.gz')):
if encoding not in request.accept_encodings:
continue
encpath = path + extension
if os.path.isfile(encpath):
2017-08-29 14:46:32 +02:00
resp = make_response(
original_static(filename=filename + extension))
resp.headers.set('content-encoding', encoding)
resp.headers.set('vary', 'accept-encoding')
2017-08-29 14:46:32 +02:00
mimetype = (mimetypes.guess_type(filename)[0]
or 'application/octet-stream')
resp.headers.set('content-type', mimetype)
return resp
return original_static(filename=filename)
if static:
app.view_functions['static'] = static_maybe_gzip_brotli
if dynamic:
2017-09-08 23:19:59 +02:00
cache = BrotliCache(redis_uri=app.config.get('REDIS_URI'), **kwargs)
app.after_request(cache.wrap_response)