What is the purpose of Spring Cloud Gateway Server MVC? Can it be used in place of Spring Cloud Gateway Reactive? If so, can someone provide an example?
Here is documentation I'm referring to
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway-server-webmvc.html
I'm trying to make it work but having an issue that I do not understand.
Here is my configuration.
import static org.springframework.security.config.Customizer.withDefaults;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(exchanges->exchanges.requestMatchers("/oauth/**","/login/**").permitAll())
.authorizeHttpRequests(https->https.anyRequest().authenticated())
.oauth2Login(withDefaults());
return http.build();
}
}
Here is my yml file
server:
port: 9000
spring:
cloud:
gateway:
mvc:
routes:
- id: service1
uri: http://localhost:8081/
predicates:
- Path=/api/**
security:
oauth2:
client:
registration:
azure:
client-id: dummy737373737373
client-secret: dummypassword737373737373
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
scope: User.Read
provider:
azure:
authorization-uri: https://login.microsoftonline.com/dummypasswordtenantid/oauth2/v2.0/authorize
token-uri: https://login.microsoftonline.com/dummypasswordtenantid/oauth2/v2.0/token
user-info-uri: https://graph.microsoft.com/oidc/userinfo
jwk-set-uri: https://login.microsoftonline.com/dummypasswordtenantid/discovery/v2.0/keys
user-name-attribute: name
Here is my pom
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>swiss</groupId>
<artifactId>manoj-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>manoj-gateway</name>
<description>manoj-gateway</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0-RC1</spring-cloud.version>
</properties>
<dependencies>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server-mvc</artifactId>
</dependency>-->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>-->
<!--
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway-mvc -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway-mvc</artifactId>
</dependency>
<!--
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
</dependency>
-->
<!--
https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-loadbalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
I have one more spring boot service running on 8081. I need to redirect it to that service but It is not working. Can someone explain to me what needs to be change? Thanks for the help. I have made a change as suggested but I am getting following error. I also removed spring security just to check if the route is working properly.
java.lang.AbstractMethodError: Receiver class org.springframework.cloud.gateway.server.mvc.handler.RestClientProxyExchange$$Lambda$906/0x000000080060f988 does not define or inherit an implementation of the resolved method 'abstract java.lang.Object exchange(org.springframework.http.HttpRequest, org.springframework.web.client.RestClient$RequestHeadersSpec$ConvertibleClientHttpResponse)' of interface org.springframework.web.client.RestClient$RequestHeadersSpec$ExchangeFunction.
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchangeInternal(DefaultRestClient.java:456) ~[spring-web-6.1.1.jar:6.1.1]
at org.springframework.web.client.DefaultRestClient$DefaultRequestBodyUriSpec.exchange(DefaultRestClient.java:429) ~[spring-web-6.1.1.jar:6.1.1]
at org.springframework.cloud.gateway.server.mvc.handler.RestClientProxyExchange.exchange(RestClientProxyExchange.java:40) ~[spring-cloud-gateway-server-mvc-4.1.0-RC1.jar:4.1.0-RC1]
at org.springframework.cloud.gateway.server.mvc.handler.ProxyExchangeHandlerFunction.handle(ProxyExchangeHandlerFunction.java:106) ~[spring-cloud-gateway-server-mvc-4.1.0-RC1.jar:4.1.0-RC1]
at org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions$LookupProxyExchangeHandlerFunction.handle(HandlerFunctions.java:94) ~[spring-cloud-gateway-server-mvc-4.1.0-RC1.jar:4.1.0-RC1]
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$ofRequestProcessor$3(HandlerFilterFunction.java:83) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$0(HandlerFilterFunction.java:58) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar.lambda$getRouterFunction$3(GatewayMvcPropertiesBeanDefinitionRegistrar.java:172) ~[spring-cloud-gateway-server-mvc-4.1.0-RC1.jar:4.1.0-RC1]
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$0(HandlerFilterFunction.java:58) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$ofRequestProcessor$3(HandlerFilterFunction.java:83) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$1(HandlerFilterFunction.java:59) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$andThen$1(HandlerFilterFunction.java:59) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.function.HandlerFilterFunction.lambda$apply$2(HandlerFilterFunction.java:70) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.function.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:107) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.1.jar:6.1.1]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.1.jar:6.1.1]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.16.jar:6.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.1.jar:6.1.1]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.16.jar:6.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter.doFilter(WeightCalculatorFilter.java:229) ~[spring-cloud-gateway-server-mvc-4.1.0-RC1.jar:4.1.0-RC1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.springframework.cloud.gateway.server.mvc.filter.FormFilter.doFilter(FormFilter.java:97) ~[spring-cloud-gateway-server-mvc-4.1.0-RC1.jar:4.1.0-RC1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.1.jar:6.1.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.1.jar:6.1.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.1.jar:6.1.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.1.jar:6.1.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.16.jar:10.1.16]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Yes, Spring cloud gateway MVC is an alternative of Spring cloud gateway Reactive. They are both quite similar, the difference between them comes from the methods and classes they use - for example Spring cloud gateway Reactive uses the reactive approach thus classes such as Mono
, Flux
, ServerWebExchange
, GatewayFilterFactory
and RouteLocator
to name a few while Spring cloud gateway MVC uses classes such as RouterFunction
to define routes, equivalent to RouteLocator
.
The following is an example of defining a route predicate and filter using both spring cloud gateway MVC and Reactive. The example below implements a route which will only accept request after a certain time has passed and later on modify the request by adding a request header X-Trace-Token
:
EXAMPLE USING SPRING CLOUD GATEWAY REACTIVE
@Bean
public RouteLocator routeConfig(RouteLocatorBuilder builder) {
return builder.routes()
.route(p -> p.path("/accounts").and().after(ZonedDateTime.parse("2023-12-06T19:03:47.789-05:00[America/New_York]")))
.filters(f -> f.addRequestHeader("X-Trace-Token", "cd855f1a-0df3-4199-bc33-821dc797fc29")
.uri("http://example.com"))
.build();
}
`SAME EXAMPLE USING SPRING CLOUD GATEWAY MVC
@Bean
public RouterFunction<ServerResponse> routeConfig() {
return route("route_id")
.route(after(ZonedDateTime.parse("2023-12-06T19:03:47.789-05:00[America/New_York]")), GET("/accounts"), http("http://example.com"))
.after(addRequestHeader("X-Trace-Token", "cd855f1a-0df3-4199-bc33-821dc797fc29"))
.build();
}
The full documentation for spring cloud gateway MVC and Reactive can be found in the link you described above.
EDIT
The gateway routes configuration defined should work fine. Some possible fixes are:
1
. Define the gateway server port in your yaml config eg.
server:
port: 9000
then make the request to the service on port 8081 using the gateway server port for example:
http://localhost:9000/api/some_path
will be redirected to the service on port 8081.
2
. To define the gateway routes use spring.cloud.gateway.mvc.routes
instead because you are using spring cloud gateway mvc.
3
. The error might be caused by the security policies you defined in your spring security configuration hence preventing the gateway server from redirecting to the service, if possible try testing the gateway routes while you have disable the spring security policies to identify if this is the problem.