目录
28、RewriteLocationResponseHeader
一、概览
SpringGateway的过滤器分为内置过滤器Filter与自定义过滤器GlobalFilter。
内置Filter都实现GatewayFilter接口。使用时filter
s属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX内置Filter都实现GatewayFilter接口。使用时filters属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX。其中内置过滤器实现类如下:
同理在SpringCloudGateway中可以找到加载这些实现类的工厂方法:
二、内置过滤器
内置Filter是使用工厂模式加匿名内部类实现的。
所有的Filter最终一定要调用chain.filter()方法,代表向下执行,在这句话之前调用的逻辑,是微服务的前置过滤,在这之后的都是远程微服务调用的后置过滤。
所有内置过滤器中,常用的四种已经标红,重点演示前四种。SpringCloudGateWay项目参考:SpringCloudGateway--自动路由映射与手动路由映射_雨欲语的博客-CSDN博客
1、StripPrefix
StripPrefix是最常用的内置过滤器,含义是:过滤转发地址前缀, 也就是过滤掉url中前几节,然后转发给下游,比如以下配置:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
当我们访问http://localhost:9999/service/nacos/test时,谓词校验service,uri的lb是service-one,此时转发地址是 http://service/service-one/nacos/test,然后后面有过滤器StripPrefix,表示删除第一节service,因此最终转发地址是http://service-one/nacos/test。
我们看一下源码中的处理方式:
import java.util.Arrays;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
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 reactor.core.publisher.Mono;
public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory<StripPrefixGatewayFilterFactory.Config> {
public static final String PARTS_KEY = "parts";
public StripPrefixGatewayFilterFactory() {
super(StripPrefixGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("parts");
}
public GatewayFilter apply(StripPrefixGatewayFilterFactory.Config config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());
// 获取url的path
String path = request.getURI().getRawPath();
// 将url以/进行分割成字符串数组
String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");
StringBuilder newPath = new StringBuilder("/");
for(int i = 0; i < originalParts.length; ++i) {
// 如果当前索引下标大于配置,则添加到newPath中,否则相当于直接跳过
if (i >= config.getParts()) {
if (newPath.length() > 1) {
newPath.append('/');
}
newPath.append(originalParts[i]);
}
}
if (newPath.length() > 1 && path.endsWith("/")) {
newPath.append('/');
}
// 重新buildurl地址
ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
return chain.filter(exchange.mutate().request(newRequest).build());
}
public String toString() {
return GatewayToStringStyler.filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()).toString();
}
};
}
public static class Config {
private int parts;
public Config() {
}
public int getParts() {
return this.parts;
}
public void setParts(int parts) {
this.parts = parts;
}
}
}
2、AddRequestHeader
添加请求头参数,参数和值之间使用逗号分隔
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- AddRequestHeader=MyHeader,test
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
将之前的服务稍微修改一下,返回MyHeader:
@RestController
@RequestMapping("/nacos")
public class NacosTestController {
@GetMapping("/test")
public String test(@RequestHeader("MyHeader") String myHeader){
return myHeader;
}
}
启动后访问:http://localhost:9999/service/nacos/test
可以看到返回内容:
源码就比较简单,就从配置文件中拿到header,然后添加进去即可:
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public AddRequestHeaderGatewayFilterFactory() {
}
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
ServerHttpRequest request = exchange.getRequest().mutate().headers((httpHeaders) -> {
httpHeaders.add(config.getName(), value);
}).build();
return chain.filter(exchange.mutate().request(request).build());
}
public String toString() {
return GatewayToStringStyler.filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();
}
};
}
}
3、AddResponseHeader
在响应的header中添加参数
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- AddResponseHeader=addHeader, test
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
打开浏览器,F12显示控制台,访问之后可以看到响应的请求头中出现我们自己添加的数据
源码也很简单,也是在响应的header中添加参数即可:
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
public AddResponseHeaderGatewayFilterFactory() {
}
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
exchange.getResponse().getHeaders().add(config.getName(), value);
return chain.filter(exchange);
}
public String toString() {
return GatewayToStringStyler.filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();
}
};
}
}
4、DedupeResponseHeader
对指定响应头去重复,也即某个响应头key的value有多个值,去除一些重复的,有三种策略:
RETAIN_FIRST 默认值,保留第一个
RETAIN_LAST 保留最后一个
RETAIN_UNIQUE 保留唯一的,出现重复的属性值,会保留一个。例如有两个My:bbb的属性,最后会只留一个。
我们利用上面添加参数,添加两个key一样的,value不一样的header,然后利用DedupeResponseHeader进行去重:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- AddResponseHeader=addHeader, test
- AddResponseHeader=addHeader,test1
- DedupeResponseHeader=addHeader, RETAIN_LAST
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
测试返回结果:
这个的源码稍微复杂一点点,需要根据不同的策略进行选择不同的去重规则:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class DedupeResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<DedupeResponseHeaderGatewayFilterFactory.Config> {
private static final String STRATEGY_KEY = "strategy";
public DedupeResponseHeaderGatewayFilterFactory() {
super(DedupeResponseHeaderGatewayFilterFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("name", "strategy");
}
public GatewayFilter apply(DedupeResponseHeaderGatewayFilterFactory.Config config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// return中调用dedupe方法
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
DedupeResponseHeaderGatewayFilterFactory.this.dedupe(exchange.getResponse().getHeaders(), config);
}));
}
public String toString() {
return GatewayToStringStyler.filterToStringCreator(DedupeResponseHeaderGatewayFilterFactory.this).append(config.getName(), config.getStrategy()).toString();
}
};
}
void dedupe(HttpHeaders headers, DedupeResponseHeaderGatewayFilterFactory.Config config) {
String names = config.getName();
DedupeResponseHeaderGatewayFilterFactory.Strategy strategy = config.getStrategy();
if (headers != null && names != null && strategy != null) {
// 根据空格进行分组,可以看出如果添加多个key,只需要使用空格隔开即可
String[] var5 = names.split(" ");
int var6 = var5.length;
// 遍历需要去重的header
for(int var7 = 0; var7 < var6; ++var7) {
String name = var5[var7];
this.dedupe(headers, name.trim(), strategy);
}
}
}
// 根据不同策略进行不同的去重策略
private void dedupe(HttpHeaders headers, String name, DedupeResponseHeaderGatewayFilterFactory.Strategy strategy) {
List<String> values = headers.get(name);
if (values != null && values.size() > 1) {
switch(strategy) {
case RETAIN_FIRST:
headers.set(name, (String)values.get(0));
break;
case RETAIN_LAST:
headers.set(name, (String)values.get(values.size() - 1));
break;
case RETAIN_UNIQUE:
headers.put(name, new ArrayList(new LinkedHashSet(values)));
}
}
}
public static class Config extends NameConfig {
private DedupeResponseHeaderGatewayFilterFactory.Strategy strategy;
public Config() {
this.strategy = DedupeResponseHeaderGatewayFilterFactory.Strategy.RETAIN_FIRST;
}
public DedupeResponseHeaderGatewayFilterFactory.Strategy getStrategy() {
return this.strategy;
}
public DedupeResponseHeaderGatewayFilterFactory.Config setStrategy(DedupeResponseHeaderGatewayFilterFactory.Strategy strategy) {
this.strategy = strategy;
return this;
}
}
// 定义的三种策略
public static enum Strategy {
RETAIN_FIRST,
RETAIN_LAST,
RETAIN_UNIQUE;
private Strategy() {
}
}
}
5、AddRequestParameter
添加请求参数
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- AddRequestParameter=name,test
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
6、CircuitBreaker
实现熔断时使用,支持CircuitBreaker和Hystrix两种,这个功能会单开一篇文章阐述。
7、FallbackHeaders
添加降级时的异常信息,一般与CircuitBreaker配合使用。
8、RequestRateLimiter
限流,会单开文章阐述。地址:SpringCloudGateway--基于redis实现令牌桶算法_雨欲语的博客-CSDN博客
9、RedirectTo
重定向,连个参数,status和url,status是重定向300系列状态码
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- RedirectTo=302, https://www.baidu.com
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
10、RemoveRequestHeader
删除请求头
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- AddRequestHeader=MyHeader, test
- RemoveRequestHeader=MyHeader
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
11、RemoveResponseHeader
删除响应头
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- AddResponseHeader=addHeader, test
- RemoveResponseHeader=addHeader
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
12、RemoveRequestParameter
删除请求参数
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- RemoveRequestParameter=name
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
13、RewritePath
重写请求路径,比如我们之前都是访问http://localhost:9999/service/nacos/test,现在我们重写路径,并访问http://localhost:9999/service/test1/test,gateway会帮助我们将test1换成nacos
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- RewritePath=/test1/?(?<segment>.*), /nacos/$\{segment}
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
14、RewriteResponseHeader
修改响应的header,三个参数,一是key,二是value,可用正则,三是修改value的结果:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- AddResponseHeader=addHeader, test1
- RewriteResponseHeader=addHeader,test1,test
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
15、SaveSession
如果项目中使用Spring Security和Spring Session整合时,想确保安全信息都传到下游机器,需要使用此Filter,在转发到后端微服务请求之前,强制执行WebSession::Save操作。用在那种像Spring session延迟数据存储的,并希望在请求转发前确保session状态保存情况。
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- SaveSession
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
16、SecureHeaders
在响应的头中添加很多安全相关的信息:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- SecureHeaders
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
也可以让某些信息不显示,需要进行配置,比如我关闭strict-transport-security和x-download-options:
filter:
secure-headers:
disable:
- strict-transport-security
- x-download-options
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- SecureHeaders
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
17、SetPath
功能和StripPrefix有点类似,语法更贴近restful,是将predicates中的路径进行修改:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/{segment}
filters:
- SetPath=/{segment}
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
18、SetRequestHeader
设置请求头header,将指定的key的value值修改为指定的value,如果不存在就新建:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- SetRequestHeader=myHeader, test
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
19、SetResponseHeader
设置响应的header,将指定的key的value值修改为指定的value,如果不存在就新建:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- SetResponseHeader=addHeader, test
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
20、SetStatus
设置返回的code:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- SetStatus=500
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
21、PrefixPath
给请求路径path添加前缀:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- PrefixPath=/nacos
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
22、Retry
设置重试次数:
①retries:重试次数
②statuses:遇到什么样的返回状态才重试,取值参考:org.springframework.http.HttpStatus
③methods:那些类型的方法会才重试(GET、POST等),取值参考:org.springframework.http.HttpMethod
④series:遇到什么样的series值才重试,取值参考:org.springframework.http.HttpStatus.Series
⑤exceptions:遇到什么样的异常才重试
⑥backoff:重试策略,由多个参数构成,例如firstBackoff
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
23、RequestSize
控制请求大小,单位有KB、MB、B,默认是B,如果没有设置,默认上限是5MB:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- RequestSize=200
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
24、ModifyRequestBody
修改请求体body内容,官方推荐使用代码完成。
25、ModifyResponseBody
修改响应body的内容,也是推荐使用代码完成。
26、MapRequestHeader
用于键值对的赋值,以下意思是如果请求的header中有myHeader,就新增myHeader1,值和myHeader一样:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- MapRequestHeader=myHeader, myHeader1
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
如果原有的header中已经有myHeader1,同时也有myheader,那么会新增一个myHeader1,值和myHeader一样。
27、PreserveHostHeader
在转发请求到服务提供者时,保留host信息:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- PreserveHostHeader
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
28、RewriteLocationResponseHeader
用于改写reponse中的location信息,一共四个参数:stripVersionMode、locationHeaderName、hostValue、protocolsRegex,其中stripVersionMode策略一共三种:
NEVER_STRIP:不执行
AS_IN_REQUEST :原始请求没有vesion,就执行
ALWAYS_STRIP :固定执行
Location用于替换host:port部分,如果没有就是用Request中的host;protocolsRegex用于匹配协议,如果匹配不上,name过滤器啥都不做
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms
29、SetRequestHostHeader
修改请求header中的host值:
routes:
- id: service-one
uri: lb://service-one
predicates:
- Path=/service/**
filters:
- StripPrefix=1
- SetRequestHostHeader=name,test
metadata:
connect-timeout: 15000 #ms
response-timeout: 15000 #ms