feat(rpc): support POST method

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2022-02-07 00:49:34 -08:00
parent 26f0b014f9
commit 2dfa41c9a5
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
3 changed files with 75 additions and 16 deletions

View file

@ -7,7 +7,7 @@ from urllib.parse import unquote
import orjson import orjson
from fastapi import APIRouter, Query, Request, Response from fastapi import APIRouter, Form, Query, Request, Response
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from aurweb import defaults from aurweb import defaults
@ -65,20 +65,13 @@ def parse_args(request: Request):
JSONP_EXPR = re.compile(r'^[a-zA-Z0-9()_.]{1,128}$') JSONP_EXPR = re.compile(r'^[a-zA-Z0-9()_.]{1,128}$')
@router.get("/rpc.php/") # Temporary! Remove on 03/04 async def rpc_request(request: Request,
@router.get("/rpc.php") # Temporary! Remove on 03/04 v: Optional[int] = None,
@router.get("/rpc/") type: Optional[str] = None,
@router.get("/rpc") by: Optional[str] = defaults.RPC_SEARCH_BY,
async def rpc(request: Request, arg: Optional[str] = None,
v: Optional[int] = Query(default=None), args: Optional[List[str]] = [],
type: Optional[str] = Query(default=None), callback: Optional[str] = None):
by: Optional[str] = Query(default=defaults.RPC_SEARCH_BY),
arg: Optional[str] = Query(default=None),
args: Optional[List[str]] = Query(default=[], alias="arg[]"),
callback: Optional[str] = Query(default=None)):
if not request.url.query:
return documentation()
# Create a handle to our RPC class. # Create a handle to our RPC class.
rpc = RPC(version=v, type=type) rpc = RPC(version=v, type=type)
@ -100,7 +93,14 @@ async def rpc(request: Request,
# Prepare list of arguments for input. If 'arg' was given, it'll # Prepare list of arguments for input. If 'arg' was given, it'll
# be a list with one element. # be a list with one element.
arguments = []
if request.url.query:
arguments = parse_args(request) arguments = parse_args(request)
else:
if arg:
arguments.append(arg)
arguments += args
data = rpc.handle(by=by, args=arguments) data = rpc.handle(by=by, args=arguments)
# Serialize `data` into JSON in a sorted fashion. This way, our # Serialize `data` into JSON in a sorted fashion. This way, our
@ -128,3 +128,33 @@ async def rpc(request: Request,
content = f"/**/{callback}({content.decode()})" content = f"/**/{callback}({content.decode()})"
return Response(content, headers=headers) return Response(content, headers=headers)
@router.get("/rpc.php/") # Temporary! Remove on 03/04
@router.get("/rpc.php") # Temporary! Remove on 03/04
@router.get("/rpc/")
@router.get("/rpc")
async def rpc(request: Request,
v: Optional[int] = Query(default=None),
type: Optional[str] = Query(default=None),
by: Optional[str] = Query(default=defaults.RPC_SEARCH_BY),
arg: Optional[str] = Query(default=None),
args: Optional[List[str]] = Query(default=[], alias="arg[]"),
callback: Optional[str] = Query(default=None)):
if not request.url.query:
return documentation()
return await rpc_request(request, v, type, by, arg, args, callback)
@router.get("/rpc.php/") # Temporary! Remove on 03/04
@router.get("/rpc.php") # Temporary! Remove on 03/04
@router.post("/rpc/")
@router.post("/rpc")
async def rpc_post(request: Request,
v: Optional[int] = Form(default=None),
type: Optional[str] = Form(default=None),
by: Optional[str] = Form(default=defaults.RPC_SEARCH_BY),
arg: Optional[str] = Form(default=None),
args: Optional[List[str]] = Form(default=[], alias="arg[]"),
callback: Optional[str] = Form(default=None)):
return await rpc_request(request, v, type, by, arg, args, callback)

View file

@ -28,6 +28,22 @@ Package information can be obtained by issuing HTTP GET requests of the form
+/rpc?v=5&type=info&arg[]=_pkg1_&arg[]=_pkg2_&...+ where _pkg1_, _pkg2_, ... +/rpc?v=5&type=info&arg[]=_pkg1_&arg[]=_pkg2_&...+ where _pkg1_, _pkg2_, ...
are the names of packages to retrieve package details for. are the names of packages to retrieve package details for.
Request Types
-------------
The GET method here parses arguments in an odd way due to `v=5` historically
supporting this ordering. Later versions will remove support for this kind
of parsing, and the POST method is our first step toward solving it.
* `GET`
- Search arguments are constructed using the last found argument(s).
If the last related argument is `arg[]`, we collect arguments from
end to start until we hit a non-`arg[]` argument. If the last related
argument is `arg`, it used as the one and only argument.
* `POST`
- Search arguments are constructed using `[arg] + args` where
`args == arg[]`.
Examples Examples
-------- --------

View file

@ -782,3 +782,16 @@ def test_rpc_jsonp_callback(client: TestClient):
response = request.get("/rpc", params=params) response = request.get("/rpc", params=params)
assert response.headers.get("content-type") == "application/json" assert response.headers.get("content-type") == "application/json"
assert response.json().get("error") == "Invalid callback name." assert response.json().get("error") == "Invalid callback name."
def test_rpc_post(client: TestClient, packages: List[Package]):
data = {
"v": 5,
"type": "info",
"arg": "big-chungus",
"arg[]": ["chungy-chungus"]
}
with client as request:
resp = request.post("/rpc", data=data)
assert resp.status_code == int(HTTPStatus.OK)
assert resp.json().get("resultcount") == 2