0

When using a @ControllerAdvice and having a MVC-Controller with a GET-Mapping that has a path variable, this path variable is sometimes assigned a wrong value when used in concurrent requests.

The original function of the controller method in question was to receive an id for which an image was fetched and returned as a ResponseEntity<byte[]>. But the supplied id via the path variable was sometimes wrong, resulting in a wrongly served image. I could narrow it down to the usage of a controller advice that registered a custom editor (String) via data binding.

When removing the data binding of the property editor from the controller advice, everything was hunky dory again.

But why is this happening and how can it be avoided? Is a controller advice the wrong approach here? Should the string editor be bound in every controller rather than using a controller advice?

The following code parts can be used to reproduce the error:

Thymeleaf-Template that triggers the result:

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:lang="${#locale.language}">

<head>
    <meta charset="UTF-8">
    <title>example</title>
    <div th:each="exampleCounter : ${#numbers.sequence(0, 99999)}">
        <img th:src="@{/example/{number}(number = ${exampleCounter})}"/>
    </div>
</head>
<body>

</body>
</html>

String editor in question (whole class):

package org.example.support;
import org.springframework.stereotype.Component;
import java.beans.PropertyEditorSupport;

@Component
public class StringEscapeEditor extends PropertyEditorSupport {
    public void setAsText(String text) {
        // do something here, not important for result in question
        setValue(text);
    }
}

Controller advice that binds the string editor (relevant fragment):

@ControllerAdvice
public class ExampleControllerAdvice {

    private final StringEscapeEditor stringEscapeEditor;

    @Autowired
    public ExampleControllerAdvice(StringEscapeEditor stringEscapeEditor) {
        this.stringEscapeEditor = stringEscapeEditor;
    }
    @InitBinder
    public void dataBinding(WebDataBinder webDataBinder) {
        webDataBinder.registerCustomEditor(String.class, stringEscapeEditor);
    }
}

Controller with @GetMapping/@PathVariable (relevant fragment):

@Controller
public class ExampleController {

    @GetMapping(path = "/example/{xyz}")
    public ResponseEntity<byte[]> getImageAsResponseEntity(@PathVariable("xyz") String xyz, HttpServletRequest request) {
        if (!request.getRequestURI().contains(xyz)) {
            System.out.println("URI: " +request.getRequestURI() + " | PathVariable: "+ xyz);
        }
        // ... return some imagebytes ...
    }
}

When requesting the url that serves above thymeleaf-template you'll see that the @PathVariable-Variable xyz sometimes is different from the path-segment in the uri that was requested. The path variable (xyz) is wrong. The path-segment received via request is correct.

Thus the code fragment is triggered:

if (!request.getRequestURI().contains(xyz)) {
    System.out.println("URI: " +request.getRequestURI() + " | PathVariable: "+ xyz);
}

URI: /example/62 | PathVariable: 61

URI: /example/76 | PathVariable: 77

URI: /example/80 | PathVariable: 79

URI: /example/133 | PathVariable: 134

URI: /example/207 | PathVariable: 208

URI: /example/333 | PathVariable: 323

--- Solution --- The Binding of the Custom Property Editor (StringEscapeEditor) was wrong. You have to bind it as a new instance rather than injecting it. This is how the binding within the controller advice should look like instead:

@ControllerAdvice
public class ExampleControllerAdvice {
    @InitBinder
    public void dataBinding(WebDataBinder webDataBinder) {
        webDataBinder.registerCustomEditor(String.class, new StringEscapeEditor());
    }    
}
bushd0c
  • 1
  • 1
  • Okay just realised after posting, it was not the controller advice, it was the way I bound the property editor. added solution in my post. – bushd0c Nov 24 '22 at 15:14

0 Answers0