This guide walks you through the process of routing and filtering requests to a microservice application by using the Netflix Zuul edge service library.

What You Will Build

You will write a simple microservice application and then build a reverse proxy application that uses Netflix Zuul to forward requests to the service application. You will also see how to use Zuul to filter requests that are made through the proxy service.

What You Need

How to complete this guide

Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

To start from scratch, move on to Starting with Spring Initializr.

To skip the basics, do the following:

When you finish, you can check your results against the code in gs-routing-and-filtering/complete.

Starting with Spring Initializr

For all Spring applications, you should start with the Spring Initializr. The Initializr offers a fast way to pull in all the dependencies you need for an application and does a lot of the set up for you.

This guide needs two applications. The first application (the book application) needs only the Spring Web dependency. The following image shows the Initializr set up for the book application:

initializr book
The preceding image shows the Initializr with Maven chosen as the build tool. You can also use Gradle. It also shows values of com.example and routing-and-filtering-book as the Group and Artifact, respectively. You will use those values throughout the rest of this sample.

The following listing shows the pom.xml file (for the book application) that is created when you choose Maven:

<?xml version="1.0" encoding="UTF-8"?>
<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>2.2.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>routing-and-filtering-book</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>routing-and-filtering-book</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

The following listing shows the build.gradle file (for the book application) that is created when you choose Gradle:

plugins {
	id 'org.springframework.boot' version '2.2.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

The second application (the routing and filtering application) needs the Spring Web and Zuul dependencies. The following image shows the Initializr set up for the routing and filtering application:

initializr gateway
The preceding image shows the Initializr with Maven chosen as the build tool. You can also use Gradle. It also shows values of com.example and routing-and-filtering-gateway as the Group and Artifact, respectively. You will use those values throughout the rest of this sample.

The following listing shows the pom.xml file (for the routing and filtering application) that is created when you choose Maven:

<?xml version="1.0" encoding="UTF-8"?>
<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>2.2.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>routing-and-filtering-gateway</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>routing-and-filtering-gateway</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.M3</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</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>
		</repository>
	</repositories>

</project>

The following listing shows the build.gradle file (for the routing and filtering application) that is created when you choose Gradle:

plugins {
	id 'org.springframework.boot' version '2.2.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
	maven { url 'https://repo.spring.io/milestone' }
}

ext {
	set('springCloudVersion', "Hoxton.M3")
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

test {
	useJUnitPlatform()
}
For convenience, we have provided build files (a pom.xml file and a build.gradle file) at the top of the project (one directory above the book and gateway directories) that you can use to build both projects at once. We also added the Maven and Gradle wrappers there.

Set up a Microservice

The Book service will be as simple as a Spring application can be. Edit RoutingAndFilteringBookApplicationBookApplication.java so that it matches the following listing (from book/src/main/java/com/example/routingandfilteringbook/RoutingAndFilteringBookApplication.java):

package com.example.routingandfilteringbook;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class RoutingAndFilteringBookApplication {

  @RequestMapping(value = "/available")
  public String available() {
    return "Spring in Action";
  }

  @RequestMapping(value = "/checked-out")
  public String checkedOut() {
    return "Spring Boot in Action";
  }

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

The RoutingAndFilteringBookApplicationBookApplication class is now a REST controller. The @RestController annotation marks the class as a controller class and ensures that return values from @RequestMapping methods in this class are automatically and appropriately converted and written directly to the HTTP response.

Speaking of @RequestMapping methods, we have added two: available() and checkedOut(). They handle requests to the /available and /checked-out paths, each of which returns the String name of a book.

Set the application name (book) in src/main/resources/application.properties, as the following listing shows:

spring.application.name=book

server.port=8090

Set server.port here, too, so that it does not conflict with your edge service when you get both services up and running locally.

Create an Edge Service

Spring Cloud Netflix includes an embedded Zuul proxy, which you can enable with the @EnableZuulProxy annotation. This will turn the Gateway application into a reverse proxy that forwards relevant calls to other services — such as our book application.

Open the Gateway application’s RoutingAndFilteringGatewayApplicationGatewayApplication class and add the @EnableZuulProxy annotation, as the following listing (from gateway/src/main/java/com/example/routingandfilteringgateway/RoutingAndFilteringGatewayApplication.java) shows:

package com.example.routingandfilteringgateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import com.example.routingandfilteringgateway.filters.pre.SimpleFilter;

@EnableZuulProxy
@SpringBootApplication
public class RoutingAndFilteringGatewayApplication {

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

  @Bean
  public SimpleFilter simpleFilter() {
    return new SimpleFilter();
  }

}

To forward requests from the Gateway application, you need to tell Zuul the routes that it should watch and the services to which to forward requests that are made to those routes. We specify routes by setting properties under zuul.routes. Each of our microservices can have an entry under zuul.routes.NAME, where NAME is the application name (as stored in the spring.application.name property).

Add the application.properties file to a new directory (src/main/resources) in the Gateway application. It should match the following listing (from gateway/src/main/resources/application.properties):

zuul.routes.books.url=http://localhost:8090

ribbon.eureka.enabled=false

server.port=8080

Spring Cloud Zuul automatically sets the path to the application name. In this sample, set zuul.routes.books.url so that Zuul will proxy requests to /books to this URL.

Notice the second property in the application.properties file, Spring Cloud Netflix Zuul uses Netflix’s Ribbon to perform client-side load balancing. By default, Ribbon would use Netflix Eureka for service discovery. For this simple example, you can skip service discovery, so set ribbon.eureka.enabled to false. Since Ribbon now cannot use Eureka to look up services, we must specify a url for the book service.

Add a Filter

Now you can see how to filter requests through your proxy service. Zuul has four standard filter types:

  • pre filters run before the request is routed.

  • route filters can handle the actual routing of the request.

  • post filters run after the request has been routed.

  • error filters run if an error occurs in the course of handling the request.

You are going to write a pre filter. Spring Cloud Netflix picks up, as a filter, any @Bean that extends com.netflix.zuul.ZuulFilter and is available in the application context. The following listing (from gateway/src/main/java/com/example/routingandfilteringgateway/filters/pre/SimpleFilter.java) shows the filter you need:

package com.example.routingandfilteringgateway.filters.pre;

import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.ZuulFilter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleFilter extends ZuulFilter {

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

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

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

  @Override
  public boolean shouldFilter() {
    return true;
  }

  @Override
  public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();

    log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));

    return null;
  }

}

Filter classes implement four methods:

  • filterType(): Returns a String that stands for the type of the filter — in this case, pre. (It would be route for a routing filter.)

  • filterOrder(): Gives the order in which this filter is to be run, relative to other filters.

  • shouldFilter(): Contains the logic that determines when to run this filter (this particular filter is always run).

  • run(): Contains the functionality of the filter.

Zuul filters store request and state information in (and share it by means of) the RequestContext. You can use that to get at the HttpServletRequest and then log the HTTP method and URL of the request before it is sent on its way.

The GatewayApplication class is annotated with @SpringBootApplication, which includes (among others) the @Configuration annotation that tells Spring to look in a given class for @Bean definitions. Put the filter in the application class, as shown in the following listing (from gateway/src/main/java/com/example/routingandfilteringgateway/RoutingAndFilteringGatewayApplication.java):

package com.example.routingandfilteringgateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import com.example.routingandfilteringgateway.filters.pre.SimpleFilter;

@EnableZuulProxy
@SpringBootApplication
public class RoutingAndFilteringGatewayApplication {

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

  @Bean
  public SimpleFilter simpleFilter() {
    return new SimpleFilter();
  }

}

Testing Your Application

Make sure that both applications are running. In a browser, visit one of the book application’s endpoints through the Gateway application. If you have used the configuration shown in this guide, you can access the book application directly at localhost:8090/available and through the Gateway service at localhost:8080/books/available.

Visit one of the Book service endpoints (localhost:8080/books/available or localhost:8080/books/checked-out) and you should see your request’s method logged by the Gateway application before it is handed on to the Book application, as the following sample logging output shows:

2019-10-02 10:58:34.694  INFO 11608 --- [nio-8080-exec-4] c.e.r.filters.pre.SimpleFilter           : GET request to http://localhost:8080/books/available

Summary

Congratulations! You have used Spring to develop an edge service application that can proxy and filter requests for your microservices.

See Also

The following guides may also be helpful:

Want to write a new guide or contribute to an existing one? Check out our contribution guidelines.

All guides are released with an ASLv2 license for the code, and an Attribution, NoDerivatives creative commons license for the writing.