. 1. 简介
本文环境:springboot 2.6.4 springcloud 2021.0.1.0
关于Springcloud gateway的报文获取,网上的写法较多都是参考ModifyRequestBodyGatewayFilterFactory,确实可以解决问题,但相对来说对filter的顺序也做了比较死的约束,对于那些每种路由需要不同的过滤顺序的需求不能做到很好的支持,且据说性能下降剧烈(没有具体实验);
本文同样参考springcloud gateway 源码,只不过参考的是ReadBodyRoutePredicateFactory,相对上述方案来说有性能提升(没有实验佐证);
2. 参考ModifyRequestBodyGatewayFilterFactory的写法
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class RequestBodyOperationFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (request.getMethod() != HttpMethod.POST) {
return chain.filter(exchange);
}
return operationExchange(exchange, chain);
}
private Mono<Void> operationExchange(ServerWebExchange exchange, GatewayFilterChain chain) {
// mediaType
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
// read & modify body
ServerRequest serverRequest = new DefaultServerRequest(exchange);
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
.flatMap(body -> {
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
// 对原先的body进行修改操作
String newBody = "{\"testName\":\"testValue\"}";
return Mono.just(newBody);
}
return Mono.empty();
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}));
}
@Override
public int getOrder() {
return -1;
}
}
3. 参考ReadBodyRoutePredicateFactory
package com.nahefa.gateway.predicate;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
/**
* ReadBodyRoutePredicateFactory 仅支持RouteLocator 的方式,对YML配置不友好
* 为解决此问题,自定义一个路由谓词工厂,方便YML的配置
* <p>
* 例:
* spring.cloud.gateway.routes[0].id=abc
* spring.cloud.gateway.routes[0].uri=lb://wjj-abc
* spring.cloud.gateway.routes[0].predicates[0]=Path=/abc/**
* # 使用ReadBodyPredicateFactory断言,将body读入缓存
* spring.cloud.gateway.routes[0].predicates[1].name=GatewayReadBodyPredicateFactory
* <p>
* 参考: https://github.com/spring-cloud/spring-cloud-gateway/issues/1307
*
* @author mtung 2022/11/30 15:49
*/
@Component
public class GatewayReadBodyPredicateFactory extends ReadBodyRoutePredicateFactory {
/**
* 必须等于ReadBodyRoutePredicateFactory中的REQUEST_BODY_OBJECT_KEY
*/
public static final String REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
@Override
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
config.setPredicate(t -> true);
// 为减少配置麻烦,此处强制为字符串,使用方自行转响应类型
config.setInClass(String.class);
return super.applyAsync(config);
}
/**
* 配置文件中对应的名称
* spring.cloud.gateway.routes[0].predicates[0].name=xxx
*
* @return
*/
@Override
public String name() {
return "GatewayReadBodyPredicateFactory";
}
}
注意该方式不适用与表单方式提交
4.参考地址
https://github.com/spring-cloud/spring-cloud-gateway/issues/1307