日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Cloud Gateway 之获取请求体的几种方式

發(fā)布時(shí)間:2024/4/13 javascript 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Cloud Gateway 之获取请求体的几种方式 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、直接在全局?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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。