-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Middleware #55
Comments
I haven't played with rocket yet, and the guard system looks cool but I'm particularly interested in logging and request timing and unsure how to go about doing those things. My current plan would be to write wrapper functions and manually wrap all my routes. |
Some inspiration for what middleware is commonly like can be seen on Iron's README. Also even though it is written about Go, this article describes the motivation behind middleware in a web framework. |
Since the whole handler is just a function call, all middleware functionality require is really being able to conveniently wrap the request function in another function, that could do some pre-work, call the actual handler, then do some post-work. Seems to me that integrating some common state that can be associated to a given instance of a rocket http server and used in the handlers should also be mentioned. Here's the usecase to make a good example. Let's say I want to use |
Maybe consider the design of Elixir's plug. It is a very nice way to deal with such things:
Basically it just hands down a request/response data structure a function pipeline which in turn reads/writes to the structure (JSON parsers, auth systems...), thus providing a very simple and composable API. Certain endpoints can have different pipelines (think |
Middleware is a concept that I see as overused and abused. It feels like a crutch and catch-all for things that don't fit into a framework's model. It's difficult to work with, and more importantly, hard to follow and understand. Rocket will not support middleware in the customary sense. There must be a better way, and I hope we discover it. I believe there is a category of problems for which something like middleware, but much, much lighter, is a good fit. Let's call these undiscovered, light-weight things "fairings". Fairings should be able to handle situations for which request guards, data handlers, parameter handlers, and responders are a poor fit. Rocket's been in the public eye for about a month, and I think we've discovered a few cases for which fairings would be a good fit:
The following are things typically handled with middleware that I believe Rocket's primitives handle in a superior fashion:
One common use for middleware is to pass pre-processing information to request handlers. Interestingly, none of the instances identified have a need to pass information. The To the community: what would you like to do with fairings? What other use cases are Rocket's existing primitives a poor fit for? Let's work together to find a better solution. |
Not sure I agree on premise (middleware is bad) but I'll talk through the use cases. I'm going to use the word middleware below because I'm more comfortable with that word.
Consider altering the method before the request is handled (eg. Consider also altering the request URL to normalize, for instance, Those two things could be hard-coded into Rocket as configurable areas.
I don't understand your mental model here. Middleware that does this I can agree is badly written and this kind of thing doesn't exist in any framework I've worked with/on. Edit: I should clarify here. I mean that if you take away the ability for middleware to be able to send arbitrary data to a request.. what is left that you dislike? I'd also argue that the ability to do so is not a property of middleware in general but rather the request or context object used in conjunction.
I feel I need to stress that is kind of the point of middleware. The core idea is to extend the system in way the framework doesn't understand (and shouldn't have to understand). Really what a general middleware system gives us is flexibility. I'm not understand your dislike of middleware or (again) even what mental model of middleware you're coming from. There are a few I can think of that are all quite different. Can you list what you don't like about middleware and we can discuss from there? |
@mehcode I seem to have struck a chord with you. My apologies if I did! Certainly not my intention! My "mental model" is surely no different than yours. I wouldn't even go as far as to call it a "mental model"; it's simply what middleware is: code that sits before and/or after a request/response and optionally modifies the request/response and/or adds information to the request for later use. My concept of middleware is based on its implementation in frameworks such as Rails (through Rack), Django, and Laravel. My main objection to the general idea is that middleware tends to hide the flow of a request. One of the core philosophies guiding Rocket is to always be aware of how a request will be handled. In particular, I want to always be able to answer the question: "What needs to be true for this request to be handled successfully?" I feel that Rocket presently does a great job of allowing one to answer that question, and I fear that middleware may muddle that clarity.
I never stated that modifying the request is something Rocket shouldn't allow. In fact, I believe it should, and further, that it's necessary for exactly these cases. I tried to be very explicit in qualifying with directly when I said: "information should never directly flow from pre-processors to request handlers". What you've illustrated is an example of indirect information; the request handler has no idea, nor does it need to know, that the request method has been changed.
Just about every implementation of middleware I've encountered allow this, and most use this feature for many core tasks. For an example, see Rail's list of default middleware.
The "request/context" object is just about the only means by which pre/post processors function, so I'm a bit confused with this statement. And, again, most middleware implementations do allow you to pass arbitrary information to request handlers. Even the Play framework allows this! Perhaps my clarifications here will allow you to read my original response in a different light. I look forward to any additional use cases you might have. |
@SergioBenitez Not at all! I apologize if I seem harsh/rough.. I tend to get very passionate about this sort of stuff.
Apologies on how I explained it here. I mean..
This is the end of my experience. Because Rust is a static language, middleware can't just add some data to the Request unless we expose a generic get/set system like Go web frameworks do.
Fair enough. You can't just look at a request and understand exactly what a request needs if there is a middleware looking at a header and rejecting the request. I don't think we can get around this.. de-centralized flow.. though. We're trying to add some generic bit of code that happens at the start of every single request and, for example, sets a request header. The key bit here is I don't want requests to know about this (eg. profiling) because it needs to be decoupled and perhaps only added during benchmarking, development, or testing. I'm thinking that the reason you feel this way is that during development of a web service you've encountered the "why is my request not hitting my handler/route?!" and spending X hours debugging only to find its a middleware with a typo on the header its checking. I'd feel that clear, concise debug logging (which is very possible if we structure the middleware right) of why, when, and where rejections are coming from would go a long way to making this feel more natural.
|
This is what Iron does, for instance. I'd really like to avoid it.
What I'm advocating for is a system ("fairings") that makes it so that this "decentralized flow" is opaque to request handlers. A request handler doesn't know when it will run, so blocking requests is opaque to it. It doesn't know the headers, method, etc. of the original request, so changing these things, too, is an opaque action.
I agree wholeheartedly. Rocket takes great care to log the flow of a request at present. The introduction of fairings shouldn't hinder this. To reiterate, the input that I feel is crucial here is: Which use cases are Rocket's existing primitives a poor fit for? Do any of these use cases require arbitrary information to be passed to request handlers? |
Rocket, for example, can parse body of http request as json, when a particular attribute type and annotations are used. In express (js), I would use a middleware to do this parsing. Is it then fair to say that some common middleware functionality is absorbed into rocket's prebuilt machinery? Sometimes you want to check header of the request, and reply back without even trying to parse body. Can this be done now in rocket? With middleware we put layer-by-layer, i.e. developer of an app has a strict control of operations in a pipeline. Can rocket give this control, or is it a tool sharpened for some default pipeline shape? On another note, what rocket does with types is super-cool, and is a magic that is worth it. Generic get/set system is so ancient, that ... . Ya :) . Netty uses pipeline concept. May it better be adopted, in a rusty way. |
@SergioBenitez , how do you mount rocket app inside of bigger rocket app on some route? |
I definitely love the ways the Rocket currently removes the need for middleware, including ManagedState and RequestGuards which are both powerful and effective tools. (There is possible use for a RequestGuard that doesn't provide a value?). The case where Rocket's current tool-set doesn't work well for me is "request/response timing/profiling" which might then be collected with My fast and lazy approach to handle metrics was to write code like:
Some apparent flaws to this hack include that it doesn't capture render time; it doesn't capture routing time; it has boilerplate that's easy to mistype; and it doesn't behave well with exceptions/panics. I'm confident if I spend more time I could mitigate some of those issues by implementing a RequestGuard to start the data collection, a Response wrapper which uses the guard, and possibly codegen to hide the extra boilerplate; however, it would still be an incomplete solution. As an extension to this, if someone wanted to use a tracing system like OpenTracing or Zipkin, there could be a use case for timing (or lifecycle hooks :/ ) in each part of the request lifecycle to be able to track and measure each portion of a single request (maybe data handling, request guards, or forwarding take time or perform complex checks). Overall, I'm advocating against Rails-like Middleware, but am still searching for a good solution to collecting metrics. |
Fairings have landed in ac0c78a! All of the use-cases we've mentioned, and many we haven't, should be easily implementable on-top of fairings. Looking forward to experience reports! The fairings example contains a simple illustration on how to use them. |
Pretty much every framework out there supports some concept of a middleware.
(Possibly mutating) actions on the request, that run before and after the actual handler.
Useful for all kinds of things, including authentication, authorization, logging, profiling, etc.
This should really be implemented in some form.
The text was updated successfully, but these errors were encountered: