目录
28、RewriteLocationResponseHeader
SpringGateway的过滤器分为内置过滤器Filter与自定义过滤器GlobalFilter。
内置Filter都实现GatewayFilter接口。使用时filter
s属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX内置Filter都实现GatewayFilter接口。使用时filters属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX。其中内置过滤器实现类如下:

同理在SpringCloudGateway中可以找到加载这些实现类的工厂方法:

内置Filter是使用工厂模式加匿名内部类实现的。

所有的Filter最终一定要调用chain.filter()方法,代表向下执行,在这句话之前调用的逻辑,是微服务的前置过滤,在这之后的都是远程微服务调用的后置过滤。
所有内置过滤器中,常用的四种已经标红,重点演示前四种。SpringCloudGateWay项目参考:SpringCloudGateway--自动路由映射与手动路由映射_雨欲语的博客-CSDN博客
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
{ - public static final String PARTS_KEY = "parts";
-
- public StripPrefixGatewayFilterFactory() {
- super(StripPrefixGatewayFilterFactory.Config.class);
- }
-
- public List
shortcutFieldOrder() { - return Arrays.asList("parts");
- }
-
- public GatewayFilter apply(StripPrefixGatewayFilterFactory.Config config) {
- return new GatewayFilter() {
- public Mono
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;
- }
- }
- }
添加请求头参数,参数和值之间使用逗号分隔
- 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
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();
- }
- };
- }
- }
在响应的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
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();
- }
- };
- }
- }
对指定响应头去重复,也即某个响应头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
{ - private static final String STRATEGY_KEY = "strategy";
-
- public DedupeResponseHeaderGatewayFilterFactory() {
- super(DedupeResponseHeaderGatewayFilterFactory.Config.class);
- }
-
- public List
shortcutFieldOrder() { - return Arrays.asList("name", "strategy");
- }
-
- public GatewayFilter apply(DedupeResponseHeaderGatewayFilterFactory.Config config) {
- return new GatewayFilter() {
- public Mono
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
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() {
- }
- }
- }
添加请求参数
- 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
实现熔断时使用,支持CircuitBreaker和Hystrix两种,这个功能会单开一篇文章阐述。
添加降级时的异常信息,一般与CircuitBreaker配合使用。
限流,会单开文章阐述。地址:SpringCloudGateway--基于redis实现令牌桶算法_雨欲语的博客-CSDN博客
重定向,连个参数,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
删除请求头
- 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
删除响应头
- 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
删除请求参数
- 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
重写请求路径,比如我们之前都是访问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
修改响应的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

如果项目中使用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
在响应的头中添加很多安全相关的信息:
- 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

功能和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
设置请求头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
设置响应的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
设置返回的code:
- routes:
- - id: service-one
- uri: lb://service-one
- predicates:
- - Path=/service/**
- filters:
- - SetStatus=500
- metadata:
- connect-timeout: 15000 #ms
- response-timeout: 15000 #ms
给请求路径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
设置重试次数:
①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
控制请求大小,单位有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
修改请求体body内容,官方推荐使用代码完成。
修改响应body的内容,也是推荐使用代码完成。
用于键值对的赋值,以下意思是如果请求的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一样。
在转发请求到服务提供者时,保留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
用于改写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
修改请求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