需求
獲取request中的body內容
解決方案
SCG自帶ReadBodyPredicateFactory斷言,可以將body中的內容讀取到exchange對象中,使用exchange.getAttribute("cachedRequestBodyObject")獲取,但是當body爲空時,無法匹配該路由,導致返回404錯誤。
常規用法
- GCS route配置如下
- id: r_fapi #測試
uri: lb://LIZZ-GATEWAY
predicates:
- Path=/fapi/**
- name: ReadBodyPredicateFactory #讀取body斷言
args:
inClass: "#{T(String)}" #body數據類型
predicate: "#{@testRequestBody}" #自定義斷言處理器
- 自定義斷言處理器
/**
* @description: ReadBodyPredicateFactory 判斷器
* @author: lizz
* @date: 2020/6/6 17:03
*/
@Component
public class TestRequestBody implements Predicate {
/**
* 根據內容判斷是否匹配該路由
* @param o body的內容
* @return ture-匹配成功,false-匹配失敗
*/
@Override
public boolean test(Object o) {
//可以對body內容進行判斷處理
//這裏不做處理直接返回成功
return true;
}
}
- 在filter中的exchange讀取body
/**
* @description: 輸出請求記錄
* @author: lizz
* @date: 2020/2/28 1:09 下午
*/
@Component
public class LogFilter implements GlobalFilter, Ordered {
...
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestBody = exchange.getAttribute("cachedRequestBodyObject");
logger.info("fgwRequest req={},body={}", JSON.toJSONString(exchange.getRequest()), requestBody);
//添加header
//ServerHttpRequest newReq = request.mutate()
// .header(HEADER_REQUEST_ID, rid)
// .build();
//生成新的exchange
//ServerWebExchange newExchange = exchange.mutate().request(newReq).build();
//return chain.filter(newExchange);
return chain.filter(exchange);
}
...
}
自定義用法
目的:body爲空也可以匹配到該路由。
處理辦法:自建一個ReadBodyPredicateFactory在處理過程中添加.thenReturn(true)
- 根據ReadBodyPredicateFactory自建GwReadBodyPredicateFactory
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
/**
* @description: 自定義ReadBodyPredicateFactory,copy之ReadBodyPredicateFactory
* 實現request body爲空時也可以匹配如有成功。
* @author: lizz
* @date: 2020/6/8 14:22
*/
@Component
public class GwReadBodyPredicateFactory extends AbstractRoutePredicateFactory<GwReadBodyPredicateFactory.Config> {
protected static final Log log = LogFactory.getLog(GwReadBodyPredicateFactory.class);
private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies
.withDefaults().messageReaders();
public GwReadBodyPredicateFactory() {
super(GwReadBodyPredicateFactory.Config.class);
}
public GwReadBodyPredicateFactory(Class<GwReadBodyPredicateFactory.Config> configClass) {
super(configClass);
}
@Override
@SuppressWarnings("unchecked")
public AsyncPredicate<ServerWebExchange> applyAsync(GwReadBodyPredicateFactory.Config config) {
return new AsyncPredicate<ServerWebExchange>() {
@Override
public Publisher<Boolean> apply(ServerWebExchange exchange) {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
Mono<?> modifiedBody;
// We can only read the body from the request once, once that happens if
// we try to read the body again an exception will be thrown. The below
// if/else caches the body object as a request attribute in the
// ServerWebExchange so if this filter is run more than once (due to more
// than one route using it) we do not try to read the request body
// multiple times
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put(TEST_ATTRIBUTE, test);
return Mono.just(test);
} catch (ClassCastException e) {
if (log.isDebugEnabled()) {
log.debug("Predicate test failed because class in predicate "
+ "does not match the cached body object", e);
}
}
return Mono.just(false);
} else {
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
(serverHttpRequest) -> ServerRequest
.create(exchange.mutate().request(serverHttpRequest)
.build(), messageReaders)
.bodyToMono(inClass)
.doOnNext(objectValue -> exchange.getAttributes().put(
CACHE_REQUEST_BODY_OBJECT_KEY,
//去除多餘符號
objectValue.toString().replaceAll("[\n\t\r'']", "")))
.map(objectValue -> config.getPredicate()
.test(objectValue))
//總是返回true
.thenReturn(true));
}
}
@Override
public String toString() {
return String.format("ReadBody: %s", config.getInClass());
}
};
}
@Override
@SuppressWarnings("unchecked")
public Predicate<ServerWebExchange> apply(GwReadBodyPredicateFactory.Config config) {
throw new UnsupportedOperationException(
"GwReadBodyPredicateFactory is only async.");
}
public static class Config {
private Class inClass;
private Predicate predicate;
private Map<String, Object> hints;
public Class getInClass() {
return inClass;
}
public GwReadBodyPredicateFactory.Config setInClass(Class inClass) {
this.inClass = inClass;
return this;
}
public Predicate getPredicate() {
return predicate;
}
public GwReadBodyPredicateFactory.Config setPredicate(Predicate predicate) {
this.predicate = predicate;
return this;
}
public <T> GwReadBodyPredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
setInClass(inClass);
this.predicate = predicate;
return this;
}
public Map<String, Object> getHints() {
return hints;
}
public GwReadBodyPredicateFactory.Config setHints(Map<String, Object> hints) {
this.hints = hints;
return this;
}
}
}
- route配置,predicates換成自建的GwReadBodyPrediscateFactory即可
- id: r_fapi #測試
uri: lb://LIZZ-GATEWAY
predicates:
- Path=/fapi/**
- name: GwReadBodyPredicateFactory #讀取body斷言
args:
inClass: "#{T(String)}" #body數據類型
predicate: "#{@testRequestBody}" #自定義斷言處理器