spring cloud gateway - 日志

本文详细介绍了在SpringCloud Gateway中实现请求和响应日志的两种方法,包括如何在不同版本下正确读取requestbody,以及如何配置Predicate方法readBody以避免重复读取问题。

spring cloud gateway - 日志

实现日志

前提:spring cloud gateway是基于webflux的项目,所以不能像使用spring mvc一样直接获取request body。

参考博客:

下面会讲述两种获取request body的方式(对应不同的版本)

第一种:重新构造再转发

​ 如果我们在spring cloud gateway 封装之前读取了一次request body,比如打印request body日志,在下游获取数据的时候会出现错误:[spring cloud] [error] java.lang.IllegalStateException: Only one connection receive subscriber allowed. 因为request body只能读取一次,它是属于消费类型的。

实现思路为:

​ 首先读取原请求的数据,然后构造一个新的请求,将原请求的数据封装到新的请求中,然后再转发出去。

这种方法在spring-boot-starter-parent 2.0.6.RELEASE + Spring Cloud Finchley.SR2 body 中生效

但是在spring-boot-starter-parent 2.1.0.RELEASE + Spring Cloud Greenwich.M3 body 中不生效,总是为空

重写的拦截方法filter

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    Route gatewayUrl = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
       URI uri = gatewayUrl.getUri();
       ServerHttpRequest request = (ServerHttpRequest)exchange.getRequest();
       String URIPath = request.getURI().toString();
       String path = request.getPath().value();
       String method = request.getMethodValue();
       String instance = uri.getAuthority();
       HttpHeaders header = request.getHeaders();
 
			 //缓存读取的request body信息
       AtomicReference<String> bodyRef = new AtomicReference<>();
       Flux<DataBuffer> fluxBody = exchange.getRequest().getBody();
       fluxBody.subscribe(buffer -> {
             CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
             DataBufferUtils.release(buffer);
             bodyRef.set(charBuffer.toString());
       });
       String bodyStr = bodyRef.get();
  		 //获取request body
       log.info("请求体参数:{}",bodyStr);       
       //封装新的request
       DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
       Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
       request = new ServerHttpRequestDecorator(request){
            @Override
            public Flux<DataBuffer> getBody() {
                return bodyFlux;
            }
       }; 
       return chain.filter(exchange.mutate().request(request).build());    
}

缓存body的方法

protected DataBuffer stringBuffer(String value) {
    byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
    NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
    DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
    buffer.write(bytes);
    return buffer;
}

第二种:获取缓存里面的body

​ 这种方式就是解决在spring-boot-starter-parent 2.1.0.RELEASE + Spring Cloud Greenwich.M3 body 中不生效,总是为空的问题

实现思路:

​ 在filter中通过cachedRequestBodyObject缓存字段获取request body信息,这种解决,一不会带来重复读取问题,二不会带来requestbody取不全问题。三在低版本的Spring Cloud Finchley.SR2也可以运行。

路由配置:

在配置中添加Predicate方法readBody(Object.class,requestBody -> true)

@Bean
public RouteLocator routeLocatorPermission(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(PERMISSION_ROUTE_ID,
                    r -> r.path(PERMISSION_ROUTE_PATH).and()
                    .readBody(Object.class,requestBody -> true)
                    .filters(f -> f
                            .stripPrefix(3)
                            .hystrix(config -> config.setName("hystrixName").setFallbackUri("forward:/fallback"))
                            .requestRateLimiter(config -> config.setKeyResolver(ipKeyResolver).setRateLimiter(myRedisRateLimiter))
                            .filter(oauthFilter)
                    )
                    .uri("lb://BOSS-BES-PERMISSION")).build();
}

配置日志拦截

可以通过Object requestBody = exchange.getAttribute("cachedRequestBodyObject")直接获取body

@Component
public class GatewayRequestLogFilter implements GlobalFilter, Ordered {

    private static Logger logger = LoggerFactory.getLogger(GatewayRequestLogFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        logger.info("path: " + request.getPath());
        logger.info("address: " + request.getRemoteAddress());
        logger.info("method: " + request.getMethodValue());
        logger.info("URI: " + request.getURI());
        logger.info("Headers: " + request.getHeaders());
        Object requestBody = exchange.getAttribute("cachedRequestBodyObject");
        logger.info("body: "+ requestBody);
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

注意事项:

如果是get请求,get请求使用readBody会报错

如果是post请求,Content-Type是application/x-www-form-urlencoded,readbody为 String.class

如果是post请求,Content-Type是application/json,readbody为 Object.class

最后附上响应拦截记录日志代码:

@Component
public class GatewayResponseLogFilter implements GlobalFilter, Ordered {

    private static Logger logger = LoggerFactory.getLogger(GatewayResponseLogFilter.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {

            @Override
            @SuppressWarnings("unchecked")
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        DataBufferUtils.release(dataBuffer);
                        String result = new String(content, Charset.forName("UTF-8"));

                        logger.info("response body: " + result);

                        byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    @Override
    public int getOrder() {
        return -2;
    }
}
<think>嗯,用户现在想了解SpringCloud Gateway作为API网关的作用和功能。之前我们讨论过OpenFeign的服务间调用,现在转向API网关,可能需要先理清两者的关系。用户可能是在构建微服务架构,需要了解不同组件的分工。SpringCloud Gateway和OpenFeign都属于SpringCloud生态,但职责不同,前者负责入口流量管理,后者处理服务间调用。首先,我需要根据提供的引用内容整理Gateway的作用。引用[1]提到它处理请求路由、认证授权、限流降级,高性能且与Spring生态集成。引用[2]补充了负载均衡、服务发现等功能。引用[4]提到Filter分为GatewayFilter和GlobalFilter,用于处理请求。用户的问题可能希望得到详细的功能分类,比如路由、过滤、负载均衡等。需要确保每个功能点都有对应的引用支持。同时,用户之前的讨论涉及OpenFeign的声明式调用和负载均衡,这里可以对比Gateway的负载均衡是服务端还是客户端?根据引用[2],Gateway自己处理负载均衡,可能集成Ribbon或者SpringCloud LoadBalancer。 需要注意回答的结构,按照核心作用分点,每个点下展开子功能。比如路由转发作为第一点,包括动态路由、路径匹配等。然后安全相关,如认证授权,接着是流量控制,如限流降级。还有过滤器机制,集成监控等。此外,要区分与Zuul的差异,比如性能更高,基于异步非阻塞模型。可能用户没有明确问比较,但提到其他网关如Zuul,可以简要说明优势。同时,确保每个功能点都有引用标注,比如动态路由引用[1][2],过滤器引用[4],限流引用[1][2]等。需要避免技术术语过于复杂,用易懂的语言解释。比如动态代理和路由的区别,可能需要关联用户之前了解的OpenFeign动态代理,但这里是网关级别的路由。另外,问题部分需要生成相关问题,可能涉及配置步骤、过滤器使用、性能优化、与Zuul对比、限流实现等,确保问题有逻辑关联,帮助用户深入理解。</think>### Spring Cloud Gateway作为API网关的作用与功能 --- #### 1. **统一入口与路由转发** - **动态路由配置**:通过简单的YAML或Java配置,将外部请求路由到不同的微服务实例[^1][^2]。例如,将`/user/**`的请求转发到用户服务,`/order/**`的请求转发到订单服务。 - **路径匹配与重写**:支持通配符、正则表达式匹配请求路径,并可对请求路径进行重写[^3]。 - **负载均衡**:集成服务发现(如Eureka、Consul),自动从注册中心获取服务实例列表,并通过负载均衡策略(如轮询、权重)分配请求[^2]。 #### 2. **安全控制与权限管理** - **认证与鉴权**:通过集成OAuth2、JWT等机制,实现请求的身份验证(如校验Token)和权限控制(如角色校验)[^1][^2]。 - **请求头处理**:可添加、修改或删除请求头,例如透传认证信息或添加自定义标头。 #### 3. **流量控制与容错保护** - **限流(Rate Limiting)**:基于令牌桶、漏桶算法限制接口的请求频率,防止突发流量压垮服务[^1][^2]。例如配置每秒最多100次请求。 - **熔断降级**:与Hystrix或Resilience4j集成,在服务响应超时或异常时触发熔断,返回预设的降级响应(如默认错误页)[^1]。 - **重试机制**:针对服务调用失败的情况,配置重试次数和条件(如仅对GET请求重试)。 #### 4. **请求与响应处理** - **过滤器链(Filters)**:通过内置或自定义的`GatewayFilter`和`GlobalFilter`,实现请求的预处理和响应的后处理[^4]。例如: - **日志记录**:记录请求参数和响应状态。 - **请求耗时统计**:添加计时过滤器统计接口性能。 - **请求体修改**:压缩请求内容或加密敏感数据。 - **跨域处理(CORS)**:统一配置跨域请求的允许来源、方法和头部信息。 #### 5. **监控与可观测性** - **集成监控工具**:支持对接Prometheus、Spring Boot Actuator,暴露网关的流量指标(如请求量、延迟、错误率)[^1]。 - **链路追踪**:通过集成Sleuth和Zipkin,为请求添加唯一Trace ID,实现全链路追踪。 #### 6. **性能优化** - **异步非阻塞模型**:基于Netty和Reactor实现高并发处理能力,对比传统同步网关(如Zuul 1.x)性能提升显著[^3]。 - **响应缓存**:缓存高频请求的响应结果,减少后端服务压力。 --- ### 示例:路由配置 ```yaml spring: cloud: gateway: routes: - id: user_service uri: lb://user-service # 负载均衡到user-service服务 predicates: - Path=/api/user/** filters: - StripPrefix=2 # 移除路径前缀/api/user - AddRequestHeader=X-Request-From, gateway ``` ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值