I have been working with Glassfish/Jackson for over a year and I always have this problem when introducing a new endpoint implementation: when the endpoint is not reached and I want to understand why, the only hints I have to go on are the returned request, since the execution doesn't reach the desired endpoint or resource (routing/mapping error).
I want to intercept the Jersey mapping/routing execution before reaching endpoints/resources, with the "raw" request, so that I can better understand resource/endpoint mapping and routing problems.
This answer to a different question, by #xeye, solved this problem for me:
Create a filter that implements ContainerRequestFilter, and override its filter method. This will be where we can intercept all requests for debugging.
// Executed whenever a request is sent to the API server.
// Useful for debugging errors that don't reach the desired endpoint implementation.
#Provider
#Priority(value = 0)
public class MyFilter implements ContainerRequestFilter {
#Context // request scoped proxy
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
try {
// put a breakpoint or log desired attributes of requestContext here.
} catch (Exception e) {
// ignore
}
}
}
Then register this new class in your ConfigResource implementation.
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig(){
register(MyFilter.class);
// ...
}
(It's OK to Ask and Answer Your Own Questions)
Related
im working ins Spring web flux project and I used functional endpoints instead of controller annotation but I didn't find a solution to handle multiple exceptions for the same endpoint , this is my code :
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.GET("/router/users/{id}"),this::renderException);
}
private Mono<ServerResponse> renderException(ServerRequest request) {
Map<String, Object> error = this.getErrorAttributes(request, ErrorAttributeOptions.defaults());
error.remove("status");
error.remove("requestId");
return ServerResponse.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(error));
}
for the endpoint /router/users/{id} i trigger UserNotFoundException and UserException and I want to return 404 for UserNotFoundException and 500 for UserException but I don't know how to do that in the functional endpoint. anyone can guide me on how to do this in the correct way like we did in using #ExceptionHandler in rest controller?
If returning proper code is all you care about then adding #ResponseStatus for your custom exceptions might be the best solution.
#ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
// more methods...
public UserNotFoundException(final String message) {
super(message);
}
}
But if you want to build ServerResponse by yourself, make use of project reactor operators, like switchIfEmpty() or onErrorMap(), etc. which could be used as following
Mono<ServerResponse> response() {
return exampleService.getUser()
.flatMap(user -> ServerResponse.ok().body(user, User.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
You might want to take a look at docs Which operator do I need? - handling errors
I am using Spring Boot 1.5, and I have a controller that executes asynchronously, returning a CompletableFuture<User>.
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private final UserService service;
#GetMapping("/{id}/address")
public CompletableFuture<Address> getAddress(#PathVariable String id) {
return service.findById(id).thenApply(User::getAddress);
}
}
The method UserService.findById can throw a UserNotFoundException. So, I develop dedicated controller advice.
#ControllerAdvice(assignableTypes = UserController .class)
public class UserExceptionAdvice {
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public String handleUserNotFoundException(UserNotFoundException ex) {
return ex.getMessage();
}
}
The problem is that tests are not passing returning an HTTP 500 status and not a 404 status in case of an unknown user request to the controller.
What's going on?
The problem is due to how a completed exceptionally CompletableFuture handles the exception in subsequent stages.
As stated in the CompletableFuture javadoc
[..] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. [..]
In my case, the thenApply method creates a new instance of CompletionStage that wraps with a CompletionException the original UserNotFoundException :(
Sadly, the controller advice does not perform any unwrapping operation. Zalando developers also found this problem: Async CompletableFuture append errors
So, it seems to be not a good idea to use CompletableFuture and controller advice to implement asynchronous controllers in Spring.
A partial solution is to remap a CompletableFuture<T> to a DeferredResult<T>. In this blog, an implementation of a possible Adapter was given.
public class DeferredResults {
private DeferredResults() {}
public static <T> DeferredResult<T> from(final CompletableFuture<T> future) {
final DeferredResult<T> deferred = new DeferredResult<>();
future.thenAccept(deferred::setResult);
future.exceptionally(ex -> {
if (ex instanceof CompletionException) {
deferred.setErrorResult(ex.getCause());
} else {
deferred.setErrorResult(ex);
}
return null;
});
return deferred;
}
}
So, my original controller would change to the following.
#GetMapping("/{id}/address")
public DeferredResult<Address> getAddress(#PathVariable String id) {
return DeferredResults.from(service.findById(id).thenApply(User::getAddress));
}
I cannot understand why Spring natively supports CompletableFuture as return values of a controller, but it does not handle correctly in controller advice classes.
Hope it helps.
For those of you who still run into trouble with this : even though Spring correctly unwraps the ExecutionException, it doesn't work if you have a handler for the type "Exception", which gets chosen to handle ExecutionException, and not the handler for the underlying cause.
The solution : create a second ControllerAdvice with the "Exception" handler, and put #Order(Ordered.HIGHEST_PRECEDENCE) on your regular handler. That way, your regular handler will go first, and your second ControllerAdvice will act as a catch all.
In Jersey, one can add a ContainerRequestFilter or a ContainerResponseFilter
public class RequestFilter implements ContainerRequestFilter {
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
// logic
}
}
public class ResponseFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// logic
}
}
Both are added using PackagesResourceConfig:
PackagesResourceConfig prc = new PackagesResourceConfig("com.company.xxx");
prc.getContainerRequestFilters().add(new RequestFilter());
prc.getContainerResponseFilters().add(new ResponseFilter());
Although the response filter also has access to the request, it is called AFTER the endpoint is called. What we require is the ability to access the request and the response in the same filter BEFORE the endpoint is called. The request filter is called before the endpoint, but does not have access to the response.
There seems to be no way to add a standard javax.servlet.Filter which has access to both the HttpServletRequest and the HttpServletResponse, and is called before the endpoint is invoked.
Thanks
You cannot filter anything without calling the endpoint, because that's how the request comes to your service. If you meant without hitting the controller, that's a different story. But even then, you want something like pre-matching response filter, which naturally does not exist. It defeats the purpose of your service. Why not just add whatever header you want on all responses after the controller and any other pieces in the handling chain finish their work?! You can access both the request context and the response context in the response filter, so as Paul mentioned in the comments you could store some information you will need in the requestContext while in the ContainerRequestFilter.
JAX-RS spec has some good examples of both filters and interceptors.
To replace a legacy system and not breaking the interface, I'm looking for a way to implement the following scenario:
If a REST client hasn't set a specific HTTP header (applicationId) but sends it as a query-paramter (aka RequestParameter), this value should be taken as a method parameter in a Spring Boot RestController.
The current method looks like this:
#RequestMapping(value = "/something", method = RequestMethod.GET)
public void doSomething(#RequestHeader("applicationId") String applicationId) { }
I think there could be two possible ways:
Annotate the method somehow to map a query-parameter OR a header to a method parameter
Write an Interceptor which reads all query-parameters of a request and set non-existing headers with their values. This way, the method wouldn't have to be touched at all.
In both ways I'm not sure how to implement them (don't know if 1. is even possible). I tried with an own HandlerInterceptor which reads query-params in preHandle (successfully) but isn't able to set headers in the request before it is forwarded to the RestController.
Write a Filter that wraps the incoming request using a HttpServletRequestWrapper. This wrapper should override the getHeader method.
public ParameterToHeaderWrappingRequestFilter extends OncePerRequestFilter {
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(new ParameterToHeaderWrappingRequest(request), response, filterChain);
}
}
Register this filter as a #Bean in your Spring Boot application and it will be applied automatically.
public class ParameterToHeaderWrappingRequest extends HttpServletRequestWrapper {
public String getHeader(String name) {
String header = super.getHeader(name);
if (header == null) {
header = getParameter(name);
}
return header;
}
}
Something like that should do the trick. Depending on your needs you might want/need to override some additional header based methods and you probably want to limit the number of headers to override with parameters.
The rest of your code can now be written as is.
For the rest interface the Spring MVC + RxJava + DeferredResult returned from controllers is used.
I am thinking about adding Hateoas support to the endpoints. The natural choice would be the Spring Hateoas. The problem is that Spring Hateoas would not work in the asynchronous/multi-threading environment since it uses ThreadLocal.
Is there any way to workaround that constraint? I do not think so but maybe someone has any suggestions.
Has anyone used other APIs to add Hateoas support to the rest endpoints?
Thank you.
So the solution I've used is to closure in the request attributes and then apply them as part of a lift operator
public class RequestContextStashOperator<T> implements Observable.Operator<T, T> {
private final RequestAttributes attributes;
/**
* Spring hateoas requires the request context to be set but observables may happen on other threads
* This operator will reapply the context of the constructing thread on the execution thread of the subscriber
*/
public RequestContextStashOperator() {
attributes = RequestContextHolder.currentRequestAttributes();
}
#Override
public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
return new Subscriber<T>() {
#Override
public void onCompleted() {
subscriber.onCompleted();
}
#Override
public void onError(Throwable e) {
subscriber.onError(e);
}
#Override
public void onNext(T t) {
RequestContextHolder.setRequestAttributes(attributes);
subscriber.onNext(t);
}
};
}
}
which you can then use on an observable like
lift(new RequestContextStashOperator<>())
as long as the object is created in the same thread as the request. You can then use a map after in the observable chain to map your object up to being a resource and add your hateoas links in.
So answer is a bit late, but probably someone will find it useful.
You are right about ThreadLocal - if you generate hateoas links in different thread, then it fails with exception. I found some kind of workaround for this:
#RequestMapping(path = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
DeferredResult<ResponseEntity<ProductResource>> example(#PathVariable("id") final String productId, final HttpServletRequest request) {
final DeferredResult<ResponseEntity<ProductResource>> deferredResult = new DeferredResult<>();
request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
final RequestAttributes requestAttributes = new ServletRequestAttributes(request);
productByIdQuery.byId(UUID.fromString(productId)).subscribe(productEntity -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
deferredResult.setResult(result, HttpStatus.OK))
}, deferredResult::setErrorResult);
return deferredResult;
}
So as you see, I save RequestAttributes so I can set them later in the callback. This solves just part of the problem - you'll get another exception because you'll loose contextPath attribute. To avoid this save it explicitly:
request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
After those changes everything seems to work, but looks messy of course. I hope that somebody can provide more elegant solution.