javaspringspring-bootspring-mvcspring-cloud-gateway

Spring Cloud Gateway Server MVC - Can we use Spring MVC with Spring Cloud Gateway?


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]

Solution

  • 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.