I'm trying to use Grails interceptors to match specific uri having specific HTTP methods. The method argument of the match method is however ignored, despite the fact that I upgraded my Grails version from 3.1.1 to 3.1.16, where that issue should be fixed.
An simplified version of my code would be:
@GrailsCompileStatic
class MyInterceptor {
int order = HIGHEST_PRECEDENCE
MyInterceptor () {
match(uri: '/api/domain/*', method: 'PUT')
match(uri: '/api/domain/*', method: 'DELETE')
match(uri: '/api/domain/*', method: 'POST')
}
}
With the following interceptor test:
@TestFor(MyInterceptor)
class MyInterceptorSpec extends Specification {
@Unroll
def "it matches '#method #uri'"() {
when: "A request matches the interceptor"
withRequest(uri: uri, method: method)
then:"The interceptor does match"
interceptor.doesMatch()
where:
uri | method
'/api/domain/1' | 'PUT'
'/api/domain/1' | 'POST'
'/api/domain/1' | 'DELETE'
}
@Unroll
def "it does not match '#method #uri'"() {
when:
withRequest(uri: uri, method: method)
then:
!interceptor.doesMatch()
where:
uri | method
'/api/domain' | 'GET'
'/api/domain/1' | 'GET' // failing test
}
}
How can I ensure that the interceptor match uris only for given HTTP methods?
It is not possible by default in Grails.
Looking at the code of the UrlMappingMatcher we can see that when we define a matching rule with an uri, the method part is ignored:
@Override
Matcher matches(Map arguments) {
if(arguments.uri) {
uriPatterns << arguments.uri.toString()
}
else {
controllerRegex = regexMatch( arguments, "controller")
actionRegex = regexMatch( arguments, "action")
namespaceRegex = regexMatch( arguments, "namespace")
methodRegex = regexMatch( arguments, "method")
}
return this
}
@Override
Matcher excludes(Map arguments) {
if(arguments.uri) {
uriExcludePatterns << arguments.uri.toString()
}
else {
def exclude = new MapExclude()
exclude.controllerExcludesRegex = regexMatch( arguments, "controller", null)
exclude.actionExcludesRegex = regexMatch( arguments, "action", null)
exclude.namespaceExcludesRegex = regexMatch( arguments, "namespace", null)
exclude.methodExcludesRegex = regexMatch( arguments, "method", null)
excludes << exclude
}
return this
}
You can however create a subclass of UrlMappingMatcher that takes into account the method even when an uri is defined, and use it instead of the regular one in your Interceptor:
// in MyInterceptor.groovy
Matcher match(Map arguments) {
// use your own implementation of the UrlMappingMatcher
def matcher = new MethodFilteringUrlMappingMatcher(this)
matcher.matches(arguments)
matchers << matcher
return matcher
}