-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Jetty-12 Review Handler/Processor design #8929
Comments
Throwing in some other API idea: public class ExampleProcessingHandler extends Handler.Abstract
{
@Override
public void handle(Request request) throws Exception
{
// accept the request
Response response = request.accept();
// process the request, produce the response and complete the callback.
response.getCallback().succeeded();
}
}
public class ExampleWrappingHandler extends Handler.Wrapper
{
@Override
public void handle(Request request) throws Exception
{
Request.Wrapper wrapper = new Request.Wrapper(request);
super.handle(wrapper);
}
} |
@lorban I think we tried the idea |
Another problem. And so the response can now be "touched" before the request is accepted, but only certain methods like As an example see The split of the calls between |
Current modelPros
Cons
Back to Square 11 ModelPros:
Cons
|
Sure it it better to hide these instances before they are used, but even with separate handle/process they can be misused. Note that in current jetty<12, we do have handlers that set response headers without producing a full response. Allowing later handlers to complete. I guess the only difference is that currently in 12 we can know if something later has accepted the request before we start messing with the response..... however, it only needs a
Ah so because we currently only need to wrap the request, then look for a handler, we can avoid wrapping response and callback if there is no process (i.e. no accept). For GzipHandler, that is not such a big problem, as it tends t o be deployed high up the tree and thus there will always be something accepting it (unless it is a 404). But if it were a problem, that could speak to a solution that makes the response/callback available from a return from accept, so accept could then be wrapped to wrap them (not that I like wrapping to wrap other things). |
@sbordet @lorban So thinking about how to implement handlers that want to modify response headers I can see a couple of options:
So whilst it is a bit of an issue, it is no worse than 9/10/11, but there are several things we can do to be better than 9/10/11 |
I think there is also an interesting use-case for wrapping accept in So I'm not thinking that wrapping |
@sbordet I'm experimenting with this in jetty-12.0.x...jetty-12-handler-as-processor There is a lot more work to do to make this even testable... but I'm wondering what are the handlers you are most concerned with for this approach? I'll tackle those first. |
@gregw we know that handlers can be implemented in both ways, so I'm not concerned about whether they can be implemented or not. However, I think you should look into the cross-origin handler, delayed handlers, statistics handler, gzip handler, and the capability of re-handling. The cross-origin handler (which is not implemented yet) is perhaps an important use case, since it needs to modify response headers upfront before calling the application, which may result in a 404. Also, the |
A big CON of the current model is in
Currently this has to be done on the |
Jetty version(s)
12
Background
This issue is to discuss some alternatives to #8793, which whilst initially inspired by reusable Processors, was ultimately a PR about cleaning up some strange aspects of the current jetty-12 API.
The Current Design
The current design of
Handler
andRequest.Processor
is designed to:Handler
tree to determine which handler (if any) would handle the request. Instead of callingrequest.setHandled(true)
as in previous jetty versions, acceptance of request handling is now indicated by returning aRequest.Processor
.Request.Processor
)process(request, response, callback);
The Problem(s)
Handler
that needs to wrap the request, must also wrap theRequest.Processor
only so it can capture and re-use the request (wrapped inhandle()
) inprocess()
. The extra allocation can be avoided by having aWrapperProcessor
that is-aRequest.Wrapper
that also implementsRequest.Processor
, but this results in further complication and obscure code.Handler.handle(Request)
andRequest.Processor(Request, Response, Callback)
, there are often cases where there are two request instances and one must be ignored. We have already seen bugs where the wrong request has been used.Request.Processor
wrapper, some handlers use theWrapperProcessor
the processors they return are one use only and cannot be reused for other requests (even though the signature looks like it can be). This results in two classes ofProcessor
, those that must be able to be reused (like those used as error processors) and those that must not be reused (like some returned fromhandle()
).handle()
vsprocess()
. For example,GzipHandler
does some handling inhandle()
, more in a processor wrapper and more in the wrapped request/response/callback.Discussion
There are at least two styles of Handler: ones that wish to process the request; ones that wish to wrap the request that will be processed by another handler:
Observations:
ExampleProcessingHandler
is simple enough, although it ignores the request passed tohandle()
.ExampleWrappingHandler
must wrap theRequest.Processor
even though it has no processing of its own to do. The processor is wrapped only so that the wrapped request instance can be remembered and substituted for the ignored request passed toprocess()
. We could do the assert, but we never do (and that is a band-aid anyway).setHandled(true)
as a means of accepting the request with the return of aRequest.Processor
, which is a new entity that needs to be wrapped that is in addition to theHandler
,Request
,Response
andCallback
, which all can be wrapped. In an attempt to reduce allocations, we make either theHandler
or theRequest
implementProcessor
, but each results in an extra request object that needs to be ignored.Alternative 0
Keep the design, but improve convenience implementations to hide the duplicate request:
This has already been done to some extent, yet it doesn't need an example to be much more complex than these simple ones before the duplicate re-appears. Perhaps more can be done.
Alternative 1
One way to avoid having an ignored request is to get rid of the second request object by simply not passing it in
process
:This now avoid the ignored requests entirely but:
ExampleProcessingHandler
must always allocate a processor just to capture the request. This is now an expensive way to avoid aboolean
acceptance.ExampleWrappingHandler
is more or less as before.Request.Processor
can now no longer be used for error handling, as no request is provided. Either a new interface with the request is needed for error handling, or we need to go back to anErrorHandler
and call bothhandle
andprocess
Alternative 2
Go back to a single method on
Handler
with a mechanism to explicitly accept the request. We remove the whole concept ofRequest.Processor
and instead only haveHandler
Note that:
accept()
replacingsetHandled(true)
handle
vsprocess
) as there is only one.Alternative N
You tell me!
Conclusion
We have introduced a whole new entity that needs to be instantiated/wrapped/defined/documented simply in order to avoid a call like
request.accept()
and to avoid exposingresponse
andcallback
objects before they are active. I think that made more sense when we tried to have different request APIs for the two calls, but it no longer makes sense now they are the same (to allow only one wrapping). I think we need to think about this a bit more.The text was updated successfully, but these errors were encountered: