Spring Boot in standalone Tomcat ignores exceptions set in DeferredResults - java

I'm running a Spring Boot 1.2.1 application in standalone Tomcat.
I have two controller mappings, which both for simplicity always throw an exception. The first one is for a GET request and it returns a String for the view name:
#RequestMapping(value = {
"/index"
}, method = RequestMethod.GET)
public String init() throws MyRequestProcessingException {
if (true) {
throw new MyRequestProcessingException(
"Something went wrong processing request");
}
return "init";
}
This is the exception definition:
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public class MyRequestProcessingException extends Exception {
public MyRequestProcessingException(String message) {
super(message);
}
}
In embedded Tomcat as well as in standalone Tomcat, trying to access /index always results in a 500 with some JSON error data being returned to the client.
My controller has another method which accepts a POST and returns a DeferredResult:
#RequestMapping(value = "html/start", method = RequestMethod.POST, consumes = APPLICATION_FORM_URLENCODED)
public DeferredResult<String> start(final HttpServletResponse response,
#Valid #ModelAttribute final InitialisationStartAttributes model,
final SessionData sessionExisting) throws MyRequestProcessingException {
final DeferredResult<String> finalResult = new DeferredResult<>(5000);
// Just return an error, so we can test
if (true) {
finalResult.setErrorResult(new MyRequestProcessingException(
"Something went wrong processing request"));
}
return finalResult;
}
In embedded Tomcat, a POST to /html/start returns a 500 with some JSON data in the response body, just like the other request method. However, when I try to reproduce this behaviour using a standalone Tomcat instance, I always get a 200 response with no response body.
I'm using Tomcat 8 in embedded and Tomcat 7.0.52 standalone, but I've also tried with standalone Tomcat 8 and it doesn't make a difference.
My application is deployed in the root application context by modifying /etc/tomcat/Catalina/localhost/ROOT.xml.
EDIT: I've done a bit more testing, and it does seem that DeferredResult is the culprit. I have yet to override handleErrorResult() to see what happens. I'm a bit surprised though, because I don't recall seeing anything in the documentation about the difference between returning a DeferredResult in embedded vs standalone Tomcat.

If you throw an exception in a #RequestMapping and don't handle it anywhere, then the response status is not set before you hit the ErrorPageFilter. If you want to map status to error path in your customizer, you need to handle the error and get the status set in the filter chain somewhere (e.g. with an #ExceptionHandler or by using an Exception with a #ResponseStatus). Another way to get your custom error page to render would be to map exceptions instead of (or as well as) the HttpStatus, using new ErrorPage(Exception.class, "/html/error").

This behaviour was due to a bug in Spring Boot's ErrorPageFilter; see bug report on GitHub. It was fixed in Spring Boot 1.2.3.

Related

How do I change only the status code on a Spring MVC error with Boot?

I'm writing a Web application that makes downstream calls using RestTemplate. If the underlying service returns a 401 Unauthorized, I want to also return a 401 to the calling application; the default behavior is to return a 500. I want to keep the default Spring Boot error response as provided by BasicErrorController; the only change I want is to set the status code.
In custom exceptions, I'd just annotate the exception class with #ResponseStatus, but I can't do that here because HttpClientErrorException.Unauthorized is provided by Spring. I tried two approaches with #ControllerAdvice:
#ExceptionHandler(HttpClientErrorException.Unauthorized.class)
#ResponseStatus(UNAUTHORIZED)
public void returnsEmptyBody(HttpClientErrorException.Unauthorized ex) {
}
#ExceptionHandler(HttpClientErrorException.Unauthorized.class)
#ResponseStatus(UNAUTHORIZED)
public void doesNotUseBasicErrorController(HttpClientErrorException.Unauthorized ex) {
throw new RuntimeException(ex);
}
How can I configure MVC to continue to use all of the built-in Boot error handling except for explicitly overriding the status code?
The below code works for me -- in an app consisting of a #RestController whose one method consisted of throw new HttpClientException(HttpStatus.UNAUTHORIZED), running on an embedded Tomcat. If you're running on a non-embedded Tomcat (or, I suspect, on an embedded non-Tomcat) odds are you'll have to do something at least somewhat different, but I hope this answer is at least somewhat helpful anyway.
#ControllerAdvice
public class Advisor {
#ExceptionHandler(HttpClientException.class)
public String handleUnauthorizedFromApi(HttpClientException ex, HttpServletRequest req) {
if (/* ex instanceof HttpClientException.Unauthorized or whatever */) {
req.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 401);
}
return "forward:/error";
}
}
Explanation: when a HttpClientException is thrown while we're processing request X (in an embedded servlet), what normally happens is that it bubbles all the way up to some org.apache class. (I might fire the debugger up again and work out which one, but this is a pretty high-level explanation so it doesn't matter much.) That class then sends request X back to the application, except this time the request goes to "/error", not to wherever it was originally going. In a Spring Boot app (as long as you don't turn some autoconfiguration off), that means that request X is ultimately processed by some method in BasicErrorController.
OK, so why does this whole system send a 500 to the client unless we do something? Because that org.apache class mentioned above sets something on request X which says "processing this went wrong". It is right to do so: processing request X did, after all, result in an exception which the servlet container had to catch. As far as the container is concerned, the app messed up.
So we want to do a couple of things. First, we want the servlet container to not think we messed up. We achieve this by telling Spring to catch the exception before it reaches the container, ie by writing an #ExceptionHandler method. Second, we want the request to go to "/error" even though we caught the exception. We achieve this by the simple method of sending it there ourselves, via a forward. Third, we want the BasicErrorController to set the correct status and message on the response it sends. It turns out that BasicErrorController (working in tandem with its immediate superclass) looks at an attribute on the request to determine what status code to send to the client. (Figuring this out requires reading the class's source code, but that source code is on github and perfectly readable.) We therefore set that attribute.
EDIT: I got a bit carried away writing this and forgot to mention that I don't think using this code is good practice. It ties you to some implementation details of BasicErrorController, and it's just not the way that the Boot classes are expected to be used. Spring Boot generally assumes that you want it to handle your error completely or not at all; this is a reasonable assumption, too, since piecemeal error handling is generally not a great idea. My recommendation to you -- even if the code above (or something like it) does wind up working -- is to write an #ExceptionHandler that handles the error completely, meaning it sets both status and response body and doesn't forward to anything.
You can customize the error handler of the RestTemplate to throw your custom exception, and then handle that exception with the #ControllerAdvice as you mentioned.
Something like this:
#Configuration
public class RestConfig {
#Bean
public RestTemplate restTemplate(){
// Build rest template
RestTemplate res = new RestTemplate();
res.setErrorHandler(new MyResponseErrorHandler());
return res;
}
private class MyResponseErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
if (HttpStatus.UNAUTHORIZED.equals(response.getStatusCode())) {
// Throw your custom exception here
}
}
}
}

Logging a request header before Spring Security filter chain

I want to log the contents of a given incoming request header as early as possible.
I know about approaches like CommonsRequestLoggingFilter or a logging HandlerInterceptor, however these seem to only log after Spring has executed a lot of other code, such as the Spring Security filter chain.
I want to log before Spring has done any of that, as early as possible based on a single requirement: the log message needs to be able to extract a header from the HTTP request.
Is there a way to do this?
I have found a way to do this using the embedded Tomcat. Since this receives the request before Spring does, you can capture the entire dispatched request from here.
public class CustomLoggerValve extends ValveBase {
private static final Logger logger = LoggerFactory.getLogger(CustomLoggerValve.class);
#Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
MDC.put("requestId", UUID.randomUUID().toString());
logger.info("Received request");
getNext().invoke(request, response);
} finally {
MDC.remove("requestId");
}
}
}
Since I'm using Spring without Spring Boot, I can just add this to my Tomcat directly:
Tomcat tomcat = // ... your tomcat setup
tomcat.getService().getContainer().getPipeline().addValve(new CustomLoggerValve());
I haven't tried, but it looks like you could add this quite easily in Spring Boot too.
Presumably a similar approach would work with embedded Jetty/other JVM web servers.

Spring boot endpoint parameter signature, is throwing ambiguous endpoint exception when both parameters are given

Due to the way it was engineered in the past, there was one endpoint created for a specific parameter called foo in this case.
However the requirement meant that the endpoint could be used either with foo or a new parameter called bobby.
After trying to consolidate into one endpoint, the refactor work was too much.
So I opted for overloading the endpoint and using the spring boot trick to have the signature dictated by the request params.
Like so:
#GetMapping(params = {"foo"})
public CollectionResource<FooResource> get(#RequestParam("foo") String foo, ...) {} ...
#GetMapping(params = {"bobby"})
public CollectionResource<FooResource> get(#RequestParam("bobby") {} ...
This works well when interacting with the endpoints like so:
localhost:8080/testEndpoint?foo=bar
localhost:8080/testEndpoint?bobby=tables
However I discovered an edge case when trying the following:
localhost:8080/testEndpoint?bobby=tables&foo=bar
Which throws the following runtime exception
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/testEndpoint/':
This endpoint is not hit by users but programmatically, so its very low chance this case would happen. However is there a way to setup the controller so it can handle this and just throw a BadRequest etc. instead of blowing up?
Spring Boot Version : 1.5.16.RELEASE
Why not choose a primary endpoint?
For the first, just add the additional parameter to it
public CollectionResource<FooResource> get(#RequestParam("foo") String foo, ...
,#RequestParam("bobby")) {
By that first endpoint will be chosen in this corner case
Spring can not distinguish the endpoints on the basis of Request param.
Instead of two endpoint for serving two Request Param, have only one end point with two Request params. You have the options of making it not required.
#RequestParam("foo") String foo required = false, #RequestParam("bobby") String foo required = false
This gives you more simpler way to handle the API
try with exception handlers
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse error = new ErrorResponse("Server Error", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}

Spring Boot how to ignore HttpStatus Exceptions

I'm building an Application using Spring Boot. This application is distributed, which means I have multiple API's that call each others.
One of my underlying services interacts with a database and responds with the requested data. If a request to an unexisting ID is made, I response with a 404 HttpStatus:
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
(Same with 400 error on certain operations, or 204 for deleting an entry etc).
The problem is that I have some other Spring Boot applications that call these API's, throw an org.springframework.web.client.HttpClientErrorException: 404 Not Found Exception when they request, in this example, an unexisting entry. But the 404 status code is intended and should not return this exception (causing my Hystrix circuit breaker to call its fallback function).
How can I solve this problem?
The call to the service is implemented like this in my code: ResponseEntity<Object> data = restTemplate.getForEntity(url, Object.class);
My RestTemplate is set up like this:
private RestTemplate restTemplate = new RestTemplate();
Spring's RestTemplate uses a ResponseErrorHandler to handle errors in responses. This interface provides both a way to determine if the response has an error (ResponseErrorHandler#hasError(ClientHttpResponse)) and how to handle it (ResponseErrorHandler#handleError(ClientHttpResponse)).
You can set the RestTemplate's ResponseErrorHandler with RestTemplate#setErrorHandler(ResponseErrorHandler) whose javadoc states
By default, RestTemplate uses a DefaultResponseErrorHandler.
This default implementation
[...] checks for the status code on the
ClientHttpResponse: any code with series
HttpStatus.Series.CLIENT_ERROR or HttpStatus.Series.SERVER_ERROR is
considered to be an error. This behavior can be changed by overriding
the hasError(HttpStatus) method.
In case of an error, it throws the exception you are seeing.
If you want to change this behavior, you can provide your own ResponseErrorHandler implementation (maybe by overriding DefaultResponseErrorHandler) which doesn't consider 4xx as an error or that doesn't throw an exception.
For example
restTemplate.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false; // or whatever you consider an error
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// do nothing, or something
}
});
You can then check the status code of the ResponseEntity returned by getForEntity and handle it yourself.

NotSerializableException escaping Spring Controller and causing problems with Google App Engine Queue

I have a Spring Controller that is being invoked via an HTTP POST from the GAE Queue Scheduler.
#Controller
#RequestMapping(value = RSSPoller.RSS_POLLER_URL)
public class RSSPoller implements Serializable {
private static final long serialVersionUID = -4925178778477404709L;
public static final String RSS_POLLER_URL = "/rsspoller";
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void pollAndProcessRssFeed() throws ServiceException {
try {
// do some stuff
}
catch(Exception e) {
throw new ServiceException("Can't process RSS feed because", e);
}
}
}
However when it gets invoked, the response code is 500 with a Critical log message of
Uncaught exception from servlet
java.lang.RuntimeException: java.io.NotSerializableException: <some spring class that changes all the time, but does not implement java.io.Serializable>
The same log message shows up in the logs with a Warning level as well.
I get similar warning messages (but not critical) in my logs when I invoke other Spring Controllers that either render a web page (GET), or returns some XML data (essentially RPC invokes which use HTTP POST). When I do an HTTP GET/POST to those URLs, the response code is 200 and the output is correct (and I ignore the warning message in the logs).
That leads me to two questions:
Why do I get the Critical error message/HTTP 500 for the POST from the queue, but not the GET/POST to other Spring Controllers in my app?
How can I trap the exception and essentially discard it; as to my purposes the task is complete.
I can post the full exception log if it's of use; for brevity I've omitted it.
you should make your <some spring class that changes all the time, but does not implement java.io.Serializable> just Serializable (not only Controller). Me at least helped it.

Categories