4 ms·
For a public API I wouldn’t do this, but for private APIs we just do POST /api/doThingy with a JSON body, easy peasy RPC anyone can participate in with the most
by jitl 2y ago
For a public API I wouldn’t do this, but for private APIs we just do POST /api/doThingy with a JSON body, easy peasy RPC anyone can participate in with the most basic HTTP client. Works great on every OS and in every browser, no fucking around with “what goes in the URL path” vs “what goes in query params” vs “what goes in the body”.
You can even do this with gRPC if you’re using Buf or Connect - one of the server thingies that try not to suck; they will accept JSON via HTTP happily.
- pandemic_region 2y agoThis. The amount of time lost debating correct rest semantics for a use case is staggering.
- spelunker 2y agoArguing the Right Way To Do REST was a favorite passtime amongst people at one of my previous jobs. Huge waste of time.
- porridgeraisin 2y agoYeah, when it matters in close to 0% of cases. Everyone reads the docs for everything anyways, any shared knowledge granting implicit meaning to things is very close to useless in practice with REST APIs.
- ryathal 2y agoI'd argue just making everything POST is the correct way to do a public Api too. REST tricks you into endpoints no one really wants, or you break it anyway to support functionality needed. SOAP was heavy with it's request/respone, but it was absolutely correct that just sending everything as POST across the wire is easier to work with.
- porridgeraisin 2y agoYeah, I like doing this as well. And all the data goes in the request body. No query parameters. Especially when the primary intended client is an SPA, where the URL shown is decoupled with the API URL. Little bit of a memory jolt: I once built a (not for prod) backend in python as follows: write a list of functions, one for each RPC, in a file `functions.py` then write this generic function for flask: import server.functions as functions @server.post("/<method>") def api(method: str): data: Any = request.json if request.is_json else {} fn = lookup(functions, method) if fn is None: return {"error": "Method not found."} return fn(data) And `lookup()` looks like: def lookup(module: ModuleType, method: str): md = module.__dict__ mn = module.__name__ is_present = method in md is_not_imported = md[method].__module__ == mn is_a_function = inspect.isfunction(md[method]) if is_present and is_not_imported and is_a_function: return md[method] return None So writing a new RPC is just writing a new function, and it all gets automatically wired up to `/api/function_name`. Quite nice. The other nice feature there was automatic "docs" generation, from the python docstring of the function. You see, in python you can dynamically read the docstring of an object. So, I wrote this: def get_docs(module: ModuleType): md = module.__dict__ mn = module.__name__ docs = "" for name in md: if not inspect.isfunction(md[name]) or md[name].__module__ != mn: continue docs += md[name].__doc__ + "\n<br>\n" return docs[:-6] Gives a simple text documentation which I served at an endpoint. Of course you could also write the docstring in openapi yaml format and serve it that way too. Quite cursed overall, but hey, its python. One of the worst footguns here is that you could accidentally expose helper functions, so you have to be sure to not write those in the functions file :P
- jitl 2y agoUse a decorator to expose functions explicitly, otherwise sounds like security issue waiting to happen. All your decorator needs to do is add the function to an __exposed__ set, then when you’re looping over the dict, only expose keys who’s values are in the __exposed__ set
- porridgeraisin 2y agoGood idea Although I suppose using an approach like mine precludes any notion of a serious application.
- curt15 2y agoSome of the AWS APIs work this way too. See for example the Cloudwatch API: https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/Welcome https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIRefer..., which is really JSON-RPC, not REST.