grailsgrails3grails-3.1

Grails 3.1.16 Interceptors not filtering on method


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?


Solution

  • 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
    }