version:spring-cloud 2021.0.1,spring-boot 2.6.3,spring-cloud-alibaba 2021.0.1.0
SpringCloudGateway中Post请求参数只能读取一次。
这是因为Gateway网关默认使用的是SpringWebflux,解决这个问题需要容重新构造一个request来替换原先的request。
CacheBodyGlobalFilter这个全局过滤器把原有的request请求中的body内容读出来,并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装,重写getBody方法,并把包装后的请求放到过滤器链中传递下去。这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时,实际上就是调用的重载后的getBody方法,获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。
//
过滤器的Ordered.HIGHEST_PRECEDENCE,即最高优先级的过滤器。优先级设置高的原因是某些系统内置的过滤器可能也会去读body。
Request Log
@Configuration
public class RequestLogGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
//String path = request.getPath().value();
String path = request.getPath().pathWithinApplication().value();//打印请求路径
String requestUrl = this.getOriginalRequestUrl(exchange);//打印请求url
String method = request.getMethodValue();
//cors
HttpHeaders headers = request.getHeaders();
log.info("--> method: {} url: {} header: {}", method, requestUrl, headers);
if ("POST".equals(method)) {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
log.info("--> {}", bodyString);
//log.info("--> {}", formatStr(bodyString)); //formData
exchange.getAttributes().put("POST_BODY", bodyString);
DataBufferUtils.release(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() { return cachedFlux; }
};
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
} else if ("GET".equals(method)) {
MultiValueMap<String, String> queryParams = request.getQueryParams();
log.info("请求参数:" + queryParams);
return chain.filter(exchange);
}
return chain.filter(exchange);
}
private String getOriginalRequestUrl(ServerWebExchange exchange) {
ServerHttpRequest req = exchange.getRequest();
LinkedHashSet<URI> uris = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI requestUri = uris.stream().findFirst().orElse(req.getURI());
MultiValueMap<String, String> queryParams = req.getQueryParams();
//打印 /api/rest/feign/order/detail
// return UriComponentsBuilder.fromPath(requestUri.getRawPath()).queryParams(queryParams).build().toUriString();
return requestUri.toString(); // http://localhost:8091/api/rest/feign/order/detail
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
/**
* 去掉FormData 空格,换行和制表符
*/
private static String formatStr(String str){
if (str != null && str.length() > 0) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(str);
return m.replaceAll("");
}
return str;
}}
Response Log
响应报文内容分段传输导致不全的解决方法:
1.大报文对fluxBody流循环拼接处理,把fluxBody.map变为fluxBody.buffer().map,从而可以foreach循环Body体了。
2.排除Excel导出。
3.对JSON统一格式处理,日期统一格式处理
@Configuration
public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
try {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
HttpStatus statusCode = originalResponse.getStatusCode();
if (statusCode != HttpStatus.OK) {
return chain.filter(exchange);//降级处理返回数据
}
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
// 合并多个流集合,解决返回体分段传输
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer buff = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[buff.readableByteCount()];
buff.read(content);
DataBufferUtils.release(buff);//释放掉内存
//排除Excel导出,不是application/json不打印。若请求是上传图片则在最上面判断。
MediaType contentType = originalResponse.getHeaders().getContentType();
if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
return bufferFactory.wrap(content);
}
// 构建返回日志
String joinData = new String(content);
String result = modifyBody(joinData);
List<Object> rspArgs = new ArrayList<>();
rspArgs.add(originalResponse.getStatusCode().value());
rspArgs.add(exchange.getRequest().getURI());
rspArgs.add(result);
log.info("<-- {} {}\n{}", rspArgs.toArray());
getDelegate().getHeaders().setContentLength(result.getBytes().length);
return bufferFactory.wrap(result.getBytes());
}));
} else {
log.error("<-- {} 响应code异常", getStatusCode());
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
} catch (Exception e) {
log.error("gateway log exception.\n" + e);
return chain.filter(exchange);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
//返回统一的JSON日期数据 2024-02-23 11:00, null转空字符串
private String modifyBody(String jsonStr){
JSONObject json = JSON.parseObject(jsonStr, Feature.AllowISO8601DateFormat);
JSONObject.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm";
return JSONObject.toJSONString(json, (ValueFilter) (object, name, value) -> value == null ? "" : value, SerializerFeature.WriteDateUseDateFormat);
}
}
// public void setJSONObjectWriteNullStringAsEmpty() {
// JSON.toJSONString(data,SerializerFeature.WriteNullStringAsEmpty);
// JSONObject jsonObj = JSON.parseObject(data, Feature.AllowISO8601DateFormat);
// JSON.toJSONString(data,SerializerFeature.DisableCircularReferenceDetect);
// JSONObject.DEFFAULT_DATE_FORMAT ="yyyy-MM-dd HH:mm";
// JSONObject.toJSONString(jsonObject,SerializerFeature.WriteDateUseDateFormat);
//
// String dataJson = JSON.toJSONString(data, (ValueFilter) (object, name, value) -> {
// log.info("data:{} ", data);
//
// log.info("object:{}, name:{}, value:{}", object, name, value);
// if (value == null) {
// return "";
// }
// return value;
// });
// JSONObject jsonObject = new JSONObject();
// 把json对象转换成字节数组
// byte[] bits = data.getBytes(StandardCharsets.UTF_8);
// DataBuffer buffer = originalResponse.bufferFactory().wrap(bits);
// originalResponse.writeWith(Mono.just(buffer));
// }
}
CorsWebFilterConfig 跨域配置
Gateway跨域处理
https://blog.csdn.net/weixin_43730516/article/details/127040628
@Configuration
public class CorsWebFilterConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfigurationSource corsConfigurationSource = new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange serverWebExchange) {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedHeader("*");
corsConfig.addAllowedMethod("*");
corsConfig.addAllowedOriginPattern("*");
corsConfig.setMaxAge(1800L);
corsConfig.setAllowCredentials(true);
return corsConfig;
}
};
return new CorsWebFilter(corsConfigurationSource);
} }
参考资料
SpringCloud Gateway 打印请求和响应信息
https://blog.csdn.net/liben0429/article/details/126106528
Gateway网关日志
https://blog.csdn.net/small_to_large/article/details/125326498
原文链接:https://blog.csdn.net/sowei2009/article/details/126104934
https://www.jb51.net/article/240256.htm
https://blog.csdn.net/chaojunma/article/details/122595612
SpringCloud Gateway 请求响应日志
https://blog.csdn.net/smart_an/article/details/137722360
http://t.zoukankan.com/lifengdi-p-12524092.html
https://www.icode9.com/content-4-1302983.html
https://www.cnblogs.com/lifengdi/p/12524092.html
https://www.51ufo.cn/%E5%88%86%E5%B8%83%E5%BC%8F/2021/12/23/SpringCloud-Gateway%E7%BD%91%E5%85%B3%E6%94%B6%E9%9B%86%E8%AF%B7%E6%B1%82%E6%97%A5%E5%BF%97.html
乱码问题处理
https://blog.csdn.net/qq_38380025/article/details/100032490
其它
云原生之 Gateway 的 Filter 过滤器
https://blog.csdn.net/m0_62436868/article/details/127644772
Gateway应用实践
https://blog.csdn.net/weixin_58668484/article/details/120852726