3

I Have a Spring Boot application which has the org.springframework.web.servlet.i18n.CookieLocaleResolver for locale resolver. If there is a invalid language cookie like !en then there will be an exception java.lang.IllegalArgumentException: Locale part "!en" contains invalid characters.

The problem is this exception is not handled by Spring Boot instead it is forwarded to Servlet container. So the default error page of the the container is shown (In my case it is JBoss EAP 6) which will show the stacktrace.

Other exceptions from the controllers are handled properly. For example I have a controller mapping which will throw / by zero error which is handled properly.

I have tried error page configuration in web.xml as follows.

<error-page>
    <location>/500</location>
</error-page>

And mapped both /error and /500 to a MVC controller as follows.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class CustomErrorController extends AbstractErrorController {
    public static final String ERROR_500 = "/500";
    private static final String ERROR_PATH=  "/error";

    @Autowired
    public CustomErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }


    /**
     * Responsible for handling all errors and throw especial exceptions
     * for some HTTP status codes. Otherwise, it will return a map that
     * ultimately will be converted to a json error.
     */
    @RequestMapping({ERROR_PATH,ERROR_500})
    public ResponseEntity<?> handleErrors(HttpServletRequest request) {
        return ResponseEntity.status(getStatus(request)).body(getErrorAttributes(request, false));
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
}

But still I'm getting the container's default error page. How to resolve this.

seenukarthi
  • 8,241
  • 10
  • 47
  • 68

2 Answers2

2

The FrameworkServlet, which processes the request, determines the Locale prior to sending the request through the dispatcher as such an exception thrown when resolving the Locale doesn't get caught in the processDispatchResult and as such doesn't get handled like a normal WebMvc error. For context the FrameworkServlet is extended by the DispatcherServlet which overrides the buildLocaleContext(request) and that in turn calls the CookieLocaleResolver intance.

/**
 * Process this request, publishing an event regardless of the outcome.
 * <p>The actual event handling is performed by the abstract
 * {@link #doService} template method.
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    // Here the locale is determined
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
        // here is where the WebMvc processing happens
        doService(request, response);
    }
    catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }

        if (logger.isDebugEnabled()) {
            if (failureCause != null) {
                this.logger.debug("Could not complete request", failureCause);
            }
            else {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    logger.debug("Leaving response open for concurrent processing");
                }
                else {
                    this.logger.debug("Successfully completed request");
                }
            }
        }

        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

DispatcherServlet method for buildLocaleContext()

/**
 * Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
 * <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
 * which might change during a request.
 * @param request current HTTP request
 * @return the corresponding LocaleContext
 */
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
    if (this.localeResolver instanceof LocaleContextResolver) {
        return ((LocaleContextResolver) this.localeResolver).resolveLocaleContext(request);
    }
    else {
        return new LocaleContext() {
            @Override
            public Locale getLocale() {
                return localeResolver.resolveLocale(request);
            }
        };
    }
}
Shawn Clark
  • 3,330
  • 2
  • 18
  • 30
  • Thanks Shawn, now I cannot change the mapping to the dispatcher servlet then I have to change the code in lot of places. Is there any way to exclude the error page configured in web.xml from the dispatcher servlet. – seenukarthi Aug 22 '16 at 11:34
  • Not sure what you are trying to accomplish. Do you still want an error page to be displayed when there is a localization failure? If so I would put an issue in with the project to see if they can enhance that aspect to allow the normal error handling to process that case. Or are you wanting no error page to display if there is a localization string issue? – Shawn Clark Aug 23 '16 at 00:04
  • The issue was when there is an exception in the framework servlet it was redirected to the error page but the framework servlet is mapped to `\*` the error request will also go through the framework servlet because of this to avoid infinite loop spring delegate the error to the container. I don't want the error request to be mapped to framework servlet is there a way to exclude it. – seenukarthi Aug 23 '16 at 06:55
1

I think you also need to mention error-code in the params of your web.xml like below. It works for me.

<error-page>
    <error-code>500</error-code>
    <location>/500</location>
</error-page>

and then catch it in the controller

@RequestMapping(value = "/500", method = RequestMethod.GET)
public String error500(Model model) {
    //System.out.println("Error 500 ");
    return "500"; //will return the 500.jsp
}
UsamaAmjad
  • 4,175
  • 3
  • 28
  • 35