-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
update docs to explain intended pattern for common code #58
Comments
This is a good question. There's no built-in facility for generic handlers like this. Our approach so far has been to just put an explicit function call in the various handlers that need it. Since just about every other framework that I've seen makes heavy use of handlers like what you linked to, we should explain this choice. This comment is probably a lot more information than you wanted, but this was an important design point and it's not written down anywhere yet so this feels like a good place to record it. In designing Dropshot, we're trying to avoid a few different problems that we've experienced with some Java and Node frameworks in the past. The first problem is where various handlers like the ones you refer to are written in a way that assumes that some previous handler has already run, but there's no way to statically determine whether that's true. Commonly, an authentication or authorization handler might run early and attach some state (like the uuid of the authenticated user) to a A related problem is that when you have a chain of handlers the way restify does, including both "every request" handlers and a sequence of route-specific handlers (some of which might be functions that are shared across routes), it can become quite hard to follow the control flow for an individual request. Combined with the first problem, I found that it got very difficult to evolve a complex system. When you were modifying a route handler, it was really hard to know which handlers would always have run and which state they would have populated. Even if you analyzed it correctly, the resulting code now makes an assumption about that. That assumption isn't verified by anything, so future authors need to be aware of the dependency and re-do the analysis. The result was a system that was both slow to evolve and tended to break more than it should. In Dropshot, we could certainly provide a chain of handlers with a model similar to Restify: such as a chain of every-request pre-route handlers, a chain of route-specific handlers, and a chain of every-request post-route handlers. We could provide a similar grab bag of request state. Using This leads to the idea of avoiding this pattern altogether (that is, the pattern of using generic handler functions and passing state through an untyped map of Anyway, I'm sure this was more than you wanted, but that's the explanation of why there's nothing there today for this. |
This was actually very insightful and is exactly what I wanted. In this new pattern the the needed context should be explicitly gotten by the handler. I imagine an a small example or section explaining this in RustDoc would help others. Thanks Again, |
Good idea! I'll update this ticket to cover a doc update. |
@davepacheco Hi I just saw you guys on hacker news today and I remembered seeing this ticket a couple of years ago. It got me excited back then, as I've had trouble with managing the composition in middleware based backend frameworks before. I'm wondering how your experience has been using this alternative approach now that you've used it for a while? |
(Sorry for the slow reply -- we've been busy!) I would say that this has worked out very well, though I'd also be interested to hear what my coworkers think! We basically did what we said we were going to do. Specifically, the most obvious need for something like this is for authentication. To do that, almost all of our HTTP entry points start with this call to authenticate the user, which produces an An This value is passed down most layers of the application, first to our "app" layer: and then to the "datastore" layer: where we use it to do authz checks: So, the risk of requiring every handler to do authentication explicitly is that someone adds a new handler but forgets to do authentication and you wind up with a gaping security hole. But that's mitigated by the fact that you can't really do much without an There's still risk though and we also added a coverage test that uses the OpenAPI spec to hit every endpoint without authentication (as well as using an authenticated, unprivileged user) to make sure it produces the right error. So, the big pro we were going for is that control flow is super clear and explicit. I think that's paid off very well. There are a few use cases where it is still easy to get this wrong and I kind of wish we had a better way to do this. The main ones are request-level logging and metric updates. You see this the way every handler also has this boilerplate: https://github.com/oxidecomputer/omicron/blob/f513182346be09008f6ccc0114386db8e3f90153/nexus/src/external_api/http_entrypoints.rs#L470 Also, our app is pretty large. It takes a while to build. To improve build times, we split things up into separate crates. Overall though I'm much, much happier with this approach than the systems I worked on previously. |
Good Day,
I'm currently learning dropshot, and am curious on how to configure a handler to run on all requests.
Similar to http://restify.com/docs/server-api/#use (https://github.com/joyent/sdc-cloudapi/blob/master/lib/app.js#L426)
Or perhaps there is an alternative pattern I should use while using DropShot?
Thanks,
Bruce
The text was updated successfully, but these errors were encountered: