Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Boot WebFlux validation of invalid inputs #31045

Closed
amidukr opened this issue Aug 13, 2023 · 3 comments
Closed

Spring Boot WebFlux validation of invalid inputs #31045

amidukr opened this issue Aug 13, 2023 · 3 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) theme: kotlin An issue related to Kotlin support type: bug A general bug
Milestone

Comments

@amidukr
Copy link

amidukr commented Aug 13, 2023

I am building java application using Spring Boot 2.7.12
And I have very simpler REST API controller like that:

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@RestController
@RequestMapping("/sample")
class SampleController {

    data class Arguments (
        val intValue: Int
    )


    @GetMapping("/echo")
    fun resource(args: Arguments) = "Return back: ${args.intValue}"
}

And the issue when I am making a regular regular request as expected, everything works fine:

$ curl http://localhost:8080/sample/echo?intValue=111
Return back: 111

However if I will enter non-numeric value, Spring Boot is crashing:

$ curl http://localhost:8080/sample/echo?intValue=111aaa
{"timestamp":"2023-07-27T16:03:34.023+00:00","path":"/sample/echo","status":500,"error":"Internal Server Error","requestId":"de11f158-6"}

If it is non-valid input, it should rather be 400 Bad Requests, than 500 Internal errors, and such things should be handled by the framework.

We using the Spring Boot framework, because we need some framework that will handle all edge case scenarios with mapping of the HTTP request parameters to controller input arguments, and that obviously should be handled properly by the framework.

Could you please advise, how to handle such HTTP requests properly?


Steps to reproduce
Download attach sample project: demo.zip

Run following command:

echo $JAVA_HOME
echo 
./gradlew clean build


java -jar ./build/libs/demo-0.0.1-SNAPSHOT.jar >logs &
jdpid=$(echo $!)
echo $jdpid

sleep 10

echo
echo '================================================================'
echo 'Call sample/echo?intValue=1111, return 200 as expected'
echo 
curl -v http://localhost:8080/sample/echo?intValue=1111;

echo
echo '================================================================'
echo 'Call sample/echo?intValue=1111aaa, Returns 500 but should 400'
echo
curl -v http://localhost:8080/sample/echo?intValue=1111aaaa;

kill $jdpid

echo
echo 'To see log type `less logs`'

Listing output:

C:\Program Files\OpenJDK\jdk-20.0.2


Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.2.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 6s
8 actionable tasks: 7 executed, 1 up-to-date
[1] 1317
1317

================================================================
Call sample/echo?intValue=1111, return 200 as expected

*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /sample/echo?intValue=1111 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 17
<
Return back: 1111* Connection #0 to host localhost left intact

================================================================
Call sample/echo?intValue=1111aaa, Returns 500 but should 400

*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /sample/echo?intValue=1111aaaa HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 500 Internal Server Error
< Content-Type: application/json
< Content-Length: 137
<
{"timestamp":"2023-08-13T15:14:29.742+00:00","path":"/sample/echo","status":500,"error":"Internal Server Error","requestId":"292fce01-2"}* Connection #0 to host localhost left intact

To see log type `less logs`

Stack trace: log.txt

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 13, 2023
@sdeleuze sdeleuze self-assigned this Aug 14, 2023
@sdeleuze sdeleuze changed the title Spring Boot WebFlux validation of invalid inputs [Reopen: 30960] Spring Boot WebFlux validation of invalid inputs Aug 14, 2023
@sdeleuze sdeleuze added in: web Issues in web modules (web, webmvc, webflux, websocket) theme: kotlin An issue related to Kotlin support labels Aug 14, 2023
@sdeleuze
Copy link
Contributor

I can indeed reproduce with the repro provided with WebFlux but not with WebMVC.

With WebMvc, ModelAttributeMethodProcessor#constructAttribute catches the TypeMismatchException thrown, set the bindingFailure boolean to false which throws a MethodArgumentNotValidException mapped to HttpStatus.BAD_REQUEST status code (400).

With WebFlux, ModelAttributeMethodArgumentResolver#constructAttribute, the TypeMismatchException is thrown as it is without specific processing, leading to an http response with a 500 status code.

As far as I can tell, it does not look like Kotlin specific.

@amidukr
Copy link
Author

amidukr commented Aug 14, 2023

I can indeed reproduce with the repro provided with WebFlux but not with WebMVC.

With WebMvc, ModelAttributeMethodProcessor#constructAttribute catches the TypeMismatchException thrown, set the bindingFailure boolean to false which throws a MethodArgumentNotValidException mapped to HttpStatus.BAD_REQUEST status code (400).

With WebFlux, ModelAttributeMethodArgumentResolver#constructAttribute, the TypeMismatchException is thrown as it is without specific processing, leading to an http response with a 500 status code.

As far as I can tell, it does not look like Kotlin specific.

When I've used Java POJO instead of Kotlin Data class, it works well and returns 400 instead of crashing with 500, so it related to Kotlin data class, which quite bit different to POJO.

@RestController
@RequestMapping("/sample")
class SampleController {

    data class Arguments(val intValue: Int) {
        class  Pojo{
            var intValue: Int = -1

            fun toData() = Arguments(intValue =  this.intValue)
        }
    }

    @GetMapping("/echo")
    fun resource(args: Arguments.Pojo) = "Return back: ${args.toData().intValue}"
}

@sdeleuze
Copy link
Contributor

If the class have a single default constructor, then ModelAttributeMethodArgumentResolver#resolveArgument does proper exception handling by catching binding errors and throwing a WebExchangeBindException, but if the class can be initialized via its constructor, then you get the same 500 status code, either with Kotlin or Java.

@sdeleuze sdeleuze added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Aug 18, 2023
@sdeleuze sdeleuze added this to the 6.0.12 milestone Aug 18, 2023
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 18, 2023
Adapt spring-projectsgh-31045 fix to the main branch, and throw a
WebExchangeBindException instead of a ServerWebInputException.
sdeleuze added a commit to sdeleuze/spring-framework that referenced this issue Aug 18, 2023
Adapt spring-projectsgh-31045 fix to the main branch, and throw a
WebExchangeBindException instead of a ServerWebInputException.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) theme: kotlin An issue related to Kotlin support type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants