The scope of GatewayFilter is the specified routing configuration, and the routing configuration options require filters to specify the list of GatewayFilters that you want to use. We can customize the GatewayFilter to do additional extensions to achieve some functionality that does not exist in the built-in GatewayFilter and apply it to our routing configuration.

spring

How to customize GatewayFilter

To customize GatewayFilter, you need to implement the org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory interface, and the definition of GatewayFilterFactory is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@FunctionalInterface
public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {

	String NAME_KEY = "name";

	String VALUE_KEY = "value";

	default GatewayFilter apply(Consumer<C> consumer) {
		C config = newConfig();
		consumer.accept(config);
		return apply(config);
	}

	default Class<C> getConfigClass() {
		throw new UnsupportedOperationException("getConfigClass() not implemented");
	}

	@Override
	default C newConfig() {
		throw new UnsupportedOperationException("newConfig() not implemented");
	}

	GatewayFilter apply(C config);

	default String name() {
		return NameUtils.normalizeFilterFactoryName(getClass());
	}

	@Deprecated
	default ServerHttpRequest.Builder mutate(ServerHttpRequest request) {
		return request.mutate();
	}
}  

public interface ShortcutConfigurable {

    default ShortcutType shortcutType() {
        return ShortcutType.DEFAULT;
    }

    default List<String> shortcutFieldOrder() {
        return Collections.emptyList();   
    }

    default String shortcutFieldPrefix() {
        return "";
    }    
}

public interface Configurable<C> {

	Class<C> getConfigClass();

	C newConfig();
}

It looks quite complicated, but in fact many of them are the default methods of the interface, and there are actually very few methods to implement.

Another way is to inherit from org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory and look at the definition of the abstract class AbstractGatewayFilterFactory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class AbstractGatewayFilterFactory<C> extends AbstractConfigurable<C>
		implements GatewayFilterFactory<C> {

	@SuppressWarnings("unchecked")
	public AbstractGatewayFilterFactory() {
		super((Class<C>) Object.class);
	}

	public AbstractGatewayFilterFactory(Class<C> configClass) {
		super(configClass);
	}

	public static class NameConfig {

		private String name;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}
	}
}

1 generic parameter C is the configuration class, from the existing AbstractGatewayFilterFactory or GatewayFilterFactory subclass implementation, the configuration class is generally defined as a public static internal class.

  1. implement the GatewayFilterFactory interface or inherit from AbstractGatewayFilterFactory.

  2. the corresponding subclasses are registered to the Spring container.

  3. add the corresponding GatewayFilter configuration to the filters property in the routing configuration. Note that the filter name is determined by GatewayFilterFactory#name().

Practices

The following is an attempt to implement the GatewayFilterFactory interface and to inherit from the AbstractGatewayFilterFactory abstract class.

Implement the GatewayFilterFactory interface

The GatewayFilterFactory interface is implemented with reference to the SetRequestHeaderGatewayFilterFactory to add a custom request header for each request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Component
public class CustomAddRequestHeaderGatewayFilterFactory implements
        GatewayFilterFactory<CustomAddRequestHeaderGatewayFilterFactory.CustomAddRequestHeaderConfig> {

    private final Class<CustomAddRequestHeaderConfig> configClass = CustomAddRequestHeaderConfig.class;

    @Override
    public List<String> shortcutFieldOrder() {
        return new ArrayList<>(Arrays.asList("headerName", "headerValue"));
    }

    @Override
    public GatewayFilter apply(CustomAddRequestHeaderConfig config) {
        return ((exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {
                httpHeaders.set(config.getHeaderName(), config.getHeaderValue());
            }).build();
            return chain.filter(exchange.mutate().request(request).build());
        });
    }

    @Override
    public Class<CustomAddRequestHeaderConfig> getConfigClass() {
        return configClass;
    }

    @Override
    public CustomAddRequestHeaderConfig newConfig() {
        return BeanUtils.instantiateClass(this.configClass);
    }

    public static class CustomAddRequestHeaderConfig {

        private String headerName;
        private String headerValue;

        public String getHeaderName() {
            return headerName;
        }

        public void setHeaderName(String headerName) {
            this.headerName = headerName;
        }

        public String getHeaderValue() {
            return headerValue;
        }

        public void setHeaderValue(String headerValue) {
            this.headerValue = headerValue;
        }
    }
}

As you can see, the core functional operation is actually required to implement the GatewayFilter apply(C config) method to write a custom function. Note the logic of this Lambda expression:

1
2
3
4
5
6
return ((exchange, chain) -> {
    ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {
        httpHeaders.set(config.getHeaderName(), config.getHeaderValue());
    }).build();
    return chain.filter(exchange.mutate().request(request).build());
});

In fact, it can be simply understood as an anonymous implementation of the GatewayFilter interface.

The application.yaml configuration file is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spring:
  cloud:
    gateway:
      routes:
        - id: custom_add_request_header_route
          uri: http://localhost:9091
          predicates:
            - Host=localhost:9090
          filters:
            - CustomAddRequestHeader=customHeaderName,customHeaderValue

To match the test, the downstream service adds an endpoint.

1
2
3
4
@GetMapping(value = "/customAddRequestHeader")
public ResponseEntity<String> customAddRequestHeader(@RequestHeader(name = "customHeaderName") String value) {
    return ResponseEntity.ok(value);
}
1
2
3
4
curl localhost:9090/order/customAddRequestHeader

// response
customHeaderValue

Inherits AbstractGatewayFilterFactory abstract class

Inheriting AbstractGatewayFilterFactory is actually pretty much the same way, we try to make a relatively complex modification: for each successful request (status code 200) response, add a custom cookie and a response header. GatewayFilterFactory abstract class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@Component
public class CustomResponseGatewayFilterFactory extends
        AbstractGatewayFilterFactory<CustomResponseGatewayFilterFactory.Config> {

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

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            ServerHttpResponse response = exchange.getResponse();
            if (HttpStatus.OK.equals(response.getStatusCode())) {
                for (Map.Entry<String, String> entry : toMap(config.getCookie()).entrySet()) {
                    response.addCookie(ResponseCookie.from(entry.getKey(), entry.getValue()).build());
                }
                for (Map.Entry<String, String> entry : toMap(config.getHeader()).entrySet()) {
                    response.getHeaders().add(entry.getKey(), entry.getValue());
                }
                return chain.filter(exchange.mutate().response(response).build());
            } else {
                return chain.filter(exchange);
            }
        });
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList(NAME_KEY);
    }

    private static Map<String, String> toMap(String value) {
        String[] split = value.split("=");
        Map<String, String> map = new HashMap<>(8);
        map.put(split[0], split[1]);
        return map;
    }

    public static class Config {

        private String cookie;
        private String header;

        public String getCookie() {
            return cookie;
        }

        public void setCookie(String cookie) {
            this.cookie = cookie;
        }

        public String getHeader() {
            return header;
        }

        public void setHeader(String header) {
            this.header = header;
        }
    }
}

The application.yaml configuration file is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
spring:
  cloud:
    gateway:
      routes:
        - id: custom_response_route
          uri: http://localhost:9091
          predicates:
            - Host=localhost:9090
          filters:
            - name: CustomResponse
              args:
                cookie: customCookieName=customCookieValue
                header: customHeaderName=customHeaderValue

Note here that the name property under the filters collection is required to point to the name() method of the AbstractGatewayFilterFactory implementation class, and the args property is used to specify the properties assembled to the Config class.

1
2
3
4
5
6
7
8
curl localhost:9090/order/remote

// response header
customHeaderName: customHeaderValue
Content-Type: text/plain;charset=UTF-8
Content-Length: 6
Date: Sun, 05 May 2019 11:06:35 GMT
set-cookie: customCookieName=customCookieValue

Summary

Customizing GatewayFilter allows us to flexibly extend the filter and add custom properties or judgment logic to the request or response. The fact that GatewayFilter is not globally valid allows us to flexibly combine the functionality of multiple instances of GatewayFilter that have already been written when writing routing configurations.


Reference https://www.throwx.cn/2019/05/05/spring-cloud-gateway-custom-gateway-filter/