详解Spring Cloud Gateway修改请求和响应body的内容

 更新时间:2021年09月06日 09:39:48   作者:程序员欣宸  
这篇文章主要介绍了Spring Cloud Gateway修改请求和响应body的内容的相关资料,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 作为《Spring Cloud Gateway实战》系列的第九篇,咱们聊聊如何用Spring Cloud Gateway修改原始请求和响应内容,以及修改过程中遇到的问题
  • 首先是修改请求body,如下图,浏览器是请求发起方,真实参数只有user-id,经过网关时被塞入字段user-name,于是,后台服务收到的请求就带有user-name字段了

在这里插入图片描述

其次是修改响应,如下图,服务提供方provider-hello的原始响应只有response-tag字段,经过网关时被塞入了gateway-response-tag字段,最终浏览器收到的响应就是response-tag和gateway-response-tag两个字段:

在这里插入图片描述

总的来说,今天要做具体事情如下:

  1. 准备工作:在服务提供者的代码中新增一个web接口,用于验证Gateway的操作是否有效
  2. 介绍修改请求body和响应body的套路
  3. 按套路开发一个过滤器(filter),用于修改请求的body
  4. 按套路开发一个过滤器(filter),用于修改响应的body
  5. 思考和尝试:如何从Gateway返回错误?

在实战过程中,咱们顺便搞清楚两个问题:

  1. 代码配置路由时,如何给一个路由添加多个filter?
  2. 代码配置路由和yml配置是否可以混搭,两者有冲突吗?

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议

这个git项目中有多个文件夹,本篇的源码在spring-cloud-tutorials文件夹下,如下图红框所示:

在这里插入图片描述

spring-cloud-tutorials文件夹下有多个子工程,本篇的代码是gateway-change-body,如下图红框所示:

在这里插入图片描述

准备工作

为了观察Gateway能否按预期去修改请求和响应的body,咱们给服务提供者provider-hello增加一个接口,代码在Hello.java中,如下:

 @PostMapping("/change")
    public Map<String, Object> change(@RequestBody Map<String, Object> map) {
        map.put("response-tag", dateStr());
        return map;
    }
  • 可见新增的web接口很简单:将收到的请求数据作为返回值,在里面添加了一个键值对,然后返回给请求方,有了这个接口,咱们就能通过观察返回值来判断Gateway对请求和响应的操作是否生效
  • 来试一下,先启动nacos(provider-hello需要的)
  • 再运行provider-hello应用,用Postman向其发请求试试,如下图,符合预期:

在这里插入图片描述

  • 准备工作已完成,开始开发吧

修改请求body的套路

  1. 修改请求body是通过自定义filter实现的
  2. 配置路由及其filter的时候,有yml配置文件和代码配置两种方式可以配置路由,官方文档给出的demo是代码配置的,因此今天咱们也参考官方做法,通过代码来配置路由和过滤器
  3. 在代码配置路由的时候,调用filters方法,该方法的入参是个lambda表达式
  4. 此lambda表达式固定调用modifyRequestBody方法,咱们只要定义好modifyRequestBody方法的三个入参即可
  5. modifyRequestBody方法的第一个入参是输入类型
  6. 第二个入参是返回类型
  7. 第三个是RewriteFunction接口的实现,这个代码需要您自己写,内容是将输入数据转换为返回类型数据具体逻辑,咱们来看官方Demo,也就是上述套路了:
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org")
            .filters(f -> f.prefixPath("/httpbin")
                .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
                    (exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri))
        .build();
}

修改响应body的套路

  • 用Spring Cloud Gateway修改响应body的套路和前面的请求body如出一辙
  • 通过代码来配置路由和过滤器
  • 在代码配置路由的时候,调用filters方法,该方法的入参是个lambda表达式
  • 此lambda表达式固定调用modifyResponseBody方法,咱们只要定义好modifyResponseBody方法的三个入参即可
  • modifyRequestBody方法的第一个入参是输入类型
  • 第二个入参是返回类型
  • 第三个是RewriteFunction接口的实现,这个代码要您自己写,内容是将输入数据转换为返回类型数据具体逻辑,咱们来看官方Demo,其实就是上述套路:
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
            .filters(f -> f.prefixPath("/httpbin")
                .modifyResponseBody(String.class, String.class,
                    (exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri))
        .build();
}

套路总结出来了,接下来,咱们一起撸代码?

按套路开发一个修改请求body的过滤器(filter)

  • 废话不说,在父工程spring-cloud-tutorials下新建子工程gateway-change-body,pom.xml无任何特殊之处,注意依赖spring-cloud-starter-gateway即可
  • 启动类毫无新意:
package com.bolingcavalry.changebody;

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

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

配置文件千篇一律:

server:
  #服务端口
  port: 8081
spring:
  application:
    name: gateway-change-body

然后是核心逻辑:修改请求body的代码,既RewriteFunction的实现类,代码很简单,将原始的请求body解析成Map对象,取出user-id字段,生成user-name字段放回map,apply方法返回的是个Mono:

package com.bolingcavalry.changebody.function;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Map;


@Slf4j
public class RequestBodyRewrite implements RewriteFunction<String, String> {

    private ObjectMapper objectMapper;

    public RequestBodyRewrite(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    /**
     * 根据用户ID获取用户名称的方法,可以按实际情况来内部实现,例如查库或缓存,或者远程调用
     * @param userId
     * @return
     */
    private  String mockUserName(int userId) {
        return "user-" + userId;
    }

    @Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {
            Map<String, Object> map = objectMapper.readValue(body, Map.class);

            // 取得id
            int userId = (Integer)map.get("user-id");

            // 得到nanme后写入map
            map.put("user-name", mockUserName(userId));

            // 添加一个key/value
            map.put("gateway-request-tag", userId + "-" + System.currentTimeMillis());

            return Mono.just(objectMapper.writeValueAsString(map));
        } catch (Exception ex) {
            log.error("1. json process fail", ex);
            // json操作出现异常时的处理
            return Mono.error(new Exception("1. json process fail", ex));
        }
    }
}

然后是按部就班的基于代码实现路由配置,重点是lambda表达式执行modifyRequestBody方法,并且将RequestBodyRewrite作为参数传入:

package com.bolingcavalry.changebody.config;

import com.bolingcavalry.changebody.function.RequestBodyRewrite;
import com.bolingcavalry.changebody.function.ResponseBodyRewrite;
import com.fasterxml.jackson.databind.ObjectMapper;
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 org.springframework.http.MediaType;
import reactor.core.publisher.Mono;

@Configuration
public class FilterConfig {
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper) {
        return builder
                .routes()
                .route("path_route_change",
                        r -> r.path("/hello/change")
                                .filters(f -> f
                                        .modifyRequestBody(String.class,String.class,new RequestBodyRewrite(objectMapper))
                                        )
                        .uri("http://127.0.0.1:8082"))
                .build();
    }
}

代码写完了,运行工程gateway-change-body,在postman发起请求,得到响应如下图,红框中可见Gateway添加的内容已成功:

在这里插入图片描述

现在修改请求body已经成功,接下来再来修改服务提供者响应的body

修改响应body

  • 接下来开发修改响应body的代码
  • 新增RewriteFunction接口的实现类ResponseBodyRewrite.java
package com.bolingcavalry.changebody.function;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Map;

@Slf4j
public class ResponseBodyRewrite implements RewriteFunction<String, String> {

    private ObjectMapper objectMapper;

    public ResponseBodyRewrite(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public Publisher<String> apply(ServerWebExchange exchange, String body) {
        try {
            Map<String, Object> map = objectMapper.readValue(body, Map.class);

            // 取得id
            int userId = (Integer)map.get("user-id");

            // 添加一个key/value
            map.put("gateway-response-tag", userId + "-" + System.currentTimeMillis());

            return Mono.just(objectMapper.writeValueAsString(map));
        } catch (Exception ex) {
            log.error("2. json process fail", ex);
            return Mono.error(new Exception("2. json process fail", ex));
        }
    }
}

路由配置代码中,lambda表达式里面,filters方法内部调用modifyResponseBody,第三个入参是ResponseBodyRewrite:

package com.bolingcavalry.changebody.config;

import com.bolingcavalry.changebody.function.RequestBodyRewrite;
import com.bolingcavalry.changebody.function.ResponseBodyRewrite;
import com.fasterxml.jackson.databind.ObjectMapper;
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 org.springframework.http.MediaType;
import reactor.core.publisher.Mono;

@Configuration
public class FilterConfig {

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper) {
        return builder
                .routes()
                .route("path_route_change",
                        r -> r.path("/hello/change")
                                .filters(f -> f
                                        .modifyRequestBody(String.class,String.class,new RequestBodyRewrite(objectMapper))
                                        .modifyResponseBody(String.class, String.class, new ResponseBodyRewrite(objectMapper))
                                        )
                        .uri("http://127.0.0.1:8082"))
                .build();
    }
}

还记得咱们的第一个问题吗?通过上面的代码,您应该已经看到了答案:用代码配置路由时,多个过滤器的配置方法就是在filters方法中反复调用内置的过滤器相关API,下图红框中的都可以:

在这里插入图片描述

运行服务,用Postman验证效果,如下图红框,Gateway在响应body中成功添加了一个key&value:

在这里插入图片描述

代码配置路由和yml配置是否可以混搭?

前面有两个问题,接下来回答第二个,咱们在application.yml中增加一个路由配置:

server:
  #服务端口
  port: 8081
spring:
  application:
    name: gateway-change-body
  cloud:
    gateway:
      routes:
        - id: path_route_str
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/str

把gateway-change-body服务启动起来,此时已经有了两个路由配置,一个在代码中,一个在yml中,先试试yml中的这个,如下图没问题:

在这里插入图片描述

再试试代码配置的路由,如下图,结论是代码配置路由和yml配置可以混搭

在这里插入图片描述

如何处理异常

  • 还有个问题必须要面对:修改请求或者响应body的过程中,如果发现问题需要提前返回错误(例如必要的字段不存在),代码该怎么写?
  • 咱们修改请求body的代码集中在RequestBodyRewrite.java,增加下图红框内容:

在这里插入图片描述

  • 再来试试,这次请求参数中不包含user-id,收到Gateway返回的错误信息如下图:

在这里插入图片描述

看看控制台,能看到代码中抛出的异常信息:

在这里插入图片描述

  • 此时,聪明的您应该发现问题所在了:咱们想告诉客户端具体的错误,但实际上客户端收到的是被Gateway框架处理后的内容
  • 篇幅所限,上述问题从分析到解决的过程,就留给下一篇文章吧
  • 本篇的最后,请容许欣宸唠叨两句,聊聊为何要网关来修改请求和响应body的内容,如果您没兴趣还请忽略

网关(Gateway)为什么要做这些?

看过开篇的两个图,聪明的您一定发现了问题:为什么要破坏原始数据,一旦系统出了问题如何定位是服务提供方还是网关?

  • 按照欣宸之前的经验,尽管网关会破坏原始数据,但只做一些简单固定的处理,一般以添加数据为主,网关不了解业务,最常见的就是鉴权、添加身份或标签等操作
  • 前面的图中确实感受不到网关的作用,但如果网关后面有多个服务提供者,如下图,这时候诸如鉴权、获取账号信息等操作由网关统一完成,比每个后台分别实现一次更有效率,后台可以更加专注于自身业务:

在这里插入图片描述

  • 经验丰富的您可能会对我的狡辩不屑一顾:网关统一鉴权、获取身份,一般会把身份信息放入请求的header中,也不会修改请求和响应的内容啊,欣宸前面的一堆解释还是没说清楚为啥要在网关位置修改请求和响应的内容!
  • 好吧,面对聪明的您,我摊牌了:本篇只是从技术上演示Spring Cloud Gateway如何修改请求和响应内容,请不要将此技术与实际后台业务耦合;

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

到此这篇关于Spring Cloud Gateway修改请求和响应body的内容的文章就介绍到这了,更多相关Spring Cloud Gateway请求响应body内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java+opencv3.2.0实现模板匹配

    Java+opencv3.2.0实现模板匹配

    这篇文章主要为大家详细介绍了Java+opencv3.2.0实现模板匹配的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • SpringMVC中的@RequestMapping注解解析

    SpringMVC中的@RequestMapping注解解析

    这篇文章主要介绍了SpringMVC中的@RequestMapping注解解析,SpringMVC使用@RequestMapping注解为控制器指定可以处理哪些 URL 请求,在控制器的类定义及方法定义处都可标注@RequestMapping,需要的朋友可以参考下
    2023-12-12
  • SpringBoot mybatis 实现多级树形菜单的示例代码

    SpringBoot mybatis 实现多级树形菜单的示例代码

    这篇文章主要介绍了SpringBoot mybatis 实现多级树形菜单的示例代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • Java 批量删除Word中的空白段落示例代码

    Java 批量删除Word中的空白段落示例代码

    这篇文章主要介绍了Java 批量删除Word中的空白段落,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • java input 调用手机相机和本地照片上传图片到服务器然后压缩的方法

    java input 调用手机相机和本地照片上传图片到服务器然后压缩的方法

    今天小编就为大家分享一篇java input 实现调用手机相机和本地照片上传图片到服务器然后压缩的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • spring中的FactoryBean代码示例

    spring中的FactoryBean代码示例

    这篇文章主要介绍了spring中的FactoryBean代码示例,涉及FactoryBean的实现等相关内容,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • 深入浅析 Spring Security 缓存请求问题

    深入浅析 Spring Security 缓存请求问题

    这篇文章主要介绍了 Spring Security 缓存请求问题,本文通过实例文字相结合的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2019-04-04
  • 浅谈java中的路径表示

    浅谈java中的路径表示

    下面小编就为大家带来一篇浅谈java中的路径表示。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • Java设置String字符串编码方法详解

    Java设置String字符串编码方法详解

    因为String字符串很常用,所以我们在使用它的过程中,可能会面临各种问题,比如”中文乱码“问题等,那么为什么中文会乱码?本文将给大家介绍一下Java如何设置String字符串编码,来避免和解决这一常见问题,需要的朋友可以参考下
    2023-05-05
  • 学习在一台新电脑上配置JAVA开发环境

    学习在一台新电脑上配置JAVA开发环境

    本文主要介绍了如何在一台新电脑上配置JAVA开发环境,每一个步骤都有对应的截图和文字说明,需要的朋友可以参考下
    2015-07-07

最新评论