Something that's critical to our microservice-based social media platform is sharing the session details when putting things together. When we load the main page, it may have to pull together bits of data from multiple places. This means that after logging in to the system, the session ID that is generated has to be passed along seamlessly.
Spring Cloud Gateway can forward various requests, but Spring Session has a lazy approach to things. This means, we need to step up and save the session immediately; otherwise, the first few remote calls might fail.
To do so, we need to create a custom Spring Cloud Gateway filter as follows:
@Configuration public class GatewayConfig { private static final Logger log = LoggerFactory.getLogger(GatewayConfig.class); /** * Force the current WebSession to get saved */ static class SaveSessionGatewayFilterFactory implements GatewayFilterFactory { @Override public GatewayFilter apply(Tuple args) { return (exchange, chain) -> exchange.getSession() .map(webSession -> { log.debug("Session id: " + webSession.getId()); webSession.getAttributes().entrySet() .forEach(entry -> log.debug(entry.getKey() + " => " + entry.getValue())); return webSession; }) .map(WebSession::save) .then(chain.filter(exchange)); } } @Bean SaveSessionGatewayFilterFactory saveSessionGatewayFilterFactory() { return new SaveSessionGatewayFilterFactory(); } }
This preceding filter can be described as follows:
- The @Configuration annotation indicates that this class contains beans to be picked up by Boot's component scanning
- There is an Slf4j Logger to print out debug statements
- static class SaveSessionGatewayFilterFactory implements the Spring Cloud Gateway's GatewayFilterFactory interface, allowing us to write a custom filter, which is, essentially, a function call where the inputs are transformed into a GatewayFilter
- To implement this functional interface, we write a lambda, accepting a WebFlux WebServerExchange and GatewayFilterChain, which gives us access to the request as well as the chain of filters to hand it off to
- We grab the exchange's WebSession and map over it in order to print out all its details
- Next, we map over the same WebSession and invoke its save function through a method reference
- We wrap things up with a then() call to invoke the filter chain on the exchange
- With @Bean, we define a bean in the application context that implements SaveSessionGatewayFilterFactory
Spring Cloud Gateway's default policy is to use the classname of the filter with GatewayFilterFactory removed as the name of the filter itself. Hence, SaveSessionGatewayFilterFactory becomes simply SaveSession for purposes of inserting into our configuration file, as we saw earlier.
spring: cloud: gateway: routes: - id: imagesService uri: lb://IMAGES predicates: - Path=/imagesService/** filters: - RewritePath=/imagesService/(?<segment>.*), /${segment} - RewritePath=/imagesService, / - SaveSession ...
With the preceding little filter in place, we can guarantee that all the forwarded calls made by Spring Cloud Gateway will first ensure that the current WebSession has been saved.