解决spring cloud gateway 获取body内容并修改的问题

之前写过一篇文章,如何获取body的内容。

Spring Cloud Gateway获取body内容,不影响GET请求

确实能够获取所有body的内容了,不过今天终端同学调试接口的时候和我说,遇到了400的问题,报错是这样的HTTP method names must be tokens,搜了一下,都是说https引起的。可我的项目还没用https,排除了。

想到是不是因为修改了body内容导致的问题,试着不修改body的内容,直接传给微服务,果然没有报错了。

问题找到,那就好办了,肯定是我新构建的REQUEST对象缺胳膊少腿了,搜索一通之后发现一篇大牛写的文章:

Spring Cloud Gateway(读取、修改 Request Body)

这里要再次表扬一下古哥,同样是中文文章,度娘却搜不到

不过文章中的spring cloud版本是

Spring Cloud: Greenwich.RC2

我本地是最新的Release版本RS3,并不能完全照搬过来,不过算是给了很大的启发(如何获取body以及重构)

下面给出我的代码

网关中对body内容进行解密然后验签

/**
 * @author tengdj
 * @date 2019/8/13 11:08
 * 设备接口验签,解密
 **/
@Slf4j
public class TerminalSignFilter implements GatewayFilter, Ordered {

 private static final String AES_SECURTY = "XXX";
 private static final String MD5_SALT = "XXX";

 @Override
 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
 exchange.getAttributes().put("startTime", System.currentTimeMillis());
 if (exchange.getRequest().getMethod().equals(HttpMethod.POST)) {
  //重新构造request,参考ModifyRequestBodyGatewayFilterFactory
  ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
  MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
  //重点
  Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
  //因为约定了终端传参的格式,所以只考虑json的情况,如果是表单传参,请自行发挥
  if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)) {
   JSONObject jsonObject = JSONUtil.toJO(body);
   String paramStr = jsonObject.getString("param");
   String newBody;
   try{
   newBody = verifySignature(paramStr);
   }catch (Exception e){
   return processError(e.getMessage());
   }
   return Mono.just(newBody);
  }
  return Mono.empty();
  });
  BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
  HttpHeaders headers = new HttpHeaders();
  headers.putAll(exchange.getRequest().getHeaders());
  //猜测这个就是之前报400错误的元凶,之前修改了body但是没有重新写content length
  headers.remove("Content-Length");
  //MyCachedBodyOutputMessage 这个类完全就是CachedBodyOutputMessage,只不过CachedBodyOutputMessage不是公共的
  MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);
  return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
  ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);
  return returnMono(chain, exchange.mutate().request(decorator).build());
  }));
 } else {
  //GET 验签
  MultiValueMap<String, String> map = exchange.getRequest().getQueryParams();
  if (!CollectionUtils.isEmpty(map)) {
  String paramStr = map.getFirst("param");
  try{
   verifySignature(paramStr);
  }catch (Exception e){
   return processError(e.getMessage());
  }
  }
  return returnMono(chain, exchange);
 }
 }

 @Override
 public int getOrder() {
 return 1;
 }

 private Mono<Void> returnMono(GatewayFilterChain chain,ServerWebExchange exchange){
 return chain.filter(exchange).then(Mono.fromRunnable(()->{
  Long startTime = exchange.getAttribute("startTime");
  if (startTime != null){
  long executeTime = (System.currentTimeMillis() - startTime);
  log.info("耗时:{}ms" , executeTime);
  log.info("状态码:{}" , Objects.requireNonNull(exchange.getResponse().getStatusCode()).value());
  }
 }));
 }

 private String verifySignature(String paramStr) throws Exception{
 log.info("密文{}", paramStr);
 String dParamStr;
 try{
  dParamStr = AESUtil.decrypt(paramStr, AES_SECURTY);
 }catch (Exception e){
  throw new Exception("解密失败!");
 }
 log.info("解密得到字符串{}", dParamStr);
 String signature = SignUtil.sign(dParamStr, MD5_SALT);
 log.info("重新加密得到签名{}", signature);
 JSONObject jsonObject1 = JSONUtil.toJO(dParamStr);
 if (!jsonObject1.getString("signature").equals(signature)) {
  throw new Exception("签名不匹配!");
 }
 return jsonObject1.toJSONString();
 }

 private Mono processError(String message) {
  /*exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
  return exchange.getResponse().setComplete();*/
 log.error(message);
 return Mono.error(new Exception(message));
 }

 ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, MyCachedBodyOutputMessage outputMessage) {
 return new ServerHttpRequestDecorator(exchange.getRequest()) {
  public HttpHeaders getHeaders() {
  long contentLength = headers.getContentLength();
  HttpHeaders httpHeaders = new HttpHeaders();
  httpHeaders.putAll(super.getHeaders());
  if (contentLength > 0L) {
   httpHeaders.setContentLength(contentLength);
  } else {
   httpHeaders.set("Transfer-Encoding", "chunked");
  }
  return httpHeaders;
  }
  public Flux<DataBuffer> getBody() {
  return outputMessage.getBody();
  }
 };
 }
}

代码到这里就结束了,希望看到的朋友可以少走点弯路,少踩点坑。

补充知识:springcloud gateway之addRequestParameter详细使用及踩坑注意

SpringCloud的网关gateway提供了多个内置Filter,其中addRequestHeader是添加header的,这个无坑,比较简单。还有一个添加参数的,addRequestParameter,这个就有点问题了。具体往下看。

版本如下,请注意Springboot版本,这是本篇Post请求异常的关键。

1 对应的uri只能是get请求

看一个简单的示例,addRequestParameter,我们匹配/addParam请求,并将请求转发至http://localhost:8888/header

这个是8888端口的服务

如果发起Get请求到网关,那么可以正常请求,一切OK。此时,调用发起方和最终的服务提供方都是Get请求,没有问题。

如果发起的请求是Get,但是服务提供方是如下的Post。

注意,这里我用了PostMapping,然后分别启动两个工程,再访问localhost:8080/addParam,而后会报错,这个也可以理解。

但是,如果调用发起方和服务提供方都是Post请求,理论上应该也是OK的。

但是事实上不是的

网关程序会报错如下:

这个就很尴尬了,作为一个网关,居然在代理非Get请求时出现异常,必然是不能容忍的。

经过一番探索,发现这是Springboot不同版本的原因导致,在Springboot2.0.5之前,不存在该问题,之后就有这种问题了。需要加以注意,解决方案会在下一篇写。

2 添加的参数value值必须合法(不能含有空格)

上面已经知道了,addRequestParameter对应的后端请求是Get型,那么明显添加的parameter只能是Get请求支持的,能在浏览器地址栏直接敲上去合法的。

这里,我将value的值变成带空格的,然后去访问后端的服务。

然后会发现控制台报错,Invalid URI query。这是因为get请求的value值不能含有非法字符.

同理

像这样的,后台接收的是

如果是这样的参数

后台这样

结果是:

这样就可以添加多个parameter了。

同时添加header和parameter

结束了addRequestParameter的说明,我们可以来看看,假如某个path,既想addHeader,又想addParameter,而系统的这两个方法,都是一个path只能搭配一个add的filter,即便写了两个也不生效,如

结果就只有header被打印了

那么就是想同时添加header和parameter该怎么办呢。

貌似通过java代码是无法实现了,好在可以通过yml配置来实现。

spring:
 cloud:
  gateway:
   routes:
   - id: header
    uri: http://localhost:8888/header
    filters:
    - AddRequestHeader=NewHeader, Bar
    - AddRequestParameter=NewParam, Param
    predicates:
    - Path=/header

在yml就可以在filters里,添加多个filter了,注意不要写错了filter的名字。

可以看到结果

发现header和param都传过来了。

以上这篇解决spring cloud gateway 获取body内容并修改的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Cloud Gateway 记录请求应答数据日志操作

    我就废话不多说了,大家还是直接看代码吧~ public class GatewayContext { public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext"; /** * cache json body */ private String cacheBody; /** * cache formdata */ private MultiValueMap<String, String> f

  • 基于Nacos实现Spring Cloud Gateway实现动态路由的方法

    简介 该文档主要介绍以Nacos为配置中心,实现Spring Cloud GateWay 实现动态路由的功能.Spring Cloud Gateway启动时候,就将路由配置和规则加载到内存里,无法做到不重启网关就可以动态的对应路由的配置和规则进行增加,修改和删除.通过nacos的配置下发的功能可以实现在不重启网关的情况下,实现动态路由. 集成 Spring Cloud GateWay集成 spring-cloud-starter-gateway:路由转发.请求过滤(权限校验.限流以及监控等) s

  • 详解SpringCloudGateway内存泄漏问题

    SpringCloudGateway内存泄漏问题 项目完善差不多,在进入压力测试阶段期间,发现了gateway有内存泄漏问题,问题发现的起因是,当时启动一台gateway,一台对应的下游应用服务,在压力测试期间,发现特别不稳定,并发量时高时低,而且会有施压机卡住的现象,然后找到容器对应的宿主机,并使用container stats命令观察内存,经过观察发现,压力测试时内存会暴涨,并由于超过限制最大内存导致容器挂掉(这里由于用的swarm所以会自动选择节点重启)最终发现由于之前测试服务器配置低,所

  • springcloud项目改名的操作方法

    1.原来项目结构 2.子模块改名 2.1rename操作 2.2修改子模块的pom文件 2.3修改父模块pom文件module里面子模块的名称,如果没有modules,就自己加上去 3.父模块改名字 3.1右键中父模块,Rename操作,结果如下图 3.2修改pom文件 3.3修改子模块parent标签父模块的名称(可选) 3.4退出idea 3.5重命名项目文件夹名称 3.6找到项目根目录,删除.idea文件 3.7打开idea重新导入项目即可 3.8由于删除了.idea文件,所有idea存储

  • 解决spring cloud gateway 获取body内容并修改的问题

    之前写过一篇文章,如何获取body的内容. Spring Cloud Gateway获取body内容,不影响GET请求 确实能够获取所有body的内容了,不过今天终端同学调试接口的时候和我说,遇到了400的问题,报错是这样的HTTP method names must be tokens,搜了一下,都是说https引起的.可我的项目还没用https,排除了. 想到是不是因为修改了body内容导致的问题,试着不修改body的内容,直接传给微服务,果然没有报错了. 问题找到,那就好办了,肯定是我新构

  • 解决Spring Cloud Gateway获取body内容,不影响GET请求的操作

    废话 这几天换了新工作,需要重新开发一套系统,技术选用Spring Cloud.在对接终端接口的时候要做验签,就涉及到在网关做拦截器,然后取出BODY里面的数据. 网上找了几个方法,有的拿不到数据,有的拿到数据之后不支持GET请求了.没有一个合理的解决办法,最后想到在动态路由构建的时候可以指定METHOD,于是有了如下解决办法 解决 @Bean public RouteLocator vmRouteLocator(RouteLocatorBuilder builder) { return bui

  • Spring Cloud Gateway 获取请求体(Request Body)的多种方法

    一.直接在全局拦截器中获取,伪代码如下 private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest){ Flux<DataBuffer> body = serverHttpRequest.getBody(); AtomicReference<String> bodyRef = new AtomicReference<>(); body.subscribe(buffer -> {

  • Spring Cloud Gateway全局通用异常处理的实现

    为什么需要全局异常处理 在传统 Spring Boot 应用中, 我们 @ControllerAdvice 来处理全局的异常,进行统一包装返回 // 摘至 spring cloud alibaba console 模块处理 @ControllerAdvice public class ConsoleExceptionHandler { @ExceptionHandler(AccessException.class) private ResponseEntity<String> handleAc

  • Nacos+Spring Cloud Gateway动态路由配置实现步骤

    目录 前言 一.Nacos环境准备 1.启动Nacos配置中心并创建路由配置 2.连接Nacos配置中心 二.项目构建 1.项目结构 2.编写测试代码 三.测试动态网关配置 1.启动服务,观察注册中心 2.访问网关,观察服务日志 四.总结 前言 Nacos最近项目一直在使用,其简单灵活,支持更细粒度的命令空间,分组等为麻烦复杂的环境切换提供了方便:同时也很好支持动态路由的配置,只需要简单的几步即可.在国产的注册中心.配置中心中比较突出,容易上手,本文通过gateway.nacos-consume

  • Spring Cloud Gateway 整合 knife4j 聚合接口文档功能

    当系统中微服务数量越来越多时,如果任由这些服务散落在各处,那么最终管理每个项目的接口文档将是一件十分麻烦的事情,单是记住所有微服务的接口文档访问地址就是一件苦差事了.当如果能够将所有微服务项目的接口文档都统一汇总在同一个可视化页面,那么将大大减少我们的接口文档管理维护工作,为此,我们可以基于 Spring Cloud Gateway 网关 + nacos + knife4j 对所有微服务项目的接口文档进行聚合,从而实现我们想要的文档管理功能 注:本案例需要 springboot 提前整合 nac

  • Spring Cloud Gateway自定义异常处理Exception Handler

    版本: Spring Cloud 2020.0.3 常见的方法有 实现自己的 DefaultErrorWebExceptionHandler 或 仅实现ErrorAttributes. 方法1: ErrorWebExceptionHandler (仅供示意) 自定义一个 GlobalErrorAttributes: @Component public class GlobalErrorAttributes extends DefaultErrorAttributes{ @Override pub

  • Spring Cloud Gateway自定义异常处理Exception Handler的方法小结

    版本: Spring Cloud 2020.0.3 常见的方法有 实现自己的 DefaultErrorWebExceptionHandler 或 仅实现ErrorAttributes. 方法1: ErrorWebExceptionHandler (仅供示意) 自定义一个 GlobalErrorAttributes: @Component public class GlobalErrorAttributes extends DefaultErrorAttributes{ @Override pub

  • Spring Cloud Gateway不同频率限流的解决方案(每分钟,每小时,每天)

    SpringCloud Gateway 简介 SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式. SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuu

随机推荐