diff --git a/README.adoc b/README.adoc index aa8aa0868..f304f4a91 100644 --- a/README.adoc +++ b/README.adoc @@ -107,3 +107,13 @@ and then uses it as though it had just been created), and `"fail"` (which causes the server to exit immediately with an error). |=== + +== Design notes + +=== Why is there no way to add an API handler function that runs on every request? + +In designing Dropshot, we've tried to avoid a few problems we found with frameworks we used in the past. Many (most?) web frameworks, whether in Rust or another language, let you specify a chain of handlers for each route. You can usually specify some handlers that run before or after every request, regardless of the route. We found that after years of evolving a complex API server using this approach, it can get quite hard to follow the control flow for a particular request and to understand the implicit dependencies between different handlers within the chain. This made it time-consuming and error-prone to work on these API servers. (For more details, see https://github.com/oxidecomputer/dropshot/issues/58#issuecomment-713175039[the discussion in issue 58].) + +With Dropshot, we wanted to try something different: if the primary purpose of these handlers is to share code between handlers, what if we rely instead on existing mechanisms -- i.e., function calls. The big risk is that it's easy for someone to accidentally forget some important function call, like the one that authenticates or authorizes a user. We haven't gotten far enough in a complex implementation to need this yet, but the plan is to create a pattern of utility functions that return typed values. For example, where in Node.js you might add an early authentication handler that fills in `request.auth`, with Dropshot you'd have an authentication function that _returns_ an `AuthzContext` struct. Then anything that needs authentication consumes the `AuthzContext` as a function argument. As an author of a handler, you know if you've got an `AuthzContext` available and, if not, how to get one (call the utility function). This composes, too: you can have an authorization function that returns an `AuthnContext`, and the utility function that returns one can consume the `AuthzContext`. Then anything that requires authorization can consume just the `AuthnContext`, and you know it's been authenticated and authorized (possibly with details in that structure). + +It's early, and we may find we need richer facilities in the framework. But we're hopeful this approach will make it faster and smoother to iterate on complex API servers. diff --git a/dropshot/src/lib.rs b/dropshot/src/lib.rs index b5d9b627b..2bd8941b5 100644 --- a/dropshot/src/lib.rs +++ b/dropshot/src/lib.rs @@ -310,6 +310,12 @@ * the OpenAPI spec will not include any status code or type information in * this case. * + * ## What about generic handlers that run on all requests? + * + * There's no mechanism in Dropshot for this. Instead, it's recommended that + * users commonize code using regular Rust functions and calling them. See the + * design notes in the README for more on this. + * * * ## Support for paginated resources *