-
Notifications
You must be signed in to change notification settings - Fork 422
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
Intercepting requests, more flexible server interpreter callbacks #1065
Comments
PR: #1089 |
Hi Adam, I haven't yet find time to dig into the implementation but I doubt that's necessary ;) The whole idea is really great. Abstracting over decodingFailureHandler and logginHandler seems so logical and natural that it is hard to believe that no one thought about it before. One small thing. Would it make sense to introduce a new type for expressing the control flow of interceptors? So instead of returning |
I though about that (similarly also in
The interceptor's task is to return an optional response - not decide, how the server interpreter should behave. |
Implemented in 0.18.0-M1 |
Currently, there are two types of callbacks which are invoked when interpreting an endpoint (or a list of endpoints) as a server:
LogRequestHandling
)DecodeFailureHandler
)This works to some degree, but especially the decode failure handler is a source of confusion. I think we can do a better (and more general) job.
The idea isn't new - intercepting and manipulating the request before it is handled is present in each web framework/library (be it servlet filters, akka directives or http4s middleware). So far tapir relied on these interpreter-specific constructs, but even for tapir, an abstraction might be useful. And not even one, but two.
Endpoint interceptors
First, I propose adding
EndpointInterceptors
. These would implement the following:The
ServerRequest
class is an abstraction on top of an underlying request representation. When a request arrives, it is decoded as specified by the given endpoint's inputs. This can fail - for whatever reason, ranging from path mismatch, to an exception when parsing the body - and then theonDecodeFailure
method is called. The result is an optional response. ReturningF[None]
here means that the next endpoint should be tried.If the inputs match, then the
onDecodeSuccess
method is called. In this case, we must return a response (or a failed effectF
, if the server logic or encoding the result to a response fails).An interceptor should perform its logic, which can involve calling the next interceptor in the stack (using
next
), or omitting that step and returning a response right away.By default, we would have a stack of two endpoint interceptors, which would
DecodeFailureHandler
)LogRequestHandling
'; by the way, this should allow fixing Add server option for logging the Left part in F[Either[E, O]]? #719 and Effectful handling of decoding failures #578)The lowest level (the "last"
next
) would returnF[None]
foronDecodeFailure
, and run the server logic & encode outputs foronDecodeSuccess
.This would give us much greater flexibility, and allow implementing functionalities such as:
The default stack would of course be fully interchangeable, so both decode failure handling and log handling could be customised.
Request interceptors
Second, we could add interceptors which would be called even earlier in the process, before any endpoint<->request matching is done:
This would allow changing the server request, and potentially implementing features such as global metrics (which are not endpoint-aware, but can capture e.g. the timing more accurately - before most of tapir's logic is run), CORS, correlation ids, timeouts or compression (this would still be interpreter-specific, basing on the representation of the request's body, of course).
In comparison, endpoint interceptors have much more context (the specific endpoint, and decoding result), but they are called multiple times for a request - once for each endpoint. On the other hand, request interceptors would be called once per request.
Unifying server interpreters
To implement the above, the implementation of server interpreters will have to be unified, so that the general signature of an interpreter is similar to a function
ServerRequest => F[Option[ServerResponse]]
. This would then have to be converted into to a library-specific representation (akkaRoute
s, http4sRoute
s etc.)This means refactoring the server interpreters, capturing some common abstractions and extracting common code (which in general is a good thing). Also, we'll have to introduce an abstraction on top of responses (
ServerResponse
).Naming & feedback
What do you think? Any suggestions on functionalities / signatures?
I'm also considering various names: possible suffixes include
XyzListener
,XyzFilter
,XyzDecorator
and possibly others. I thinkXyzInterceptor
captures the intention, but maybe a better naming scheme is possible. The same applies to theEndpoint
/Request
prefixes.Some ongoing work is available on the https://github.com/softwaremill/tapir/tree/interceptors branch.
The text was updated successfully, but these errors were encountered: