Affects: 5.3.19


Steps to reproduce

1. start following code:

@SpringBootApplication
class OptionsBugDemoApplication

fun main(args: Array<String>) {
    runApplication<OptionsBugDemoApplication>(*args)
}

@RequestMapping("foo")
@RestController
class FooController {
    @GetMapping("{id}")
    fun get(@PathVariable id: String) = Unit

    @DeleteMapping("{id}")
    fun delete(@PathVariable id: String) = Unit

    @PostMapping("customAction")
    fun customAction() = Unit
}

2. run tests:

curl -L -X OPTIONS -v http://localhost:8080/foo/customAction

response:

*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> OPTIONS /foo/customAction HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.82.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Allow: GET,HEAD,DELETE,POST,OPTIONS
< Accept-Patch:
< Content-Length: 0
< Date: Fri, 29 Apr 2022 09:00:31 GMT
<
* Connection #0 to host localhost left intact

3. issue:

preflight return wrong method list there: Allow: GET,HEAD,DELETE,POST,OPTIONS. I expect HEAD,POST,OPTIONS.

puzzle

The path /foo/customAction seems to be overridden by the pattern /foo/{id}, and I'm confused about that.

Comment From: bclozel

Hello @ymind

First, let me underline that the curl request you're showing does not qualify as a CORS preflight request. Such requests must use the HTTP OPTIONS method, have an "Origin" header and an "Access-Control-Request-Method". So in this case, the CORS support in Spring Framework is not involved.

Instead this is a partial match as implemented in org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleNoMatch. All handlers that are partial matches are collected and all methods are listed as possible candidates in the "Allow" response header.

If you run your sample application, you'll see that the following requests will be mapped to the controller methods listed in your controller:

curl -L -X GET -v http://localhost:8080/foo/customAction
curl -L -X DELETE -v http://localhost:8080/foo/customAction

Since "/foo/customAction" matches the "/foo/{id}" pattern, they're partial matches, as expected. I'm closing this issue as it works as designed. If you need more assistance, you can create a StackOverflow question. Thanks!