本次和大家分享在使用gateway作为网关时比如在日志采集、请求结构修改、返回数据结构修改时,使用filter进行请求数据和返回数据的修改。针对不同的请求类型 application/json 、application/x-www-form-urlencoded 、multipart/form-data。其中针对文件上传的请求修改时比较麻烦。解决了post请求请求体不能多次读取的问题。下面的例子为通过网关采集日志。本文章基于 springboot3.x说明,针对2.x的使用有区别请自行调整。
1. 过滤器使用
@Slf4j
@Configuration
@RequiredArgsConstructor
public class LogGlobalFilter implements GlobalFilter, Ordered {
private final ServerCodecConfigurer codecConfigurer;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
ServerHttpResponseDecorator responseDecorator = new CustomServerHttpResponseDecorator(exchange, exchange.getResponse().bufferFactory(), logResFunction(exchange));
ServerWebExchange serverWebExchange = exchange.mutate().response(responseDecorator).build();
ServerHttpRequest request = exchange.getRequest();
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
if (request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.PUT) {
MediaType contentType = request.getHeaders().getContentType();
//针对json请求和表单请求
if (contentType.isCompatibleWith(MediaType.APPLICATION_JSON) || contentType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) {
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(logReqFunction(route, request));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
return FilterUtils.writeModifyMessage(serverWebExchange, chain, bodyInserter);
} else if (contentType.isCompatibleWith(MediaType.MULTIPART_FORM_DATA)) {
return FilterUtils.readFormMultiPartData(exchange, chain, readFormMultiPartData(exchange, route));
}
} else {
MultiValueMap<String, String> queryParams = request.getQueryParams();
String params = HttpUtil.toParams(queryParams);
logReqFunction(route, request).apply(params);
}
return chain.filter(serverWebExchange);
}
@Override
public int getOrder() {
return -10;
}
/**
* 解析文件表单
*
* @param exchange
* @param route
* @return
*/
private Function<MultiValueMap<String, Part>, MultiValueMap<String, Part>> readFormMultiPartData(ServerWebExchange exchange, Route route) {
return multiValueMap -> {
Map<String, String> data = new HashMap<>();
multiValueMap.forEach((key, value) -> {
List<String> collect = value.stream()
.filter(val -> val instanceof FormFieldPart)
.map(val -> ((FormFieldPart) val).value())
.toList();
if (CollUtil.isNotEmpty(collect)) {
data.put(key, collect.size() == 1 ? collect.get(0) : JSON.toJSONString(collect));
}
});
logReqFunction(route, exchange.getRequest()).apply(JSON.toJSONString(data));
return multiValueMap;
};
}
/**
* 处理返回数据
*
* @param exchange
* @return
*/
private Function<byte[], byte[]> logResFunction(ServerWebExchange exchange) {
return responseData -> {
ServerHttpRequest request = exchange.getRequest();
Route route = exchange.getAttribute("org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute");
log.info("网关返回日志 转发服务:" + route.getUri() + "请求方式:" + request.getMethod() + " Content-Type: " + request.getHeaders().getContentType() + " 请求地址:" + request.getURI().getRawPath() + " 返回数据:" + responseData);
return responseData;
};
}
/**
* 进行请求数据记录
*
* @param route
* @param request
* @return
*/
private Function<String, ? extends Mono<String>> logReqFunction(Route route, ServerHttpRequest request) {
return requestData -> {
log.info("网关请求日志 转发服务: " + route.getUri() + " 请求方式: " + request.getMethod() + " Content-Type: " + request.getHeaders().getContentType() + " " + " 请求地址:" + request.getURI().getRawPath() + " 请求数据:" + requestData);
return Mono.just(requestData);
};
}
2. 构建请求缓存和修改类
这块主要处理请求数据的缓存和请求头的中ContentLength的修改
public class CustomServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private final ServerWebExchange exchange;
private final HttpHeaders headers;
private final CachedBodyOutputMessage outputMessage;
public CustomServerHttpRequestDecorator(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
super(exchange.getRequest());
this.exchange = exchange;
this.headers = headers;
this.outputMessage = outputMessage;
}
@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();
}
}
3. 构建返回修改类
public class CustomServerHttpResponseDecorator extends ServerHttpResponseDecorator {
private final DataBufferFactory bufferFactory;
private final Function<byte[],byte[]> handler;
private final ServerWebExchange exchange;
public CustomServerHttpResponseDecorator(ServerWebExchange exchange, DataBufferFactory bufferFactory,Function<byte[],byte[]> handler) {
super(exchange.getResponse());
this.bufferFactory = bufferFactory;
this.handler = handler;
this.exchange = exchange;
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
content = handler.apply(content);
return bufferFactory.wrap(content);
}));
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
}
4. 写出修改后的请求体
/**
* 写出消息
*
* @param exchange
* @param chain
* @return
*/
public static Mono<Void> writeModifyMessage(ServerWebExchange exchange, GatewayFilterChain chain, BodyInserter bodyInserter) {
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
ServerHttpRequest requestDecorator = new CustomServerHttpRequestDecorator(exchange, headers, outputMessage);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> chain.filter(exchange.mutate().request(requestDecorator).build())));
}
4. 针对文件上传的请求体缓存
针对文件上传这种类型的请求,编码和解码会比较麻烦这里扩展webflux中的ClientHttpRequest来对请求体进行编码
public class MultipartCachedBodyOutputMessage implements ClientHttpRequest {
private ServerWebExchange serverWebExchange;
private ServerHttpRequest serverRequest;
private HttpHeaders httpHeaders;
private boolean cached = false;
private Flux<DataBuffer> body = Flux
.error(new IllegalStateException("数据异常"));
public MultipartCachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
this.serverWebExchange = exchange;
this.serverRequest = exchange.getRequest();
this.httpHeaders = httpHeaders;
}
@Override
public HttpMethod getMethod() {
return serverRequest.getMethod();
}
@Override
public URI getURI() {
return serverRequest.getURI();
}
@Override
public MultiValueMap<String, HttpCookie> getCookies() {
return serverRequest.getCookies();
}
@Override
public <T> T getNativeRequest() {
return (T) serverRequest;
}
@Override
public DataBufferFactory bufferFactory() {
return serverWebExchange.getResponse().bufferFactory();
}
@Override
public void beforeCommit(Supplier<? extends Mono<Void>> action) {
}
@Override
public boolean isCommitted() {
return false;
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
this.body = Flux.from(body);
this.cached = true;
return Mono.empty();
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMap(p -> p));
}
@Override
public Mono<Void> setComplete() {
return writeWith(Flux.empty());
}
@Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
public boolean isCached() {
return this.cached;
}
/**
* @return body as {@link Flux}
*/
public Flux<DataBuffer> getBody() {
return this.body;
}
5. 针对文件上传的请求体包装
public class MultipartServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private HttpHeaders headers;
private MultipartServerHttpRequestDecorator outputMessage;
public MultipartServerHttpRequestDecorator(ServerWebExchange exchange, HttpHeaders headers, MultipartCachedBodyOutputMessage outputMessage) {
super(exchange.getRequest());
this.headers= headers;
this.outputMessage = outputMessage;
}
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(headers);
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
}
6. 读取文件上传请求和转换写出
public static Mono<Void> readFormMultiPartData(ServerWebExchange exchange, GatewayFilterChain chain,
Function<MultiValueMap<String, Part>, MultiValueMap<String, Part>> function) {
return exchange.getMultipartData()
.map(data -> function.apply(data))
.map(data -> BodyInserters.fromMultipartData(data))
.flatMap(bodyInserter -> {
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
MultipartCachedBodyOutputMessage outputMessage = new MultipartCachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> chain.filter(exchange.mutate().request(new MultipartServerHttpRequestDecorator(exchange, headers, outputMessage)).build())))
.onErrorResume((Function<Throwable, Mono<Void>>) throwable -> {
if (outputMessage.isCached()) {
return outputMessage.getBody().map(DataBufferUtils::release).then(Mono.error(throwable));
}
return Mono.error(throwable);
});
});
}