码迷,mamicode.com
首页 > 编程语言 > 详细

springcloud gateway 获取Post请求体

时间:2021-06-02 19:06:50      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:cached   build   app   react   忘记   前端   方法   循环   一起   

方法一:通过body.subscribe从Flux<DataBuffer>中获取请求body/返回结果的body

private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
 
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
    }

方式二:

方案1

选择是使用代码的形式配置路由,在路由里面配置ReadBodyPredicate预言类。

RouteLocatorBuilder.Builder serviceProvider = builder.
                routes().route("info-service",
                    r -> r.readBody(String.class, requestBody -> {
                        log.info("requestBody is {}", requestBody);
                        return true;
                }).and().path("/info/test").
                        filters(f -> {
                            f.filter(requestFilter);
                            return f;
                        })
                        .uri("info-service"));
RouteLocator routeLocator = serviceProvider.build();

然后通过全局Filter中的 exchange的方式获取属性的形式获取

String body = exchange.getAttribute("cachedRequestBodyObject");
获取post请求的字符串形式以后呢,转换也比较麻烦,如果不是form表单形式的post请求还比较好转换,如果是form表单的形式,再加上"multipart/form-data"的形式,直接获取的就是http请求过来的数据,没有封装。

方案2 ReadBodyPredicateFactory方式

通过继承ReadBodyPredicateFactory的形式,可以达到route编码的形式

参考代码地址:https://github.com/spring-cloud/spring-cloud-gateway/issues/1307

继承ReadBodyPredicateFactory

@Component
public class GatewayReadBodyPredicate extends ReadBodyPredicateFactory {
    public static final String REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
    @Override
    public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
        config.setPredicate(t -> true);
        return super.applyAsync(config);
    }
}

 

配置:

routes:
        - id: info-service  
          uri: lb://info-service
          predicates:
            - Path=/info/test/**
            - name: GatewayReadBodyPredicate
              args:
                inClass: java.lang.String

springcloud-gateway,github上给出的配置中,可以将配置信息中的参数的inClass设置为org.springframework.util.MultiValueMap,但是在实际使用过程中,如果是文件和form表单一起提交的话,抛出异常。

github截图

技术图片

异常信息如下:

org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE
"Content type ‘multipart/form-data;boundary=-------------------------
-873485462073103209590464‘ not supported for bodyType=org.springframework.util.MultiValueMap<?, ?>"
1
2
3
个人没有找到合适的类型来接收请求内容类型是**“multipart/form-data”**的java类型参数,最终放弃使用这种形式。

方案三:通过Filter的形式,转换新的request请求,然后获取body内容。

最近在做网关改造,想要通过Gateway过滤器获取ResponseBody的值,查看了网上的帖子和官网内容:

帖子:https://cloud.tencent.com/developer/article/1384111

官网:https://github.com/spring-cloud/spring-cloud-gateway/blob/master/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactory.java

但在实际操作中却掉坑里了,居然不起作用,就是不进重写但writeWith方法

示例:

1 @Component
 2 @Order(-2)
 3 public class EncryptResponseBodyFilter implements GlobalFilter {
 4 
 5     @Override
 6     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
 7         ServerHttpResponse originalResponse = exchange.getResponse();
 8         DataBufferFactory bufferFactory = originalResponse.bufferFactory();
 9         ServerHttpResponseDecorator response = new ServerHttpResponseDecorator(originalResponse) {
10 
11             @Override
12             public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
13                 if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
14 
15                     Flux<? extends DataBuffer> fluxBody = Flux.from(body);
16                     return super.writeWith(fluxBody.map(dataBuffer -> {
17                         // probably should reuse buffers
18                         byte[] content = new byte[dataBuffer.readableByteCount()];
19                         dataBuffer.read(content);
20                         //释放掉内存
21                         DataBufferUtils.release(dataBuffer);
22                         String s = new String(content, Charset.forName("UTF-8"));
23                         //TODO,s就是response的值,想修改、查看就随意而为了
24                         byte[] uppedContent = s.getBytes();
25                         return bufferFactory.wrap(uppedContent);
26                     }));
27                 }
28                 return super.writeWith(body);
29             }
30 
31             @Override
32             public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
33                 return writeWith(Flux.from(body)
34                         .flatMapSequential(p -> p));
35             }
36         };37         return chain.filter(exchange.mutate().response(response).build());
38     }

 

后来与帖子和官网代码内容对比,只有Order的方式不同,就换了下实现Ordered重写getOrder方法方式试试,结果出乎意料,居然好用了。order<-2才行。

后来发现响应数据存在分段响应的问题:既然返回内容过大存在分段传出,比如json传输了一半,另一半丢失,所以我们需要考虑报文拼装方式,只有报文全部返回才统一返回给前端。大报文我们需要对fluxBody流循环拼接处理,所以改变方式如下,把fluxBody.map变为fluxBody.buffer().map,从而可以foreach循环Body体了。《参考:https://blog.csdn.net/fayeyiwang/article/details/9137// JSON响应数据拼接容器》

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator response = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
                    // 获取ContentType,判断是否返回JSON格式数据
                    String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    if(StringUtils.isNotBlank(originalResponseContentType) && originalResponseContentType.contains("application/json")) {
                        // 如果需要加密才进行处理
                        if (canEncrypt(exchange.getRequest())) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                                DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                                DataBuffer join = dataBufferFactory.join(dataBuffers);
                                byte[] content = new byte[join.readableByteCount()];
                                join.read(content);
                                DataBufferUtils.release(join);
                                String responseData = new String(content, Charsets.UTF_8);
//                                responseData = beforeBodyWriteInternal(responseData, exchange.getRequest());
                                responseData = "\"" + beforeBodyWriteInternal(responseData, exchange.getRequest()) + "\""; //自定义处理返回结果的方法
                                byte[] uppedContent = new String(responseData.getBytes(), Charset.forName("UTF-8")).getBytes();
                                originalResponse.getHeaders().setContentLength(uppedContent.length);
                                // 设置加密头标识
                                originalResponse.getHeaders().set("encrypt", "true");
                                return bufferFactory.wrap(uppedContent);
                            }));
                        }
                    }
                }
                return super.writeWith(body);
            }
 
            @Override
            public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body)
                        .flatMapSequential(p -> p));
            }
        };
        return chain.filter(exchange.mutate().response(response).build());
    }

踩过的坑

1、响应体报文过大: 起初直接读取buffer的响应信息,包小的情况没有问题,但是包大了会抛出json无法转换异常,因为没能读取完整的响应内容,参考ModifyRequestBodyGatewayFilter,等待buffer全部读完再转为数组,然后执行处理。本质原因是底层的Reactor-Netty的数据块读取大小限制导致获取到的DataBuffer实例里面的数据是不完整的。
2、修改响应信息后,响应的ContentLength会发生变化,忘记修改response中的Content-Length长度,导致前端请求无法获取修改后的响应结果。
flux = flux.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
3、order值必须小于-1,因为覆盖返回响应体,自定义的GlobalFilter必须比NettyWriteResponseFilter处理完后执行。order越小越早进行处理,越晚处理响应结果。

参考:https://blog.csdn.net/lance_lan/article/details/103885177

https://blog.csdn.net/weiwoyonzhe/article/details/90814680

https://www.cnblogs.com/commissar-Xia/p/11651196.html

springcloud gateway 获取Post请求体

标签:cached   build   app   react   忘记   前端   方法   循环   一起   

原文地址:https://www.cnblogs.com/duanxz/p/14832886.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!