Managing sessions outside of your app/JVM provides flexibility with deployments and it makes both blue/green and zero downtime deployments possible. Previously, this required setting up a session manager in Tomcat, but with Grails 3 it’s much easier.
Since Grails 3 is based on SpringBoot, we can easily use the Spring Session project (along with spring-data-redis) to persist session information outside Tomcat and utilize Redis - a distributed key/value store.
Configuration:
First, lets add Spring Session and Redis to our build.gradle
:
dependencies {
compile "org.springframework.session:spring-session:1.1.1.RELEASE"
compile "org.springframework.boot:spring-boot-starter-redis"
...
}
Next, tell Grails where the Redis server is located in application.yml
:
spring:
redis:
host: localhost
port: 6379
server:
session-timeout: 3600 # session timeout in seconds
For Grails 3.1.x that’s it! Grails 3.0.x needs a ordering fix
Verification
Start up your app and browse to a page. You should see the session stored in Redis:
$ grails run-app
# put something on the session,
# eg: session.foo = "bar" in a controller
# or login to your app if you have security setup
$ redis-cli keys '*'
1) "spring:session:expirations:1441301640000"
2) "spring:session:sessions:91dcfa08-00e1-4cab-98c2-34bf0da40184"
Conclusion
With two dependancies, one class, and a little yml we’ve added the ability to store sessions outside of Tomcat and share them with multiple JVMs!
You will now be able to:
- Stop and start the app and still have the values for your session
- Users will not be logged out between deployments
- Orchestrate a blue/green or canary deployment without downtime
- Ability to share sessions across multiple JVMs on the same or different servers and round-robbin between them without loosing the session.
- Redis data is persisted to disk so sessions will survive a server restart
With Grails 3 we have all the power of SpringBoot. My key takeaway from this is to not limit yourself to just Grails Plugins with a Grails 3 app and to take a look at the SpringBoot ecosystem as well.
Checkout my talk at Gr8Conf US 2015 for this and a lot of other topics related to running and building a Grails app for production.
Notes
- Tested on JDK 8u60, Grails 3.1.4, Redis 3.0.3
- Tested with Grails Spring Security Plugin 3.0.4
- Install Redis
brew install redis
- Run Redis
redis-server
- Full working example is now in spring-session https://github.com/spring-projects/spring-session/tree/master/samples/grails3
3.0.x
To fix an ordering issue in Grails 3.0.x add a new class to tie everything together under grails-app/init/<yourapp>/SessionConfig.groovy
.
package <yourapp>
import org.springframework.boot.context.embedded.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.Ordered
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession
import org.springframework.session.ExpiringSession
import org.springframework.session.web.http.SessionRepositoryFilter
@EnableRedisHttpSession
@Configuration
public class SessionConfig {
/**
* By Default SessionRepositoryFilter is registered as +50, but we need it to be before GrailsWebRequestFilter which is registered at +30. This
* overrides the default order.
* This override is not needed after SpringBoot 1.3.0.M5+ is included in Grails.
*/
@Bean
FilterRegistrationBean springSessionFilterRegistration(SessionRepositoryFilter<? extends ExpiringSession> filter) {
new FilterRegistrationBean(filter:filter, order: Ordered.HIGHEST_PRECEDENCE + 15)
}
}
You will need to register this bean in grails-app/conf/spring/resources.groovy
or simply add @ComponentScan
to your grails-app/init/<yourapp>/Application.groovy
class:
...
import org.springframework.context.annotation.ComponentScan
@ComponentScan
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
}