spring cloud gateway获取response body

网关发起请求后,微服务返回的response的值要经过网关才发给客户端。本文主要讲解在spring cloud gateway 的过滤器中获取微服务的返回值,因为很多情况我们需要对这个返回进行处理。网上有很多例子,但是都没有解决我的实际问题,最后研究了下源码找到了解决方案。

本节内容主要从如下几个方面讲解,首先需要了解我的博文的内容:API网关spring cloud gateway和负载均衡框架ribbon实战spring cloud gateway自定义过滤器 本文也将根据上面两个项目的代码进行扩展。代码见spring-cloud

  • 新增一个rest接口:我们在三个服务提供者(provider1001、provider1002、provider1003)里面新建一个查询人群信息接口。

本次代码:spring cloud gateway获取response body

一:新增一个rest接口



1,首先在github上面把spring-cloud 克隆到本地。启动三个服务提供者,再启动网关,通过网关能正常访问服务,然后再根据下面的代码进行本节课的学学习。

注意:

  • gateway配置文件的 - Auth 最好注释起来,除非使用postman把认证信息传进去,可以参考本节开头提到的两篇博客进行操作。

2,新建一个rest接口,返回大量的数据

注意三个provider里面都要添加一个这样的类,内容

package com.yefengyu.cloud.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
public class PersonController {

    @GetMapping("/persons")
    public List<Person> listPerson(){
        List<Person> personList = new ArrayList<>();
        for(int i = 0; i < 100; i++){
            Person person = new Person();
            person.setId(i);
            person.setName("王五" + i);
            person.setAddress("北京" + i);
            personList.add(person);
        }
        return personList;
    }

    public static class Person{
        private Integer id;
        private String name;
        private String address;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    ", name=‘" + name + ‘\‘‘ +
                    ", address=‘" + address + ‘\‘‘ +
                    ‘}‘;
        }
    }
}

一模一样,无需区分是哪个provider 返回的,本节重点不是负载均衡。然后先重启三个provider,再重启gateway,通过gateway访问这个接口。在浏览器输入http://localhost:8080/gateway/persons就可以看到结果输出。

至此我们在原有的微服务上面增加了一个接口,并且通过网关能正常访问。

二:需求



现在我们需要新建一个局部或者全局过滤器,在过滤器中获取微服务返回的body。在网上查询的时候,发现很多博文都没有讲清楚,主要体现在以下几点:

  • 报文可以在网关的过滤器中获取,但是没有返回到客户端
  • 报文体太长,导致返回客户端的报文不全
  • 中文乱码

我根据很多博文中的内容进行测试,都无法满足我的需求。于是看了官网的5.29节:说明只可以通过 配置类 的方式来获取返回的body内容。

第一小节我们启动了三个 provider和gateway进行测试,现在为了测试配置类这中形式,我们只启动上面的一个provider1001,然后新建一个简单的gateway,注意此gateway代码我不会上传到GitHub,只是验证官网给的例子是正确的。

1、新建一个工程gateway,添加依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yefengyu.cloud</groupId>
    <artifactId>gateway</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
    </dependencies>

</project>

2,新建一个启动类GatewayApplication

package com.yefengyu.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class);
    }
}

3,新建一个配置文件application.yml

server:
  port: 8000
spring:
  application:
    name: gateway_server
  cloud:
    gateway:
      default-filters:
      routes:
        - id: my_route
          uri: http://localhost:1001
          predicates:
            - Path=/gateway/**
          filters:
            - StripPrefix=1

4、测试:在浏览器访问 http://localhost:8000/gateway/persons 如果可以获取到结果则说明我们通过网关调用到了微服务。

5、使用配置类的形式:我们从官网已经知道,通过配置类的形式可以在代码中获取到返回的body内容。那么我们新建一个配置类:

package com.yefengyu.cloud;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

@Configuration
public class MyConfig {
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("my_route", r -> r.path("/gateway/**")
                        .filters(f -> f.stripPrefix(1)
                                .modifyResponseBody(String.class, String.class,
                                        (exchange, s) -> {
                                            //TODO: 此处可以获取返回体的所有信息
                                                   System.out.println(s);
                                            return Mono.just(s);
                                        })).uri("http://localhost:1001"))
                .build();
    }
}

特别注意:

  • 官网的例子,拷贝过来少了一个括号,在.build的点之前加一个括号,注意对比。
  • 官网代码中的host改为了path。
  • 对照代码和上面的修改之前的yml文件,可知他们是一一对应的。

6,配置文件需要修改,不需要使用配置文件形式的,只需要配置端口即可

server:
  port: 8000

7,测试:访问 http://localhost:8000/gateway/persons ,不仅浏览器可以返回数据,控制台也可以打印数据,也就是说我们在代码中可以获取到完整的body数据了。

三:更进一步



我们一般喜欢使用配置文件的形式,而不是配置类的形式来配置网关,那么怎么实现呢?这次我们抛弃上面临时新建的gateway工程,那只是验证配置类形式来获取body的。下面我们依然使用从GitHub下载的代码,在那里面来研究。三个provider 无需变动,只要启动就好。

我们思考下:为什么使用配置类的形式就能获取到返回body的数据呢?这是因为spring cloud gateway内部已经实现了这个过滤器(ModifyResponseBodyGatewayFilterFactory),我们要做的是模仿他重新写一个。

1,在我们的网关代码中,我们新建一个局部过滤器ResponseBodyGatewayFilterFactory,并把刚才所有的代码拷贝进去:注意不要拷贝包,并且把ModifyResponseBodyGatewayFilterFactory全部替换为ResponseBodyGatewayFilterFactory。

2,去掉代码中的配置类

public class ModifyResponseBodyGatewayFilterFactory extends
        AbstractGatewayFilterFactory<ModifyResponseBodyGatewayFilterFactory.Config> {

替换为

public class ResponseBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {

3,删除

    public ResponseBodyGatewayFilterFactory() {
        super(Config.class);
    }

    @Deprecated
    public ResponseBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) {
        this();
    }

4,apply方法更改

    @Override
    public GatewayFilter apply(Config config) {
        ModifyResponseGatewayFilter gatewayFilter = new ModifyResponseGatewayFilter(
                config);
        gatewayFilter.setFactory(this);
        return gatewayFilter;
    }

替换为

    @Override
    public GatewayFilter apply(Object config) {
        return new ModifyResponseGatewayFilter();
    }

注意有错误暂时不管。

5,删除静态内部类Config的所有内容,直到下面的ModifyResponseGatewayFilter类定义处。下面我们来操作ModifyResponseGatewayFilter类内部内容。

6,删除

        private final Config config;

        private GatewayFilterFactory<Config> gatewayFilterFactory;

        public ModifyResponseGatewayFilter(Config config) {
            this.config = config;
        }

7,删除

        @Override
        public String toString() {
            Object obj = (this.gatewayFilterFactory != null) ? this.gatewayFilterFactory
                    : this;
            return filterToStringCreator(obj)
                    .append("New content type", config.getNewContentType())
                    .append("In class", config.getInClass())
                    .append("Out class", config.getOutClass()).toString();
        }

        public void setFactory(GatewayFilterFactory<Config> gatewayFilterFactory) {
            this.gatewayFilterFactory = gatewayFilterFactory;
        }

8,删除无效和错误的依赖引入,特别是:

import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;

9,此时还有三处报错,都是config对象引起的。第一二处:

Class inClass = config.getInClass();
Class outClass = config.getOutClass();

改为:

Class inClass = String.class;
Class outClass = String.class;

第三处:

Mono modifiedBody = clientResponse.bodyToMono(inClass)
                            .flatMap(originalBody -> config.rewriteFunction
                                    .apply(exchange, originalBody));

这里最为重要,是我们获取返回报文的地方。改为:

Mono modifiedBody = clientResponse.bodyToMono(inClass)
                            .flatMap(originalBody -> {
                                //TODO:此次可以对返回的body进行操作
                                     System.out.println(originalBody);
                                return Mono.just(originalBody);
                            });

10,配置文件增加这个局部过滤器ResponseBody即可:

filters:
            - StripPrefix=1
#            - Auth
            - IPForbid=0:0:0:0:0:0:0:1
            - ResponseBody

11,将ResponseBodyGatewayFilterFactory注册到容器中,添加一个@Component注解即可。

12,启动网关,访问 http://localhost:8080/gateway/persons ,不仅浏览器可以返回数据,控制台也可以打印数据,也就是说我们在过滤器的代码中可以获取到完整的body数据了。

完整代码如下:

package com.yefengyu.gateway.local;

import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.server.ServerWebExchange;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

@Component
public class ResponseBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {

    @Override
    public GatewayFilter apply(Object config) {

        return new ModifyResponseGatewayFilter();
    }

    public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return chain.filter(exchange.mutate().response(decorate(exchange)).build());
        }

        @SuppressWarnings("unchecked")
        ServerHttpResponse decorate(ServerWebExchange exchange) {
            return new ServerHttpResponseDecorator(exchange.getResponse()) {

                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                    Class inClass = String.class;
                    Class outClass = String.class;

                    String originalResponseContentType = exchange
                            .getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    HttpHeaders httpHeaders = new HttpHeaders();

                    httpHeaders.add(HttpHeaders.CONTENT_TYPE,
                            originalResponseContentType);

                    ClientResponse clientResponse = ClientResponse
                            .create(exchange.getResponse().getStatusCode())
                            .headers(headers -> headers.putAll(httpHeaders))
                            .body(Flux.from(body)).build();

                    Mono modifiedBody = clientResponse.bodyToMono(inClass)
                            .flatMap(originalBody -> {
                                //TODO:此次可以对返回的body进行操作
                                System.out.println(originalBody);
                                return Mono.just(originalBody);
                            });

                    BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
                            outClass);
                    CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
                            exchange, exchange.getResponse().getHeaders());
                    return bodyInserter.insert(outputMessage, new BodyInserterContext())
                            .then(Mono.defer(() -> {
                                Flux<DataBuffer> messageBody = outputMessage.getBody();
                                HttpHeaders headers = getDelegate().getHeaders();
                                if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                                    messageBody = messageBody.doOnNext(data -> headers
                                            .setContentLength(data.readableByteCount()));
                                }
                                return getDelegate().writeWith(messageBody);
                            }));
                }

                @Override
                public Mono<Void> writeAndFlushWith(
                        Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body).flatMapSequential(p -> p));
                }
            };
        }

        @Override
        public int getOrder() {
            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
        }
    }
}

原文地址:https://www.cnblogs.com/ye-feng-yu/p/11748390.html

时间: 2024-08-30 09:44:10

spring cloud gateway获取response body的相关文章

那些年我们一起踩过的Spring Cloud Gateway获取body的那些坑

Spring Cloud Gateway-获取body踩坑实践 问题1:无法获取body内容 问题原因分析 在使用过程中碰到过滤器中获取的内容一直都是空的,尝试了网上的各种解析body内容的方法,但是得到结果都是一样,死活获取不到body数据,一度很崩溃.后来进行了各种尝试,最终发现使用不同的spring boot版本和spring cloud版本,对结果影响很大. 最佳实践 方案1:降低版本 springboot版本:2.0.5-RELEASE springcloud版本:Finchley.R

Spring Cloud Gateway 结合配置中心限流

前言 假设你领导给你安排了一个任务,具体需求如下: 针对具体的接口做限流 不同接口限流的力度可以不同 可以动态调整限流配置,实时生效 如果你接到上面的任务,你会怎么去设计+实现呢? 每个人看待问题的角度不同,自然思考出来的方案也不同,正所谓条条大路通罗马,能到达目的地的路那就是一条好路. 如何分析需求 下面我给出我的实现方式,仅供各位参考,大牛请忽略. 具体问题具体分析,针对需求点,分别去做分析. 需求一 "如何针对具体的接口做限流" 这个在上篇文章中也有讲过,只需要让KeyResol

最全面的改造Zuul网关为Spring Cloud Gateway(包含Zuul核心实现和Spring Cloud Gateway核心实现)

前言: 最近开发了Zuul网关的实现和Spring Cloud Gateway实现,对比Spring Cloud Gateway发现后者性能好支持场景也丰富.在高并发或者复杂的分布式下,后者限流和自定义拦截也很棒. 提示: 本文主要列出本人开发的Zuul网关核心代码以及Spring Cloud Gateway核心代码实现.因为本人技术有限,主要是参照了 Spring Cloud Gateway 如有不足之处还请见谅并留言指出. 1:为什么要做网关 (1)网关层对外部和内部进行了隔离,保障了后台服

微服务下使用网关 Spring Cloud Gateway

Spring Cloud Gateway 工作原理 客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特定的请求过滤器链. 过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求之前或之后执行逻辑.所有 "pre" 过滤器逻辑先执行,然后执行代理请求,代理请求完成后,执行 "post" 过滤器逻辑. 如何启动 Spring Cloud Gateway 1.新建 Maven

Spring Cloud Gateway初体验

前段时间项目上打算使用gateway替换掉zuul1.0于是我简单的体验了一下. gateway是什么:Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式.这里需要注意一下gateway使用的netty+webflux实现,不要加入web依赖,需要加入webflux依赖. gatewa

Spring Cloud Gateway服务网关

原文:https://www.cnblogs.com/ityouknow/p/10141740.html Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSockets,Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets,支持限流等新特性. Spring Clou

API网关spring cloud gateway和负载均衡框架ribbon实战

通常我们如果有一个服务,会部署到多台服务器上,这些微服务如果都暴露给客户,是非常难以管理的,我们系统需要有一个唯一的出口,API网关是一个服务,是系统的唯一出口.API网关封装了系统内部的微服务,为客户端提供一个定制的API.客户端只需要调用网关接口,就可以调用到实际的微服务,实际的服务对客户不可见,并且容易扩展服务. API网关可以结合ribbon完成负载均衡的功能,可以自动检查微服务的状况,及时剔除或者加入某个微服务到可用服务列表.此外网关可以完成权限检查.限流.统计等功能.下面我们将一一完

spring cloud gateway自定义过滤器

在API网关spring cloud gateway和负载均衡框架ribbon实战文章中,主要实现网关与负载均衡等基本功能,详见代码.本节内容将继续围绕此代码展开,主要讲解spring cloud gateway自定义过滤器的功能.本节内容的代码也会提交到GitHub上,注意提交的内容. 本节主要讲解全局过滤器和局部过滤器.注意下面的示例不能作为生产环境的代码,只是简单的演示自定义过滤器的使用方式,无需纠结实现的功能是否完善.下面主要针对不同的过滤器选择几种场景进行代码演示,不代表某个场景就必须

跟我学SpringCloud | 第十二篇:Spring Cloud Gateway初探

SpringCloud系列教程 | 第十二篇:Spring Cloud Gateway初探 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 前面我们在聊服务网关Zuul的时候提到了Gateway,那么Zuul和Gateway都是服务网关,这两个有什么区别呢? 1. Zuul和Gateway的恩怨情仇 1.1 背景 Zuul是Netflix开源的一个项目,Spring只是将Zuul集成在了Spring