在使用springcloud gateway(2.2.8.RELEASE)的过程中遇到如下问题:
上传文件时会在临时目录下创建临时文件夹而不会删除,参考解决方案中都是把spring-web的版本升级到5.2.16.RELEASE,然而没有生效,请见代码,目前判断问题出在readFormData方法实现上,但一直未定位到问题所在
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
import cn.hutool.core.collection.CollectionUtil;
import com.xxxx.gateway.entity.GatewayLog;
import com.xxxx.gateway.service.AccessLogService;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.*;
@Slf4j
@Component
public class AccessLogFilter2 implements GlobalFilter, Ordered {
@Autowired
ServerCodecConfigurer codecConfigurer;
@Override
public int getOrder() {
return -100;
}
@Override
@SuppressWarnings("unchecked")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
MediaType mediaType = request.getHeaders().getContentType();
if (mediaType == null) {
mediaType = MediaType.APPLICATION_JSON;
}
Map<String, String> headers = request.getHeaders().toSingleValueMap();
Map<String, String> logHeaders = new HashMap<>();
for (String headerName : AccessLogService.HEADER_NAMES) {
if (headers.containsKey(headerName)) {
logHeaders.put(headerName, headers.get(headerName));
}
}
GatewayLog gatewayLog = new GatewayLog();
exchange.getAttributes().put(AccessLogService.CACHE_GATEWAY_CONTEXT, gatewayLog);
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType) || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
return readFormData(exchange, chain, gatewayLog);
} else {
//其他格式
return chain.filter(exchange);
}
}
private ServerHttpRequest wrapperServerHttpRequest(ServerWebExchange exchange, DataBuffer dataBuffer) {
DataBufferUtils.retain(dataBuffer);
final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
return new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
@Override
public MultiValueMap<String, String> getQueryParams() {
return UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build().getQueryParams();
}
};
}
/**
* 读取form-data数据
*/
private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) {
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
final ServerHttpRequest mutatedRequest = wrapperServerHttpRequest(exchange, dataBuffer);
final HttpHeaders headers = exchange.getRequest().getHeaders();
if (headers.getContentLength() == 0) {
return chain.filter(exchange);
}
ResolvableType resolvableType;
if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) {
resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);
} else {
//解析 application/x-www-form-urlencoded
resolvableType = ResolvableType.forClass(String.class);
}
return codecConfigurer.getReaders().stream().filter(reader -> reader.canRead(resolvableType, mutatedRequest.getHeaders().getContentType())).findFirst()
.orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader."))
.readMono(resolvableType, mutatedRequest, Collections.emptyMap()).flatMap(resolvedBody -> {
if (resolvedBody instanceof MultiValueMap) {
MultiValueMap map = (MultiValueMap) resolvedBody;
if (CollectionUtil.isNotEmpty(map)) {
StringBuilder builder = new StringBuilder();
map.forEach((key, val) -> {
final Part valPart = (Part) map.getFirst(key);
if (valPart instanceof FormFieldPart) {
String value = ((FormFieldPart) valPart).value();
if (builder.length() > 0) {
builder.append("&");
}
builder.append(key).append("=").append(value);
}
});
accessLog.setRequestBody(builder.toString());
}
} else {
accessLog.setRequestBody((String) resolvedBody);
}
//获取响应体
ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);
return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build());
});
});
}
/**
* 记录响应日志
*/
private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) {
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = response.bufferFactory();
return new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
// 获取响应类型,如果是 json 就打印
String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpStatus statusCode = this.getStatusCode();
if (ObjectUtils.equals(statusCode, HttpStatus.OK)
&& !StringUtil.isNullOrEmpty(originalResponseContentType)
&& originalResponseContentType.contains("application/json")) {
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);
return bufferFactory.wrap(content);
}));
} else if (!ObjectUtils.equals(statusCode, HttpStatus.OK)) {
// gatewayLog.wrapperResponseContent(statusCode.value(), statusCode.value(), statusCode.getReasonPhrase());
}
}
// if body is not a flux. never got there.
return super.writeWith(body);
}
};
}
}
每次上传文件都会在临时目录下创建一个临时文件夹,会导致文件产生过多而报错:Too many links
期望不会创建临时文件夹