22

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]

ssc-hrep3
  • 15,024
  • 7
  • 48
  • 87
samanime
  • 25,408
  • 15
  • 90
  • 139
  • Not sure this works but did you try creating a generic `@ExceptionHandler({Exception.class})` and see if the exception which was caught by this handler class is IllegalArgumentException type. Ex - `ex.getClass().getName == "IllegalArgumentException"` then send it has 400 else 500?. – Imran May 15 '19 at 17:05
  • 1
    @Imran Strings are _not_ compared like that! – Eugene May 16 '19 at 09:02
  • @Eugene oops, being moved from other languages to Java, sometimes gets the syntax wrong. I meant `"IllegalArgumentException".equalsIgnoreCase(ex.getClass().getName))` – Imran May 17 '19 at 00:32
  • 4
    Maybe you don't find answers because you don't provide a simple [MCVE](http://stackoverflow.com/help/mcve) for others to reproduce the problem. – kriegaex May 17 '19 at 06:06
  • Can you provide a full stack trace of the exception? – Ortomala Lokni May 18 '19 at 19:00
  • what is the exact error msg you are getting? – swayamraina May 19 '19 at 06:02
  • How to replicate this? – swayamraina May 19 '19 at 06:11
  • This was posted almost two years ago, so I don't remember everything exactly. But basically, using Spring Boot (probably 1.x) if you had something weird in your cookies, the `LegacyCookieProcessor` would throw an exception. Doesn't matter why it threw the exception, the question is just how it can be intercepted to be handle. – samanime May 19 '19 at 08:51
  • @kriegaex I have now added a simple MCVE. – ssc-hrep3 May 20 '19 at 09:55
  • @Imran The exception is not caught when using `@ExceptionHandler({Exception.class})`. It does not go into any ExceptionHandler. – ssc-hrep3 May 20 '19 at 09:58
  • @swayamraina The exact error message is the following: `Error parsing HTTP request header Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.` And then right below: `java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986`. – ssc-hrep3 May 20 '19 at 09:59
  • @OrtomalaLokni Please see the updated question. – ssc-hrep3 May 20 '19 at 10:03
  • 5
    You cannot intercept this. This exception is thrown before it is handled by anything Servlet API related, So no servlet filter, listener or `DispatcherServlet` is even getting involved here. It is just tomcat blowing up internally and very early. – M. Deinum May 20 '19 at 10:11
  • @M.Deinum Is there any way to customize this behavior? E.g. returning a hard-coded JSON string whenever Tomcat is blowing up? – ssc-hrep3 May 20 '19 at 10:50
  • 1
    @ssc-hrep3 oddly, I am not able to replicate same behavior on my side for `http://localhost:8080/not-working?demo=test^123`, its giving me 200. can you please advise which version of Java you are using and also any special properties enabled?. A sample complete github repo helps to try few options. – Imran May 20 '19 at 19:33
  • @Imran, Did you try with curl (`curl -i http://localhost:8080/not-working?demo=test^123`)? Sometimes the browser escapes the argument automatically. I can set up a repo though, if it does not work like that. – ssc-hrep3 May 20 '19 at 21:35
  • 1
    @ssc-hrep3, see how useful the MCVE is? New people - not necessarily me who asked for the MCVE in the first place because I am an AOP expert but don't know much about containers - enter the discussion and contribute valuable information, reviving a 2.5 years old question. `:-)` – kriegaex May 21 '19 at 02:03
  • @ssc-hrep3 you were right!. With postman, it was escaping it and with curl, able to get the exception. – Imran May 21 '19 at 13:59
  • @samanime are you solved this issue? I also want catch all Tomcat execeptions and throw more readable exeception message to the client – dawis11 Jan 21 '22 at 00:42
  • @dawis11 This was quite a while back, but I don't believe we ever did. – samanime Jan 21 '22 at 21:13

2 Answers2

7

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.

  • 1
    Thanks for your response, this is interesting. However, I don't want to allow those characters. I just want to respond to all exceptions in the same way: By returning an HTTP package with JSON in it (and custom headers). Allowing those characters and then handling them in each controller again doesn't seem to be a good solution. And it doesn't actually prevent Tomcat to throw other exceptions (I assume there are other scenarios, Tomcat would throw an exception apart of special characters). – ssc-hrep3 May 21 '19 at 09:11
  • Allowing those characters in each controller is just an example for this scenario. What you could do is build a HTTP interceptor and check for the parameter with special character and throw the IllegalArgumentException from the interceptor to have a common implementation. – Rahul kalivaradarajalu May 21 '19 at 09:37
  • 3
    While the question has evolved to talking about query params, the original question was actually about a weird value in the cookies, not the query params. Does this work for both scenarios? – samanime May 21 '19 at 14:16
0

Try to catch the IllegalArgumentException in your filter, then call HttpServletResponse.sendError(int sc, String msg);. This may catch the IllegalArgumentExceptions that do not come from Tomcat though. But I suppose you already handle them properly.

Laurel
  • 5,965
  • 14
  • 31
  • 57