一、需求分析
SpringCloud多服务项目环境,前端请求经过网关中转发到各个服务节点。日志中需要记录请求头中的部分参数、请求的body、响应状态及响应内容,并在请求头中新增一个标识。
二、代码实现
1. 装饰Request
@Component
public class RequestFilter implements GlobalFilter, Ordered {
/**
* 1.封装 HttpRequest,使 Request body 可以重复读取;
* 2.封装 HttpRequestHeader,添加requestId;
*/
@Override
public int getOrder() {
// 最高优先级的执行顺序
return HIGHEST_PRECEDENCE;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Request.
ServerHttpRequest request = exchange.getRequest();
// 新建Header 添加 requestId.
String requestId = new SnowFlakeId().get();
request.mutate().headers(httpHeaders -> {
httpHeaders.set("requestId", requestId);
}).build();
if (Objects.requireNonNull(request.getMethod()).equals(HttpMethod.POST)) {
// body 为null, 不会执行里面的重分派.
DefaultDataBufferFactory defaultDataBufferFactory = new DefaultDataBufferFactory();
DefaultDataBuffer defaultDataBuffer = defaultDataBufferFactory.allocateBuffer(0);
//构建新数据流, 当body为空时,构建空流
Flux<DataBuffer> bodyDataBuffer = exchange.getRequest().getBody().defaultIfEmpty(defaultDataBuffer);
return DataBufferUtils.join(bodyDataBuffer)
.flatMap(dataBuffer -> {
// Request.
DataBufferUtils.retain(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
else {
return chain.filter(exchange);
}
}
}
PS:
1.Order 最高优先级。
2.Request Header 不可添加值,需重新创建后绑定。
3.Request Body IO流,读取一次后就空了;新建一个Request绑定到请求中。
4. POST请求,Body为空时,没有执行新建Request,需要构建一个空的输入流使新建Request可以执行。
2. 装饰Response
@Component
@Slf4j
@AllArgsConstructor
public class ResponseFilter implements GlobalFilter, Ordered {
/**
* 1.封装HttpResponse,读取Respond 内容;
* 2.记录操作日志:请求信息、响应状态、响应信息等;
*/
@Override
public int getOrder() {
// before write response.
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// Response
ServerHttpResponse mutatedResponse = responseDecorator(exchange);
return chain.filter(exchange.mutate()
.response(mutatedResponse)
.build());
}
public ServerHttpResponse responseDecorator(ServerWebExchange exchange) {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
return new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
// 读取 Response 的响应流中的信息后,再写回去
if (body instanceof Flux) {
// 获取ContentType,判断是否返回JSON格式数据
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
if (StringUtils.isNotBlank(originalResponseContentType) && originalResponseContentType.contains(APPLICATION_JSON)) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
// Response Content.
// String responseContent = resolveContentFromResponse(dataBuffers, bufferFactory);
DataBuffer join = bufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
String responseContent = formatStr(new String(content, StandardCharsets.UTF_8));
// 添加操作日志.
ServerHttpRequest request = exchange.getRequest();
String apiPath = request.getPath().toString();
HttpHeaders headers = request.getHeaders();
String requestBody = resolveBodyFromRequest(request.getBody());
appendSystemLog(apiPath, headers, requestBody, responseContent, getStatusCode());
// 写回Response Content.
// String 转为 byte[]后再写入DataBuffer,可能出现中文全部为?的情况
// byte[] uppedContent = new String(responseContent.getBytes(), StandardCharsets.UTF_8).getBytes();
// originalResponse.getHeaders().setContentLength(uppedContent.length);
return bufferFactory.wrap(content);
}));
}
}
return super.writeWith(body);
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
}
/**
* 解析 RequestBody
*/
public String resolveBodyFromRequest(Flux<DataBuffer> body) {
AtomicReference<String> bodyRef = new AtomicReference<>();
// 缓存读取的request body信息
body.subscribe(dataBuffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
bodyRef.set(charBuffer.toString());
});
return formatStr(bodyRef.get());
}
/**
* 解析 ResponseBody
*/
public String resolveContentFromResponse(List<? extends DataBuffer> dataBuffers, DataBufferFactory bufferFactory) {
//(返回数据内如果字符串过大,默认会切割)解决返回体分段传输
// 遍历 List时,拼接字符串会遇到汉字乱码的现象。应全部读取后再转成字符串。
DataBuffer join = bufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
return formatStr(new String(content, StandardCharsets.UTF_8));
}
/**
* 去掉空格,换行和制表符
*/
private String formatStr(String str){
if (str != null && str.length() > 0) {
Pattern p = Pattern.compile("\t|\r|\n");
Matcher m = p.matcher(str);
return m.replaceAll("");
}
return str;
}
/**
* 记录操作日志
*/
public void appendSystemLog(String apiPath, HttpHeaders headers, String requestBody, String responseBody, HttpStatus status) {
try {
// todo 记录日志
// service
} catch (Exception e) {
log.error("==== Append SystemLog error.", e);
}
}
}
PS:
1.Order 需要在Response内容被NettyWriteResponseFilter cleanup之前读取响应类容和RequestBody。
2. 读取Response内容时,需要注意由于相容内容分段(List),导致读取时阶段汉字的bytes出现个别汉字乱码。文章来源:https://www.toymoban.com/news/detail-619098.html
3.Response内容读取后回写。文章来源地址https://www.toymoban.com/news/detail-619098.html
到了这里,关于gateway过滤器中实现记录访问日志的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!