Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle Embedded Tomcat Exception in Spring Boot

We have an issue where embedded Tomcat is throwing IllegalArgumentException from the LegacyCookieProcessor. It throws a 500 HTTP response code.

We need to handle the exception and do something with it (specifically, send it as a 400 instead).

The typical @ExceptionHandler(IllegalArgumentException.class) doesn't seem to get triggered and Google only seems to give results for dealing with Spring Boot specific exceptions.


Example:

Here is an example to reproduce the behavior. You can execute the example by downloading the initial project including spring-web (https://start.spring.io/) in version 2.1.5.RELEASE. Then add the following two classes to your project.

DemoControllerAdvice.java

package com.example.demo;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class DemoControllerAdvice {

    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Map<String, String> forbiddenHandler() {
        Map<String, String> map = new HashMap<>();
        map.put("error", "An error occurred.");
        map.put("status", HttpStatus.FORBIDDEN.value() + " " + HttpStatus.FORBIDDEN.name());
        return map;
    }

}

DemoRestController.java

package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoRestController {

    @GetMapping(value = "/working")
    public void working() {
        throw new java.lang.IllegalArgumentException();
    }

    @GetMapping(value = "/not-working")
    public String notWorking(@RequestParam String demo) {
        return "You need to pass e.g. the character ^ as a request param to test this.";
    }

}

Then, start the server and request the following URLs in the browser:

  • http://localhost:8080/working An IllegalArgumentException is thrown manually in the controller. It is then caught by the ControllerAdvice and will therefore produce a JSON string containing the information defined in the DemoControllerAdvice
  • http://localhost:8080/not-working?demo=test^123 An IllegalArgumentException is thrown by the Tomcat, because the request param cannot be parsed (because of the invalid character ^). The exception however is not caught by the ControllerAdvice. It shows the default HTML page provided by Tomcat. It also provides a different error code than defined in the DemoControllerAdvice.

In the logs the following message is shown:

o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.

java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:467) ~[tomcat-embed-core-9.0.19.jar:9.0.19]

like image 983
samanime Avatar asked Sep 07 '16 18:09

samanime


People also ask

Can we override or replace the embedded Tomcat server in spring boot?

If you'd like to change the embedded web server to Jetty in a new Spring Boot web starter project, you'll have to: Exclude Tomcat from web starter dependency, since it is added by default. Add the Jetty dependency.

Can we exclude Tomcat in spring boot?

Exclude Tomcat:- If we want to exclude tomcat from spring boot, we don't need to do much, we just need to add one additional block(<exclusions>) to the Spring Boot dependency. <exclusions> tag is used to make us sure that given server/artifactId is being removed at the time of build.

Can we override embedded server in spring boot?

The Spring Boot framework provides the default embedded server (Tomcat) to run the Spring Boot application. It runs on port 8080. It is possible to change the port in Spring Boot.

Does spring boot use embedded Tomcat?

By default, Spring Boot provides an embedded Apache Tomcat build. By default, Spring Boot configures everything for you in a way that's most natural from development to production in today's platforms, as well as in the leading platforms-as-a-service.


1 Answers

This is a feature of Tomcat itself as mentioned in this answer.

However, you can do something like this by allowing the special characters that you are expecting as part of your request and handle them yourself.

First, you need to allow the special characters that you would need to handle by setting up the relaxedQueryChars like this.

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class TomcatCustomizer implements 
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

@Override
public void customize(TomcatServletWebServerFactory factory) {
    factory.addConnectorCustomizers((connector) -> {
        connector.setAttribute("relaxedQueryChars", "^");
    });
 }
}

and later handle the special characters in each of your requests or create an interceptor and handle it in a common place.

To handle it in the request individually you can do something like this.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoRestController {

@GetMapping(value = "/working")
public void working() {
    throw new java.lang.IllegalArgumentException();
}

@GetMapping(value = "/not-working")
public String notWorking(@RequestParam String demo) {

    if (demo.contains("^")) {
        throw new java.lang.IllegalArgumentException("^");
    }
    return "You need to pass e.g. the character ^ as a request param to test this.";
}

}

You might also want to refer this answer to decide if you really need this fix.

like image 63
Rahul kalivaradarajalu Avatar answered Sep 18 '22 12:09

Rahul kalivaradarajalu