javascript
Spring Cloud Gateway 之获取请求体的几种方式
一、直接在全局?jǐn)r截器中獲取
偽代碼如下
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest){Flux<DataBuffer> body = serverHttpRequest.getBody();AtomicReference<String> bodyRef = new AtomicReference<>();//通過訂閱,獲取body數(shù)據(jù)body.subscribe(buffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());DataBufferUtils.release(buffer);bodyRef.set(charBuffer.toString());});return bodyRef.get();}存在的缺陷:其他攔截器無法再通過該方式獲取請求體(因?yàn)檎埱篌w已被消費(fèi)),并且會拋出異常
Only one connection receive subscriber allowed.Caused by: java.lang.IllegalStateException: Only one connection receive subscriber allowed.異常原因:實(shí)際上spring-cloud-gateway反向代理的原理是,首先讀取原請求的數(shù)據(jù),然后構(gòu)造一個新的請求,將原請求的數(shù)據(jù)封裝到新的請求中,然后再轉(zhuǎn)發(fā)出去。然而我們在他封裝之前讀取了一次request body,而request body只能讀取一次。因此就出現(xiàn)了上面的錯誤。
再者受版本限制
這種方法在spring-boot-starter-parent 2.0.6.RELEASE + Spring Cloud Finchley.SR2 body 中生效,
但是在spring-boot-starter-parent 2.1.0.RELEASE + Spring Cloud Greenwich.M3 body 中不生效,總是為空
二、先在全局過濾器中獲取,然后再把request重新包裝,繼續(xù)向下傳遞傳遞
@Overridepublic GatewayFilter apply(NameValueConfig nameValueConfig) {return (exchange, chain) -> {URI uri = exchange.getRequest().getURI();URI ex = UriComponentsBuilder.fromUri(uri).build(true).toUri();ServerHttpRequest request = exchange.getRequest().mutate().uri(ex).build();if("POST".equalsIgnoreCase(request.getMethodValue())){//判斷是否為POST請求Flux<DataBuffer> body = request.getBody();AtomicReference<String> bodyRef = new AtomicReference<>();body.subscribe(dataBuffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());DataBufferUtils.release(dataBuffer);bodyRef.set(charBuffer.toString());});//讀取request body到緩存String bodyStr = bodyRef.get();//獲取request bodySystem.out.println(bodyStr);//這里是我們需要做的操作DataBuffer bodyDataBuffer = stringBuffer(bodyStr);Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);//封裝我們的requestrequest = new ServerHttpRequestDecorator(request){@Overridepublic Flux<DataBuffer> getBody() {return bodyFlux;}};}//下一個filter處理的是封裝之后的requestreturn chain.filter(exchange.mutate().request(request).build());};}protected DataBuffer stringBuffer(String value) {byte[] bytes = value.getBytes(StandardCharsets.UTF_8);NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);buffer.write(bytes);return buffer;}該方案的缺陷:request body獲取不完整(因?yàn)?strong>異步原因),只能獲取1024B的數(shù)據(jù)(netty設(shè)置的)。
三、過濾器加路線定位器
翻查源碼發(fā)現(xiàn)ReadBodyPredicateFactory里面緩存了request body的信息,于是在自定義router中配置了ReadBodyPredicateFactory,然后在filter中通過cachedRequestBodyObject緩存字段獲取request body信息。
/*** @description: 獲取POST請求的請求體* ReadBodyPredicateFactory 發(fā)現(xiàn)里面緩存了request body的信息,* 于是在自定義router中配置了ReadBodyPredicateFactory* @modified:*/ @EnableAutoConfiguration @Configuration public class RouteLocatorRequestBoby{//自定義過濾器@Resourceprivate ReqTraceFilter reqTraceFilter;@Resourceprivate RibbonLoadBalancerClient ribbonLoadBalancerClient;private static final String SERVICE = "/leap/**";private static final String HTTP_PREFIX = "http://";private static final String COLON = ":";@Beanpublic RouteLocator myRoutes(RouteLocatorBuilder builder) {//通過負(fù)載均衡獲取服務(wù)實(shí)例ServiceInstance instance = ribbonLoadBalancerClient.choose("PLATFORM-SERVICE");//拼接路徑StringBuilder forwardAddress = new StringBuilder(HTTP_PREFIX);forwardAddress.append(instance.getHost()).append(COLON).append(instance.getPort());return builder.routes()//攔截請求類型為POST Content-Type application/json application/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 -> {return true;}).and().path(SERVICE)//把請求體傳遞給攔截器reqTraceFilter.filters(f -> {f.filter(reqTraceFilter);return f;}).uri(forwardAddress.toString())).build();}/*** @description: 過濾器,用于獲取請求體,和處理請求體業(yè)務(wù),列如記錄日志* @modified:*/ @Component public class ReqTraceFilter implements GlobalFilter, GatewayFilter,Ordered {private static final String CONTENT_TYPE = "Content-Type";private static final String CONTENT_TYPE_JSON = "application/json";//獲取請求路由詳細(xì)信息Route route = exchange.getAttribute(GATEWAY_ROUTE_BEAN)private static final String GATEWAY_ROUTE_BEAN = "org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute";private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();//判斷過濾器是否執(zhí)行String requestUrl = RequestUtils.getCurrentRequest(request);if (!RequestUtils.isFilter(requestUrl)) {String bodyStr = "";String contentType = request.getHeaders().getFirst(CONTENT_TYPE);String method = request.getMethodValue();//判斷是否為POST請求if (null != contentType && HttpMethod.POST.name().equalsIgnoreCase(method) && contentType.contains(CONTENT_TYPE_JSON)) {Object cachedBody = 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("請求體內(nèi)容:{}",bodyStr);}return chain.filter(exchange);}@Overridepublic int getOrder() {return 5;} }該方案優(yōu)點(diǎn):這種解決,一不會帶來重復(fù)讀取問題,二不會帶來requestbody取不全問題。三在低版本的Spring Cloud Finchley.SR2也可以運(yùn)行。
缺點(diǎn):不支持 multipart/form-data(異常415)。
四、通過ModifyRequestBodyGatewayFilterFactory
org.springframework.cloud.gateway.filter.factory.rewrite 包下有個 ModifyRequestBodyGatewayFilterFactory ,顧名思義,這就是修改 Request Body 的過濾器工廠類。
@Component @Slf4j public class ReqTraceFilter implements GlobalFilter, GatewayFilter, Ordered {@Resourceprivate IPlatformFeignClient platformFeignClient;/*** httpheader,traceId的key名稱*/private static final String REQUESTID = "traceId";private static final String CONTENT_TYPE = "Content-Type";private static final String CONTENT_TYPE_JSON = "application/json";private static final String GATEWAY_ROUTE_BEAN = "org.springframework.cloud.gateway.support.ServerWebExchangeUtils.gatewayRoute";@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();//判斷過濾器是否執(zhí)行String requestUrl = RequestUtils.getCurrentRequest(request);if (!RequestUtils.isFilter(requestUrl)) {String bodyStr = "";String contentType = request.getHeaders().getFirst(CONTENT_TYPE);String method = request.getMethodValue();//判斷是否為POST請求if (null != contentType && HttpMethod.POST.name().equalsIgnoreCase(method) && contentType.contains(CONTENT_TYPE_JSON)) {ServerRequest serverRequest = new DefaultServerRequest(exchange);List<String> list = new ArrayList<>();// 讀取請求體Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {//記錄請求體日志final String nId = saveRequestOperLog(exchange, body);//記錄日志idlist.add(nId);return Mono.just(body);});BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);HttpHeaders headers = new HttpHeaders();headers.putAll(exchange.getRequest().getHeaders());headers.remove(HttpHeaders.CONTENT_LENGTH);CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {@Overridepublic HttpHeaders getHeaders() {long contentLength = headers.getContentLength();HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.putAll(super.getHeaders());httpHeaders.put(REQUESTID,list);if (contentLength > 0) {httpHeaders.setContentLength(contentLength);} else {httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");}return httpHeaders;}@Overridepublic Flux<DataBuffer> getBody() {return outputMessage.getBody();}};return chain.filter(exchange.mutate().request(decorator).build());}));}if (HttpMethod.GET.name().equalsIgnoreCase(method)) {bodyStr = request.getQueryParams().toString();String nId = saveRequestOperLog(exchange, bodyStr);ServerHttpRequest userInfo = exchange.getRequest().mutate().header(REQUESTID, nId).build();return chain.filter(exchange.mutate().request(userInfo).build());}}return chain.filter(exchange);}/*** 保存請求日志** @param exchange* @param requestParameters* @return*/private String saveRequestOperLog(ServerWebExchange exchange, String requestParameters) {log.debug("接口請求參數(shù):{}", requestParameters);ServerHttpRequest request = exchange.getRequest();String ip = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();SaveOperLogVO vo = new SaveOperLogVO();vo.setIp(ip);vo.setReqUrl(RequestUtils.getCurrentRequest(request));vo.setReqMethod(request.getMethodValue());vo.setRequestParameters(requestParameters);Route route = exchange.getAttribute(GATEWAY_ROUTE_BEAN);//是否配置路由if (route != null) {vo.setSubsystem(route.getId());}ResEntity<String> res = platformFeignClient.saveOperLog(vo);log.debug("當(dāng)前請求ID返回的數(shù)據(jù):{}", res);return res.getData();}@Overridepublic int getOrder() {return 5;} }該方案:完美解決以上所有問題
參考:
https://www.cnblogs.com/hyf-huangyongfei/p/12849406.html
總結(jié)
以上是生活随笔為你收集整理的Spring Cloud Gateway 之获取请求体的几种方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Cloud Gateway
- 下一篇: js 实现2的n次方计算函数_JS中数据