From 13024a0333737cf747d36694677e6639458cb62c Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 7 Dec 2025 23:18:28 +0100 Subject: [PATCH] Performance update. --- js/components/notification-list.js | 1 - proxy.py | 89 +++++++++++++++++++----------- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/js/components/notification-list.js b/js/components/notification-list.js index f8a0b9b..d850a97 100644 --- a/js/components/notification-list.js +++ b/js/components/notification-list.js @@ -173,7 +173,6 @@ class NotificationItem extends BaseComponent { const notif = this.notifData; const isUnread = notif.read === 0; const typeLabel = this.getTypeLabel(notif.type); - console.info(notif); const username = notif.username.name || notif.user_username.name || notif.name || 'Someone'; this.addClass('notification-item'); diff --git a/proxy.py b/proxy.py index 55e0280..645da3a 100644 --- a/proxy.py +++ b/proxy.py @@ -1,8 +1,4 @@ #!/usr/bin/env python3 -""" -@fileoverview CORS proxy server for Rantii -@author retoor -""" import os import asyncio @@ -15,6 +11,14 @@ API_BASE = 'https://dr.molodetz.nl/api/' ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) PORT = 8101 +API_CACHE_MAX_AGE = 60 +IMAGE_CACHE_MAX_AGE = 86400 +STATIC_CACHE_MAX_AGE = 86400 + +api_cache = {} +image_cache = {} +static_cache = {} + def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, POST, DELETE, OPTIONS' @@ -32,17 +36,23 @@ async def handle_options(request): return add_cors_headers(web.Response(status=200)) async def proxy_request(request, method, max_retries=10, retry_delay=2): - path = request.path[5:] - parsed = urlparse(path) - clean_path = posixpath.normpath(parsed.path).lstrip('/') + api_path = request.path[5:] + api_parsed_url = urlparse(api_path) + normalized_api_path = posixpath.normpath(api_parsed_url.path).lstrip('/') - if '..' in clean_path: + if '..' in normalized_api_path: response = web.json_response({'success': False, 'error': 'Invalid path'}, status=400) return add_cors_headers(response) - url = API_BASE + clean_path + api_url = API_BASE + normalized_api_path if request.query_string: - url += '?' + request.query_string + api_url += '?' + request.query_string + + if method == 'GET' and api_url in api_cache: + cached_data, cached_status, cached_content_type = api_cache[api_url] + response = web.Response(body=cached_data, status=cached_status, content_type=cached_content_type) + response.headers['Cache-Control'] = f'public, max-age={API_CACHE_MAX_AGE}' + return add_cors_headers(response) body = None if method in ('POST', 'DELETE'): @@ -55,13 +65,16 @@ async def proxy_request(request, method, max_retries=10, retry_delay=2): for attempt in range(max_retries): try: async with ClientSession() as session: - async with session.request(method, url, data=body, headers=headers) as resp: + async with session.request(method, api_url, data=body, headers=headers) as resp: data = await resp.read() response = web.Response( body=data, status=resp.status, content_type=resp.content_type ) + if method == 'GET': + api_cache[api_url] = (data, resp.status, resp.content_type) + response.headers['Cache-Control'] = f'public, max-age={API_CACHE_MAX_AGE}' return add_cors_headers(response) except Exception: if attempt < max_retries - 1: @@ -78,27 +91,34 @@ ALLOWED_IMAGE_HOSTS = [ ] async def handle_image_proxy(request): - url = request.query.get('url') - if not url: + image_url = request.query.get('url') + if not image_url: return add_cors_headers(web.Response(status=400, text='Missing url parameter')) try: - parsed = urlparse(url) + parsed = urlparse(image_url) if parsed.hostname not in ALLOWED_IMAGE_HOSTS: return add_cors_headers(web.Response(status=403, text='Host not allowed')) except Exception: return add_cors_headers(web.Response(status=400, text='Invalid URL')) + if image_url in image_cache: + cached_data, cached_content_type = image_cache[image_url] + response = web.Response(body=cached_data, content_type=cached_content_type) + response.headers['Cache-Control'] = f'public, max-age={IMAGE_CACHE_MAX_AGE}' + return add_cors_headers(response) + for attempt in range(3): try: async with ClientSession() as session: - async with session.get(url) as resp: + async with session.get(image_url) as resp: if resp.status != 200: return add_cors_headers(web.Response(status=resp.status)) data = await resp.read() content_type = resp.content_type or 'image/png' response = web.Response(body=data, content_type=content_type) - response.headers['Cache-Control'] = 'public, max-age=86400' + response.headers['Cache-Control'] = f'public, max-age={IMAGE_CACHE_MAX_AGE}' + image_cache[image_url] = (data, content_type) return add_cors_headers(response) except Exception: if attempt < 2: @@ -118,32 +138,38 @@ async def handle_api_delete(request): return await proxy_request(request, 'DELETE') async def handle_static(request): - path = request.path + requested_path = request.path - if path == '/' or path.startswith('/?'): - path = '/index.html' + if requested_path == '/' or requested_path.startswith('/?'): + requested_path = '/index.html' - path = unquote(path).lstrip('/') + requested_path = unquote(requested_path).lstrip('/') - if not is_path_safe(path): + if not is_path_safe(requested_path): return add_cors_headers(web.Response(status=403, text='Forbidden')) - full_path = os.path.join(ROOT_DIR, path) + full_file_path = os.path.join(ROOT_DIR, requested_path) - if os.path.isdir(full_path): - full_path = os.path.join(full_path, 'index.html') + if os.path.isdir(full_file_path): + full_file_path = os.path.join(full_file_path, 'index.html') - if not os.path.isfile(full_path): + if not os.path.isfile(full_file_path): return add_cors_headers(web.Response(status=404, text='Not Found')) - content_type, _ = mimetypes.guess_type(full_path) - if content_type is None: - content_type = 'application/octet-stream' + if full_file_path in static_cache: + file_data, file_content_type = static_cache[full_file_path] + else: + file_content_type, _ = mimetypes.guess_type(full_file_path) + if file_content_type is None: + file_content_type = 'application/octet-stream' - with open(full_path, 'rb') as f: - data = f.read() + with open(full_file_path, 'rb') as file_handle: + file_data = file_handle.read() - response = web.Response(body=data, content_type=content_type) + static_cache[full_file_path] = (file_data, file_content_type) + + response = web.Response(body=file_data, content_type=file_content_type) + response.headers['Cache-Control'] = f'public, max-age={STATIC_CACHE_MAX_AGE}' return add_cors_headers(response) def create_app(): @@ -161,3 +187,4 @@ if __name__ == '__main__': print('API proxy at /api/*') app = create_app() web.run_app(app, host='localhost', port=PORT, print=None) +