之前的几篇文章中,我们已经提到了如何使用SpringCloud Gateway,那几篇文章的内容已经足够做普通项目使用了,但是如果你想深入了解这个东西,或者说是看完这篇文章你用起来跟普通人就完全不是一个等级的了

路由谓语工厂的骚操作
AfterRoutePredicateFactory

前置时间路由工厂,匹配当前时间发生后的请求

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: http://example.org
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]    
1
2
3
4
5
6
7
8
BeforeRoutePredicateFactory

后置时间路由工厂,匹配当前时间发生之前的请求

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: http://example.org
        predicates:
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
1
2
3
4
5
6
7
8
BetweenRoutePredicateFactory

匹配时间段的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: http://example.org
        predicates:
        - Betweeen=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
1
2
3
4
5
6
7
8

这个谓语工厂的判断逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
		ZonedDateTime datetime1 = getZonedDateTime(config.datetime1);
		ZonedDateTime datetime2 = getZonedDateTime(config.datetime2);
		Assert.isTrue(datetime1.isBefore(datetime2),
				config.datetime1 +
				" must be before " + config.datetime2);
		return exchange -> {
			final ZonedDateTime now = ZonedDateTime.now();
			return now.isAfter(datetime1) && now.isBefore(datetime2);
		};
	}
1
2
3
4
5
6
7
8
9
10
11
CookieRoutePredicateFactory

匹配请求cookie的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: http://example.org
        predicates:
        - Cookie=chocolate, ch.p
1
2
3
4
5
6
7
8

判断逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
		return exchange -> {
			List<HttpCookie> cookies = exchange.getRequest().getCookies().get(config.name);
			if (cookies == null) {
				return false;
			}
			for (HttpCookie cookie : cookies) {
				if (cookie.getValue().matches(config.regexp)) {
					return true;
				}
			}
			return false;
		};
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
HeaderRoutePredicateFactory

匹配请求header的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: http://example.org
        predicates:
        - Header=X-Request-Id, \d+
1
2
3
4
5
6
7
8

实现逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
		return exchange -> {
			List<String> values = exchange.getRequest().getHeaders().get(config.header);
			if (values != null) {
				for (String value : values) {
					if (value.matches(config.regexp)) {
						return true;
					}
				}
			}
			return false;
		};
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
HostRoutePredicateFactory

匹配请求host的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://example.org
        predicates:
        - Host=**.somehost.org
1
2
3
4
5
6
7
8

实现逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
		return exchange -> {
			String host = exchange.getRequest().getHeaders().getFirst("Host");
			return this.pathMatcher.match(config.getPattern(), host);
		};
	}
1
2
3
4
5
6
MethodRoutePredicateFactory

匹配请求method的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: http://example.org
        predicates:
        - Method=GET
1
2
3
4
5
6
7
8

实现逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
		return exchange -> {
			HttpMethod requestMethod = exchange.getRequest().getMethod();
			return requestMethod == config.getMethod();
		};
	}
1
2
3
4
5
6
PathRoutePredicateFactory

匹配请求路径的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://example.org
        predicates:
        - Path=/foo/{segment}
1
2
3
4
5
6
7
8

实现逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
		synchronized (this.pathPatternParser) {
			config.pathPattern = this.pathPatternParser.parse(config.pattern);
		}
		return exchange -> {
			PathContainer path = parsePath(exchange.getRequest().getURI().getPath());

			boolean match = config.pathPattern.matches(path);
			traceMatch("Pattern", config.pathPattern.getPatternString(), path, match);
			if (match) {
				PathMatchInfo uriTemplateVariables = config.pathPattern.matchAndExtract(path);
				exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
				return true;
			} else {
				return false;
			}
		};
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QueryRoutePredicateFactory

匹配请求参数的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://example.org
        predicates:
        - Query=foo, ba.
1
2
3
4
5
6
7
8

实现逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
		return exchange -> {
			if (!StringUtils.hasText(config.regexp)) {
				// check existence of header
				return exchange.getRequest().getQueryParams().containsKey(config.param);
			}


			List<String> values = exchange.getRequest().getQueryParams().get(config.param);
			for (String value : values) {
				if (value.matches(config.regexp)) {
					return true;
				}
			}
			return false;
		};
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RemoteAddrRoutePredicateFactory

匹配远程地址的路由谓语工厂

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: http://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24
1
2
3
4
5
6
7
8

实现逻辑如下:

	public Predicate<ServerWebExchange> apply(Config config) {
        List<IpSubnetFilterRule> sources = convert(config.sources);

		return exchange -> {
			InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange);
			if (remoteAddress != null) {
				String hostAddress = remoteAddress.getAddress().getHostAddress();
				String host = exchange.getRequest().getURI().getHost();

				if (log.isDebugEnabled() && !hostAddress.equals(host)) {
					log.debug("Remote addresses didn't match " + hostAddress + " != " + host);
				}

				for (IpSubnetFilterRule source : sources) {
					if (source.matches(remoteAddress)) {
						return true;
					}
				}
			}

			return false;
		};
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
拦截器的骚操作
AddRequestHeaderGatewayFilterFactory

为请求中添加header的拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
1
2
3
4
5
6
7
8

实现逻辑如下:

	public GatewayFilter apply(NameValueConfig config) {
		return (exchange, chain) -> {
			ServerHttpRequest request = exchange.getRequest().mutate()
					.header(config.getName(), config.getValue())
					.build();

			return chain.filter(exchange.mutate().request(request).build());
		};
    }
1
2
3
4
5
6
7
8
9
AddRequestParameterGatewayFilterFactory

为请求中添加请求参数的拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: http://example.org
        filters:
        - AddRequestParameter=foo, bar
1
2
3
4
5
6
7
8

实现逻辑如下:

	public GatewayFilter apply(NameValueConfig config) {
		return (exchange, 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('&');
				}
			}

			//TODO urlencode?
			query.append(config.getName());
			query.append('=');
			query.append(config.getValue());

			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() + "\"");
			}
		};
	}
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
AddResponseHeaderGatewayFilterFactory

为响应中添加header的拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://example.org
        filters:
        - AddResponseHeader=X-Response-Foo, Bar
1
2
3
4
5
6
7
8

实现逻辑如下:

public GatewayFilter apply(NameValueConfig config) {
		return (exchange, chain) -> {
			exchange.getResponse().getHeaders().add(config.getName(), config.getValue());

			return chain.filter(exchange);
		};
	}
1
2
3
4
5
6
7
HystrixGatewayFilterFactory

服务熔断的拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: hytstrix_route
        uri: http://example.org
        filters:
        - Hystrix=myCommandName
          args:
            name: default
            fallbackUri: forward:/defaultfallback
1
2
3
4
5
6
7
8
9
10
11

实现逻辑如下,建议搭配之前的Hystrix源码解析阅读

	public GatewayFilter apply(Config config) {
		//TODO: if no name is supplied, generate one from command id (useful for default filter)
		if (config.setter == null) {
			Assert.notNull(config.name, "A name must be supplied for the Hystrix Command Key");
			HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(getClass().getSimpleName());
			HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(config.name);

			config.setter = Setter.withGroupKey(groupKey)
					.andCommandKey(commandKey);
		}

		return (exchange, chain) -> {
			RouteHystrixCommand command = new RouteHystrixCommand(config.setter, config.fallbackUri, exchange, chain);

			return Mono.create(s -> {
				Subscription sub = command.toObservable().subscribe(s::success, s::error, s::success);
				s.onCancel(sub::unsubscribe);
			}).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> {
				if (throwable instanceof HystrixRuntimeException) {
					HystrixRuntimeException e = (HystrixRuntimeException) throwable;
					if (e.getFailureType() == TIMEOUT) { //TODO: optionally set status
						setResponseStatus(exchange, HttpStatus.GATEWAY_TIMEOUT);
						return exchange.getResponse().setComplete();
					}
				}
				return Mono.error(throwable);
			}).then();
		};
	}
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
PrefixPathGatewayFilterFactory

为请求路径添加前缀的拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: http://example.org
        filters:
        - PrefixPath=/mypath
1
2
3
4
5
6
7
8

实现逻辑如下:

	public GatewayFilter apply(Config config) {
		return (exchange, chain) -> {

			boolean alreadyPrefixed = exchange.getAttributeOrDefault(GATEWAY_ALREADY_PREFIXED_ATTR, false);
			if (alreadyPrefixed) {
				return chain.filter(exchange);
			}
			exchange.getAttributes().put(GATEWAY_ALREADY_PREFIXED_ATTR, true);

			ServerHttpRequest req = exchange.getRequest();
			addOriginalRequestUrl(exchange, req.getURI());
			String newPath = config.prefix + req.getURI().getRawPath();

			ServerHttpRequest request = req.mutate()
					.path(newPath)
					.build();

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

			if (log.isTraceEnabled()) {
				log.trace("Prefixed URI with: "+config.prefix+" -> "+request.getURI());
			}

			return chain.filter(exchange.mutate().request(request).build());
		};
	}
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
RequestRateLimiterGatewayFilterFactory

请求限流的拦截器

这个拦截器相比于上方的各个拦截器还是稍微复杂一点的。 首先需要引入一个额外的包

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
1
2
3
4

然后呢你要基于SpringBoot做好redis的自动装配,紧接着就是设置限流key,限流key的设置需要实现keyResolver接口,然后通过它的这个方法 resolve(ServerWebExchange exchange)返回你需要设置的限流key

配置的地方是这样的

spring:
  cloud:
    gateway:
      routes:
      - id: requestratelimiter_route
        uri: http://example.org
        filters:
        - RequestRateLimiter=10, 20, #{@userKeyResolver}
1
2
3
4
5
6
7
8

其中前两个参数分别是令牌桶算法的两个参数,填充速率和限流阈值,更多关于限流算法的操作可以参考我的这篇文章:大型网站限流算法的实现和改造

拦截器实现逻辑如下:

	public GatewayFilter apply(Config config) {
		KeyResolver resolver = (config.keyResolver == null) ? defaultKeyResolver : config.keyResolver;
		RateLimiter<Object> limiter = (config.rateLimiter == null) ? defaultRateLimiter : config.rateLimiter;

		return (exchange, chain) -> {
			Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

			return resolver.resolve(exchange).flatMap(key ->
					// TODO: if key is empty?
					limiter.isAllowed(route.getId(), key).flatMap(response -> {

						for (Map.Entry<String, String> header : response.getHeaders().entrySet()) {
							exchange.getResponse().getHeaders().add(header.getKey(), header.getValue());
						}

						if (response.isAllowed()) {
							return chain.filter(exchange);
						}

						exchange.getResponse().setStatusCode(config.getStatusCode());
						return exchange.getResponse().setComplete();
					}));
		};
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
RedirectToGatewayFilterFactory

请求重定向的拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: http://example.org
        filters:
        - RedirectTo=302, http://acme.org
1
2
3
4
5
6
7
8

实现逻辑如下:

	public GatewayFilter apply(String statusString, String urlString) {
		final HttpStatus httpStatus = parse(statusString);
		Assert.isTrue(httpStatus.is3xxRedirection(), "status must be a 3xx code, but was " + statusString);
		final URL url;
		try {
			url = URI.create(urlString).toURL();
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException("Invalid url " + urlString, e);
		}
		return apply(httpStatus, url);
	}
1
2
3
4
5
6
7
8
9
10
11
RemoveRequestHeaderGatewayFilterFactory/RemoveResponseHeaderGatewayFilterFactory

删除请求、响应中的header拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: removerequestheader_route
        uri: http://example.org
        filters:
        - RemoveRequestHeader=X-Request-Foo
1
2
3
4
5
6
7
8

实现逻辑这里就贴其中一个了:

	public GatewayFilter apply(NameConfig config) {
		return (exchange, chain) -> {
			ServerHttpRequest request = exchange.getRequest().mutate()
					.headers(httpHeaders -> httpHeaders.remove(config.getName()))
					.build();

			return chain.filter(exchange.mutate().request(request).build());
		};
	}
1
2
3
4
5
6
7
8
9
RewritePathGatewayFilterFactory

重写请求路径的拦截器

spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: http://example.org
        predicates:
        - Path=/foo/**
        filters:
        - RewritePath=/foo/
1
2
3
4
5
6
7
8
9
10

实现逻辑如下:

	public GatewayFilter apply(Config config) {
		String replacement = config.replacement.replace("$\\", "$");
		return (exchange, chain) -> {
			ServerHttpRequest req = exchange.getRequest();
			addOriginalRequestUrl(exchange, req.getURI());
			String path = req.getURI().getRawPath();
			String newPath = path.replaceAll(config.regexp, replacement);

			ServerHttpRequest request = req.mutate()
					.path(newPath)
					.build();

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

			return chain.filter(exchange.mutate().request(request).build());
		};
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1

目录