springbootgateway记录请求和响应日志 springcloudgateway是基于webflux的项目,因而不能跟使用springmvc一样直接获取requestbody,因此需要重新构造再转发。 如果我们在springcloudgateway封装之前读取了一次requestbody,比如打印requestbody日志,在下游获取数据的时候会出现错误:〔springcloud〕〔error〕java。lang。IllegalStateException:Onlyoneconnectionreceivesubscriberallowed。因为requestbody只能读取一次,它是属于消费类型的。 出现这样的原因是InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true,InputStream默认不实现reset(),并且markSupported()默认也是返回false。综上,InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。 直接上代码importlombok。Data;importjava。io。Serializable;importjava。util。Date;DatapublicclassGatewayLogimplementsSerializable{privatestaticfinallongserialVersionUID1983879536575766072L;访问实例privateStringtargetServer;请求路径privateStringrequestPath;请求方法privateStringrequestMethod;协议privateStringschema;请求体privateStringrequestBody;响应体privateStringresponseData;请求ipprivateStringip;请求时间privateDaterequestTime;响应时间privateDateresponseTime;执行时间privatelongexecuteTime;返回码privatelongcode;返回数据类型privateStringresponseContentType;请求数据类型privateStringrequestContentType;请求用户idprivateStringuserId;} publicinterfaceAccessLogService{voidsaveAccessLog(GatewayLoggatewayLog);} importcn。hutool。core。collection。CollectionUtil;importcom。shouwei。gateway。entity。GatewayLog;importcom。shouwei。gateway。service。AccessLogService;importcom。shouwei。gateway。utils。IpUtils;importlombok。extern。slf4j。Slf4j;importorg。apache。commons。lang。StringUtils;importorg。reactivestreams。Publisher;importorg。springframework。beans。factory。annotation。Autowired;importorg。springframework。cloud。gateway。filter。GatewayFilterChain;importorg。springframework。cloud。gateway。filter。GlobalFilter;importorg。springframework。cloud。gateway。filter。factory。rewrite。CachedBodyOutputMessage;importorg。springframework。cloud。gateway。route。Route;importorg。springframework。cloud。gateway。support。BodyInserterContext;importorg。springframework。cloud。gateway。support。ServerWebExchangeUtils;importorg。springframework。core。Ordered;importorg。springframework。core。ResolvableType;importorg。springframework。core。io。buffer。;importorg。springframework。http。HttpHeaders;importorg。springframework。http。MediaType;importorg。springframework。http。codec。HttpMessageReader;importorg。springframework。http。codec。multipart。FormFieldPart;importorg。springframework。http。codec。multipart。Part;importorg。springframework。http。server。reactive。ServerHttpRequest;importorg。springframework。http。server。reactive。ServerHttpRequestDecorator;importorg。springframework。http。server。reactive。ServerHttpResponse;importorg。springframework。http。server。reactive。ServerHttpResponseDecorator;importorg。springframework。stereotype。Component;importorg。springframework。util。LinkedMultiValueMap;importorg。springframework。util。MultiValueMap;importorg。springframework。web。reactive。function。BodyInserter;importorg。springframework。web。reactive。function。BodyInserters;importorg。springframework。web。reactive。function。server。HandlerStrategies;importorg。springframework。web。reactive。function。server。ServerRequest;importorg。springframework。web。server。ServerWebExchange;importorg。springframework。web。util。UriComponentsBuilder;importreactor。core。publisher。Flux;importreactor。core。publisher。Mono;importjava。io。UnsupportedEncodingException;importjava。nio。charset。StandardCharsets;importjava。util。;importjava。util。regex。Matcher;importjava。util。regex。Pattern;全局拦截器,作用所有的微服务1。对请求的API调用过滤,记录接口的请求时间,方便日志审计、告警、分析等运维操作2。后期可以扩展对接其他日志系统Slf4jComponentpublicclassAccessLogFilterimplementsGlobalFilter,Ordered{defaultHttpMessageReader。privatestaticfinalListHttpMessageReaderlt;?MESSAGEREADERSHandlerStrategies。withDefaults()。messageReaders();AutowiredprivateAccessLogServiceaccessLogService;privatefinalListHttpMessageReaderlt;?messageReadersHandlerStrategies。withDefaults()。messageReaders();顺序必须是1,否则标准的NettyWriteResponseFilter将在您的过滤器得到一个被调用的机会之前发送响应也就是说如果不小于1,将不会执行获取后端响应的逻辑returnOverridepublicintgetOrder(){return100;}OverrideSuppressWarnings(unchecked)publicMonoVoidfilter(ServerWebExchangeexchange,GatewayFilterChainchain){ServerHttpRequestrequestexchange。getRequest();请求路径StringrequestPathrequest。getPath()。pathWithinApplication()。value();RouteroutegetGatewayRoute(exchange);StringipAddressIpUtils。getIpAddress(request);GatewayLoggatewayLognewGatewayLog();gatewayLog。setSchema(request。getURI()。getScheme());gatewayLog。setRequestMethod(request。getMethodValue());gatewayLog。setRequestPath(requestPath);gatewayLog。setTargetServer(route。getId());gatewayLog。setRequestTime(newDate());gatewayLog。setIp(ipAddress);MediaTypemediaTyperequest。getHeaders()。getContentType();gatewayLog。setRequestContentType(mediaType。getType()mediaType。getSubtype());json格式if(MediaType。APPLICATIONJSON。isCompatibleWith(mediaType)){returnwriteBodyLog(exchange,chain,gatewayLog);}formdata格式elseif(MediaType。MULTIPARTFORMDATA。isCompatibleWith(mediaType)MediaType。APPLICATIONFORMURLENCODED。isCompatibleWith(mediaType)){returnreadFormData(exchange,chain,gatewayLog);}其他格式else{returnwriteBasicLog(exchange,chain,gatewayLog);}}privateMonoVoidwriteBasicLog(ServerWebExchangeexchange,GatewayFilterChainchain,GatewayLogaccessLog){returnDataBufferUtils。join(exchange。getRequest()。getBody())。flatMap(dataBuffer{DataBufferUtils。retain(dataBuffer);finalFluxDataBuffercachedFluxFlux。defer(()Flux。just(dataBuffer。slice(0,dataBuffer。readableByteCount())));finalServerHttpRequestmutatedRequestnewServerHttpRequestDecorator(exchange。getRequest()){OverridepublicFluxDataBuffergetBody(){returncachedFlux;}OverridepublicMultiValueMapString,StringgetQueryParams(){returnUriComponentsBuilder。fromUri(exchange。getRequest()。getURI())。build()。getQueryParams();}};StringBuilderbuildernewStringBuilder();MultiValueMapString,StringqueryParamsexchange。getRequest()。getQueryParams();if(CollectionUtil。isNotEmpty(queryParams)){for(Map。EntryString,ListStringentry:queryParams。entrySet()){builder。append(entry。getKey())。append()。append(StringUtils。join(entry。getValue(),,));}}accessLog。setRequestBody(builder。toString());获取响应体ServerHttpResponseDecoratordecoratedResponserecordResponseLog(exchange,accessLog);returnchain。filter(exchange。mutate()。request(mutatedRequest)。response(decoratedResponse)。build())。then(Mono。fromRunnable((){打印日志writeAccessLog(accessLog);}));});}读取formdata数据paramexchangeparamchainparamaccessLogreturnprivateMonoVoidreadFormData(ServerWebExchangeexchange,GatewayFilterChainchain,GatewayLogaccessLog){returnDataBufferUtils。join(exchange。getRequest()。getBody())。flatMap(dataBuffer{DataBufferUtils。retain(dataBuffer);finalFluxDataBuffercachedFluxFlux。defer(()Flux。just(dataBuffer。slice(0,dataBuffer。readableByteCount())));finalServerHttpRequestmutatedRequestnewServerHttpRequestDecorator(exchange。getRequest()){OverridepublicFluxDataBuffergetBody(){returncachedFlux;}OverridepublicMultiValueMapString,StringgetQueryParams(){returnUriComponentsBuilder。fromUri(exchange。getRequest()。getURI())。build()。getQueryParams();}};finalHttpHeadersheadersexchange。getRequest()。getHeaders();if(headers。getContentLength()0){returnchain。filter(exchange);}ResolvableTyperesolvableType;if(MediaType。MULTIPARTFORMDATA。isCompatibleWith(headers。getContentType())){resolvableTypeResolvableType。forClassWithGenerics(MultiValueMap。class,String。class,Part。class);}else{解析applicationxwwwformurlencodedresolvableTypeResolvableType。forClass(String。class);}returnMESSAGEREADERS。stream()。filter(readerreader。canRead(resolvableType,mutatedRequest。getHeaders()。getContentType()))。findFirst()。orElseThrow(()newIllegalStateException(nosuitableHttpMessageReader。))。readMono(resolvableType,mutatedRequest,Collections。emptyMap())。flatMap(resolvedBody{if(resolvedBodyinstanceofMultiValueMap){LinkedMultiValueMapmap(LinkedMultiValueMap)resolvedBody;if(CollectionUtil。isNotEmpty(map)){StringBuilderbuildernewStringBuilder();finalPartbodyPartInfo(Part)((MultiValueMap)resolvedBody)。getFirst(body);if(bodyPartInfoinstanceofFormFieldPart){Stringbody((FormFieldPart)bodyPartInfo)。value();log。info(bodybody);builder。append(body)。append(body);}finalPartuidPartInfo(Part)((MultiValueMap)resolvedBody)。getFirst(uid);if(uidPartInfoinstanceofFormFieldPart){Stringuid((FormFieldPart)uidPartInfo)。value();log。info(uiduid);accessLog。setUserId(uid);if(builder。length()0){builder。append(uid)。append(uid);}else{builder。append(uid)。append(uid);}}finalParttimeStampPartInfo(Part)((MultiValueMap)resolvedBody)。getFirst(timeStamp);if(timeStampPartInfoinstanceofFormFieldPart){StringtimeStamp((FormFieldPart)timeStampPartInfo)。value();log。info(timeStamptimeStamp);if(builder。length()0){builder。append(timeStamp)。append(timeStamp);}else{builder。append(timeStamp)。append(timeStamp);}}finalParttokenPartInfo(Part)((MultiValueMap)resolvedBody)。getFirst(token);if(tokenPartInfoinstanceofFormFieldPart){Stringtoken((FormFieldPart)tokenPartInfo)。value();log。info(tokentoken);if(builder。length()0){builder。append(token)。append(token);}else{builder。append(token)。append(token);}}finalPartsignPartInfo(Part)((MultiValueMap)resolvedBody)。getFirst(sign);if(signPartInfoinstanceofFormFieldPart){Stringsign((FormFieldPart)signPartInfo)。value();log。info(signsign);if(builder。length()0){builder。append(sign)。append(sign);}else{builder。append(sign)。append(sign);}}accessLog。setRequestBody(builder。toString());}}else{accessLog。setRequestBody((String)resolvedBody);}获取响应体ServerHttpResponseDecoratordecoratedResponserecordResponseLog(exchange,accessLog);returnchain。filter(exchange。mutate()。request(mutatedRequest)。response(decoratedResponse)。build())。then(Mono。fromRunnable((){打印日志writeAccessLog(accessLog);}));});});}解决requestbody只能读取一次问题,参考:org。springframework。cloud。gateway。filter。factory。rewrite。ModifyRequestBodyGatewayFilterFactoryparamexchangeparamchainparamgatewayLogreturnSuppressWarnings(unchecked)privateMonowriteBodyLog(ServerWebExchangeexchange,GatewayFilterChainchain,GatewayLoggatewayLog){ServerRequestserverRequestServerRequest。create(exchange,messageReaders);MonoStringmodifiedBodyserverRequest。bodyToMono(String。class)。flatMap(body{gatewayLog。setRequestBody(body);returnMono。just(body);});通过BodyInserter插入body(支持修改body),避免requestbody只能获取一次BodyInserterbodyInserterBodyInserters。fromPublisher(modifiedBody,String。class);HttpHeadersheadersnewHttpHeaders();headers。putAll(exchange。getRequest()。getHeaders());thenewcontenttypewillbecomputedbybodyInserterandthensetintherequestdecoratorheaders。remove(HttpHeaders。CONTENTLENGTH);CachedBodyOutputMessageoutputMessagenewCachedBodyOutputMessage(exchange,headers);returnbodyInserter。insert(outputMessage,newBodyInserterContext())。then(Mono。defer((){重新封装请求ServerHttpRequestdecoratedRequestrequestDecorate(exchange,headers,outputMessage);记录响应日志ServerHttpResponseDecoratordecoratedResponserecordResponseLog(exchange,gatewayLog);记录普通的returnchain。filter(exchange。mutate()。request(decoratedRequest)。response(decoratedResponse)。build())。then(Mono。fromRunnable((){打印日志writeAccessLog(gatewayLog);}));}));}打印日志paramgatewayLog网关日志authorjavadailydate202132414:53privatevoidwriteAccessLog(GatewayLoggatewayLog){accessLogService。saveAccessLog(gatewayLog);}privateRoutegetGatewayRoute(ServerWebExchangeexchange){returnexchange。getAttribute(ServerWebExchangeUtils。GATEWAYROUTEATTR);}请求装饰器,重新计算headersparamexchangeparamheadersparamoutputMessagereturnprivateServerHttpRequestDecoratorrequestDecorate(ServerWebExchangeexchange,HttpHeadersheaders,CachedBodyOutputMessageoutputMessage){returnnewServerHttpRequestDecorator(exchange。getRequest()){OverridepublicHttpHeadersgetHeaders(){longcontentLengthheaders。getContentLength();HttpHeadershttpHeadersnewHttpHeaders();httpHeaders。putAll(super。getHeaders());if(contentLength0){httpHeaders。setContentLength(contentLength);}else{TODO:thiscausesaHTTP1。1411LengthRequiredonhttpbin。orghttpHeaders。set(HttpHeaders。TRANSFERENCODING,chunked);}returnhttpHeaders;}OverridepublicFluxDataBuffergetBody(){returnoutputMessage。getBody();}};}记录响应日志通过DataBufferFactory解决响应体分段传输问题。privateServerHttpResponseDecoratorrecordResponseLog(ServerWebExchangeexchange,GatewayLoggatewayLog){ServerHttpResponseresponseexchange。getResponse();DataBufferFactorybufferFactoryresponse。bufferFactory();returnnewServerHttpResponseDecorator(response){OverridepublicMonoVoidwriteWith(Publisherlt;?extendsDataBufferbody){if(bodyinstanceofFlux){DateresponseTimenewDate();gatewayLog。setResponseTime(responseTime);计算执行时间longexecuteTime(responseTime。getTime()gatewayLog。getRequestTime()。getTime());gatewayLog。setExecuteTime(executeTime);获取响应类型,如果是json就打印StringoriginalResponseContentTypeexchange。getAttribute(ServerWebExchangeUtils。ORIGINALRESPONSECONTENTTYPEATTR);log。info(originalResponseContentTypeoriginalResponseContentType);gatewayLog。setResponseContentType(originalResponseContentType);gatewayLog。setCode(this。getStatusCode()。value());if(ObjectUtils。equals(this。getStatusCode(),HttpStatus。OK)!StringUtil。isNullOrEmpty(originalResponseContentType)originalResponseContentType。contains(applicationjson)){Fluxlt;?extendsDataBufferfluxBodyFlux。from(body);returnsuper。writeWith(fluxBody。buffer()。map(dataBuffers{合并多个流集合,解决返回体分段传输DataBufferFactorydataBufferFactorynewDefaultDataBufferFactory();DataBufferjoindataBufferFactory。join(dataBuffers);byte〔〕contentnewbyte〔join。readableByteCount()〕;join。read(content);释放掉内存DataBufferUtils。release(join);StringresponseResultnewString(content,StandardCharsets。UTF8);log。info(responseResultresponseResult);gatewayLog。setResponseData(responseResult);returnbufferFactory。wrap(content);}));}}ifbodyisnotaflux。nevergotthere。returnsuper。writeWith(body);}};}} 用到的依赖dependencygroupIdorg。springframework。cloudgroupIdspringcloudstartergatewayartifactIdversion2。2。5。RELEASEversiondependencydependencygroupIdorg。projectlombokgroupIdlombokartifactIdversion1。18。22versiondependency!huTool工具箱大全dependencygroupIdcn。hutoolgroupIdhutoolallartifactIdversion5。2。5versiondependency