行业知识
SpringCloudGateway获取报文大小
2026-10-31 20:03:48
来源:互联网转载
Overview
SpringCloud Gateway使用过程中,希望获取报文大小。由于SpringCloud Gateway底层基于Netty实现,直接读取报文,会大幅影响网关性能。因此本文将通过其他方式获取报文大小。本文基于2.2.9 SpringCloud Gateway开发。
()读取请求报文大小
实现自定义Filter,读取请求报文大小,具体可参考以下代码。
import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpRequestDecorator;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;@Slf4j@Componentpublic class ReadRequestBodyFilter implements GlobalFilter { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 分配空的DataBuffer DataBuffer emptyBuffer = exchange.getResponse().bufferFactory().allocateBuffer(0); // 使用 DataBufferUtils.join 将DataBuffer数据流聚合为一个Mono // 聚合后的 DataBuffer 为一个完整报文的 DataBuffer。 // 如果请求报文为空,使用分配的空 DataBuffer。 return DataBufferUtils.join(exchange.getRequest().getBody().defaultIfEmpty(emptyBuffer)) .flatMap(dataBuffer -> { // 获取报文大小,不直接获取报文内容。 int size = dataBuffer.readableByteCount(); log.info("=====> request body size: {}", size); if (size == 0) { // 如果报文内容为空,需要主动释放创建的空的DataBuffer。 DataBufferUtils.release(dataBuffer); return chain.filter(exchange); } // 复制一份DataBuffer,slice方法不会retain DataBuffer,只是复制的指针坐标。 Flux cachedFlux = Flux.defer(() -> Flux.just( dataBuffer.slice(0, dataBuffer.readableByteCount()))); // 构建新的 Request,重写 getBody 返回复制的报文 ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( exchange.getRequest()) { @Override public Flux getBody() { return cachedFlux; } }; // 继续处理请求 return chain.filter(exchange.mutate().request(mutatedRequest).build()); }); }} 读取响应报文大小
重写NettyWriteResponseFilter,实现响应报文大小获取。此方法有点不够优雅。
()// 重写 filter 方法@Overridepublic Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_CONN_ATTR is not added // until the NettyRoutingFilter is run // @formatter:off return chain.filter(exchange) .doOnError(throwable -> cleanup(exchange)) .then(Mono.defer(() -> { Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR); if (connection == null) { return Mono.empty(); } if (log.isTraceEnabled()) { log.trace("NettyWriteResponseFilter start inbound: " + connection.channel().id().asShortText() + ", outbound: " + exchange.getLogPrefix()); } ServerHttpResponse response = exchange.getResponse(); // TODO: needed? final Flux body = connection .inbound() .receive() .retain() .map(byteBuf -> wrap(byteBuf, response)); MediaType contentType = null; try { contentType = response.getHeaders().getContentType(); } catch (Exception e) { if (log.isTraceEnabled()) { log.trace("invalid media type", e); } } // 重写部分代码,如果为 stream 类型报文,则忽略报文读取 // stream media type, we can not join databuffer, just ignore body size. if (isStreamingMediaType(contentType)) { return response.writeAndFlushWith(body.map(Flux::just)); } // 分配空的 DataBuffer DataBuffer emptyBuffer = exchange.getResponse().bufferFactory().allocateBuffer(0); // 聚合 DataBuffer 构建大的 DataBuffer 包含完整报文 return DataBufferUtils.join(body.defaultIfEmpty(emptyBuffer)) .flatMap(dataBuffer -> { // 读取报文大小 int size = dataBuffer.readableByteCount(); log.info("=====> response body size: " + size); // 复制 DataBuffer Flux cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, size))); // 向 response 写入数据 return response.writeWith(cachedFlux); }); })).doOnCancel(() -> cleanup(exchange));} 总结
通过上述方式,可以获取到完整的请求和响应报文大小。根据压测结果,此方法对性能无影响。
上一篇:测速服务器怎么选?
下一篇:WIFIDNS服务器怎么调?