diff --git a/api/__init__.py b/api/__init__.py index d3aa3c9..2d98b92 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1,11 +1,10 @@ -#api/__init__.py import base64 import os from functools import wraps from cryptography import x509 from cryptography.hazmat.primitives import serialization -from flask import jsonify, Blueprint, request +from flask import jsonify, Blueprint, request, make_response from flask_limiter import Limiter from flask_limiter.util import get_remote_address from jwt import decode, ExpiredSignatureError, InvalidTokenError, get_unverified_header @@ -14,6 +13,11 @@ import requests from threading import Lock import env_provider +import hashlib +import json + + + _public_key_cache = {} _lock = Lock() @@ -135,3 +139,24 @@ def register_blueprints(app): app.register_blueprint(bp) + +def generate_etag(data): + serialized = json.dumps(data, sort_keys=True).encode('utf-8') + return hashlib.md5(serialized).hexdigest() + +def etag_response(f): + @wraps(f) + def decorator(*args, **kwargs): + response = f(*args, **kwargs) + if response[1] in (200, 201): + if isinstance(response[0], (dict, list)): + etag = generate_etag(response[0]) + if_none_match = request.headers.get("if_none_match") + if if_none_match == etag: + return jsonify({}), 200 + resp = make_response(response[0], response[1]) + resp.headers["ETag"] = etag + return resp + return response + return decorator + diff --git a/api/config.py b/api/config.py index 7b57f59..5e4bafb 100644 --- a/api/config.py +++ b/api/config.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify, request -from api import require_auth, rate_limits +from api import require_auth, rate_limits, etag_response import re config_bp = Blueprint('config', __name__, url_prefix='/api/config') @@ -10,6 +10,7 @@ def is_valid_rate_limit(limit): return bool(RATE_LIMIT_REGEX.match(limit)) @config_bp.route('/limits', methods=['GET']) @require_auth(roles=['admin']) +@etag_response def limits(): return jsonify(rate_limits), 200 diff --git a/api/markdown.py b/api/markdown.py index 7bf0451..8c09562 100644 --- a/api/markdown.py +++ b/api/markdown.py @@ -2,7 +2,7 @@ from flask import Blueprint, request, jsonify import api -from api import require_auth +from api import require_auth, etag_response from contexts.RequestContext import RequestContext from db import get_db from db.models.Markdown import Markdown @@ -14,6 +14,7 @@ markdown_bp = Blueprint('markdown', __name__, url_prefix='/api/markdown') @markdown_bp.route('/', methods=['GET']) @limiter.limit(api.get_rate_limit) +@etag_response def get_markdowns(): with get_db() as session: mds = session.query(Markdown).all() @@ -21,12 +22,14 @@ def get_markdowns(): @markdown_bp.route('/by_path/', methods=['GET']) @limiter.limit(api.get_rate_limit) +@etag_response def get_markdowns_by_path(path_id): with get_db() as session: markdowns = session.query(Markdown).filter(Markdown.path_id == path_id).all() return jsonify([md.to_dict() for md in markdowns]), 200 @markdown_bp.route('/get_index/', methods=['GET']) @limiter.limit(api.get_rate_limit) +@etag_response def get_index(path_id): with get_db() as session: markdown = session.query(Markdown).filter(Markdown.path_id == path_id).filter(Markdown.title == "index").first() @@ -38,6 +41,7 @@ def get_index(path_id): @markdown_bp.route('/', methods=['GET']) @limiter.limit(api.get_rate_limit) +@etag_response def get_markdown(markdown_id): with get_db() as session: markdown = session.query(Markdown).get(markdown_id) diff --git a/api/path.py b/api/path.py index 394d4b9..67385b8 100644 --- a/api/path.py +++ b/api/path.py @@ -1,7 +1,7 @@ from flask import Blueprint, request, jsonify import api -from api import require_auth +from api import require_auth, etag_response from db import get_db from db.models.Markdown import Markdown from db.models.Path import Path @@ -13,6 +13,7 @@ path_bp = Blueprint('path', __name__, url_prefix='/api/path') @path_bp.route('/', methods=['GET']) @limiter.limit(api.get_rate_limit) +@etag_response def get_root_paths(): with get_db() as session: paths = session.query(Path).filter(Path.parent_id == 1) @@ -20,6 +21,7 @@ def get_root_paths(): @path_bp.route('/', methods=['GET']) @limiter.limit(api.get_rate_limit) +@etag_response def get_path(path_id): with get_db() as session: path = session.query(Path).get(path_id) @@ -29,6 +31,7 @@ def get_path(path_id): @path_bp.route('/parent/', methods=['GET']) @limiter.limit(api.get_rate_limit) +@etag_response def get_path_by_parent(parent_id): with get_db() as session: paths = session.query(Path).filter(Path.parent_id == parent_id).all() diff --git a/api/resource.py b/api/resource.py index b161a2c..6b5d0d7 100644 --- a/api/resource.py +++ b/api/resource.py @@ -4,12 +4,13 @@ from flask import Blueprint, jsonify, request from contexts.RequestContext import RequestContext from db import get_db from db.models.Resource import Resource -from api import require_auth, limiter +from api import require_auth, limiter, etag_response import logging resource_bp = Blueprint('resource', __name__, url_prefix='/api/resource') logger = logging.getLogger(__name__) @resource_bp.route('/', methods=['GET']) @limiter.limit(api.get_rate_limit) + def get_resource(identifier): with get_db() as session: resource = session.query(Resource).get(identifier)