top | item 42805024

(no title)

ryathal | 1 year ago

I'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.

discuss

order

porridgeraisin|1 year ago

Yeah, 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|1 year ago

Use 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