Here you will find instructions for developing a custom extension for VMware Spring Cloud Gateway for Kubernetes.
Gateway extensions are packaged as Java archives (JARs), and contain classes which extend the base set of Spring Cloud Gateway for Kubernetes features. This is done by adding custom Spring Cloud Gateway Filter and Predicate Factories, or Global Filters.
The requirements to build a Gateway extension are:
com.vmware.scg.extensions
package.You can use any IDE and build system with the appropriate dependencies and packaging setup.
To create a new extension project using Gradle:
Initialize a new Gradle project of type library
, implementation Java
and Groovy
as build script DLS. When prompted, make sure you set the source package to com.vmware.scg.extensions
:
gradle init
Update the build.gradle
file for your extension library:
plugins {
id 'java-library'
}
group = 'com.vmware.scg.extensions'
version = '0.0.1-SNAPSHOT'
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:3.1.12')
implementation platform('org.springframework.cloud:spring-cloud-dependencies:2022.0.7')
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.github.tomakehurst:wiremock-jre8-standalone:3.3.1'
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = 'full'
}
}
Important While other versions may work for development, only Spring Boot version 3.1.12 and Spring Cloud 2022.0.7 are fully supported at runtime.
Note It's safe to add more dependencies, if they don't cause classpath conflicts with the existing ones. However, it's not recommended to overburden extensions with dependencies, given the possible negative impacts on resource consumption and performance.
Delete any .java
files created by the generator.
To create a new extension project using Maven:
Generate a Maven library archetype. Make sure you set the groupId
to com.vmware.scg.extensions
.
mvn archetype:generate \
-DgroupId=com.vmware.scg.extensions \
-DarchetypeArtifactId=maven-archetype-quickstart
Update the pom.xml
file for your extension library:
<?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>3.1.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.vmware.scg.extensions</groupId>
<artifactId>mycustomfilter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mycustomfilter</name>
<description>Spring Cloud Gateway for Kubernetes extension</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.7</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8-standalone</artifactId>
<version>3.3.1</version>
<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>
</project>
Important While other versions may work for development, only Spring Boot version 3.1.12 and Spring Cloud 2022.0.7 are fully supported at runtime.
Note It's safe to add more dependencies, if they don't cause classpath conflicts with the existing ones. However, it's not recommended to overburden extensions with dependencies, given the possible negative impacts on resource consumption and performance.
Delete any .java
files created by the generator.
The following is a simple example of a custom extension. The extension implements a new filter called AddMyCustomHeader
, which adds an HTTP header to the request sent to the target service.
For more in-depth examples, such as implementing custom predicates or custom configurations, refer to the Spring Cloud Gateway Developer Guide.
To create a custom filter you must implement GatewayFilterFactory
as a bean. The simplest way to do this is to extend AbstractGatewayFilterFactory
, as shown below:
package com.vmware.scg.extensions.filter;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
@Component
class AddMyCustomHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(AddMyCustomHeaderGatewayFilterFactory.class);
private static final String MY_HEADER_KEY = "X-My-Header";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerWebExchange updatedExchange = exchange.mutate()
.request(request -> request.headers(headers -> {
headers.put(MY_HEADER_KEY, List.of("my-header-value"));
LOGGER.info("Processed request, added " + MY_HEADER_KEY + " header");
}))
.build();
return chain.filter(updatedExchange);
};
}
}
In the code, you can see that:
AddMyCustomHeaderGatewayFilterFactory
. This will make a filter named AddMyCustomHeader
available for use in route configurations. You must ensure your extension name does not collide with any of the existing predicates or filters.@Component
annotation, but for more complex configurations you might alternatively choose to use Spring @Configuration
classes.AbstractGatewayFilterFactory
with a configuration type of Object
is sufficient for our needs.apply
method of this simple example we only need to add our header. You can be as creative as you like here! See the Spring Cloud Gateway Developer Guide section on Custom GatewayFilter Factories for further information.org.slf4j.Logger
to provide traces. These will appear in the Gateway Pod logs.To test the extension we'll use Spring Boot conventional tools. Extensions can be tested in isolation - Kubernetes is not required.
First, ensure your project has com.github.tomakehurst:wiremock-jre8-standalone:3.3.1
or higher as a test dependency. We'll use WireMock to simulate a service that responds to routed traffic, and to assert on the requests the service receives.
Next, create a test class like this one:
package com.vmware.scg.extensions.filter;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.matching.EqualToPattern;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.test.web.reactive.server.WebTestClient;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class AddMyCustomHeaderTest {
final WireMockServer wireMock = new WireMockServer(9090);
@Autowired
WebTestClient webTestClient;
@BeforeAll
void setUp() {
wireMock.stubFor(get("/add-header").willReturn(ok()));
wireMock.start();
}
@AfterAll
void tearDown() {
wireMock.stop();
}
@Test
void shouldApplyExtensionFilter() {
webTestClient.get()
.uri("/add-header")
.exchange()
.expectStatus()
.isOk();
wireMock.verify(getRequestedFor(urlPathEqualTo("/add-header"))
.withHeader("X-My-Header", new EqualToPattern("my-header-value")));
}
@SpringBootApplication
public static class GatewayApplication {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder,
AddMyCustomHeaderGatewayFilterFactory filterFactory) {
return builder.routes()
.route("test_route", r -> r.path("/add-header/**")
.filters(f -> f.filters(filterFactory.apply(new Object())))
.uri("http://localhost:9090"))
.build();
}
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
}
Finally, add this configuration to your application.yaml
under src/test/resources
:
spring:
cloud:
gateway:
routes:
- uri: http://localhost:9090
predicates:
- Path=/add-header/**
filters:
- StripPrefix=0
- AddMyCustomHeader
Important External configuration files under src/main/resources
are not supported yet and may cause issues.
In the code above, you can see that:
GatewayApplication
to initialize a basic context for testing purposes.builder.routes().route()
method directly in the test.webTestClient
class-level variable with @AutoConfigureWebTestClient
for both REST calls and assertions.Now, you can build the extension jar file holding the necessary code. Use either ./gradlew build
or mvn package
to obtain the jar under build/libs
or target
folders.
A extension like in the examples should build a JAR of a few kilobytes in size. If the size is of the order of megabytes, ensure that the respective Spring Boot plugins are not present.
id 'org.springframework.boot'
under the plugins
section.org.springframework.boot
and artifactId spring-boot-maven-plugin
is configured under build.plugins
.Finally, follow the instructions in the Configuring Extensions guide to fully deploy the extension in a Spring Cloud Gateway for Kubernetes instance.