69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
"""WSGI / ASGI application entrypoint. Gunicorn target stays app:asgi_app.
|
|
|
|
Routes, caching, validation, and Redis statistics live in the adlist package.
|
|
"""
|
|
import hashlib
|
|
from pathlib import Path
|
|
from flask import Flask
|
|
from flask_compress import Compress
|
|
from flask_limiter import Limiter
|
|
from asgiref.wsgi import WsgiToAsgi
|
|
import config
|
|
from adlist import services, pages, converter, metrics
|
|
|
|
app = Flask(__name__)
|
|
app.config.update(MAX_CONTENT_LENGTH=config.MAX_CONTENT_LENGTH, SECRET_KEY=config.SECRET_KEY)
|
|
app.debug = config.FLASK_DEBUG
|
|
|
|
limiter = Limiter(
|
|
key_func=services.get_client_ip,
|
|
app=app,
|
|
default_limits=[config.RATE_LIMIT_DEFAULT],
|
|
storage_uri=config.REDIS_URL,
|
|
)
|
|
Compress(app)
|
|
app.before_request(services.track_request_data)
|
|
app.after_request(services.finalize_response)
|
|
app.add_template_filter(services.datetimeformat_filter, "datetimeformat")
|
|
|
|
# Fingerprinting keeps long-lived cache safe for upgraded CSS / JS assets.
|
|
def _asset_revision():
|
|
root = Path(__file__).resolve().parent / "static"
|
|
digest = hashlib.sha256()
|
|
for asset in sorted(root.rglob("*.css")) + sorted(root.rglob("*.js")):
|
|
digest.update(asset.relative_to(root).as_posix().encode("utf-8"))
|
|
digest.update(asset.read_bytes())
|
|
return digest.hexdigest()[:12]
|
|
|
|
ASSET_REVISION = _asset_revision()
|
|
|
|
@app.context_processor
|
|
def inject_common():
|
|
return {"asset_revision": ASSET_REVISION}
|
|
|
|
app.add_url_rule("/", "index", pages.index, methods=["GET"])
|
|
app.add_url_rule("/favicon.ico", "favicon", pages.favicon, methods=["GET"])
|
|
app.add_url_rule("/convert", "convert", limiter.limit(config.RATE_LIMIT_CONVERT)(converter.convert), methods=["GET", "HEAD"])
|
|
|
|
for name, func in (("stats", metrics.stats), ("stats_json", metrics.stats_json)):
|
|
wrapped = services.basic_auth_required(
|
|
realm=config.STATS_BASIC_AUTH_REALM,
|
|
user=config.STATS_BASIC_AUTH_USER,
|
|
password=config.STATS_BASIC_AUTH_PASS,
|
|
)(func)
|
|
app.add_url_rule("/" + ("stats" if name == "stats" else "stats.json"), name, wrapped, methods=["GET"])
|
|
|
|
for code in (400, 403, 404, 413, 415, 422, 429, 500, 502):
|
|
app.register_error_handler(code, pages.handle_errors)
|
|
|
|
# Compatibility: integrations refer to app:app or app:asgi_app.
|
|
asgi_app = WsgiToAsgi(app)
|
|
convert = converter.convert
|
|
stats = metrics.stats
|
|
stats_json = metrics.stats_json
|
|
redis_client = services.redis_client
|
|
cache_headers = converter.cache_headers
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host=config.BIND_HOST, port=config.BIND_PORT)
|