Skip to content

Commit accb0b9

Browse files
committed
Add filter to force WebSession::save.
In some scenarios, such as making a remote call while using Spring Session between two processes, forcing a WebSession::save before making the call ensures that session data is in place on the other side. Resolves #58.
1 parent 6fe7207 commit accb0b9

File tree

5 files changed

+155
-56
lines changed

5 files changed

+155
-56
lines changed

spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
import java.util.List;
2121
import java.util.function.Consumer;
2222

23+
import reactor.core.publisher.Flux;
24+
import reactor.ipc.netty.http.client.HttpClient;
25+
import reactor.ipc.netty.http.client.HttpClientOptions;
26+
import reactor.ipc.netty.resources.PoolResources;
27+
import rx.RxReactiveStreams;
2328
import org.springframework.beans.factory.ObjectProvider;
2429
import org.springframework.beans.factory.annotation.Qualifier;
2530
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
@@ -41,41 +46,13 @@
4146
import org.springframework.cloud.gateway.filter.RemoveHopByHopHeadersFilter;
4247
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
4348
import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter;
44-
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
45-
import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
46-
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
47-
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
48-
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
49-
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
50-
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
51-
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
52-
import org.springframework.cloud.gateway.filter.factory.RemoveNonProxyHeadersGatewayFilterFactory;
53-
import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory;
54-
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
55-
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
56-
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
57-
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
58-
import org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties;
59-
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
60-
import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
61-
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
62-
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
49+
import org.springframework.cloud.gateway.filter.factory.*;
6350
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
6451
import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver;
6552
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
6653
import org.springframework.cloud.gateway.handler.FilteringWebHandler;
6754
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
68-
import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory;
69-
import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory;
70-
import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
71-
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
72-
import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory;
73-
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
74-
import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory;
75-
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
76-
import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory;
77-
import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory;
78-
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
55+
import org.springframework.cloud.gateway.handler.predicate.*;
7956
import org.springframework.cloud.gateway.route.CachingRouteLocator;
8057
import org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator;
8158
import org.springframework.cloud.gateway.route.CompositeRouteLocator;
@@ -98,13 +75,6 @@
9875

9976
import com.netflix.hystrix.HystrixObservableCommand;
10077

101-
import reactor.core.publisher.Flux;
102-
import reactor.ipc.netty.http.client.HttpClient;
103-
import reactor.ipc.netty.http.client.HttpClientOptions;
104-
import reactor.ipc.netty.http.client.HttpClientRequest;
105-
import reactor.ipc.netty.resources.PoolResources;
106-
import rx.RxReactiveStreams;
107-
10878
/**
10979
* @author Spencer Gibb
11080
*/
@@ -404,6 +374,10 @@ public SetStatusGatewayFilterFactory setStatusGatewayFilterFactory() {
404374
return new SetStatusGatewayFilterFactory();
405375
}
406376

377+
@Bean
378+
public SaveSessionGatewayFilterFactory saveSessionGatewayFilterFactory() {
379+
return new SaveSessionGatewayFilterFactory();
380+
}
407381

408382
@ManagementContextConfiguration
409383
@ConditionalOnProperty(value = "management.gateway.enabled", matchIfMissing = true)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.cloud.gateway.filter.factory;
17+
18+
import org.springframework.cloud.gateway.filter.GatewayFilter;
19+
import org.springframework.tuple.Tuple;
20+
import org.springframework.web.server.WebSession;
21+
22+
/**
23+
* Save the current {@link WebSession} before executing the rest of the {@link org.springframework.cloud.gateway.filter.GatewayFilterChain}.
24+
*
25+
* Filter is very useful for situation where the WebSession is lazy (e.g. Spring Session MongoDB) and making a remote call requires
26+
* that {@link WebSession#save()} be called before the remote call is made.
27+
*
28+
* @author Greg Turnquist
29+
*/
30+
public class SaveSessionGatewayFilterFactory implements GatewayFilterFactory {
31+
32+
@Override
33+
public GatewayFilter apply(Tuple args) {
34+
return (exchange, chain) -> exchange.getSession()
35+
.map(WebSession::save)
36+
.then(chain.filter(exchange));
37+
}
38+
}

spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.cloud.gateway.route.builder;
1818

19+
import static org.springframework.tuple.TupleBuilder.*;
20+
1921
import java.net.URI;
2022
import java.net.URL;
2123
import java.util.Arrays;
@@ -25,32 +27,14 @@
2527
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2628
import org.springframework.cloud.gateway.filter.GatewayFilter;
2729
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
28-
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
29-
import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
30-
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
31-
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
32-
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
33-
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
34-
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
35-
import org.springframework.cloud.gateway.filter.factory.RemoveNonProxyHeadersGatewayFilterFactory;
36-
import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory;
37-
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
38-
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
39-
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
40-
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
41-
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
42-
import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
43-
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
44-
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
30+
import org.springframework.cloud.gateway.filter.factory.*;
4531
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
4632
import org.springframework.cloud.gateway.route.Route;
4733
import org.springframework.http.HttpStatus;
4834
import org.springframework.tuple.Tuple;
4935

5036
import com.netflix.hystrix.HystrixObservableCommand;
5137

52-
import static org.springframework.tuple.TupleBuilder.tuple;
53-
5438
public class GatewayFilterSpec extends UriSpec {
5539

5640
static final Tuple EMPTY_TUPLE = tuple().build();
@@ -197,4 +181,8 @@ public GatewayFilterSpec setStatus(String status) {
197181
public GatewayFilterSpec setStatus(HttpStatus status) {
198182
return filter(getBean(SetStatusGatewayFilterFactory.class).apply(status));
199183
}
184+
185+
public GatewayFilterSpec saveSession() {
186+
return filter(getBean(SaveSessionGatewayFilterFactory.class).apply(EMPTY_TUPLE));
187+
}
200188
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.cloud.gateway.filter.factory;
17+
18+
import static org.mockito.Mockito.*;
19+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.*;
20+
import static org.springframework.web.reactive.function.BodyExtractors.*;
21+
22+
import java.time.Duration;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
26+
import org.junit.Test;
27+
import org.junit.runner.RunWith;
28+
import reactor.core.publisher.Mono;
29+
import reactor.test.StepVerifier;
30+
import org.springframework.boot.SpringBootConfiguration;
31+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
32+
import org.springframework.boot.test.context.SpringBootTest;
33+
import org.springframework.cloud.gateway.test.BaseWebClientTests;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Import;
36+
import org.springframework.test.annotation.DirtiesContext;
37+
import org.springframework.test.context.ActiveProfiles;
38+
import org.springframework.test.context.junit4.SpringRunner;
39+
import org.springframework.web.server.WebSession;
40+
import org.springframework.web.server.session.WebSessionManager;
41+
42+
/**
43+
* @author Greg Turnquist
44+
*/
45+
@RunWith(SpringRunner.class)
46+
@SpringBootTest(webEnvironment = RANDOM_PORT)
47+
@DirtiesContext
48+
@ActiveProfiles(profiles = "save-session-web-filter")
49+
public class SaveSessionGatewayFilterFactoryTests extends BaseWebClientTests {
50+
51+
static WebSession mockWebSession = mock(WebSession.class);
52+
53+
@Test
54+
public void webCallShouldTriggerWebSessionSaveAction() {
55+
56+
when(mockWebSession.getAttributes()).thenReturn(new HashMap<>());
57+
when(mockWebSession.save()).thenReturn(Mono.empty());
58+
59+
Mono<Map> result = webClient.get()
60+
.uri("/get")
61+
.exchange()
62+
.flatMap(response -> response.body(toMono(Map.class)));
63+
64+
StepVerifier.create(result)
65+
.consumeNextWith(response -> {/* Don't care about data, just need to catch signal */})
66+
.expectComplete()
67+
.verify(Duration.ofMinutes(10));
68+
69+
verify(mockWebSession, times(2)).getAttributes();
70+
verify(mockWebSession).save();
71+
verifyNoMoreInteractions(mockWebSession);
72+
}
73+
74+
@EnableAutoConfiguration
75+
@SpringBootConfiguration
76+
@Import(DefaultTestConfig.class)
77+
static class TestConfig {
78+
79+
@Bean
80+
WebSessionManager webSessionManager() {
81+
return exchange -> Mono.just(mockWebSession);
82+
}
83+
}
84+
85+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
test:
2+
hostport: httpbin.org:80
3+
uri: lb://testservice
4+
5+
spring:
6+
cloud:
7+
gateway:
8+
routes:
9+
- id: save_session_test
10+
uri: ${test.uri}
11+
predicates:
12+
- Path=/get
13+
filters:
14+
- SaveSession

0 commit comments

Comments
 (0)