Spring Cloud Gateway 获取请求体(Request Body)的多种方法
一、直接在全局拦截器中获取,伪代码如下
privateStringresolveBodyFromRequest(ServerHttpRequestserverHttpRequest){ Fluxbody=serverHttpRequest.getBody(); AtomicReference bodyRef=newAtomicReference<>(); body.subscribe(buffer->{ CharBuffercharBuffer=StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); DataBufferUtils.release(buffer); bodyRef.set(charBuffer.toString()); }); returnbodyRef.get(); }
存在的缺陷:其他拦截器无法再通过该方式获取请求体(因为请求体已被消费),并且会抛出异常
Onlyoneconnectionreceivesubscriberallowed.Causedby:java.lang.IllegalStateException:Onlyoneconnectionreceivesubscriberallowed.
异常原因:实际上spring-cloud-gateway反向代理的原理是,首先读取原请求的数据,然后构造一个新的请求,将原请求的数据封装到新的请求中,然后再转发出去。然而我们在他封装之前读取了一次requestbody,而requestbody只能读取一次。因此就出现了上面的错误。
再者受版本限制
这种方法在spring-boot-starter-parent2.0.6.RELEASE+SpringCloudFinchley.SR2body中生效,
但是在spring-boot-starter-parent2.1.0.RELEASE+SpringCloudGreenwich.M3body中不生效,总是为空
二、先在全局过滤器中获取,然后再把request重新包装,继续向下传递传递
@Override publicGatewayFilterapply(NameValueConfignameValueConfig){ return(exchange,chain)->{ URIuri=exchange.getRequest().getURI(); URIex=UriComponentsBuilder.fromUri(uri).build(true).toUri(); ServerHttpRequestrequest=exchange.getRequest().mutate().uri(ex).build(); if("POST".equalsIgnoreCase(request.getMethodValue())){//判断是否为POST请求 Fluxbody=request.getBody(); AtomicReference bodyRef=newAtomicReference<>(); body.subscribe(dataBuffer->{ CharBuffercharBuffer=StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer()); DataBufferUtils.release(dataBuffer); bodyRef.set(charBuffer.toString()); });//读取requestbody到缓存 StringbodyStr=bodyRef.get();//获取requestbody System.out.println(bodyStr);//这里是我们需要做的操作 DataBufferbodyDataBuffer=stringBuffer(bodyStr); Flux bodyFlux=Flux.just(bodyDataBuffer); request=newServerHttpRequestDecorator(request){ @Override publicFlux getBody(){ returnbodyFlux; } };//封装我们的request } returnchain.filter(exchange.mutate().request(request).build()); }; } protectedDataBufferstringBuffer(Stringvalue){ byte[]bytes=value.getBytes(StandardCharsets.UTF_8); NettyDataBufferFactorynettyDataBufferFactory=newNettyDataBufferFactory(ByteBufAllocator.DEFAULT); DataBufferbuffer=nettyDataBufferFactory.allocateBuffer(bytes.length); buffer.write(bytes); returnbuffer; }
该方案的缺陷:requestbody获取不完整(因为异步原因),只能获取1024B的数据。并且请求体超过1024B,会出现响应超慢(因为我是开启了熔断)。
三、过滤器加路线定位器
翻查源码发现ReadBodyPredicateFactory里面缓存了requestbody的信息,于是在自定义router中配置了ReadBodyPredicateFactory,然后在filter中通过cachedRequestBodyObject缓存字段获取requestbody信息。
/** *@description:获取POST请求的请求体 *ReadBodyPredicateFactory发现里面缓存了requestbody的信息, *于是在自定义router中配置了ReadBodyPredicateFactory *@modified: */ @EnableAutoConfiguration @Configuration publicclassRouteLocatorRequestBoby{ //自定义过滤器 @Resource privateReqTraceFilterreqTraceFilter; @Resource privateRibbonLoadBalancerClientribbonLoadBalancerClient; privatestaticfinalStringSERVICE="/leap/**"; privatestaticfinalStringHTTP_PREFIX="http://"; privatestaticfinalStringCOLON=":"; @Bean publicRouteLocatormyRoutes(RouteLocatorBuilderbuilder){ //通过负载均衡获取服务实例 ServiceInstanceinstance=ribbonLoadBalancerClient.choose("PLATFORM-SERVICE"); //拼接路径 StringBuilderforwardAddress=newStringBuilder(HTTP_PREFIX); forwardAddress.append(instance.getHost()) .append(COLON) .append(instance.getPort()); returnbuilder.routes() //拦截请求类型为POSTContent-Typeapplication/jsonapplication/json;charset=UTF-8 .route(r->r .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE+MediaType.APPLICATION_JSON_UTF8_VALUE) .and() .method(HttpMethod.POST) .and() //获取缓存中的请求体 .readBody(Object.class,readBody->{ returntrue; }) .and() .path(SERVICE) //把请求体传递给拦截器reqTraceFilter .filters(f->{ f.filter(reqTraceFilter); returnf; }) .uri(forwardAddress.toString())).build(); } /** *@description:过滤器,用于获取请求体,和处理请求体业务,列如记录日志 *@modified: */ @Component publicclassReqTraceFilterimplementsGlobalFilter,GatewayFilter,Ordered{ privatestaticfinalStringCONTENT_TYPE="Content-Type"; privatestaticfinalStringCONTENT_TYPE_JSON="application/json"; //获取请求路由详细信息Routeroute=exchange.getAttribute(GATEWAY_ROUTE_BEAN) privatestaticfinalStringGATEWAY_ROUTE_BEAN="org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute"; privatestaticfinalStringCACHE_REQUEST_BODY_OBJECT_KEY="cachedRequestBodyObject"; @Override publicMonofilter(ServerWebExchangeexchange,GatewayFilterChainchain){ ServerHttpRequestrequest=exchange.getRequest(); //判断过滤器是否执行 StringrequestUrl=RequestUtils.getCurrentRequest(request); if(!RequestUtils.isFilter(requestUrl)){ StringbodyStr=""; StringcontentType=request.getHeaders().getFirst(CONTENT_TYPE); Stringmethod=request.getMethodValue(); //判断是否为POST请求 if(null!=contentType&&HttpMethod.POST.name().equalsIgnoreCase(method)&&contentType.contains(CONTENT_TYPE_JSON)){ ObjectcachedBody=exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); if(null!=cachedBody){ bodyStr=cachedBody.toString(); } } if(HttpMethod.GET.name().equalsIgnoreCase(method)){ bodyStr=request.getQueryParams().toString(); } log.info("请求体内容:{}",bodyStr); } returnchain.filter(exchange); } @Override publicintgetOrder(){ return5; } }
该方案优点:这种解决,一不会带来重复读取问题,二不会带来requestbody取不全问题。三在低版本的SpringCloudFinchley.SR2也可以运行。
缺点:不支持multipart/form-data(异常415),这个致命。
四、通过org.springframework.cloud.gateway.filter.factory.rewrite包下有个ModifyRequestBodyGatewayFilterFactory,顾名思义,这就是修改RequestBody的过滤器工厂类。
@Component @Slf4j publicclassReqTraceFilterimplementsGlobalFilter,GatewayFilter,Ordered{ @Resource privateIPlatformFeignClientplatformFeignClient; /** *httpheader,traceId的key名称 */ privatestaticfinalStringREQUESTID="traceId"; privatestaticfinalStringCONTENT_TYPE="Content-Type"; privatestaticfinalStringCONTENT_TYPE_JSON="application/json"; privatestaticfinalStringGATEWAY_ROUTE_BEAN="org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute"; @Override publicMonofilter(ServerWebExchangeexchange,GatewayFilterChainchain){ ServerHttpRequestrequest=exchange.getRequest(); //判断过滤器是否执行 StringrequestUrl=RequestUtils.getCurrentRequest(request); if(!RequestUtils.isFilter(requestUrl)){ StringbodyStr=""; StringcontentType=request.getHeaders().getFirst(CONTENT_TYPE); Stringmethod=request.getMethodValue(); //判断是否为POST请求 if(null!=contentType&&HttpMethod.POST.name().equalsIgnoreCase(method)&&contentType.contains(CONTENT_TYPE_JSON)){ ServerRequestserverRequest=newDefaultServerRequest(exchange); List list=newArrayList<>(); //读取请求体 Mono modifiedBody=serverRequest.bodyToMono(String.class) .flatMap(body->{ //记录请求体日志 finalStringnId=saveRequestOperLog(exchange,body); //记录日志id list.add(nId); returnMono.just(body); }); BodyInserterbodyInserter=BodyInserters.fromPublisher(modifiedBody,String.class); HttpHeadersheaders=newHttpHeaders(); headers.putAll(exchange.getRequest().getHeaders()); headers.remove(HttpHeaders.CONTENT_LENGTH); CachedBodyOutputMessageoutputMessage=newCachedBodyOutputMessage(exchange,headers); returnbodyInserter.insert(outputMessage,newBodyInserterContext()) .then(Mono.defer(()->{ ServerHttpRequestDecoratordecorator=newServerHttpRequestDecorator( exchange.getRequest()){ @Override publicHttpHeadersgetHeaders(){ longcontentLength=headers.getContentLength(); HttpHeadershttpHeaders=newHttpHeaders(); httpHeaders.putAll(super.getHeaders()); httpHeaders.put(REQUESTID,list); if(contentLength>0){ httpHeaders.setContentLength(contentLength); }else{ httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,"chunked"); } returnhttpHeaders; } @Override publicFlux getBody(){ returnoutputMessage.getBody(); } }; returnchain.filter(exchange.mutate().request(decorator).build()); })); } if(HttpMethod.GET.name().equalsIgnoreCase(method)){ bodyStr=request.getQueryParams().toString(); StringnId=saveRequestOperLog(exchange,bodyStr); ServerHttpRequestuserInfo=exchange.getRequest().mutate() .header(REQUESTID,nId).build(); returnchain.filter(exchange.mutate().request(userInfo).build()); } } returnchain.filter(exchange); } /** *保存请求日志 * *@paramexchange *@paramrequestParameters *@return */ privateStringsaveRequestOperLog(ServerWebExchangeexchange,StringrequestParameters){ log.debug("接口请求参数:{}",requestParameters); ServerHttpRequestrequest=exchange.getRequest(); Stringip=Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress(); SaveOperLogVOvo=newSaveOperLogVO(); vo.setIp(ip); vo.setReqUrl(RequestUtils.getCurrentRequest(request)); vo.setReqMethod(request.getMethodValue()); vo.setRequestParameters(requestParameters); Routeroute=exchange.getAttribute(GATEWAY_ROUTE_BEAN); //是否配置路由 if(route!=null){ vo.setSubsystem(route.getId()); } ResEntity res=platformFeignClient.saveOperLog(vo); log.debug("当前请求ID返回的数据:{}",res); returnres.getData(); } @Override publicintgetOrder(){ return5; } }
该方案:完美解决以上所有问题
参考文档:https://www.codercto.com/a/52970.html
到此这篇关于SpringCloudGateway获取请求体(RequestBody)的多种方法的文章就介绍到这了,更多相关SpringCloudGateway获取请求体内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。