Refactor gzip creation

This commit is contained in:
Yohan Boniface 2023-02-27 12:04:09 +01:00
parent abc1f119d5
commit 5b7f08ed08
3 changed files with 27 additions and 34 deletions

View file

@ -224,6 +224,7 @@ DATABASES = {
} }
} }
UMAP_READONLY = False UMAP_READONLY = False
UMAP_GZIP = True
LOCALE_PATHS = [os.path.join(PROJECT_DIR, 'locale')] LOCALE_PATHS = [os.path.join(PROJECT_DIR, 'locale')]
# ============================================================================= # =============================================================================

View file

@ -1,4 +1,5 @@
import gzip import gzip
import os
from django.urls import get_resolver from django.urls import get_resolver
from django.urls import URLPattern, URLResolver from django.urls import URLPattern, URLResolver
@ -106,9 +107,11 @@ def decorated_patterns(func, *urls):
def gzip_file(from_path, to_path): def gzip_file(from_path, to_path):
stat = os.stat(from_path)
with open(from_path, 'rb') as f_in: with open(from_path, 'rb') as f_in:
with gzip.open(to_path, 'wb') as f_out: with gzip.open(to_path, 'wb') as f_out:
f_out.writelines(f_in) f_out.writelines(f_in)
os.utime(to_path, (stat.st_mtime, stat.st_mtime))
def is_ajax(request): def is_ajax(request):

View file

@ -1,10 +1,10 @@
import hashlib
import json import json
import mimetypes
import os import os
import re import re
import socket import socket
from pathlib import Path
import mimetypes
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import logout as do_logout from django.contrib.auth import logout as do_logout
@ -689,38 +689,16 @@ class GZipMixin(object):
EXT = ".gz" EXT = ".gz"
def _path(self): @property
def path(self):
return self.object.geojson.path return self.object.geojson.path
def path(self):
"""
Serve gzip file if client accept it.
Generate or update the gzip file if needed.
"""
path = self._path()
statobj = os.stat(path)
ae = self.request.META.get('HTTP_ACCEPT_ENCODING', '')
if re_accepts_gzip.search(ae) and getattr(settings, 'UMAP_GZIP', True):
gzip_path = "{path}{ext}".format(path=path, ext=self.EXT)
up_to_date = True
if not os.path.exists(gzip_path):
up_to_date = False
else:
gzip_statobj = os.stat(gzip_path)
if statobj.st_mtime > gzip_statobj.st_mtime:
up_to_date = False
if not up_to_date:
gzip_file(path, gzip_path)
path = gzip_path
return path
def etag(self): def etag(self):
path = self.path()
# Align ETag with Nginx one, because when using X-Send-File, If-None-Match # Align ETag with Nginx one, because when using X-Send-File, If-None-Match
# and If-Modified-Since are handled by Nginx. # and If-Modified-Since are handled by Nginx.
# https://github.com/nginx/nginx/blob/4ace957c4e08bcbf9ef5e9f83b8e43458bead77f/src/http/ngx_http_core_module.c#L1675-L1709 # https://github.com/nginx/nginx/blob/4ace957c4e08bcbf9ef5e9f83b8e43458bead77f/src/http/ngx_http_core_module.c#L1675-L1709
statobj = os.stat(path) statobj = os.stat(self.path)
return 'W/"%x-%x"' % (int(statobj.st_mtime), statobj.st_size) return '"%x-%x"' % (int(statobj.st_mtime), statobj.st_size)
class DataLayerView(GZipMixin, BaseDetailView): class DataLayerView(GZipMixin, BaseDetailView):
@ -728,29 +706,40 @@ class DataLayerView(GZipMixin, BaseDetailView):
def render_to_response(self, context, **response_kwargs): def render_to_response(self, context, **response_kwargs):
response = None response = None
path = self.path() path = self.path
# Generate gzip if needed
accepts_gzip = re_accepts_gzip.search(
self.request.META.get("HTTP_ACCEPT_ENCODING", "")
)
if accepts_gzip and settings.UMAP_GZIP:
gzip_path = Path(f"{path}{self.EXT}")
if not gzip_path.exists():
gzip_file(path, gzip_path)
if getattr(settings, "UMAP_XSENDFILE_HEADER", None): if getattr(settings, "UMAP_XSENDFILE_HEADER", None):
response = HttpResponse() response = HttpResponse()
path = path.replace(settings.MEDIA_ROOT, "/internal") path = path.replace(settings.MEDIA_ROOT, "/internal")
response[settings.UMAP_XSENDFILE_HEADER] = path response[settings.UMAP_XSENDFILE_HEADER] = path
else: else:
# TODO IMS # Do not use in production
# (no cache-control/If-Modified-Since/If-None-Match)
statobj = os.stat(path) statobj = os.stat(path)
with open(path, "rb") as f: with open(path, "rb") as f:
# Should not be used in production! # Should not be used in production!
response = HttpResponse(f.read(), content_type="application/json") response = HttpResponse(f.read(), content_type="application/geo+json")
response["Last-Modified"] = http_date(statobj.st_mtime) response["Last-Modified"] = http_date(statobj.st_mtime)
response["ETag"] = self.etag() response["ETag"] = self.etag()
response["Content-Length"] = statobj.st_size response["Content-Length"] = statobj.st_size
response["Vary"] = "Accept-Encoding" response["Vary"] = "Accept-Encoding"
if path.endswith(self.EXT): if accepts_gzip and settings.UMAP_GZIP:
response["Content-Encoding"] = "gzip" response["Content-Encoding"] = "gzip"
return response return response
class DataLayerVersion(DataLayerView): class DataLayerVersion(DataLayerView):
def _path(self):
@property
def path(self):
return "{root}/{path}".format( return "{root}/{path}".format(
root=settings.MEDIA_ROOT, root=settings.MEDIA_ROOT,
path=self.object.get_version_path(self.kwargs["name"]), path=self.object.get_version_path(self.kwargs["name"]),