0

Building out a new API using Grails 4, and I would like to have the option of logging the full request (headers, method, content, etc). I can see the request in an Interceptor, but the content can only be read once (using HttpServletRequest.getInputStream()), so reading it in the Interceptor prevents the content from being available in the Controller.

There are some similar questions about this on Stack Overflow already that address this need by using Grails Filters.

One reason I don't want to go down that path is that, according to the Grails docs, Filters are now considered deprecated (as of v3.0), and Interceptors should instead be used. Unfortunately, none of the solutions I can find work with Interceptors. I tried a couple of those solutions myself that involve wrapping the request inside of a HttpServletRequestWrapper to cache the body content and ran into the same issues as others with trying to get it to work with an Interceptor.

I have seen suggestions to use Java Servlet Filters, it's not obvious how that's different from Grails Filters, or if they should also be avoided.

Edit: As noted in comments below, I didn't mention that I'm using command objects, and the solution needs to work with those. I'm also using a number of other Grails features that I'm not sure will be affected by whatever solutions will be proposed, so if there are limitations to the proposed solution, it would be good to know about those.

Michael R
  • 181
  • 9
  • Grails filters are whats deprecated, servlet filters are not. eg this solution could work for your use https://stackoverflow.com/questions/34804205/how-can-i-read-request-body-multiple-times-in-spring-handlermethodargumentresol – erichelgeson May 20 '20 at 14:27
  • We are using Grails interceptors and modyfying requests in the fly, so not sure what do you mean by `the content can only be read once`. Could you show your interceptor code, please? I assume, you are using `before()` right? – Michal_Szulc May 20 '20 at 20:49
  • @Michal_Szulc You're correct, it is in the Interceptor `before()` method, all of the real work is being done in a wrapper class, almost identical to the one in this answer: https://stackoverflow.com/a/10458119/723832. When I say the content can only be read once, I'm referring to the request body, read through `HttpServletRequest.getInputStream()`, once the stream has been read it's no longer available - reading it in the Interceptor makes it not available to be read in the Controller. – Michael R May 21 '20 at 16:10

1 Answers1

0

Added PoC project: https://github.com/majkelo/grails4requestinterceptor

basically added an interceptor PROJECT/grails-app/controllers/testrequest/RequestModificatorInterceptor.groovy (modyfiyng all the requests):

package testrequest


class RequestModificatorInterceptor {

    RequestModificatorInterceptor() {
        match(controller: "*", action: "*")
    }

    boolean before() {
        println "INSIDE INTERCEPTOR; BEFORE"
        println request.properties
        println request.JSON
        request.JSON.before = "added"
        true
    }

    boolean after() {
        true
    }

    void afterView() {
    // no-op
    }
}

and test controller PROJECT/grails-app/controllers/testrequest/TestController.groovy:

package testrequest

import grails.converters.JSON

class TestController {

    def index() {
        println "INSIDE CONTROLLER"
        println request.JSON
        request.JSON.incontroller = true
        render request.JSON as JSON
    }
}

it's reading/modyfing POST request in interceptor AND in the controller. Example request:

curl --location --request POST 'http://localhost:8080/test' \
--header 'Content-Type: application/json' \
--data-raw '{"js":"on"}'```
Michal_Szulc
  • 4,097
  • 6
  • 32
  • 59
  • This adds properties to the request map, but doesn't give access to the body content, for example in a POST request. It appears you still have to use `getInputStream()` for that. – Michael R May 22 '20 at 16:03
  • As presented, you can log json body of POST request (even change it in the fly). you can also log other request properties, like query params or method. lemme know if you found it useful @MichaelR – Michal_Szulc May 28 '20 at 08:02
  • This doesn't work when using Command objects (like we are) in controllers. If the request body has been read into `request.JSON` before automatic binding in the command object happens, the command object won't be bound. Likewise, if I try to read `request.JSON` in the controller (or interceptor) after the command object's been bound, `request.JSON` empty. Thanks for the suggestions, but I'm going to put this idea on hold for now, and probably later come back around to try using a servlet Filter. – Michael R May 28 '20 at 21:56
  • You should probably create a new question. You haven't mentioned about using command objects in your original question at all. – Michal_Szulc May 29 '20 at 00:05
  • I edited my question to include a note about using command objects, even though that doesn't seem like such a particularly niche feature of Grails that it needs to be called out. In looking for a solution myself, I found multiple references to the fact that using `request.JSON` will cause issues with auto-binding. So, thanks for pointing out that I should have been more specific in my question, but I don't think it warrants an entirely new question when I can just add that one tidbit of information. – Michael R May 29 '20 at 13:14