Performance update.
This commit is contained in:
parent
2d080bfb61
commit
13024a0333
@ -173,7 +173,6 @@ class NotificationItem extends BaseComponent {
|
|||||||
const notif = this.notifData;
|
const notif = this.notifData;
|
||||||
const isUnread = notif.read === 0;
|
const isUnread = notif.read === 0;
|
||||||
const typeLabel = this.getTypeLabel(notif.type);
|
const typeLabel = this.getTypeLabel(notif.type);
|
||||||
console.info(notif);
|
|
||||||
const username = notif.username.name || notif.user_username.name || notif.name || 'Someone';
|
const username = notif.username.name || notif.user_username.name || notif.name || 'Someone';
|
||||||
|
|
||||||
this.addClass('notification-item');
|
this.addClass('notification-item');
|
||||||
|
|||||||
89
proxy.py
89
proxy.py
@ -1,8 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
|
||||||
@fileoverview CORS proxy server for Rantii
|
|
||||||
@author retoor <retoor@molodetz.nl>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -15,6 +11,14 @@ API_BASE = 'https://dr.molodetz.nl/api/'
|
|||||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
PORT = 8101
|
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):
|
def add_cors_headers(response):
|
||||||
response.headers['Access-Control-Allow-Origin'] = '*'
|
response.headers['Access-Control-Allow-Origin'] = '*'
|
||||||
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, DELETE, OPTIONS'
|
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))
|
return add_cors_headers(web.Response(status=200))
|
||||||
|
|
||||||
async def proxy_request(request, method, max_retries=10, retry_delay=2):
|
async def proxy_request(request, method, max_retries=10, retry_delay=2):
|
||||||
path = request.path[5:]
|
api_path = request.path[5:]
|
||||||
parsed = urlparse(path)
|
api_parsed_url = urlparse(api_path)
|
||||||
clean_path = posixpath.normpath(parsed.path).lstrip('/')
|
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)
|
response = web.json_response({'success': False, 'error': 'Invalid path'}, status=400)
|
||||||
return add_cors_headers(response)
|
return add_cors_headers(response)
|
||||||
|
|
||||||
url = API_BASE + clean_path
|
api_url = API_BASE + normalized_api_path
|
||||||
if request.query_string:
|
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
|
body = None
|
||||||
if method in ('POST', 'DELETE'):
|
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):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
async with ClientSession() as session:
|
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()
|
data = await resp.read()
|
||||||
response = web.Response(
|
response = web.Response(
|
||||||
body=data,
|
body=data,
|
||||||
status=resp.status,
|
status=resp.status,
|
||||||
content_type=resp.content_type
|
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)
|
return add_cors_headers(response)
|
||||||
except Exception:
|
except Exception:
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
@ -78,27 +91,34 @@ ALLOWED_IMAGE_HOSTS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
async def handle_image_proxy(request):
|
async def handle_image_proxy(request):
|
||||||
url = request.query.get('url')
|
image_url = request.query.get('url')
|
||||||
if not url:
|
if not image_url:
|
||||||
return add_cors_headers(web.Response(status=400, text='Missing url parameter'))
|
return add_cors_headers(web.Response(status=400, text='Missing url parameter'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = urlparse(url)
|
parsed = urlparse(image_url)
|
||||||
if parsed.hostname not in ALLOWED_IMAGE_HOSTS:
|
if parsed.hostname not in ALLOWED_IMAGE_HOSTS:
|
||||||
return add_cors_headers(web.Response(status=403, text='Host not allowed'))
|
return add_cors_headers(web.Response(status=403, text='Host not allowed'))
|
||||||
except Exception:
|
except Exception:
|
||||||
return add_cors_headers(web.Response(status=400, text='Invalid URL'))
|
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):
|
for attempt in range(3):
|
||||||
try:
|
try:
|
||||||
async with ClientSession() as session:
|
async with ClientSession() as session:
|
||||||
async with session.get(url) as resp:
|
async with session.get(image_url) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
return add_cors_headers(web.Response(status=resp.status))
|
return add_cors_headers(web.Response(status=resp.status))
|
||||||
data = await resp.read()
|
data = await resp.read()
|
||||||
content_type = resp.content_type or 'image/png'
|
content_type = resp.content_type or 'image/png'
|
||||||
response = web.Response(body=data, content_type=content_type)
|
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)
|
return add_cors_headers(response)
|
||||||
except Exception:
|
except Exception:
|
||||||
if attempt < 2:
|
if attempt < 2:
|
||||||
@ -118,32 +138,38 @@ async def handle_api_delete(request):
|
|||||||
return await proxy_request(request, 'DELETE')
|
return await proxy_request(request, 'DELETE')
|
||||||
|
|
||||||
async def handle_static(request):
|
async def handle_static(request):
|
||||||
path = request.path
|
requested_path = request.path
|
||||||
|
|
||||||
if path == '/' or path.startswith('/?'):
|
if requested_path == '/' or requested_path.startswith('/?'):
|
||||||
path = '/index.html'
|
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'))
|
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):
|
if os.path.isdir(full_file_path):
|
||||||
full_path = os.path.join(full_path, 'index.html')
|
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'))
|
return add_cors_headers(web.Response(status=404, text='Not Found'))
|
||||||
|
|
||||||
content_type, _ = mimetypes.guess_type(full_path)
|
if full_file_path in static_cache:
|
||||||
if content_type is None:
|
file_data, file_content_type = static_cache[full_file_path]
|
||||||
content_type = 'application/octet-stream'
|
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:
|
with open(full_file_path, 'rb') as file_handle:
|
||||||
data = f.read()
|
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)
|
return add_cors_headers(response)
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
@ -161,3 +187,4 @@ if __name__ == '__main__':
|
|||||||
print('API proxy at /api/*')
|
print('API proxy at /api/*')
|
||||||
app = create_app()
|
app = create_app()
|
||||||
web.run_app(app, host='localhost', port=PORT, print=None)
|
web.run_app(app, host='localhost', port=PORT, print=None)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user