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

  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Leveraging Salesforce Using Spring Boot
  • Spring Boot REST API Request Body Validation Example Using a Custom Validator
  • Spring Microservices RESTFul API Documentation With Swagger Part 1

Trending

  • Measuring the Impact of AI on Software Engineering Productivity
  • How to Convert XLS to XLSX in Java
  • Beyond ChatGPT, AI Reasoning 2.0: Engineering AI Models With Human-Like Reasoning
  • Artificial Intelligence, Real Consequences: Balancing Good vs Evil AI [Infographic]
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Dynamic Routing Through Zuul With a REST API and Spring Boot Without Spring Config — Sub-Domain to Subpath Router

Dynamic Routing Through Zuul With a REST API and Spring Boot Without Spring Config — Sub-Domain to Subpath Router

Learn about using Zuul with a REST API to route traffic from one service to another with no downtime.

By 
Vikas Anand user avatar
Vikas Anand
·
Aug. 03, 18 · Tutorial
Likes (9)
Comment
Save
Tweet
Share
64.8K Views

Join the DZone community and get the full member experience.

Join For Free

Problem Statement

Say there was a requirement to forward all the traffic coming to *.adomain.com to adomain.com/*. For Example: a.adomain.com/**/* should be forwarded to adomain.com/a/**/*. Additionally, the proxy gateway should be up all the time and new subdomains need to be registered in the gateway without any downtime. This problem was a very specific one and needed some research to achieve the same. 

Research Around Proxies

We tried a couple of things, including node http-proxy, the Java throo library (internally using Zuul), and Zuul itself. All the examples were based on prefetched configuration from a properties file for the proxy creation, except advance Zuul examples, which talk about refreshable configuration using Spring refreshable cloud configuration and RabbitMQ. There was a need for a runtime proxy creator service, preferably exposing a POST API taking some parameters and registering a new proxy at runtime. Even a delete API was required to get rid of proxies no longer being used. Being a Java developer, the natural choice was Zuul.

Pre-Requisites

Knowledge of Java, APIs, proxy, Apache, the gateway concept, and Spring Boot is needed to fully grasp the idea presented here.

Solution

While going through Zuul provided by Netflix, now added in Spring Cloud distribution, there is a nice example of how to do it by using Eureka, Zuul Server, Spring Cloud Server, RabbitMQ, and Git. Although the whole example is great and provides great flexibility, it involves many frameworks and lots of setup to be done. I could not use it as I was looking to create a very simple solution to solve our problem, considering the tradeoff of not being HA.

The architecture is as follows:

Image title

Coding

There are a couple of things needed to make the application work:

APIs

To take the parameter in POST for creating the route.

Zuul-Related Changes

  • Pom changes: It contains actuator, zuul, and spring-cloud related dependencies.
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>gateway-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.7.RELEASE</version>
        <relativePath />
        <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <!-- Dependencies -->
        <spring-cloud.version>Camden.SR7</spring-cloud.version>
    </properties>
    <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>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.handlers</resource>
                                </transformer>
                                <transformer
implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                                    <resource>META-INF/spring.factories</resource>
                                </transformer>
                                <transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.schemas</resource>
                                </transformer>
                                <transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                                <transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.application.SpringBootWebApplication</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Spring Boot Application class:

package com.mettl.gatewayservice.application;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@EnableAutoConfiguration(exclude = { RabbitAutoConfiguration.class })
@EnableZuulProxy
@ComponentScan("com.example.gatewayservice")
public class SpringBootWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebApplication.class, args);
    }
}

application.yml

server:
  port: ${appPort:80}
info.app.version: @project.version@
# Actuator endpoint path (/admin/info, /admin/health, ...)
server.servlet-path: /
management.context-path: /admin
# ribbon.eureka.enabled: false
zuul:
  ignoredPatterns: /**/admin/**, /proxyurl
  routes:
    zuulDemo1:
      path: /**
      url: http://localhost:8000/
# stripPrefix set to true if context path is set to /
      stripPrefix: true

There are two parts:

1. Registration of Proxy Routes

This part is done by creating a service class by auto-wiring two dependencies, like this:  

@Service    
public class ZuulDynamicRoutingService {         

private static final String HTTP = "http://";         
private final ZuulProperties zuulProperties;         
private final ZuulHandlerMapping zuulHandlerMapping;

  ......

Adding the new route is achieved by the following code.

Creation of uuid can be done by following any standard way. The only thing is that it should be a unique key, as it is going to be used in the map used by zuulProperties.getRoutes().

String uuid = GenerateUID.getUID();
if (StringUtils.isEmpty(dynamicRouteRequest.getSubpath())) {
    dynamicRouteRequest.setSubpath("");
}

String url = "http://" + dynamicRouteRequest.getHost() + ":" + dynamicRouteRequest.getPort() + dynamicRouteRequest.getSubpath();
zuulProperties.getRoutes().put(uuid, new ZuulRoute(uuid, "/" + uuid + "/**", null, url, true, false, new HashSet<>()));
zuulHandlerMapping.setDirty(true);

This service is injected in a controller to receive the post request and forward the details to the service class to get the proxy created.

It can be checked with the URL /admin/routes.

2. Forwarding HTTP Requests to the Destination With Subdomain to Subpath Conversion

This requires a PreFilter class extending ZuulFilter:

@Component
public class PreFilter extends ZuulFilter {

  private static Logger log = LoggerFactory.getLogger(PreFilter.class);

  private UrlPathHelper urlPathHelper = new UrlPathHelper();

  @Override
  public String filterType() {
      return "pre";
  }

  @Override
  public int filterOrder() {
      return 1;
  }

  @Override
  public boolean shouldFilter() {

      RequestContext ctx = RequestContext.getCurrentContext();
      String requestURL = ctx.getRequest().getRequestURL().toString();
      //Here we only require to filter those URLs which contains "proxyurl" and "/admin/".
      return !(requestURL.contains("proxyurl") || requestURL.contains("/admin/"));
  }

  //The actual part where the subdomain to subpath conversion happens is as follows:

  @Override
  public Object run() {
      RequestContext ctx = RequestContext.getCurrentContext();
      String remoteHost = ctx.getRequest().getRemoteHost();
      String requestURL = ctx.getRequest().getRequestURL().toString();
      if (!requestURL.contains("proxyurl")) {
          log.info("remoteHost {} requestURL {}", new Object[]{remoteHost, requestURL});
          String originatingRequestUri = this.urlPathHelper.getOriginatingRequestUri(ctx.getRequest());
          final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
          log.info("URI {} original URI {}", new Object[]{requestURI, originatingRequestUri});
          String protocol = requestURL.substring(0, requestURL.indexOf("//") + 2);
          String urlWithoutProtocol = requestURL.substring(requestURL.indexOf("//") + 2);
          String[] split = urlWithoutProtocol.substring(0, urlWithoutProtocol.indexOf("/")).split("\\.");
          String subPath = split[0];
          final String newURL = protocol + "." + split[1] + "." + split[2];
          //Here the main thing is to create a HttpServletRequestWrapper and override the request coming from the actual request
          HttpServletRequestWrapper httpServletRequestWrapper = new HttpServletRequestWrapper(ctx.getRequest()) {
              public String getRequestURI() {
                  if (requestURI != null && !requestURI.equals("/")) {
                      if (!StringUtils.isEmpty(subPath)) {
                          return "/" + subPath + requestURI;
                      } else {
                          return requestURI;
                      }
                  }
                  if (!StringUtils.isEmpty(subPath)) {
                      return "/" + subPath;
                  } else {
                      return "/";
                  }
              }
              public StringBuffer getRequestURL() {
                return new StringBuffer(newURL);
              }
          };
          ctx.setRequest(httpServletRequestWrapper);
          HttpServletRequest request = ctx.getRequest();
          log.info("PreFilter: " + String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
       }
       return null;
    }
}

Now you can run the application by adding the missing part with mvn springboot:run or java -jar gateway-service.jar.

Now, use Postman to submit the request, like below.

Image title

To Be Done on Your Own

  • The Apache part is missing in this session. Add a configuration to forward all requests coming to *.adomain.com to the IP hosting the Zuul server with the port configured when starting the Zuul server.

  • Applications where the requests need to be up when creating proxy routes to actually see what is happening.

Summary

We learned to

  1. Create a proxy route at runtime by exposing an API 

  2. Change PreFilter (a subclass of ZuulFilter) to pre-process the HTTP request

  3. Add Apache configuration to forward requests to Zuul Gateway

Spring Framework API zuul Spring Boot REST Web Protocols Spring Cloud

Opinions expressed by DZone contributors are their own.

Related

  • Develop a Spring Boot REST API in AWS: PART 4 (CodePipeline / CI/CD)
  • Leveraging Salesforce Using Spring Boot
  • Spring Boot REST API Request Body Validation Example Using a Custom Validator
  • Spring Microservices RESTFul API Documentation With Swagger Part 1

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!