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 <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-10-29 23:10:20 -07:00
parent b3b31394e8
commit 6d376fed15
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
3 changed files with 33 additions and 6 deletions

View file

@ -1,8 +1,12 @@
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}"'
})

View file

@ -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)

View file

@ -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")