Talk about GatewayFilter of spring cloud gateway.

  springcloud

Order

This article mainly studies the GatewayFilter of spring cloud gateway.

GatewayFilter

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/GatewayFilter.java

/**
 * Contract for interception-style, chained processing of Web requests that may
 * be used to implement cross-cutting, application-agnostic requirements such
 * as security, timeouts, and others. Specific to a Gateway
 *
 * Copied from WebFilter
 *
 * @author Rossen Stoyanchev
 * @since 5.0
 */
public interface GatewayFilter extends ShortcutConfigurable {

    String NAME_KEY = "name";
    String VALUE_KEY = "value";

    /**
     * Process the Web request and (optionally) delegate to the next
     * {@code WebFilter} through the given {@link GatewayFilterChain}.
     * @param exchange the current server exchange
     * @param chain provides a way to delegate to the next filter
     * @return {@code Mono<Void>} to indicate when request processing is complete
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);

}

As mentioned in the note, it is mainly used for non-functional requirements like security and timeout control. Its direct implementation classes are OrderedGatewayFilter, ModifyResponseGatewayFilter, GatewayFilterAdapter

OrderedGatewayFilter

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java

public class OrderedGatewayFilter implements GatewayFilter, Ordered {

    private final GatewayFilter delegate;
    private final int order;

    public OrderedGatewayFilter(GatewayFilter delegate, int order) {
        this.delegate = delegate;
        this.order = order;
    }

    public GatewayFilter getDelegate() {
        return delegate;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return this.delegate.filter(exchange, chain);
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("OrderedGatewayFilter{");
        sb.append("delegate=").append(delegate);
        sb.append(", order=").append(order);
        sb.append('}');
        return sb.toString();
    }
}

The Order interface is implemented.

ModifyResponseGatewayFilter

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactory.java

    public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered {
        private final Config config;

        public ModifyResponseGatewayFilter(Config config) {
            this.config = config;
        }

        @Override
        @SuppressWarnings("unchecked")
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                    ResolvableType inElementType = ResolvableType.forClass(config.getInClass());
                    ResolvableType outElementType = ResolvableType.forClass(config.getOutClass());
                    MediaType contentType = exchange.getResponse().getHeaders().getContentType();
                    Optional<HttpMessageReader<?>> reader = getHttpMessageReader(codecConfigurer, inElementType, contentType);
                    Optional<HttpMessageWriter<?>> writer = getHttpMessageWriter(codecConfigurer, outElementType, null);

                    if (reader.isPresent() && writer.isPresent()) {

                        ResponseAdapter responseAdapter = new ResponseAdapter(body, getDelegate().getHeaders());

                        Flux<?> modified = reader.get().read(inElementType, responseAdapter, config.getInHints())
                                .cast(inElementType.resolve())
                                .flatMap(originalBody -> Flux.just(config.rewriteFunction.apply(exchange, originalBody)))
                                .cast(outElementType.resolve());

                        return getDelegate().writeWith(
                                writer.get().write((Publisher)modified, outElementType, null, getDelegate(),
                                        config.getOutHints())
                        );

                    }
                    // TODO: error? log?

                    return getDelegate().writeWith(body);
                }

                @Override
                public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body)
                            .flatMapSequential(p -> p));
                }
            };

            return chain.filter(exchange.mutate().response(responseDecorator).build());
        }

        @Override
        public int getOrder() {
            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
        }

    }

Mainly used to modify response

GatewayFilterAdapter

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/handler/FilteringWebHandler.java

    private static class GatewayFilterAdapter implements GatewayFilter {

        private final GlobalFilter delegate;

        public GatewayFilterAdapter(GlobalFilter delegate) {
            this.delegate = delegate;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return this.delegate.filter(exchange, chain);
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("GatewayFilterAdapter{");
            sb.append("delegate=").append(delegate);
            sb.append('}');
            return sb.toString();
        }
    }

Convert GlobalFilter to adapter for GatewayFilter

GatewayFilterFactory

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java

@FunctionalInterface
public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {

    String NAME_KEY = "name";
    String VALUE_KEY = "value";

    // useful for javadsl
    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() {
        //TODO: deal with proxys
        return NameUtils.normalizeFilterFactoryName(getClass());
    }

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

Spring cloud gateway uses factory mode to generate GatewayFilter. You can see that the apply method is defined here and GatewayFilter is generated according to config. GatewayFilterFactory has several abstract classes, namely, AbstractGatewayFilterFactory, AbstractNameValueGatewayFilterFactory (Inherited AbstractGatewayFilterFactory)、AbstractChangeRequestUriGatewayFilterFactory。

AbstractGatewayFilterFactory

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java

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;
        }
    }
}

Its direct implementation classes are as follows:

  • HystrixGatewayFilterFactory
  • ModifyRequestBodyGatewayFilterFactory
  • ModifyResponseBodyGatewayFilterFactory
  • PrefixPathGatewayFilterFactory
  • PreserveHostHeaderGatewayFilterFactory
  • RedirectToGatewayFilterFactory
  • RemoveRequestHeaderGatewayFilterFactory
  • RemoveResponseHeaderGatewayFilterFactory
  • RequestRateLimiterGatewayFilterFactory
  • RetryGatewayFilterFactory
  • RewritePathGatewayFilterFactory
  • SaveSessionGatewayFilterFactory
  • SecureHeadersGatewayFilterFactory
  • SetPathGatewayFilterFactory
  • SetStatusGatewayFilterFactory
  • StripPrefixGatewayFilterFactory

AbstractNameValueGatewayFilterFactory

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java

public abstract class AbstractNameValueGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractNameValueGatewayFilterFactory.NameValueConfig> {

    public AbstractNameValueGatewayFilterFactory() {
        super(NameValueConfig.class);
    }

    public List<String> shortcutFieldOrder() {
        return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY);
    }


    @Validated
    public static class NameValueConfig {
        @NotEmpty
        protected String name;
        @NotEmpty
        protected String value;

        public String getName() {
            return name;
        }

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

        public String getValue() {
            return value;
        }

        public NameValueConfig setValue(String value) {
            this.value = value;
            return this;
        }

        @Override
        public String toString() {
            return new ToStringCreator(this)
                    .append("name", name)
                    .append("value", value)
                    .toString();
        }
    }
}

Restrict generics to abstract namevaluegatewayfilterfactory. namevalueconfig, whose implementation classes are as follows:

  • AddRequestHeaderGatewayFilterFactory
  • AddRequestParameterGatewayFilterFactory
  • AddResponseHeaderGatewayFilterFactory
  • SetRequestHeaderGatewayFilterFactory
  • SetResponseHeaderGatewayFilterFactory

AbstractChangeRequestUriGatewayFilterFactory

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/filter/factory/AbstractChangeRequestUriGatewayFilterFactory.java

/**
 * This filter changes the request uri by
 * {@link #determineRequestUri(ServerWebExchange, T)} logic.
 *
 * @author Toshiaki Maki
 */
public abstract class AbstractChangeRequestUriGatewayFilterFactory<T>
        extends AbstractGatewayFilterFactory<T> {
    private final int order;

    public AbstractChangeRequestUriGatewayFilterFactory(Class<T> clazz, int order) {
        super(clazz);
        this.order = order;
    }

    public AbstractChangeRequestUriGatewayFilterFactory(Class<T> clazz) {
        this(clazz, RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 1);
    }

    protected abstract Optional<URI> determineRequestUri(ServerWebExchange exchange,
            T config);

    public GatewayFilter apply(T config) {
        return new OrderedGatewayFilter((exchange, chain) -> {
            Optional<URI> uri = this.determineRequestUri(exchange, config);
            uri.ifPresent(u -> {
                Map<String, Object> attributes = exchange.getAttributes();
                attributes.put(GATEWAY_REQUEST_URL_ATTR, u);
            });
            return chain.filter(exchange);
        }, this.order);
    }
}

Set a new uri through GATEWAY_REQUEST_URL_ATTR, whose direct implementation class is

  • RequestHeaderToRequestUriGatewayFilterFactory(Uri is set by the value of header)

GatewayFilterFactory.apply

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java

    private List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
        List<GatewayFilter> filters = filterDefinitions.stream()
                .map(definition -> {
                    GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
                    if (factory == null) {
                        throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName());
                    }
                    Map<String, String> args = definition.getArgs();
                    if (logger.isDebugEnabled()) {
                        logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName());
                    }

                    Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);

                    Object configuration = factory.newConfig();

                    ConfigurationUtils.bind(configuration, properties,
                            factory.shortcutFieldPrefix(), definition.getName(), validator);

                    GatewayFilter gatewayFilter = factory.apply(configuration);
                    if (this.publisher != null) {
                        this.publisher.publishEvent(new FilterArgsEvent(this, id, properties));
                    }
                    return gatewayFilter;
                })
                .collect(Collectors.toList());

        ArrayList<GatewayFilter> ordered = new ArrayList<>(filters.size());
        for (int i = 0; i < filters.size(); i++) {
            GatewayFilter gatewayFilter = filters.get(i);
            if (gatewayFilter instanceof Ordered) {
                ordered.add(gatewayFilter);
            }
            else {
                ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1));
            }
        }

        return ordered;
    }

Here, the GatewayFilter of each route is instantiated by factory.

Summary

The GatewayFilter of spring cloud gateway is mainly produced through GatewayFilterFactory. However, GatewayFilterFactory mainly has three abstract classes:

  • AbstractGatewayFilterFactory

    • HystrixGatewayFilterFactory
    • ModifyRequestBodyGatewayFilterFactory
    • ModifyResponseBodyGatewayFilterFactory
    • PrefixPathGatewayFilterFactory
    • PreserveHostHeaderGatewayFilterFactory
    • RedirectToGatewayFilterFactory
    • RemoveRequestHeaderGatewayFilterFactory
    • RemoveResponseHeaderGatewayFilterFactory
    • RequestRateLimiterGatewayFilterFactory
    • RetryGatewayFilterFactory
    • RewritePathGatewayFilterFactory
    • SaveSessionGatewayFilterFactory
    • SecureHeadersGatewayFilterFactory
    • SetPathGatewayFilterFactory
    • SetStatusGatewayFilterFactory
    • StripPrefixGatewayFilterFactory
  • AbstractNameValueGateWayfilFactory (inherited from AbstractGatewayFilterFactory)

    • AddRequestHeaderGatewayFilterFactory
    • AddRequestParameterGatewayFilterFactory
    • AddResponseHeaderGatewayFilterFactory
    • SetRequestHeaderGatewayFilterFactory
    • SetResponseHeaderGatewayFilterFactory
  • AbstractChangeRequestUriGatewayFilterFactory

    • RequestHeaderToRequestUriGatewayFilterFactory

doc