Class SessionModule

  • All Implemented Interfaces:
    com.google.inject.Module

    public class SessionModule
    extends ConfigurableModule<SessionCookieConfig>
    Provides support for HTTP sessions.

    This module provides the general session API (see Session), and a default SessionStore implementation that stores session data in local memory.

    The session store

    It is expected that most applications will provide alternative bindings for the SessionStore type, overriding the default. This allows arbitrary stores to be used to persist session data.

    The default, in memory, implementation stores the data in a Cache<AsciiString, ByteBuf>. This cache instance is provided by this module and defaults to storing a maximum of 1000 entries, discarding least recently used. The memoryStore(java.util.function.Consumer<? super com.google.common.cache.CacheBuilder<io.netty.util.AsciiString, io.netty.buffer.ByteBuf>>) methods are provided to conveniently construct alternative cache configurations, if necessary.

    Serialization

    Objects must be serialized to be stored in the session. In order to prevent insecure deserialization, types that are to be used in a session must be declared to be safe. The simplest way to do this is to use allowTypes(com.google.inject.Binder, java.lang.Class<?>...) to register the type. See SessionTypeFilter for more information. Trying to store or load a type that has not been allowed will result in a runtime exception. Note that all types involved in serializing/deserializing a type must be registered, including super types.

    The get/set methods SessionData allow supplying a SessionSerializer to be used for the specific value. For variants of the get/set methods where a serializer is not provided, the implementation of SessionSerializer bound with Guice will be used. The default implementation provided by this module uses Java's in built serialization mechanism. Users of this module may choose to override this binding with an alternative serialization strategy.

    However, other Ratpack extensions may require session storage and rely on Java serialization. For this reason, there is also always a JavaSessionSerializer implementation available that is guaranteed to be able to serialize any Serializable object (that conforms to the Serializable contract. Users of this module may also choose to override this binding with another implementation (e.g. one based on Kryo), but this implementation must be able to serialize any object implementing Serializable.

    It is also often desirable to provide alternative implementations for SessionSerializer and JavaSessionSerializer. The default binding for both types is an implementation that uses out-of-the-box Java serialization (which is neither fast nor efficient).

    Example usage

    
     import ratpack.guice.Guice;
     import ratpack.core.path.PathTokens;
     import ratpack.session.Session;
     import ratpack.session.SessionModule;
     import ratpack.test.embed.EmbeddedApp;
    
     import static org.junit.jupiter.api.Assertions.assertEquals;
    
     public class Example {
       public static void main(String... args) throws Exception {
         EmbeddedApp.of(a -> a
             .registry(Guice.registry(b -> b
                 .module(SessionModule.class)
             ))
             .handlers(c -> c
                 .get("set/:name/:value", ctx ->
                     ctx.get(Session.class).getData().then(sessionData -> {
                       PathTokens pathTokens = ctx.getPathTokens();
                       sessionData.set(pathTokens.get("name"), pathTokens.get("value"));
                       ctx.render("ok");
                     })
                 )
                 .get("get/:name", ctx -> {
                   ctx.get(Session.class).getData()
                     .map(d -> d.require(ctx.getPathTokens().get("name")))
                     .then(ctx::render);
                 })
             )
         ).test(httpClient -> {
           assertEquals("ok", httpClient.getText("set/foo/bar"));
           assertEquals("bar", httpClient.getText("get/foo"));
    
           assertEquals("ok", httpClient.getText("set/foo/baz"));
           assertEquals("baz", httpClient.getText("get/foo"));
         });
       }
     }
     
    • Field Detail

      • LOCAL_MEMORY_SESSION_CACHE_BINDING_NAME

        public static final String LOCAL_MEMORY_SESSION_CACHE_BINDING_NAME
        The name of the binding for the Cache implementation that backs the in memory session store.
        See Also:
        memoryStore(Consumer), Constant Field Values
      • LOCAL_MEMORY_SESSION_CACHE_BINDING_KEY

        public static final com.google.inject.Key<com.google.common.cache.Cache<io.netty.util.AsciiString,​io.netty.buffer.ByteBuf>> LOCAL_MEMORY_SESSION_CACHE_BINDING_KEY
        The key of the binding for the Cache implementation that backs the in memory session store.
        See Also:
        memoryStore(Consumer)
    • Constructor Detail

      • SessionModule

        public SessionModule()
    • Method Detail

      • memoryStore

        public static Action<com.google.inject.Binder> memoryStore​(Consumer<? super com.google.common.cache.CacheBuilder<io.netty.util.AsciiString,​io.netty.buffer.ByteBuf>> config)
        A builder for an alternative cache for the default in memory store.

        This method is intended to be used with the BindingsSpec.binder(Action) method.

        
         import ratpack.guice.Guice;
         import ratpack.session.SessionModule;
        
         public class Example {
           public static void main(String... args) {
             Guice.registry(b -> b
                 .binder(SessionModule.memoryStore(c -> c.maximumSize(100)))
             );
           }
         }
         
        Parameters:
        config - the cache configuration
        Returns:
        an action that binds the cache
        See Also:
        memoryStore(Binder, Consumer)
      • memoryStore

        public static void memoryStore​(com.google.inject.Binder binder,
                                       Consumer<? super com.google.common.cache.CacheBuilder<io.netty.util.AsciiString,​io.netty.buffer.ByteBuf>> config)
        A builder for an alternative cache for the default in memory store.

        This method can be used from within a custom Module.

        
         import com.google.inject.AbstractModule;
         import ratpack.session.SessionModule;
        
         public class CustomSessionModule extends AbstractModule {
           protected void configure() {
             SessionModule.memoryStore(binder(), c -> c.maximumSize(100));
           }
         }
         
        }

        This method binds the built cache with the LOCAL_MEMORY_SESSION_CACHE_BINDING_KEY key. It also implicitly registers a RemovalListener, that releases the byte buffers as they are discarded.

        Parameters:
        binder - the guice binder
        config - the cache configuration
      • allowTypes

        public static void allowTypes​(com.google.inject.Binder binder,
                                      Class<?>... types)
        Registers the given types as being session safe.

        This method is only effectual if the implementation of SessionTypeFilter provided by this module is not overridden.

        
         import com.google.inject.AbstractModule;
         import ratpack.session.Session;
         import ratpack.session.SessionModule;
         import ratpack.guice.Guice;
         import ratpack.test.embed.EmbeddedApp;
        
         import java.io.Serializable;
        
         import static org.junit.jupiter.api.Assertions.assertEquals;
        
         public class Example {
        
           static class AllowedType implements Serializable {
           }
        
           static class NonAllowedType implements Serializable {
           }
        
           static class NonAllowedTypeContainer implements Serializable {
             NonAllowedType nonAllowedType = new NonAllowedType();
           }
        
           public static class MySessionTypesModule extends AbstractModule {
             protected void configure() {
               SessionModule.allowTypes(binder(),
                 AllowedType.class,
                 NonAllowedTypeContainer.class
               );
             }
           }
        
           public static void main(String... args) throws Exception {
             EmbeddedApp.of(a -> a
               .registry(Guice.registry(b -> b
                 .module(SessionModule.class)
                 .module(MySessionTypesModule.class)
               ))
               .handlers(c -> c
                 .get("set/allowed", ctx ->
                   ctx.get(Session.class)
                     .set(new AllowedType())
                     .then(() -> ctx.render("ok"))
                 )
                 .get("set/nonAllowed", ctx ->
                   ctx.get(Session.class)
                     .set(new NonAllowedType())
                     .onError(e -> ctx.render(e.toString()))
                     .then(() -> ctx.render("ok"))
                 )
                 .get("set/nonAllowedContainer", ctx ->
                   ctx.get(Session.class)
                     .set(new NonAllowedTypeContainer())
                     .onError(e -> ctx.render(e.toString()))
                     .then(() -> ctx.render("ok"))
                 )
               )
             ).test(http -> {
               // Works, only references allowed types
               String response1 = http.getText("set/allowed");
               assertEquals("ok", response1);
        
               // Doesn't work, tries to set not allowed type
               String response2 = http.getText("set/nonAllowed");
               assertEquals("ratpack.session.NonAllowedSessionTypeException: Type Example$NonAllowedType is not an allowed session type. Register a SessionTypeFilterPlugin allowing it. See SessionModule.allowTypes().", response2);
        
               // Doesn't work, allowed type references not allowed type
               String response3 = http.getText("set/nonAllowedContainer");
               assertEquals("ratpack.session.NonAllowedSessionTypeException: Type Example$NonAllowedType is not an allowed session type. Register a SessionTypeFilterPlugin allowing it. See SessionModule.allowTypes().", response3);
             });
           }
         }
         

        While the above example only shows type checking being applied on writing to the session, the same checking is applied when reading from the session.

        Parameters:
        binder - the binder
        types - the types to allow to be used in a session
        Since:
        1.9
      • configure

        protected void configure()
        Overrides:
        configure in class com.google.inject.AbstractModule