Verified Commit 6d376fed authored by Kevin Morris's avatar Kevin Morris
Browse files

feat(rpc): add ETag header with md5 hash content

The ETag header can be used for client-side caching.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag



Signed-off-by: Kevin Morris's avatarKevin Morris <kevr@0cost.org>
parent b3b31394
import hashlib
from http import HTTPStatus
from typing import List, Optional
from urllib.parse import unquote
from fastapi import APIRouter, Query, Request
import orjson
from fastapi import APIRouter, Query, Request, Response
from fastapi.responses import JSONResponse
from aurweb.ratelimit import check_ratelimit
......@@ -74,4 +78,21 @@ async def rpc(request: Request,
# Prepare list of arguments for input. If 'arg' was given, it'll
# be a list with one element.
arguments = parse_args(request)
return JSONResponse(rpc.handle(arguments))
data = rpc.handle(arguments)
# Serialize `data` into JSON in a sorted fashion. This way, our
# ETag header produced below will never end up changed.
output = orjson.dumps(data, option=orjson.OPT_SORT_KEYS)
# Produce an md5 hash based on `output`.
md5 = hashlib.md5()
md5.update(output)
etag = md5.hexdigest()
# Finally, return our JSONResponse with the ETag header.
# The ETag header expects quotes to surround any identifier.
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
return Response(output.decode(), headers={
"Content-Type": "application/json",
"ETag": f'"{etag}"'
})
......@@ -99,8 +99,7 @@ class RPC:
data: Dict[str, Any]):
# Walk through all related PackageDependencies and produce
# the appropriate dict entries.
depends = package.package_dependencies
for dep in depends:
for dep in package.package_dependencies:
if dep.DepTypeID in DEP_TYPES:
key = DEP_TYPES.get(dep.DepTypeID)
......@@ -114,8 +113,7 @@ class RPC:
data: Dict[str, Any]):
# Walk through all related PackageRelations and produce
# the appropriate dict entries.
relations = package.package_relations
for rel in relations:
for rel in package.package_relations:
if rel.RelTypeID in REL_TYPES:
key = REL_TYPES.get(rel.RelTypeID)
......
......@@ -488,3 +488,11 @@ def test_rpc_ratelimit(getint: mock.MagicMock, pipeline: Pipeline):
# The new first request should be good.
response = make_request("/rpc?v=5&type=suggest-pkgbase&arg=big")
assert response.status_code == int(HTTPStatus.OK)
def test_rpc_etag():
response1 = make_request("/rpc?v=5&type=suggest-pkgbase&arg=big")
response2 = make_request("/rpc?v=5&type=suggest-pkgbase&arg=big")
assert response1.headers.get("ETag") is not None
assert response1.headers.get("ETag") != str()
assert response1.headers.get("ETag") == response2.headers.get("ETag")
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment