I'm trying to pass error information back from an error on a Spring Webflow 2 page through an AbstractFlowHandler. The relevant code looks like this:
#Override
public String handleException(FlowException e, HttpServletRequest request, HttpServletResponse response) {
if (e instanceof NoSuchFlowExecutionException) {
return DEFAULT_URL;
} else {
logger.warn(String.format("Flow threw exception %1$s", e.getMessage()), e);
return ERROR_URL;
}
}
EDIT: The "normal" result of simply returning back to the calling routine at this point is always a 302 REDIRECT, so the "request" and "response" objects attached to this call are lost, and fresh objects are created when the redirect is executed. LeandreM's comment below (using forward()) gives a workaround, but it would be nice if there was some way around that redirect INSIDE the normal processing path. However, I have not been able to find one.
What I would like to do is pass the exception object "e" along to the error page, so that I can print some information about it. But I can't figure out the "proper" way to do this - do I attach it to the request object, the response object, or do it some other way?
Related
TLDR: My method requires 2 redirects/forwards to work (1 for authentication and 1 to serve the jsp page). How can I resolve both redirects/forwards (or make it a non-requirement) so as to not run into the error, java.lang.IllegalStateException: Cannot forward after response has been committed.
For more context:
I have a java servlet with a method that looks something like the following:
#GET
#Path("/test")
#Authenticate
public Viewable test(#Context HttpServletRequest request, #Context HttpServletResponse response) {
Map<String, Object> model = createModel();
return new Viewable("/somePath/jspFile", model);
}
The #Authenticate annotation intercepts the call to do some Open ID Connect type authentication which results in the user being forwarded to a different server for all authentication needs. If the user is authenticated, they are redirected back to my application.
However, when hitting the url for this method, I am getting java.lang.IllegalStateException: Cannot forward after response has been committed. I don't know too much about using this Viewable class, but based on the fact that I don't run into that error when returning String/void/whatever else, I assume returning a new Viewable needs to do some forwarding that results in the user seeing the jsp page.
I've read the main SO post about this error, but I am unsure how to apply the fixes to my current problem. For example, I don't know how I would apply something like the following fix:
protected void doPost() {
if (someCondition) {
sendRedirect();
} else {
forward();
}
}
The fix assumes that I can I can either redirect OR forward, but my current method needs a redirect for authentication AND a forward/redirect to serve the jsp page. Maybe there's an obvious fix I'm missing that doesn't require a complete rehaul of the current code?
Edit: It would be nice if I could check if the user was authenticated first, but I assume using this annotation at all automatically entails an initial redirect
Edit: It looks like the user is redirected for the initial login authentication, but does not need to be redirected again after being authenticated once due to SSO
Ok based on some preliminary testing, it seems like the following solution has worked for me:
Check if the user has already been authenticated
Return a Response rather than a Viewable.
Since the user only needs to be redirected the first time for authentication, I can return an empty/meaningless response as a placeholder. And then once the user has been authenticated and is returned to my app, I can return a Viewable wrapped in a Response object.
So the code would look something like the following:
#GET
#Path("/test")
#Authenticate
public Response test(#Context HttpServletRequest request, #Context HttpServletResponse
response) {
Map<String, Object> model = createModel();
if (userIsAuthenticated()) {
return Response.status(401).build();
} else {
return Response.ok(new Viewable("/somePath/jspFile", model)).build();
}
}
I have a Spring application (not using Spring boot) deployed to tomcat
I'm trying to return error 401 (HttpServletResponse.SC_UNAUTHORIZED) on specific URLs in given condition using OncePerRequestFilter ,
But I keep getting Not found error:
Response code: 404
My Filter(removed conditions):
#Component
public class MyFilter extends OncePerRequestFilter {
private static final Logger logger = Logger.getLogger(MyFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Not autorized");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
try {
PrintWriter out = response.getWriter();
out.write(""); // Empty
out.flush();
} catch (IOException e) {
logger.error("error filtering", e);
}
return; // stop chain
}
}
I tried using similar code to previous similar answer
I believe you can response.sendError inside do Filter method.
EDIT
If I just throw an exception instead I get a generic error code 500
Response code: 500
EDIT 2
I'm adding filter inside onStartup method overriden WebApplicationInitializer's
FilterRegistration myFilter = servletContext.addFilter("myFilter ", MyFilter.class);
myFilter.addMappingForUrlPatterns(null, false, "/myservlet/myendpoint/*");
EDIT 3
Also my filter in #Componenet and its package include in component scan
#ComponentScan(basePackages = { "my.parent.pacakge"})
I tried your code and it works. Different error codes were getting successfully returned. Although, I wanna point out a couple of things-
The method sendError() sends the response to the client and also clears the response buffer. So, anything you write after it into the response after that is of no use.
Calling setStatus() is of no use either. The HTTP response and HTTP response code has already been sent to the client in the above line when you called sendError().
Now, this is my hypothesis why your code is not working for you, but it is for me-
The reason that you might be getting HTTP 404 is that your API is not present. This can be due to a spelling error or maybe due to a simple ignorance like calling your API named /foo/bar like /foo/bar/ i.e. with extra trailing forward slash. I believe that your filter is getting executed successfully and you must be doing something useful there, not the sample code of sendError() that you have explained in the question.
Also, when you throw an Exception in your filter, the control does not reach to the API and API lookup does not happen. Hence, the default HTTP 500 response is sent to the client due to the unhandled exception instead of HTTP 404.
It return 401 as expected only if setStatus is executed after out.write
PrintWriter out = response.getWriter();
out.write("");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
I have a method to handle a particular class of exceptions in a Spring MVC environment.
The metod (simplified) implementation follows
#ExceptionHandler(AjaxException.class)
#ResponseStatus(value=HttpStatus.BAD_REQUEST)
#ResponseBody
public Exception handleException(AjaxException ex) {
return ex;
}
This is works fine, but to return a different ResponseStatus I have to create a new handling method.
Is it possible to change the response status inside the method body instead of using the #ResponseStatus annotation without changing the return type?
If not, is it possible to achieve the same result changing the return type (maybe serializing the exception class by myself and returning it as a string)?
Add the HttpServletResponse to the method signature and simply call the setStatus method.
#ExceptionHandler(AjaxException.class)
#ResponseBody
public Exception handleException(AjaxException ex, HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return ex;
}
Something like that should work.
Easy done, reading a little more carefully the spring documentation.
It is possible to pass the HttpServletResponse as an object parameter. In such object is possible to set the return code. The syntax is as follows:
#ExceptionHandler(AjaxException.class)
#ResponseBody
public AjaxException handleException(AjaxException ex,HttpServletResponse response) {
//test code ahead, not part of the solution
//throw new NullPointerException();
//end of test code
response.setStatus(404);//example
return ex;
}
This will return the json serialization of the exception along with the specified http return code.
EDIT:
I deleted this answer yesterday because this solution didn't seem to work. The problem was a bit trickyer: when you manage an exception this way, if the method annotated with ExceptionHandler throws an exception itself then the thrown exception is ignored and the original exception is thrown instead.
My code was somehow like the solution I posted (it threw exception in the start of the method), so I couldn't see the json output, the standard spring exception handler was fired instead. To resolve I simply trycatched the exception-throwing line and everything was ok.
Continuing the thread: Global exception page in Apache Tiles and Spring MVC
I have an error page defined in my web.xml:
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/404.jsp</location>
</error-page>
I have noticed one more issue in Spring MVC:
a)
if no #RequestMapping is matched then indeed, my custom error jsp is printed.
b)
if a #RequestMapping is matched, but the method sets an error status eg.
response.setStatus(404);
then Tomcat's (7.0.29) default error page is chosen, not my jsp.
Why? How to make my 404 page be displayed always ?
I think what you're experiencing is caused by the line you mentioned: response.setStatus(404);
This method doesn't trigger the container's error page mechanism, it should be used when there is no error. To trigger the mechanism, you have to use sendError, which is recommended in the official docs.
BTW I've just found out that the behavior differs between Servlet Spec. 2.3 and 2.4
(read here). In 2.3 the two methods are said to do the very same thing, whereas in 2.4 they differ..............
With spring MVC is preferable using build-in exception handler to show error page to the users.
Take a look this tutorial: http://doanduyhai.wordpress.com/2012/05/06/spring-mvc-part-v-exception-handling/
You may want to take a look at ExceptionHandler.
It is really nice and flexible and allows you to implement logic to display different error pages and output different HTTP reponse codes depending on the exception (this is not always a requirement but is nice to know you could do that easily).
I paste here my code as I think it can be useful to solve common issues regarding this topic.
#ExceptionHandler(Exception.class)
public ModelAndView resolveException(Exception ex,
HttpServletRequest request,
HttpServletResponse response) {
// I get an email if something goes wrong so that I can react.
if (enableEmailErrorReporting)
sendExceptionEmail(request.getRequestURL().toString(), ex);
ModelAndView mav = getModelAndView(ex, request);
setStatusCode(ex, response);
return mav;
}
protected ModelAndView getModelAndView(Exception ex,
HttpServletRequest request) {
// Here you can implement custom logic to retrieve the correct
// error page depending on the exception. You should extract
// error page paths as properties or costants.
return new ModelAndView("/WEB-INF/app/error.html");
}
// This is really nice.
// Exceptions can have status codes with the [`ResponseStatus`][2] annotation.
private void setStatusCode(Exception ex, HttpServletResponse response) {
HttpStatus statusCode = HttpStatus.BAD_REQUEST;
ResponseStatus responseStatus =
AnnotationUtils.findAnnotation(ex.getClass(),
ResponseStatus.class);
if (responseStatus != null)
statusCode = responseStatus.value();
response.setStatus(statusCode.value());
}
The logic here is that a controller method throws an uncatched exception. Spring will invoke the method marked with ExceptionHandler (you can have one per controller, per exception, or a global default one, usually I make all my controllers inherit from a BaseController class where I define this method). Passed to the method are the exception itself and any other info you need to choose the right view to display. Even more, you can see if on the exception has been declared a specific HTTP response code (eg, 500 for unchecked exception, 400 for validation errors, etc...) and return that code along with your error page.
I have a function that is both an HTTP endpoint and a function that I call elsewhere in the Java program:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody MyObject getObject(final HttpServletRequest request, final HttpServletResponse response)
The problem is in error handling. Functionality I would like on error:
Function called from inside Java: throws exception
Function called from HTTP: returns error json object
Trying to figure out the best way to do it. Thought about just returning a Java.lang.Object, but that's a lot of casting and type checking.
Add a Spring exception handler when calling it from web and then have the handler return your JSON.
Better approach might be to refactor the functionality into a service method then you can have the HTTP call handle the exception in it's own way and internal calls handle it however you want.
Edit
The second approach would be slightly easier to test too.