DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Micronaut vs Spring Boot: A Detailed Comparison
  • Using Spring Cloud Gateway and Discovery Service for Seamless Request Routing
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Component Tests for Spring Cloud Microservices

Trending

  • Top Book Picks for Site Reliability Engineers
  • Event-Driven Architectures: Designing Scalable and Resilient Cloud Solutions
  • Docker Base Images Demystified: A Practical Guide
  • A Developer's Guide to Mastering Agentic AI: From Theory to Practice
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Spring Boot Gateway With Spring Cloud and WebFlux

Spring Boot Gateway With Spring Cloud and WebFlux

Let's build a gateway application in Spring Boot with the help of Spring Cloud and WebFlux, using Netty as the application server.

By 
Vishnu Viswambharan user avatar
Vishnu Viswambharan
·
Mar. 21, 25 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
6.3K Views

Join the DZone community and get the full member experience.

Join For Free

Acting as the central entry point, the gateway routes requests to different microservices while providing essential features like request routing, authentication, and rate limiting. WebFlux enables non-blocking, asynchronous request processing, ensuring both high performance and scalability.

Spring Boot gateway with Spring Cloud and WebFlux
The integration of Spring Cloud components further supports seamless service discovery, configuration management, and fault tolerance, making this solution ideal for modern microservice architectures.

Used Libraries

  • Spring Boot
  • Spring WebFlux
  • Spring Cloud

Features

  • API request routing
  • API rate limiting
  • Authentication
  • Upstream and downstream API details logging

Steps to Run Applications

  1. Install JDK 21 or the latest version.
  2. Clone the project repository to your local machine.
  3. Update the port and API routing samples in the property files, if required.
  4. Run the gateway-service application.
  5. The Spring WebFlux-based application will run under the Netty server instead of the traditional Tomcat.
  6. To run a non-blocking, reactive-based application, ensure the Netty server is used, as Tomcat cannot handle it.

How It Works

The main system configurations are listed below.

1. System Dependencies

pom.xml

XML
 
<properties>
  <java.version>21</java.version>
  <springdoc.openapi.version>2.2.0</springdoc.openapi.version>
  <spring.cloud.version>2023.0.4</spring.cloud.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <scope>runtime</scope>
  </dependency>

<!-- Spring Cloud Gateway -->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
  </dependency>

<!-- Springdoc OpenAPI for Webflux (Swagger) -->
  <dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    <version>${springdoc.openapi.version}</version>
  </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>


2. API Routing configuration

The upstream and downstream URLs and their related configurations are mentioned in the application property file.

application-test.properties

Properties files
 
# Example for a specific url routing
spring.cloud.gateway.routes[0].id=0
spring.cloud.gateway.routes[0].uri=http://192.168.1.10:5000
spring.cloud.gateway.routes[0].predicates[0]=Path=/demoService
spring.cloud.gateway.routes[0].filters[0]=RewritePath=/demoService, /demoService/
spring.cloud.gateway.routes[0].filters[1]=PreserveHostHeader

# Example for a specific API routing by filtering a method and jwt auth
spring.cloud.gateway.routes[1].id=1
spring.cloud.gateway.routes[1].uri=http://192.168.1.10:6000
spring.cloud.gateway.routes[1].predicates[0]=Path=/demoServiceOne
spring.cloud.gateway.routes[1].predicates[1]=Method=POST
spring.cloud.gateway.routes[1].filters[0]=JwtAuth
spring.cloud.gateway.routes[1].filters[1]=RewritePath=/demoServiceOne, /demoServiceOne/
spring.cloud.gateway.routes[1].filters[2]=PreserveHostHeader

# Example for a specific url with a query param and rate limiting
spring.cloud.gateway.routes[2].id=2
spring.cloud.gateway.routes[2].uri=http://192.168.1.10:7000
spring.cloud.gateway.routes[2].predicates[0]=Path=/testServiceTwo/data
spring.cloud.gateway.routes[2].predicates[1]=Query=id
spring.cloud.gateway.routes[2].filters[0]=RateLimiter
spring.cloud.gateway.routes[2].filters[1]=RewritePath=/testServiceTwo/data, /testServiceTwo/data
spring.cloud.gateway.routes[2].filters[2]=PreserveHostHeader

# Example for a common base path and dynamic endpoint
spring.cloud.gateway.routes[3].id=3
spring.cloud.gateway.routes[3].uri=http://192.168.1.10:8000
spring.cloud.gateway.routes[3].predicates[0]=Path=/testServiceThree/**
spring.cloud.gateway.routes[3].filters[0]=RewritePath=/testServiceThree/(?<segment>.*), /testServiceThree/${segment}
spring.cloud.gateway.routes[3].filters[1]=PreserveHostHeader

# Example of a common base path and dynamic endpoint along with different downstream
spring.cloud.gateway.routes[4].id=4
spring.cloud.gateway.routes[4].uri=http://192.168.1.10:9000
spring.cloud.gateway.routes[4].predicates[0]=Path=/gateway/testServiceFour/**
spring.cloud.gateway.routes[4].filters[0]=RewritePath=/gateway/testServiceFour/(?<segment>.*), /testServiceFour/${segment}
spring.cloud.gateway.routes[4].filters[1]=PreserveHostHeader


3. API Authentication Configuration

The configuration mentioned below will invoke the JWT authentication filter class. There is a connection between the filter name in the property file and the filter class name.

Plain Text
 
spring.cloud.gateway.routes[1].filters[0]=JwtAuth


If the filter class name is XYZGatewayFilterFactory.java, then it should be ‘XYZ’ in the property configuration.

Example:

Plain Text
 
spring.cloud.gateway.routes[1].filters[0]=XYZ.


The @Order(3) annotation will decide the execution priority of the filter.

JwtAuthGatewayFilterFactory.java

Java
 
@Component
@Order(3)
public class JwtAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<JwtAuthGatewayFilterFactory.Config> {

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthGatewayFilterFactory.class);

    public JwtAuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // Extract the Authorization header
            String authorizationHeader = exchange.getRequest().getHeaders().getFirst(Constants.AUTHENTICATION_HEADER_KEY);

            if (authorizationHeader == null || !authorizationHeader.startsWith(Constants.BEARER_KEY+" ")) {
                return handleUnauthorized(exchange);
            }

            // Extract token and validate (pseudo code)
            String token = authorizationHeader.substring(7);
            // Do the logic to validate Token;
            logger.info("API call is authenticated **********");
            return chain.filter(exchange);
        };
    }

    // Method to handle missing Authorization header
    private Mono<Void> handleUnauthorized(ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        ApiResponse apiResponse = new ApiResponse(Constants.API_HEADER_INVALID_VALUE_CODE,Constants.API_HEADER_INVALID_VALUE_MESSAGE,null);
        byte[] bytes = new Gson().toJson(apiResponse, ApiResponse.class).getBytes();
        return response.writeWith(Mono.just(response.bufferFactory().wrap(bytes)));
    }

        public static class Config {
        // Configuration properties, if needed
    }

}


4. API Global Filter Configuration

This filter class is responsible for capturing the complete upstream and downstream API details for analytics purposes. It is not a mandatory configuration; we can also utilize the default property file-based configuration to log API call details.

Default property file-based log configuration:

Plain Text
 
spring.reactor.netty.http.server.access-log-enabled=true


GlobalFilterConfig.java

Java
 
@Component
@Order(1)
public class GlobalFilterConfig implements GlobalFilter {

    private static final Logger logger = LoggerFactory.getLogger(GlobalFilterConfig.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // Log an upstream path (incoming request to the gateway)
        try {
            URI upstreamUri = exchange.getRequest().getURI();
            logger.info("Call received : upstream path -------- {} ", upstreamUri.getPath());

            // Log route attributes before proceeding with the chain
            Object routeAttribute = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
            if (routeAttribute != null) {
                logger.info("Route config  details  -------- {} ", routeAttribute);
            }

            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                URI downstreamUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
                if (downstreamUri != null) {
                    logger.info("Call received : downstream path -------- {} ", downstreamUri.getPath());
                } else {
                    logger.info("Downstream Path: Not available (request may have been processed)");
                }
            }));
        }catch (Exception exception){
            logger.error("Unknown exception : ", exception);
            exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
            ApiResponse apiResponse = new ApiResponse(Constants.UNKNOWN_ERROR_CODE, Constants.UNKNOWN_ERROR_MESSAGE, null);
            return exchange.getResponse().writeWith(Mono.just(exchange.getResponse()
                    .bufferFactory().wrap(new Gson().toJson(apiResponse, ApiResponse.class).getBytes())));
        }

    }

}


5. API Rate Limiter Configuration

We can control the incoming API call traffic using filters. This way, we can protect against brute force attacks, scrapers, or other malicious behavior where an attacker tries to flood your services with a high volume of requests. 

I implemented a custom rate-limiting mechanism. There are other multiple ways to achieve this feature.

RateLimiterGatewayFilterFactory.java 

Java
 
@Component
@Order(2)
public class RateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory<RateLimiterGatewayFilterFactory.Config> {

    private static final Logger logger = LoggerFactory.getLogger(RateLimiterGatewayFilterFactory.class);

    private final InMemoryRateLimiterService inMemoryRateLimiterService;
    @Autowired
    public RateLimiterGatewayFilterFactory(InMemoryRateLimiterService inMemoryRateLimiterService) {
        super(Config.class);
        this.inMemoryRateLimiterService = inMemoryRateLimiterService;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String clientIp = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
            return inMemoryRateLimiterService.isAllowed(clientIp).flatMap(allowed -> {
                if (allowed) {
                    return chain.filter(exchange);
                } else {
                    logger.warn("Rate limit exceeded for client: {}", clientIp);
                    exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                    return exchange.getResponse().setComplete();
                }
            });
        };
    }

    public static class Config { }
}


InMemoryRateLimiterServiceImpl.java

Java
 
@Service
public class InMemoryRateLimiterServiceImpl implements InMemoryRateLimiterService {
    private final ConcurrentHashMap<String, Semaphore> requestCounters = new ConcurrentHashMap<>();

    @Override
    public Mono<Boolean> isAllowed(String clientId) {
        Semaphore semaphore = requestCounters.computeIfAbsent(clientId, k -> new Semaphore(5));
        if (semaphore.tryAcquire()) {
            Mono.delay(Duration.ofSeconds(10)).doOnTerminate(semaphore::release).subscribe();
            return Mono.just(true);
        }
        return Mono.just(false);
    }
}


Git Repository

You can view this on GitHub.

Result

Download the codebase, customize the property file to align with your API requirements, and deploy the application. Ensure that all configurations are properly set to optimize performance and security. Once configured, run the application and monitor its behavior to verify that it meets your expectations.

Thanks for reading!

Spring Cloud Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Micronaut vs Spring Boot: A Detailed Comparison
  • Using Spring Cloud Gateway and Discovery Service for Seamless Request Routing
  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • Component Tests for Spring Cloud Microservices

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!