We'd like to implement API rate limiting for Grails 3 application. We use interceptor and redis for this purpose. There is however a problem of opening hibernate session (which opens database connection) for each request coming to Grails. This is a resource consumption we cannot neglect as it can easily reach MySQL connection limits with simple attack.
The question is what is the best way how to force Grails not to open hibernate session for certain urls/interceptors. I am aware of API gateways like konghq.com which is not an option for us.
I also know about GrailsOpenSessionInViewInterceptor
implementing OpenSessionInViewInterceptor
which is responsible for session management. So is it the only one option to override this interceptor? How would you open hibernate sessions for those request complying with rate limits?
Finaly, I override GrailsOpenSessionInViewInterceptor
where I open hibernate session only if rate limits are satisfied.
import org.grails.web.servlet.mvc.GrailsWebRequest
import org.grails.web.util.GrailsApplicationAttributes
import org.springframework.web.context.request.WebRequest
import javax.servlet.http.HttpServletRequest
class MyOpenSessionInViewInterceptor extends GrailsOpenSessionInViewInterceptor {
@Override
void preHandle(WebRequest request) throws DataAccessException {
GrailsWebRequest grailsRequest = (GrailsWebRequest) request.getAttribute(GrailsApplicationAttributes.WEB_REQUEST,
WebRequest.SCOPE_REQUEST)
HttpServletRequest servletRequest = grailsRequest.request
// intercept /api/* requests
if (!servletRequest.requestURI.startsWith('/api')) {
// if rate limits exceeded
if (checkRateLimits) {
servletRequest.setAttribute('MY_RATE_LIMITS_EXCEEDED', true)
return
}
super.preHandle(request)
}
}
}
Don't forget to inject your bean in resource.groovy.
// Place your Spring DSL code here
beans = {
// intercept opening of hibernate cache
openSessionInViewInterceptor(MyOpenSessionInViewInterceptor) {
hibernateDatastore = ref('hibernateDatastore')
}
}
I put this together with in house grails interceptor where I check passed attribute from Spring open session interceptor.
class V1RateLimitInterceptor {
V1RateLimitInterceptor() {
match(namespace: 'api')
}
boolean before() {
// request rate limits
boolean rateLimit = (boolean) request.getAttribute('MY_RATE_LIMITS_EXCEEDED')
if (rateLimit) {
return false
}
return true
}
}
This is however a temporary solution for us as we would use some API gateway like konghq.com in future.