简介

spring cloud gatway内置了很多过滤器用于在请求转发前后做一些特定处理,除了针对所有路由的全局过滤器之外,还提供了针对单个路由的诸如添加请求头、添加请求参数、重试、重定向、修改请求正文、修改应答主体等等数十个过滤器,官方提供的过滤器使用可参照官方文档:Spring cloud GatewayFilter Factories

在官方提供的默认实现无法满足需求时,我们也可仿照它的实现方式,定制我们自己的过滤器,添加到它的过滤器工厂中,成为网关请求转发处理中的一环。

官方实现方式

我们挑两个官方内置的过滤器,查看它的实现以及使用方式。

StripPrefixGatewayFilterFactory

处理转发前缀层级的StripPrefixGatewayFilterFactory实现:

package org.springframework.cloud.gateway.filter.factory;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import reactor.core.publisher.Mono;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;

import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;

/**
 * This filter removes the first part of the path, known as the prefix, from the request
 * before sending it downstream.
 *
 * @author Ryan Baxter
 */
public class StripPrefixGatewayFilterFactory
		extends AbstractGatewayFilterFactory<StripPrefixGatewayFilterFactory.Config> {

	/**
	 * Parts key.
	 */
	public static final String PARTS_KEY = "parts";

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

	@Override
	public List<String> shortcutFieldOrder() {
		return Arrays.asList(PARTS_KEY);
	}

	@Override
	public GatewayFilter apply(Config config) {
		return new GatewayFilter() {
			@Override
			public Mono<Void> filter(ServerWebExchange exchange,
					GatewayFilterChain chain) {
				ServerHttpRequest request = exchange.getRequest();
				addOriginalRequestUrl(exchange, request.getURI());
				String path = request.getURI().getRawPath();
				String newPath = "/"
						+ Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
								.skip(config.parts).collect(Collectors.joining("/"));
				newPath += (newPath.length() > 1 && path.endsWith("/") ? "/" : "");
				ServerHttpRequest newRequest = request.mutate().path(newPath).build();

				exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR,
						newRequest.getURI());

				return chain.filter(exchange.mutate().request(newRequest).build());
			}

			@Override
			public String toString() {
				return filterToStringCreator(StripPrefixGatewayFilterFactory.this)
						.append("parts", config.getParts()).toString();
			}
		};
	}

	public static class Config {

		private int parts;

		public int getParts() {
			return parts;
		}

		public void setParts(int parts) {
			this.parts = parts;
		}

	}

}

使用方式,appcation.yml

spring:
  cloud:
    gateway:
      routes:
      - id: nameRoot
        uri: https://nameservice
        predicates:
        - Path=/name/**
        filters:
        - StripPrefix=2

类层级关系图:

StripPrefixGatewayFilterFactory-类层级关系图

AddRequestParameterGatewayFilterFactory

添加请求参数的AddRequestParameterGatewayFilterFactory实现:

package org.springframework.cloud.gateway.filter.factory;

import java.net.URI;

import reactor.core.publisher.Mono;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;

import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;

/**
 * @author Spencer Gibb
 */
public class AddRequestParameterGatewayFilterFactory
		extends AbstractNameValueGatewayFilterFactory {

	@Override
	public GatewayFilter apply(NameValueConfig config) {
		return new GatewayFilter() {
			@Override
			public Mono<Void> filter(ServerWebExchange exchange,
					GatewayFilterChain chain) {
				URI uri = exchange.getRequest().getURI();
				StringBuilder query = new StringBuilder();
				String originalQuery = uri.getRawQuery();

				if (StringUtils.hasText(originalQuery)) {
					query.append(originalQuery);
					if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
						query.append('&');
					}
				}

				String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
				// TODO urlencode?
				query.append(config.getName());
				query.append('=');
				query.append(value);

				try {
					URI newUri = UriComponentsBuilder.fromUri(uri)
							.replaceQuery(query.toString()).build(true).toUri();

					ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri)
							.build();

					return chain.filter(exchange.mutate().request(request).build());
				}
				catch (RuntimeException ex) {
					throw new IllegalStateException(
							"Invalid URI query: \"" + query.toString() + "\"");
				}
			}

			@Override
			public String toString() {
				return filterToStringCreator(AddRequestParameterGatewayFilterFactory.this)
						.append(config.getName(), config.getValue()).toString();
			}
		};
	}

}

使用方式,application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        - AddRequestParameter=foo, bar

类层级关系图:

AddRequestParameterGatewayFilterFactory-类层级关系图

实现剖析

查看上面两个官方内置的过滤器实现可以看到其都是直接或间接继承自AbstractGatewayFilterFactory这个抽象类,而区别的地方在于AddRequestParameterGatewayFilterFactory继承的是AbstractGatewayFilterFactory的子抽象类:AbstractNameValueGatewayFilterFactory,通过查看这两个抽象类的定义和直接继承了这两个抽象类的过滤器的配置方式,不难发现继承AbstractNameValueGatewayFilterFactory和直接继承自AbstractGatewayFilterFactory的区别在于允许传入的参数数量定义不同。

回看上文两个过滤器的配置文件部分,直接继承自AbstractGatewayFilterFactoryStripPrefixGatewayFilterFactory使用的配置项是StripPrefix=2这种单一参数的配置,而继承自AbstractNameValueGatewayFilterFactoryAddRequestParameterGatewayFilterFactory使用的配置项是AddRequestParameter=foo, bar这种以,隔开的双参数的配置。

那么,在我们的场景里面就可以根据自己的需求,仅需单一参数配置就继承AbstractGatewayFilterFactory,需要KV形式的双参数则继承AbstractNameValueGatewayFilterFactory,通过继承这两个抽象类来简化自定义过滤器的代码实现。

自定义过滤器实现

spring cloud gateway内置的添加Request Header的过滤器会对满足条件的路由都添加固定的Header,而有时该Header在客户端请求时会携带,这时我们不希望增加或者覆盖该Header,这种场景下内置添加Request Header的过滤器就满足不了要求,那么我们自定义一个只有当指定Request Header不存在时才使用指定值补全的过滤器:

/**
 * 自定义过滤器(当指定请求头不存在时则使用指定值补全)
 *
 * @author WuJiaxin
 * @date 2020/1/14
 */
@Component
@Slf4j
public class AddIfHeaderNotExistGatewayFilterFactory extends AbstractGatewayFilterFactory<AddIfHeaderNotExistGatewayFilterFactory.HeaderNameValueConfig> {

    private final String NAME_KEY = "headerName";
    private final String VALUE_KEY = "herderValue";

    public AddIfHeaderNotExistGatewayFilterFactory() {
        super(HeaderNameValueConfig.class);
    }

    @Override
    public GatewayFilter apply(HeaderNameValueConfig config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = exchange.getRequest().getHeaders();
            boolean headerExist = headers.containsKey(config.headerName);
            boolean lowerCaseHeaderExist = headers.containsKey(config.headerName.toLowerCase());
            if (!headerExist && !lowerCaseHeaderExist) {
                log.debug("补齐请求头,头:{},值:{}", config.headerName, config.herderValue);
                request = request.mutate()
                        .headers(httpHeaders -> httpHeaders.add(config.headerName, config.herderValue))
                        .build();
            }
            return chain.filter(exchange.mutate().request(request).build());
        };
    }

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

    @Validated
    @Data
    public static class HeaderNameValueConfig {
        @NotEmpty
        private String headerName;
        @NotEmpty
        private String herderValue;

    }

}

然后在配置文件中声明使用该过滤器:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        - AddIfHeaderNotExist=userid, 0

上述配置达到的效果是如果请求的Header中没有携带userid,则会添加一个userid=0的Header,否则直接将请求交给下个过滤器处理。

另外过滤器在注册时,会有一个过滤器的名称,这个名称根据GatewayFilterFactory中的默认实现就是当前类的类名剔除掉GatewayFilterFactory这个后缀后的名称,所以在上述自定义过滤器的场景中,自定义的过滤器注册到spring cloud gateway过滤器中的名称就是AddIfHeaderNotExist,之后配置文件中声明AddIfHeaderNotExist=userid, 0时spring cloud gateway则会从注册的过滤器中取出AddIfHeaderNotExist这个过滤器进行处理,当然有需要也可以重写过滤器类名的实现,完全自定义过滤器的类名和注册时的名称,重写GatewayFilterFactoryname()方法即可。